Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

need a little help calling C part 2

Brian_Murphy
New Contributor II
496 Views

From fortran I want to call a routine in a C DLL that takes a string argument.  Is there a web page that explains how to do this?  The C DLL uses stdcall calling convention.  In the header file for the DLL the string argument appears to be declared as (const void *).

In a C program that calls this DLL, the string argument which is passed is created with a statement as follows: 

unsigned char str_argument[] ="blahblahblah"

 

0 Kudos
13 Replies
Steve_Lionel
Honored Contributor III
496 Views

The calling convention doesn't matter here.

If you can use BIND(C), declare the dummy argument as:

CHARACTER, DIMENSION(*)

You can pass an arbitrary-length Fortran string to this. Make sure the string you pass is NUL-terminated. 

If you're not using BIND(C), then use ATTRIBUTES directives to give both the routine AND the dummy argument the REFERENCE attribute.

0 Kudos
Brian_Murphy
New Contributor II
496 Views

No luck so far.  The code builds and runs, but the function call is not succeeding.  vendor_code is the string argument giving me trouble.

In the interface block

		function hasp_login(feature_id, vendor_code, handle)
			!DEC$ ATTRIBUTES STDCALL :: hasp_login
			import
			integer(c_int32_t) :: feature_id
			integer(c_int32_t) :: handle
			character, dimension(*) :: vendor_code
			integer(c_long) :: hasp_login
		end function

In the main program.

	USE, INTRINSIC :: ISO_C_BINDING
	integer(c_int32_t) handle, featureID
	integer status
	character(len=984,kind=C_CHAR):: vendor_code
	vendor_code = "blahblahblah" // C_NULL_CHAR
	status = hasp_login(6, vendor_code, handle)

If the vendor_code were simply wrong, there is a status return value to indicate that.  But I'm getting a status saying "invalid function parameter".  So I don't think the argument is being passed correctly.

0 Kudos
Steve_Lionel
Honored Contributor III
496 Views

Pardon my frustration, but you ignored almost every single bit of advice I gave you. I hope you're not asking me to write your code for you.

Please go back and read my previous reply.

0 Kudos
andrew_4619
Honored Contributor II
496 Views

I would expect the interface requires the reference attribute for vendor_code. I think hasp_login is being passed back so it needs to be reference also unless you are meant to pass LOC(handle) by value. You need to look at the hasp_login API doc or a C language sample call to see what is needed.

 

 

 

 

 

0 Kudos
Brian_Murphy
New Contributor II
496 Views

I very much appreciate the help.  What I'm trying to do is over my head, and I have to confess even the suggestions over my head.  I am more than willing to hire someone to do this for me.  Any takers?  Go to www.xlrotor.com and use the email link at the bottom.

0 Kudos
FortranFan
Honored Contributor II
496 Views

Brian Murphy wrote:

..  What I'm trying to do is over my head, and I have to confess even the suggestions over my head.  I am more than willing to hire someone to do this for me.  Any takers?  ..

@Brian Murphy,

You don't provide sufficient details on the 'hasp_login' function in the C DLL in question.  Suppose for a moment the prototype for this function and its implementation is as follows:

#include <stdio.h>

// Function prototype
__declspec(dllexport) int __stdcall hasp_login(int *, const void *, int *);

int __stdcall hasp_login(int *feature_id, const void *vendor_code, int *handle) {
   printf("In C function hasp_login:\n");
   printf("vendor_code is %s\n", (char *)vendor_code);
   printf("feature_id: %d\n", *feature_id);
   printf("handle: %d\n", *handle);
   return 0;
}

Then, if you intend to employ the Intel Fortran (and previously with DEC Fortran) compiler-based non-standard options toward mixed-language programming, you can write your Fortran code to make use of above C function as follows:

   interface
      function hasp_login(feature_id, vendor_code, handle) result(r)
         !DIR$ ATTRIBUTES STDCALL, REFERENCE, DECORATE :: hasp_login
         !DIR$ ATTRIBUTES REFERENCE :: vendor_code
         integer, intent(inout) :: feature_id
         character(len=*), intent(in) :: vendor_code
         integer, intent(inout) :: handle
         integer :: r
      end function
   end interface

   integer :: fid, hwnd, irc
   character(len=:), allocatable :: vc
   fid = 1 ; hwnd = 2

   vc = "BLAHBLAHBLAH" // char(0)

   irc = hasp_login(fid, vc, hwnd)

