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

Linking IVF Compiled DLL to Qt Application

J_S_1
Beginner
2,455 Views

Hi everybody,
This is my first attempt at library assembly, so please have mercy! Let me also start by saying to Steve Lionel: Wow, you answer a lot, I hope they are paying you well there at Intel!

I am trying to link an external library (written in .f90 and compiled with IVF on VS2012) to my Qt application (32bit based on Qt 5.5.0- MinGW 4.9.2).

I have a few questions:

1) Is this futile? Some of the research I have found suggests that IVF and MinGW are ABI incompatible. I really want to stay with the MinGW compiler in Qt because basically everything else we are doing with the software uses this.

2) It would be of advantage to be able to load the library only when called upon (which would only represent a fraction of cases). For this reason I have been attempting to use QLibrary but keep getting Segmentation faults when I try to call SUBROUTINES defined in my DLL (with resolve("my_function")):

I have defined my external call routines with:

 @ !DEC$ ATTRIBUTES C, REFERENCE, MIXED_STR_LEN_ARG, DLLEXPORT,   ALIAS:"sub_name" :: sub_name @
and import them using:

   @typedef int (*MyPrototype)(int i);
    MyPrototype Square = (MyPrototype) DLLname.resolve("sub_name"); @

3) Is there any way to check if the library has, in fact, loaded? Of course calling a subroutine would accomplish this, but that isn't working, and when I import the library DLLname.load() returns a positive result. and resolve.("sub_name") has a memoryaddress allocated to it. Does this suggest a type fault? Ie. passing the wrong identifier in to the fortran code.

Once again, thanks for reading and please feel free to take apart any flaws in logic I have, I'm not (yet) a programmer!

0 Kudos
46 Replies
Steven_L_Intel1
Employee
1,174 Views

First of all, since you're using a DLL you're not "linking" and this eliminates many possible issues.

I don't know Qt, but I see that you declare the argument as "int i". In C, that would mean pass-by-value, and you specified REFERENCE in Fortran, so attempting to access the argument would get you an error. What if you remove REFERENCE from the ATTRIBUTES directive?

0 Kudos
J_S_1
Beginner
1,174 Views

Thanks very much for the quick reply Steve,

I attempted the change you made and came across error #8039 that I couldn't use INTENT(OUT) types with "C" attribute. Here is the function I am trying to export (as I said, it is simply a trial to make sure the application is communicating with the dll):

@SUBROUTINE SQ(a,asquare)
!DEC$ ATTRIBUTES C, REFERENCE, MIXED_STR_LEN_ARG, DLLEXPORT, ALIAS:"SQ" :: SQ
  integer, intent(in)  :: a             ! input
  integer, intent(out) :: asquare       ! output
  asquare = a**2
END SUBROUTINE SQ   @

This is one option to reference the function, but I would prefer to prototype it in a header file, so that it is loaded at run time. Could you please indicate to me how I would prototype the function (@ extern "C" { int SQ(int) }@ etc). Again I apologise for such a banal question.

0 Kudos
Steven_L_Intel1
Employee
1,174 Views

It looks to me as if you declare the procedure in Qt as a function returning an int and taking one int argument by value, but the Fortran code is a subroutine with two integers. This isn't going to match.

Assuming you call the "square" routine as a function in Qt, I'll suggest the following alternative Fortran source:

function SQ (a) result(asquare)
use, intrinsic :: ISO_C_BINDING
!DEC$ ATTRIBUTES C, DLLEXPORT,ALIAS:"SQ" :: SQ
integer(C_INT) :: asquare
integer(C_INT), intent(IN) :: a
asquare = a**2
end function SQ

For the benefit of others, I'm not using BIND(C) here because there's no way to force the name to be just "SQ" without decoration. The ATTRIBUTES C sets the argument convention to be by value.

0 Kudos
JVanB
Valued Contributor II
1,174 Views

