- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I have DLL from someone else that is compiled from C. I'm trying to call a function that takes a single string argument and returns an integer value. My test program below builds and runs, but generates an "access violation" when it calls the function in the C DLL. Is there something simple I am missing? I tried putting a C on the string argument i.e. 'XLTRC2.dat'C but that didn't help. program Test implicit none interface Integer Function TA_PDetsFromPath(fname) character(*) fname !DEC$ ATTRIBUTES C, ALIAS:'_TA_PDetsFromPath' :: TA_PDetsFromPath END Function TA_PDetsFromPath END interface integer i i = TA_PDetsFromPath('XLTRC2.dat') end program Test
Link Copied
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Not having details of the prototype for the C function limits our ability to answer, but my first guess would be that the character variable you pass to the C function needs to be NULL terminated, i.e. change your function call to TA_PDetsFromPath('XLTRC2.dat'//char(0)).
Aside: you may want to look at the interoperability with C features of Fortran for a more portable way of calling C functions.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
C uses null-terminated strings; Fortran character variables have a length attribute, and that attribute has to be made available in some way -- the declaration of the variable or a '*' to signify "assumed length". The C routine in your DLL expects the terminating null, so it probably keeps consuming bytes beyond the logical end of the string until the access violation occurs.
Please read the mixed language chapter of the IFort user guide. ISO C-Interoperability provides portable ways to solve your problem. If you wish to keep using the DEC$ way, add a null, i.e., char(0), to the end of the Fortran character array.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Thanks for the replies.
I changed 'XLTRC2.dat' to 'XLTRC2.dat'//char(0). But that seemed to have no effect. The code still builds, but crashes at the same place with the same error. My biggest problem is I don't know what I'm doing.
I have the c header file for the DLL, and it is attached. What I think are the most relevant lines in this file are:
line 39: #define TURBOACTIVATE_API extern "C" __declspec(dllimport) line 53: #define TA_CC __cdecl line 669: TURBOACTIVATE_API HRESULT TA_CC TA_PDetsFromPath(STRCTYPE filename); I can't figure out where HRESULT is defined, but the visual studio editor popup thing says "typedef long HRESULT'.
I don't know what most of the above means. Can you tell me where to look in the Intel Fortran documentation for how to figure this out? I think I need to get the Interface definition correct, and then the calling statement correct, and then it will work.
Assuming I'm able to get this call to work, I then need to figure out how to call several other routines in the DLL, including one that needs a data structure passed to it (TA_IsGenuineEX line 483 in the .h file).
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I think the problem here is that STRCTYPE is typedefed to LPCWSTR, a wide character string (2-bytes per element), not a LPCSTR (1-byte per element).
You need to convert your Fortran character variable to an array of integer(c_short) elements; the last element must contain 0 to be the NULL terminator.
The interface should look something like
interface function TA_PDetsFromPath(filename) result(retval) bind("C", name="TA_PDetsFromPath") use, intrinsic :: iso_c_binding integer(c_short), dimension(*) :: filename ! LPCWSTR integer(c_long) :: retval ! HRESULT end function end interface
using Interoperability with C
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Thanks for the reply. I replaced my Interface with yours, and get the following compile error:
Error 1 error #5082: Syntax error, found CHARACTER_CONSTANT 'C' when expecting one of: <IDENTIFIER> C:\Users\Brian\Documents\Visual Studio 2010\Projects\LimeTest\LimeTest.f90 20
The Fortran-calls-C example project contains a line like SUBROUTINE c_routine (int_arg, str_in, str_out, str_out_len) BIND(C)
So I replace "C" with C but got a different error; Error 1 error #6633: The type of the actual argument differs from the type of the dummy argument. C:\Users\Brian\Documents\Visual Studio 2010\Projects\LimeTest\LimeTest.f90 35
Do I need to change some other setting in Visual Studio?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Once the compile error is solved, I expect to get another in the calling statement because a string is being passed to an integer argument. What do I do about that?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I tried the following, and this will build and run without crashing. However, the return value indicates the function if failing, probably because the filename string variable is no good. I'm passing it 'XLTRC2.dat'//char(0). I tried C_NULL_CHAR in place of char(0) but the compiler didn't allow that.
interface function TA_PDetsFromPath(filename) result(retval) bind(C, name="TA_PDetsFromPath") use, intrinsic :: iso_c_binding CHARACTER(KIND=C_CHAR), DIMENSION(*), INTENT(IN) :: filename integer(c_long) :: retval ! HRESULT end function end interface
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I have a walk-through of communications between Fortran and C# via C++, including the VS project, which can be found on this webpage: http://www.simdynamics.org/download.htm
Hope it helps!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Passing strings between Fortran and alternative versions of C can be a real pain - a solid work-around that I have found, is to conver the string to an integer vector, and decode after the vector is passed.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Go back to the interface of Quote #5, but try an actual argument of
IACHAR(transfer('XLTRC2.dat'//C_NULL_CHAR,[C_NULL_CHAR]),KIND=C_SHORT)
That would have the right TKR to make the compiler happy. Make sure that the caller also USEs ISO_C_BINDING.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
That worked! Thanks bunches, RO. I now have my first two calls working, each of which takes a single string input argument. The next call has a string argument for output. Can you tell me how to pass that sort of argument? I've taken a stab at it, but no luck.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
There is a bit of ambiguity in Quote #12 in that the C++ function could take an array of wchar_t, supposed allocated big enough, as an argument and then just fill it with data (type1) or it could take a pointer to a pointer to wchar_t and point the middle pointer at a wchar_t string that the library manages (type2). Accordingly I have given an example for both possibilities. Note that it uses wcslen() from MSVCRT and for older versions of ifort it needs the command line switch /assume:realloc_lhs. I compiled the C part with gcc:
#include <windows.h> wchar_t hello[] = {(wchar_t)'H',(wchar_t)'e',(wchar_t)'l', (wchar_t)'l',(wchar_t)'o',(wchar_t)',',(wchar_t)' ', (wchar_t)'w',(wchar_t)'o',(wchar_t)'r',(wchar_t)'l', (wchar_t)'d',(wchar_t)'\0'}; void type1(wchar_t *str) { int i; for(i = 0; hello != (wchar_t)'\0'; i++) { str = hello; } str = hello; } void type2(wchar_t **str) { *str = hello; }
And I compiled the fortran part with ifort (which also linked with the *.o file that gcc made):
module M use ISO_C_BINDING implicit none private public type1, type2, wcslen interface subroutine type1(str) bind(C,name='type1') import implicit none integer(C_SHORT) str(*) end subroutine type1 subroutine type2(str) bind(C,name='type2') import implicit none type(C_PTR) str end subroutine type2 end interface interface wcslen function wcslen_array(str) bind(C,name='wcslen') import implicit none integer(C_SIZE_T) wcslen_array integer(C_SHORT) str(*) end function wcslen_array function wcslen_pointer(str) bind(C,name='wcslen') import implicit none integer(C_SIZE_T) wcslen_pointer type(C_PTR), value :: str end function wcslen_pointer end interface wcslen end module M program P use M use ISO_C_BINDING implicit none integer(C_SHORT), allocatable :: str1(:) character(len=:,kind=C_CHAR), allocatable :: result1 integer(C_SIZE_T) strlen1 type(C_PTR) str2 integer(C_SIZE_T) strlen2 integer(C_SHORT), pointer :: array2(:) character(len=:,kind=C_CHAR), allocatable :: result2 allocate(str1(20)) ! Or however big it has to be to hold result call type1(str1) strlen1 = wcslen(str1) result1 = transfer(achar(str1),repeat('A',strlen1)) write(*,'(*(g0))') 'Result for type1 = ',result1 call type2(str2) strlen2 = wcslen(str2) call C_F_POINTER(str2, array2, [strlen2]) result2 = transfer(achar(array2), repeat('A',strlen2)) write(*,'(*(g0))') 'Result for type2 = ',result2 end program P
Also the above worked with gfortran. Results were the same with both fortran processors:
Result for type1 = Hello, world Result for type2 = Hello, world
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Thanks for the reply, RO. However, I have to confess this is getting to be too far over my head. My original goal was to simplify things by eliminating the use of C code to call the routines in the DLL. C code which calls the DLL's routines is pretty simple, and I have that working. That was easy to get working because I only really need to return a single integer value from C to fortran. So all the tricky calling of DLL routines is done in C.
It would have been nice to eliminate the C code and do it all in fortran if the fortran code isn't too complicated. But the way this is going, the fortran code will be too complicated for me to understand, or for the next guy after me to understand. Thus sticking with using C to call the DLL seems like my best choice.
I really appreciate the help. I've learned some new things, and I don't want to appear ungrateful.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I didn't think that two or three executable statements to convert the actual argument to something Fortran-usable was all that complicated. TT
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I wish it were at simple to me as it is to you. Converting "a few lines of code" is mushrooming into loads of code. I did try one of the methods you described, and I get an access violation in the call to wcslen, and the debugger says a bad pointer was passed to wcslen.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I was hoping you could figure out whether type1 or type2 was applicable from reading the documentation, if available, or the C code. But documentation can be sparse and C is an incomprehensible language when coming from a Fortran background. Can you give the C prototype as you did in Quote #4? Your symptoms make it look like you are assuming type2 when the *.DLL really wants type1, or that it's type2 with the string not NUL-terminated, but its length passed in another argument or the function result.
If you just can't make it work, can you give a small compilable example that shows the error (just the Fortran part, hopefully you are going to disclose the C prototype anyway).
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
type2 was the one I tried.
I don't have any documentation on the routines in the DLL other than the header file attached to post #4. I have to go to bed now, but the prototype is line 387 in the header file. Here is line 387.
TURBOACTIVATE_API HRESULT TA_CC TA_GetFeatureValue(uint32_t handle, STRCTYPE featureName, STRTYPE lpValueStr, int cchValue);
The comments in the header file say this routine is called twice, and that is what I am doing. In the first call it returns the size of the string that is going to be returned in the second call. The first call seems to go ok since cchValue returns a number that looks about right.
Assuming I get past this routine, the next one is the last one I need to call, but one of its arguments is a typedef data structure consisting of 4 long variables (thankfully no strings). Prototype for this one is line 483 in the header file, and it takes a pointer to a data structure variable. Yikes!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Here are my suggested interfaces to the two functions you mention in #18:
module turboactive_api_mod use, intrinsic :: iso_c_binding implicit none type, bind(c) :: GENUINE_OPTIONS ! The size, in bytes, of this structure. Set this value to the size of the GENUINE_OPTIONS structure. integer(c_int32_t) :: nLength; ! Flags to pass to TA_IsGenuineEx() to control its behavior. integer(c_int32_t) :: flags; ! How often to contact the LimeLM servers for validation. 90 days recommended. integer(c_int32_t) :: nDaysBetweenChecks; ! If the call fails because of an internet error, how long, in days, should the grace period last (before ! returning deactivating and returning TA_FAIL). 14 days is recommended. integer(c_int32_t) :: nGraceDaysOnInetErr; end type GENUINE_OPTIONS interface ! Add other function prototypes here... ! TURBOACTIVATE_API HRESULT TA_CC TA_GetFeatureValue(uint32_t handle, STRCTYPE featureName, STRTYPE lpValueStr, int cchValue); function TA_GetFeatureValue(handle, featureName, lpValueStr, cchValue) bind(c, name = 'TA_GetFeatureValue') result(retval) import integer(c_int32_t), value :: handle ! Note that Fortran doesn't have unsigned integer types integer(c_short), dimension(*) :: featureName ! LPCWSTR is a "Long Pointer to Constant Wide String". integer(c_short), dimension(*) :: lpValueStr ! LPWSTR is a pointer to a string of 16-bit Unicode characters, which MAY be null-terminated. integer(c_int), value :: cchValue ! Note the value attribute here integer(c_long) :: retval ! HRESULT is typedef long end function ! TURBOACTIVATE_API HRESULT TA_CC TA_IsGenuineEx(uint32_t handle, PGENUINE_OPTIONS options); function TA_IsGenuineEx(handle, options) bind(c, name = 'TA_IsGenuineEx') result(retval) import integer(c_int32_t), value :: handle ! Note that Fortran doesn't have unsigned integer types type(GENUINE_OPTIONS) :: options integer(c_long) :: retval ! HRESULT is typedef long end function end interface end module turboactive_api_mod
A few things to note:
1) You normally don't need to add the value attribute to the declarations of types that are passed as pointers to C.
2) On the other hand, you will need the value attribute for things passed by value, e.g. cchValue.
3) You could use the ifwin module to get definitions for the Windows API types (e.g. HRESULT), but this wouldn't be portable to other compilers.
4) Fortran only supports signed integers, hence the use of c_int32_t not c_uint32_t.
5) Interoperable types are quite straightforward - you need to add the bind(c) attribute.
6) The Standard Tools for Interoperability help topic is your friend :-)
7) As you mention, you would normally call TA_GetFeatureValue() twice. The first time with cchValue = 0 to get the function to return the buffer size. Once you know this, you can allocate an array of integer(c_short) to the buffer size to store the value of the custom licence field and pass this and the buffer size on the 2nd call.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Thanks bunches for the help. After some more peek and poke, I've actually got it all working! To pass a data structure variable, I am simply using the name of the variable, and that works! Too bad strings aren't that easy.
This brings to mind the old saying "I only know enough to be dangerous". I was already dangerous, and now even more so :)
I will pass this back to the Turboactivate people. They said they didn't have fortran sample code for calling their API, so they ought to want this.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Passing one string is pretty straight-forward (making sure to terminate with a null). If passing a number of strings, then translating each string into integers for transfer as a matrix seems easier to me, as it avoides all of the problems associated with memory allocation between languages. On the fortran side, the following code can be used to do the translation:
SUBROUTINE IntToChar( IntStore, len_store, text, len_text ) !******************************************************************* ! ! MODULE contains tools for communicating with C++ ! !******************************************************************* IMPLICIT none integer(4), intent(in) :: len_store, len_text integer(4) :: IntStore(len_store) character(len_text) :: text ! local variables integer(4) :: ii !******************************************************************* ! begin code !******************************************************************* ii = 1 do ii= 1, min(len_store, len_text) if (IntStore(ii).eq.0) then ! text = text(1:ii-1) // char(0) exit ! If successful, exit the loop else if ( ii.eq.1 ) then text = achar(IntStore(ii)) else text = text(1:ii-1) // achar(IntStore(ii)) end if end if end do continue END SUBROUTINE IntToChar SUBROUTINE CharToInt( clen, text, IntStore ) !******************************************************************* ! ! MODULE contains tools for communicating with C++ ! !******************************************************************* IMPLICIT none integer(4), intent(in) :: clen integer(4) :: IntStore(clen) character :: text(clen) ! local variables integer(4) :: ii !******************************************************************* ! begin code !******************************************************************* ii = 1 do while ( ii.le.clen ) IntStore(ii) = iachar(text(ii)) ii = ii + 1 end do continue END SUBROUTINE CharToInt
And, at least if working in C#, the equivalent translation can be done using:
static void StringToInt(string text, int[] text_store) { //ROUTINE TO CONVERT string VARIABLE INTO INT VECTOR FOR PORTING TO OTHER LANGUAGES int wrk = Math.Min(text.Length, text_store.Length); for (int ii = 0; ii < wrk; ii++) { text_store[ii] = Convert.ToInt32(text[ii]); } return; } static string IntToString(int clen, int[] IntText) { //ROUTINE TO INT ARRAY INTO STRING VARIABLE FOR PORTING FROM OTHER LANGUAGES string temp_sep = null; string[] text_temp = new string[clen]; for (int ii = 0; ii < clen; ii++) { text_temp[ii] = Convert.ToString(Convert.ToChar(IntText[ii])); } string text = String.Join(temp_sep, text_temp); return text; }
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page