end

Now, you can test the above for yourself by compiling and linking the C function into a DLL and for a Fortran program to dynamically link to it and produce the expected output by following the steps below in a Windows command prompt corresponding to your Intel Fortran compiler version for IA32 environment:

C:\Temp>type c.c
#include <stdio.h>

// Function prototype
__declspec(dllexport) int __stdcall hasp_login(int *, const void *, int *);

int __stdcall hasp_login(int *feature_id, const void *vendor_code, int *handle) {
   printf("In C function hasp_login:\n");
   printf("vendor_code is %s\n", (char *)vendor_code);
   printf("feature_id: %d\n", *feature_id);
   printf("handle: %d\n", *handle);
   return 0;
}


C:\Temp>cl /c /W3 /EHsc c.c
Microsoft (R) C/C++ Optimizing Compiler Version 19.23.28106.4 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

c.c

C:\Temp>link c.obj /DLL /out:c.dll
Microsoft (R) Incremental Linker Version 14.23.28106.4
Copyright (C) Microsoft Corporation.  All rights reserved.

   Creating library c.lib and object c.exp

C:\Temp>type p.f90
   interface
      function hasp_login(feature_id, vendor_code, handle) result(r)
         !DIR$ ATTRIBUTES STDCALL, REFERENCE, DECORATE :: hasp_login
         !DIR$ ATTRIBUTES REFERENCE :: vendor_code
         integer, intent(inout) :: feature_id
         character(len=*), intent(in) :: vendor_code
         integer, intent(inout) :: handle
         integer :: r
      end function
   end interface

   integer :: fid, hwnd, irc
   character(len=:), allocatable :: vc
   fid = 1 ; hwnd = 2

   vc = "BLAHBLAHBLAH" // char(0)

   irc = hasp_login(fid, vc, hwnd)

end

C:\Temp>ifort /c p.f90
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on IA-32, Version 19.0.5.281 Build 20190815
Copyright (C) 1985-2019 Intel Corporation.  All rights reserved.


C:\Temp>ifort p.obj c.lib /exe:p.exe
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on IA-32, Version 19.0.5.281 Build 20190815
Copyright (C) 1985-2019 Intel Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 14.23.28106.4
Copyright (C) Microsoft Corporation.  All rights reserved.

-out:p.exe
-subsystem:console
p.obj
c.lib

C:\Temp>p.exe
In C function hasp_login:
vendor_code is BLAHBLAHBLAH
feature_id: 1
handle: 2

C:\Temp>

Note the same principle applies in terms of your Fortran code and the !DIR$ ATTRIBUTES that need to be used if you're employing Visual Studio 2010 solution(s) and project(s) in your work.  I'll leave it up to you as exercise to try out such a unit test in case you continue to face issues.

Hope this helps,

0 Kudos
JohnNichols
Valued Contributor III
496 Views

There is a reasonable example at this link, although FF has provided most of what you need. 

I forget FortranFan on the list this morning -- my apologies. 

Fortran to C example

PS: overall lesson for the day - do not give a sick 12 year old a bell to call you when she is sick -- it becomes Pavlovian 

0 Kudos
FortranFan
Honored Contributor II
496 Views

@Brian Murphy,