But don't you want the C-style name decoration because the function is prototyped as a C function? There is about one incompatibility I can think of between MinGW and ifort in that for 32-bit code, if a function returns a struct that is too big or complicated to get returned in EAX, then the two systems will leave ESP in a different state on return.

 

0 Kudos
Steven_L_Intel1
Employee
1,174 Views

In this case, the name is referenced from the DLL, as one would do in GetProcAddress (or from VB). No decoration is applied.

That's a good thing to know about function returns, though it doesn't apply in this particular case.

0 Kudos
JVanB
Valued Contributor II
1,174 Views

You had me scared for a minute there, Steve, and I thought I might have to use a *.DEF file to get the names right, but...

! SQ.f90
! Compiled with
! gfortran -shared SQ.f90 -oSQ.dll
! or
! ifort SQ.f90 /dll
module M
   implicit none
   contains
      function SQ(a) bind(C,name='SQ')
         use, intrinsic :: ISO_C_BINDING
         implicit none
!DEC$ ATTRIBUTES DLLEXPORT :: SQ
!GCC$ ATTRIBUTES DLLEXPORT :: SQ
         integer(C_INT) SQ
         integer(C_INT),value :: a
         SQ = a**2
      end function SQ
end module M
// Cprog.c
// Compiled with
// gcc Cprog.c -oCprog
#include <stdio.h>
#include <windows.h>

int main()
{
   HMODULE dll_handle;
   int (*SQ)(int);
   int a;

// Check for bitness -- this would be easy in 64-bit mode.
   printf("This is a %d-bit program\n",8*sizeof(HANDLE));
   dll_handle = LoadLibrary("SQ.dll");
   printf("dll_handle = %p\n",dll_handle);
   SQ = (void *)GetProcAddress(dll_handle,"SQ");
   printf("SQ = %p\n",SQ);
   a = 17;
   printf("SQ(%d) = %d\n", a, SQ(a));
   return 0;
}

And whether SQ.f90 is compiled with ifort or gfortran, I still get

C:\>Cprog
This is a 32-bit program
dll_handle = 727A0000
SQ = 727A1010
SQ(17) = 289

So the undecorated name seems to be getting passed in either case.

 

0 Kudos
J_S_1
Beginner
1,174 Views

==> Repeat Offender: Seeing as I have no idea what neither EAX or ESP are, ill continue on silently nodding my head...

==> Steve Lionel: Perfect, your changes worked flawlessly Steve. Now that I am sure that the .dll is at least communicating, I can continue on and try to take the next step with my implementation. I am trying to create an implicite dynamic link to the library, so I wish to define the prototypes for my functions in a header file. I am trying to access exactly the function you have defined above, expect define it in a header and then access the function in my code.

#ifndef LINK_1
#define LINK_1
#include <iostream>

 

#if defined DLL_EXPORT
#define DECLDIR __declspec(dllexport)
#else
#define DECLDIR __declspec(dllimport)
#endif

extern "C"
{
    int SQ(int a);
}

#endif // LINK_1

As far as I can tell, I don't need _stdcall because I havnt changed anything in the Intel Fortran compiler. Is the 

__declspec

only necessary for Windows dlls? I have removed the whole DECLDIR section and regardless of this I get the error:

"Undefined reference to SQ". Could you provide me with some guidance here. Two notes:

1) MingW is the compiler for Qt, and I am supposing that incompatibility between g++ and Inteö Fortran means I just have to cross my fingers and hope it works and:

2) I plan to call a set of SUBROUTINES from my .cpp file. In order to do this I will need to define the input parameters as  Fortran types, is it not going to be better then to call ISO_C_BINDING from within my C++ application and then pass the values into the fortran modules as fortran types.

If we get through this I owe you Intel guys a beer!

0 Kudos
mecej4
Honored Contributor III
1,174 Views

