Community
cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
New Contributor I
85 Views

How to call a C function in a DLL

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

 

0 Kudos
20 Replies
Highlighted
New Contributor II
83 Views

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.

 

0 Kudos
Highlighted
Black Belt
83 Views

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.

0 Kudos
Highlighted
New Contributor I
83 Views

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).

0 Kudos
Highlighted
New Contributor II
83 Views

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

0 Kudos
Highlighted
New Contributor I
83 Views

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?

0 Kudos
Highlighted
New Contributor I
83 Views

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?

0 Kudos
Highlighted
New Contributor I
83 Views

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

 

0 Kudos
Highlighted
Beginner
83 Views

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! 

0 Kudos
Highlighted
Beginner
83 Views

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.

0 Kudos
Highlighted
Valued Contributor II
83 Views

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.

 

0 Kudos
Highlighted
New Contributor I
83 Views

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.

0 Kudos
Highlighted
Valued Contributor II
83 Views

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

 

0 Kudos
Highlighted
New Contributor I
83 Views

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.

0 Kudos
Highlighted
Valued Contributor II
83 Views

I didn't think that two or three executable statements to convert the actual argument to something Fortran-usable was all that complicated. TT

 

0 Kudos
Highlighted
New Contributor I
83 Views

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.

0 Kudos
Highlighted
Valued Contributor II
83 Views

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).

 

0 Kudos
Highlighted
New Contributor I
83 Views

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!

0 Kudos
Highlighted
New Contributor II
83 Views

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.

0 Kudos
Highlighted
New Contributor I
83 Views

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.

0 Kudos