Since you suggest in this other thread (https://software.intel.com/en-us/forums/intel-fortran-compiler/topic/842768) you're back to using C interoperability features in standard Fortran, you can consider the modified code below instead for the Fortran program in Quote #7:

   use, intrinsic :: iso_c_binding, only : c_int, c_char, c_null_char
   interface
      function hasp_login(feature_id, vendor_code, handle) result(r) bind(C, name="hasp_login")
         !DIR$ ATTRIBUTES STDCALL :: hasp_login
         import :: c_int, c_char
         integer(kind=c_int), intent(inout) :: feature_id
         character(kind=c_char,len=1), intent(in) :: vendor_code(*)
         integer(kind=c_int), intent(inout) :: handle
         integer(c_int) :: r
      end function
   end interface

   integer(c_int) :: fid, hwnd, irc
   character(kind=c_char, len=:), allocatable :: vc
   fid = 1 ; hwnd = 2

   vc = "BLAHBLAHBLAH" // c_null_char

   irc = hasp_login(fid, vc, hwnd)

end

You can try to repeat the steps shown in Quote #7 and get the same output from the program i.e., assuming you are using a reasonably recent version of Intel Fortran compiler that added support for STDCALL convention on Windows in conjunction with C interoperability features.

 

0 Kudos
Brian_Murphy
New Contributor II
496 Views

I can't thank you enough for the help.  I have installed vs2015 and composer 2018, and also have paid for license update/renewal to the latest versions, but I can't get the latest version until Intel gets my registration problems fixed, and that's proving to be a tough nut  to crack.

I hope to report back that I have it working, but give me a few days.

In the meantime, the C function prototype is complicated by a chain of include files and (to me) uncertain data types.  I will try to pick out the relevant bits from a working C console program.

enum hasp_error_codes
{
 HASP_STATUS_OK = 0,
 lots more of these
};

#define HASP_CALLCONV __stdcall
typedef unsigned long hasp_u32_t;
typedef enum hasp_error_codes hasp_status_t;
typedef hasp_u32_t hasp_size_t;
typedef hasp_u32_t hasp_handle_t;
typedef hasp_u32_t hasp_feature_t;
typedef const void *hasp_vendor_code_t;

Here is the C prototype exactly as given in a working C console program.
hasp_status_t HASP_CALLCONV hasp_login(hasp_feature_t feature_id, hasp_vendor_code_t vendor_code, hasp_handle_t *handle);
My attempt at substituting leads me to this, where I have guessed a C enum becomes an integer in fortran.  Still not clear what to do with unsigned longs in fortran, and one appears to be passed by value and one by reference.
integer __stdcall hasp_login(unsigned long feature_id, const void *vendor_code,  unsigned long *handle);

Again, give me a few days and I hope to have this working.

0 Kudos
jimdempseyatthecove
Honored Contributor III
496 Views

The hasp_login function return is an error code/success indicator and the handle is a return argument, thus passing in pointer to handle (reference on Fortran side). It is unclear as to if vendor_code is a supplied or return value. You should consult the hasp_login documentation as to the arguments. On the C side, the *ointers are usually references (exception as to when the called function expects an opaque object that is not directly used, possibly saved and returned later).

Jim Dempsey

0 Kudos
LRaim
New Contributor I
495 Views

The vendor_code is a supplied parameter (const attribute in C interface) which identifies uniquely the owner/seller company of the software application. 

0 Kudos
Brian_Murphy
New Contributor II
495 Views

Hmmm.  I could'a sworn I posted a reply last night which is now gone.  I'll try again.

With Steve's help in the other thread, a VALUE attribute for the feature_id argument did the trick.

Elsewhere, some of the C functions return a string in an argument, and I have this working, too.  All I did for that was change intent(in) to intent(inout). The returned string is null terminated, but it's length is not returned.  Is the following the recommended way to get that:  length = INDEX(strvble, c_null_char)-1

About the vendor_code.  As #12 says, that is an input, so intent(in).

0 Kudos
Steve_Lionel
Honored Contributor III
495 Views

I'll put in my usual alert about VALUE (the standard attribute, not what goes in a directive).

VALUE changes its meaning depending on whether the procedure has BIND(C) or not. If it is interoperable (BIND(C)), VALUE means that the argument is compatible with a C parameter that is received by value (doesn't have a * in the prototype.) But if BIND(C) is not used, VALUE means that an anonymous, writable copy of the argument is passed (by reference, usually, though the standard doesn't explicitly say that.)

Early Intel implementations of VALUE got this wrong, which why /assume:std_value exists.

0 Kudos
Reply