J S. : I refrained from answering earlier since I do not know anything about QT. Now I see that, perhaps, that is no disqualification, since the real issue is to write a source code in Fortran that will be compiled with IFort into a 32-bit DLL, which will be called from C code compiled with 32-bit Mingw gcc. If so, here is a working example (I used MinGW32 GCC 4.9.2 and IFort 15.0.3, but combinations of  other versions of these 32-bit compilers should work).

Note that I have not used the C-interoperability features of Fortran, nor have I used any DEC$ directives. As a result, the code will probably not build on any operating system other than some version of Windows.

The C caller (cmain.c):

#include <stdio.h>
int main(){
extern int SQ(int *);
int x,y;
x=13; y=SQ(&x);
printf("%d %d\n",x,y);
}

The Fortran DLL source code (sq.f90):

function sq(x) result(y)
integer x,y
y=x*x
return
end function

Building and running:

ifort /LD sq.f90 /link /export:SQ
gcc cmain.c sq.lib -o cmain

D:\Mixed>cmain
1.300000e+001 1.690000e+002


One point that you seem to have missed is that in Intel Fortran, the default linkage is CREF (cdecl+ref), so you have to declare "int SQ(int *)" as the prototype instead of "int SQ(int)".

0 Kudos
JVanB
Valued Contributor II
1,174 Views

As shown in Quote #7 above, dynamically linking should go through OK, but now you are trying to achieve the linkage to the procedures at link time, rather than at run time. This is easy because ifort by default creates a *.LIB file when it compiles to a *.DLL file. Just include the *.LIB file in the gcc command that does the linking step and everything should be OK. Just tested this with the SQ.dll and SQ.lib that ifort built in Quote #7:

// test2.c
// compile with
// gcc test2.c SQ.lib -otest2
#include <stdio.h>

int SQ(int);

int main()
{
   int a;
   a = 17;
   printf("SQ(%d) = %d\n",a, SQ(a));
   return 0;
}


and it works. Don't have to worry about EAX or ESP, subroutines should be safe, and the only functions you need worry about are the ones that return a user-defined type (struct in C).

While we are on the subject of VBA (Quote #6) I tried switching to STDCALL, and everything went through OK with ifort, but gfortran seems to have a bug that requires a *.DEF file to work around. My reference was https://msdn.microsoft.com/EN-US/library/office/bb687850.aspx where it seems to indicate that using __declspec(dllexport), which we hope that !DEC$ ATTRIBUTES DLLEXPORT should be emulating, handles the name decoration issues correctly.

! std.f90
! Compiled with
! gfortran -shared std.f90 std.def -ostd.dll
! or
! ifort std.f90 /dll
module M
   implicit none
   contains
      function SQ(a) bind(C,name='SQ')
         use, intrinsic :: ISO_C_BINDING
         implicit none
!DEC$ ATTRIBUTES STDCALL,DLLEXPORT :: SQ
!GCC$ ATTRIBUTES STDCALL,DLLEXPORT :: SQ
         integer(C_INT) SQ
         integer(C_INT),value :: a
         SQ = a**2
      end function SQ
end module M
// stdprog.c
// Compiled with
// gcc stdprog.c -ostdprog
#include <stdio.h>
#include <windows.h>

int main()
{
   HMODULE dll_handle;
   int (__stdcall *SQ)(int);
   int a;

// Check for bitness -- this would be easy in 64-bit mode.
   printf("This is a %d-bit program\n",8*sizeof(HANDLE));
   dll_handle = LoadLibrary("std.dll");
   printf("dll_handle = %p\n",dll_handle);
   SQ = (void *)GetProcAddress(dll_handle,"SQ");
   printf("SQ = %p\n",SQ);
   a = 17;
   printf("SQ(%d) = %d\n", a, SQ(a));
   return 0;
}

As remarked above, this works fine with ifort, but gfortran needed that workaround with a *.DEF file

; std.def
LIBRARY std
EXPORTS
   SQ = SQ@4


As can be seen, gfortran is stripping off the leading underscore but not the trailing at sign and byte count.

 

0 Kudos
J_S_1
Beginner
1,174 Views

Thankyou for the quick answer mecej4. Ok perhaps at this stage I should provide a much broader description of my task, I don't want to be moving towards progressively more confusing questions without you at least having a good overview of what I am actually trying to do.

I have an application developed in Qt (C++) which I compile with 32-bit Mingw gcc. I have an extra program, written in .f90 (which has been configured to be compiled with Intel Fortran on VS2012) which I am attempting to make a dynamic library out of, so that I can access its functionality. The fortran code is executed from the command line and if of the type PROGRAM. This PROGRAM USE's then a number of elsewhere defined modules and typedefs to input to and CALL four major subroutines. Each subroutine has a number of (very very many) dependencies and modules/subroutines associated with them.

My plan is to essentially "remove" the Program.f90 main file and emulate it within my main code so that I can interrmittently manipulate/extract the data between steps.

This includes:

- Write an extra module which allows me to export and declare the input types in my main program.

- Export each of the four major subroutines and prototype them in my main program.

- Call these four subroutines when desired from within my main program 

It could very well be that my understanding is insufficient or flawed, but as far as I can tell, it will be simplest to create the new modules necessary within the code, and export them along with the four major subroutines into a dll so that I can execute them from within my main code.

 

I have already compiled a simplified form of the dll, and as we saw above, this CAN (in a very simple way) communicate with my application. The task is now trying to export and import the subroutines from my program.

IF THERE IS ANY CONFUSION ABOUT WHAT I AM DOING; OR ANY GAPING FLAWS IN MY LOGIC; PLEASE DO NOT HESITATE TO STATE THIS NOW, I only started on this  a few weeks ago and I am new to mixed-language programming.

 

Com

0 Kudos
JVanB
Valued Contributor II
1,174 Views

Well, I tried the *.DLL built with ifort in Excel VBA and it worked, but the version built with gfortran and the *.DEF file didn't. I don't know what the problem is now with gfortran, but at least it can be seen that BIND(C) is OK for ifort, which is good in my opinion because it's closer to the standard than !DEC$ ATTRIBUTES C.

 

0 Kudos
mecej4
Honored Contributor III
1,174 Views

R.O,:

There is a similar problem with getting MSVC to give us the kind of name decoration that we want. I ran into that several months ago, and no longer remember the details well, but I think that it will only permit a limited number of combinations of @nn, prefix underscore, suffix underscore and symbol case (upper/lower).

Since symbol renaming cannot be done using <intname=extname> in .DEF files for prebuilt DLLs for which source code is not available, remaining mismatches had to be fixed on the Fortran side.

My old fallback solution is to write an assembler file containing a macro to do the necessary redecoration, and invoke the macro with something like

    cnv dgr29, DGR29

as often as needed. This macro results in creating an procedure entry "dgr29", at which point there is a jump to the external entry DGR29.

J S.:

The problem with the code extracts in #8 is that you #define-d DECDIR, but the definition does nothing because there is no place later in the code where DECDIR is used.

I think that the issues that you described in #11 should be handled easily along the lines outlined in #9. To move things along, post the Fortran interface of one of the routines that you intend to put into the DLL, and post the corresponding C prototype that you will put in the caller. Pick any non-trivial subroutine for this purpose. In particular, if you wish to pass strings between Fortran and C, you could choose that at the test case.

If you wish to pass multidimensional arrays, that will be a bit more complicated, because these are not 100 percent compatible.

0 Kudos
JVanB
Valued Contributor II
1,174 Views

I found the problem with gfortran: I don't have the gfortran bin directory on path normal %Path%, I only set it up for a special command prompt, so Excel reports file not found when it's actually gfortran's run time library *.DLL that can't be found, not the std.dll file that gfortran built. Putting gfortran's bin directory on my default %Path% permits std.dll built with gfortran to now work with gfortran.

Back to the original topic: since the task consists of porting subroutines, the incompatibility mentioned in Quote #5 is not an issue. Since BIND(C) rather than the !DEC$ ATTRIBUTES C, DLLEXPORT,ALIAS:"SQ" :: SQ of Quote #4 has been tested and works as in Quote #7 I think you should go that way... perhaps I can get Steve's vote of confidence on this? Of course this depends on the data types that are passed to the subroutines. If any user-defined types can be cast as BIND(C), life should be really easy. If not, the C code is going to have some problems digesting them anyhow, and they might have to be redesigned a bit.

 

0 Kudos
J_S_1
Beginner
1,174 Views

Thanks R.O. This is definitely a lot for me to work with!

I have tried defining the function you gave me above within a larger module as :

PUBLIC :: SQ

and I get the compilation error:

error #8143: The BIND(C) attribute for this symbol conflicts with a DEC$ ATTRIBUTES ALIAS, DECORATE, STDCALL, C, [NO_]MIXED_STR_LEN_ARG or REFERENCE attribute for this symbol.   [SQ]    ...

I'm guessing this is a result of not defining it withing its own module as you have done...I tried removing a few items but that didn't particulary help. Also, does your approach mean that if I wish to create the linkage to the procedures at link time, rather than at run time, I have no need for header files for the imported functions?

 

0 Kudos
Steven_L_Intel1
Employee
1,174 Views
RO, was your experiment 32-bit or 64-bit? If 32, I would expect BIND(C) to generate the name _SQ and the GetProcAddress to fail, but maybe it has some funny logic to ignore the decoration. If 64-bit, then there's no underscore and everything is hunky-dory.
0 Kudos
JVanB
Valued Contributor II
1,174 Views

Steve, in Quote #7 you will observe that my C main program computes and prints out the bitness as 32-bit and also printing out the pointers with %p format they are printing out as 32-bit pointers. I tried to be real careful to use only 32-bit tools, installing a new version of 32-bit gfortran on my computer along the way as well. If you go back to the link I posted in Quote #10, the name decoration properties work as described there, as far as I can tell, except for the bug noted in gfortran for STDCALL procedures in Quote #10. Try building a little *.DLL using my Fortran code from Quote #7 or Quote #10 above and just look at the *.DLL with DUMPBIN /EXPORTS. Or you could try the full example from Quote #7 or Quote #10 and test it with whatever C compiler you have available -- gcc shouldn't be necessary for these examples to work.

I was kind of surprised that everything went through so smoothly like this with BIND(C). That means it's possible for code that interoperates like this to look more like normal f2003 code.

 

0 Kudos
JVanB
Valued Contributor II
1,174 Views

In Quote #3 we see:

!DEC$ ATTRIBUTES C, REFERENCE, MIXED_STR_LEN_ARG, DLLEXPORT, ALIAS:"SQ" :: SQ

But in Quote #15,

error #8143: The BIND(C) attribute for this symbol conflicts with a DEC$ ATTRIBUTES ALIAS, DECORATE, STDCALL, C, [NO_]MIXED_STR_LEN_ARG or REFERENCE attribute for this symbol.   [SQ]

My guess is that in the actual code, you have still the ATTRIBUTES from Quote #3. The C attribute is the default for BIND(C), so it isn't needed. Also REFERENCE is the default, with no way to change it except by adding the VALUE attribute to individual arguments as needed (the standard Fortran VALUE attribute, not the !DEC$ ATTRIBUTES VALUE attribute!). The ALIAS:"SQ" is taken care of by the NAME='SQ' suffix in the BIND(C) clause.

You have to get rid of MIXED_STR_LEN_ARG because BIND(C) doesn't allow for CHARACTER arguments with LEN other than 1. If your actual subroutines do have such arguments, let us know and there is a simple way to work around this limitation. Thus with BIND(C), the only attribute you should specify is

!DEC$ ATTRIBUTES DLLEXPORT :: SQ

 

0 Kudos
J_S_1
Beginner
1,174 Views

Gentlemen I state once again, thankyou for all of your help! I continue with the issues outlined previously:

==> RO:  The error was in fact my use of STDCALL in the attributes which was causing the issue, I had removed the other attributes. I'm unsure if this is a problem, I am compiling with: Runtime Library: Multithreaded and Calling convention default. With all code exactly as yours in my main program I get the error:

invalid conversion from 'void*' to 'int(__attribute__((__stdcall__))*)(int)' [-fpermissive]

==> mecej4: The main PROGRAM file opens with:

USE FAST_IO_Subs   ! all of the ModuleName and ModuleName_types modules are inherited from FAST_IO_Subs                    
IMPLICIT  NONE
   ! Local variables:
REAL(DbKi),             PARAMETER     :: t_initial = 0.0_DbKi                    
   ! Data for the glue code:
TYPE(FAST_ParameterType)              :: p_FAST                                  
TYPE(FAST_OutputFileType)             :: y_FAST                                  
TYPE(FAST_MiscVarType)                :: m_FAST                                  
TYPE(FAST_ModuleMapType)              :: MeshMapData                             
TYPE(ElastoDyn_Data)                  :: ED                                      

... there is a list of about 14 of these variables (and example of which I will provide later) and each of these are fed into four SUBROUTINES. An example of which is given by:

CALL FAST_InitializeAll(t_initial, n_t_global, p_FAST, y_FAST, m_FAST, ED, SrvD, AD, IfW, HD, SD, MAPp, FEAM, MD, IceF, IceD, MeshMapData, ErrStat, ErrMsg )

My logic is to initialize each of the parameters (p_FAST, y_FAST etc) in my main program (Qt) and then pass these into the four subroutines ( which are declared in my dll) and that way I can control when and how the subroutines are called.

Actually passing initializing arguments to these parameters is taken care of in the subroutines, they are all assigned values in the subroutine FAST_InitializeAll, so I needn't worry about EVERY declared type, I just need to be able to initialize, say "ED" within Qt.

Now in terms of the declared types: The -ElastoDyn_Data- type for example is defined as: 

TYPE, PUBLIC :: ElastoDyn_Data
    TYPE(ED_ContinuousStateType) , DIMENSION(1:2)  :: x     
    TYPE(ED_DiscreteStateType) , DIMENSION(1:2)  :: xd      
    ...
    TYPE(ED_InputType)  :: u      ! System inputs [-]
    ...
    TYPE(ED_OutputType) , DIMENSION(:), ALLOCATABLE  :: Output      
    TYPE(ED_InputType) , DIMENSION(:), ALLOCATABLE  :: Input     
    REAL(DbKi) , DIMENSION(:), ALLOCATABLE  :: InputTimes      
  END TYPE ElastoDyn_Data

And in further detail, TYPE(ED_InputType) is defined as:

 TYPE, PUBLIC :: ED_InputType
    TYPE(MeshType) , DIMENSION(:), ALLOCATABLE  :: BladeLn2Mesh    
    ...  
    REAL(ReKi) , DIMENSION(:,:,:), ALLOCATABLE  :: TwrAddedMass 
    REAL(ReKi) , DIMENSION(1:6,1:6)  :: PtfmAddedMass      
    ...
    REAL(ReKi)  :: HSSBrTrqC     
  END TYPE ED_InputType

My logic is this time to define, say, an export function in in the fortran code such as:

function GIVEmeED() 
    implicit none
    !DEC$ ATTRIBUTES DLLEXPORT :: GIVEmeED
    !GCC$ ATTRIBUTES DLLEXPORT :: GIVEmeED
    type(ED_InputType) GIVEmeED
    type(ED_InputType) :: VAR
    GIVEmeED = VAR
end function GIVEmeED

I understand this probably still won't work, but we realise now the issue of not being able to use BIND(C) on everything, that requires redefning EVERYTHING and most likely modifying a number of subroutines.

Now you have an idea of the scale of the implementation I am planning. Is my logic flawed? Will there by major issues passing information between the two programs and/or am I going to be able to allow the subroutines to act upon the parameters.

 

0 Kudos
J_S_1
Beginner
1,174 Views

I think perhaps it would be better, from this point of view to avoid BIND(C), as this means trying to define every variable with C calling convention, whereas it may be much easier to include iso_c_binding in my main program and define my fortran variables there.

 

0 Kudos
JVanB
Valued Contributor II
1,114 Views

The issue with STDCALL is a bit of a problem. You appear of have an old version of Intel Fortran, because STDCALL has been allowed in BIND(C) procedures for a year now (2? did time fly by that fast?) You copied my example from Quote #10, which was intended just to show that STDCALL could work, but my assumption until now was that you wanted to stick with the default calling convention instead. If you want to switch to STDCALL you will have to upgrade your compiler of use !DEC$ ATTRIBUTES throughout.

ALLOCATABLEs in user-defined types is going to be a problem because C isn't going to readily be able to read or write them. Such user-defined types aren't interoperable so you can't give procedures with them as arguments the BIND property which is going to make interfacing ugly. Maybe you can get by with C knowing the addresses of instances of such types as opaque pointers and have a couple of Fortran helper functions available to extract the addresses of the arrays in the ALLOCATABLEs and return them to C.

The error the you showed was an issue about casting pointers in C. memcpy() is the sanctioned way to sanitize this casting in current C language dogma. I have an updated set of examples. First is the Fortran file that makes the *.DLL and perhaps *.LIB file.

! SQ.f90
! Compiled with
! gfortran -shared SQ.f90 -oSQ.dll -Wl,--out-implib,SQ.lib
! or
! ifort SQ.f90 /dll
module M
   implicit none
   contains
      function SQ(a) bind(C,name='SQ')
         use, intrinsic :: ISO_C_BINDING
         implicit none
!DEC$ ATTRIBUTES DLLEXPORT :: SQ
!GCC$ ATTRIBUTES DLLEXPORT :: SQ
         integer(C_INT) SQ
         integer(C_INT),value :: a
         SQ = a**2
      end function SQ
end module M

The above works in all combinations except when compiled with gfortran as shown in combination with fprog2.f90 compiled with ifort. Accordingly, we will compile this with ifort as shown. For dynamically linking with C, I provide an updated example with a header file because you asked for it, and also sanitization with memcpy().

// pSQ.h

typedef int(*pSQ)(int);
// Cprog1.c
// Compiled with
// gcc Cprog1.c -oCprog1
#include <stdio.h>
#include <windows.h>
#include "pSQ.h"

int main()
{
   HMODULE dll_handle;
   pSQ SQ;
   int a;
   FARPROC temp;

// Check for bitness -- this would be easy in 64-bit mode.
   printf("This is a %d-bit program\n",8*sizeof(HANDLE));
   dll_handle = LoadLibrary("SQ.dll");
   printf("dll_handle = %p\n",dll_handle);
   temp = GetProcAddress(dll_handle,"SQ");
   memcpy(&SQ,&temp,sizeof(temp));
   printf("SQ = %p\n",SQ);
   a = 17;
   printf("SQ(%d) = %d\n", a, SQ(a));
   return 0;
}

Output:

This is a 32-bit program
dll_handle = 64DB0000
SQ = 64DB1010
SQ(17) = 289

Now for linking via a library with C, also with a header file,

// SQ.h

int SQ(int a);
// Cprog2.c
// compile with
// gcc Cprog2.c SQ.lib -oCprog2
#include <stdio.h>
#include "SQ.h"

int main()
{
   int a;
// Check for bitness -- this would be easy in 64-bit mode.
   printf("This is a %d-bit program\n",8*sizeof(intptr_t));
   a = 17;
   printf("SQ(%d) = %d\n",a, SQ(a));
   return 0;
}


Output:

This is a 32-bit program
SQ(17) = 289

Fortran with dynamic linking

! fprog1.f90
! Compile with
! ifort fprog1.f90
program P
!   use M
   use ISO_C_BINDING
   use IFWIN
   implicit none
   abstract interface
      function SQ1(a) bind(C)
         use ISO_C_BINDING
         implicit none
         integer(C_INT) SQ1
         integer(C_INT), value :: a
      end function SQ1
   end interface
!   procedure(SQ), pointer :: pSQ
   procedure(SQ1), pointer :: pSQ
   type(C_FUNPTR) cpSQ
   integer(HANDLE) H
   integer(C_INT) a

   write(*,'(*(g0))') 'This is a ',bit_size(H),'-bit program'
   H = LoadLibrary('SQ.dll'//achar(0))
   write(*,'(z0)') H
   cpSQ = transfer(GetProcAddress(H,'SQ'//achar(0)),cpSQ)
   write(*,'(z0)') transfer(cpSQ,0_C_INTPTR_T)
   call C_F_PROCPOINTER(cpSQ,pSQ)
   a = 17
   write(*,*) a,pSQ(a)
end program P

Output:

This is a 32-bit program
64DB0000
64DB1010
          17         289

Fortran linking via a library

! fprog2.f90
! Compile with
! ifort fprog2.f90 SQ.lib
! or
! gfortran fprog2.f90 SQ.lib -ofprog2
program P
   use ISO_C_BINDING
   implicit none
   interface
      function SQ(a) bind(C,name='SQ')
         use ISO_C_BINDING
         implicit none
         integer(C_INT) SQ
         integer(C_INT), value :: a
      end function SQ
   end interface
   integer(C_INT) a

   write(*,'(*(g0))') 'This is a ',bit_size(0_C_INTPTR_T),'-bit program'
   a = 17
   write(*,*) a,SQ(a)
end program P

Output:

This is a 32-bit program
          17         289

One problem I noticed is the in the Fortran dynamically linked example (fprog1.f90 above) if you try to use the interface from module M to prototype function SQ by uncommenting the use M line and then using the first declaration for pSQ:

! fprog1.f90
! Compile with
! ifort fprog1.f90
program P
   use M
   use ISO_C_BINDING
   use IFWIN
   implicit none
   abstract interface
      function SQ1(a) bind(C)
         use ISO_C_BINDING
         implicit none
         integer(C_INT) SQ1
         integer(C_INT), value :: a
      end function SQ1
   end interface
   procedure(SQ), pointer :: pSQ
!   procedure(SQ1), pointer :: pSQ
   type(C_FUNPTR) cpSQ
   integer(HANDLE) H
   integer(C_INT) a

   write(*,'(*(g0))') 'This is a ',bit_size(H),'-bit program'
   H = LoadLibrary('SQ.dll'//achar(0))
   write(*,'(z0)') H
   cpSQ = transfer(GetProcAddress(H,'SQ'//achar(0)),cpSQ)
   write(*,'(z0)') transfer(cpSQ,0_C_INTPTR_T)
   call C_F_PROCPOINTER(cpSQ,pSQ)
   a = 17
   write(*,*) a,pSQ(a)
end program P

You get a strange error at link time

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

-out:fprog1.exe
-subsystem:console
fprog1.obj
fprog1.obj : error LNK2019: unresolved external symbol PSQ referenced in functio
n _MAIN__
fprog1.exe : fatal error LNK1120: 1 unresolved externals

The error seems to have been triggered by the !DEC$ ATTRIBUTES DLLEXPORT for SQ in SQ.f90 and then the first use of pSQ in fprog1.f90. I'm not sure that my code is consistent with ifort's documented behavior for prototyping procedure pointers, but even if it is there is no way that I should be getting an error at link time rather than at compile time. Curiously, gfortran throws an error like 'confused by previous errors, bailing' but with no previous errors on analogous code.

 

0 Kudos
Reply