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

Calling a Fortran DLL in a Fortran poject

Dave12
Novice
4,356 Views

Hello everyone,

I am writing a Fortran program using Visual Studio and the ifort compiler. I would now like to use a DLL that has been made available to me. This was also written in Fortran. I already had a similar problem with a DLL written in C++, which was solved here (https://community.intel.com/t5/Intel-Fortran-Compiler/Integrate-C-DLL-in-Fortran-Code/m-p/1410919#M162620). Of course, this solution no longer works if it is not a C++ DLL.
Unfortunately, I only find suggestions on how Fortran DLLs can be used in other programming languages. What I have found are approaches like:

      !DEC$ ATTRIBUTES DLLIMPORT :: libdiscon
      !DEC$ ATTRIBUTES C, ALIAS:'DISCON' :: DISCON
    
      call DISCON(SWAP, Fail, infile, outname, msg )

The DLL I want to use is called libdiscon.dll. It contains the subroutine DISCON(), which I would like to access. I have placed libdiscon.dll in the debug folder of Visual Studio.

I would be very grateful if someone could help me.

Best regards

 

 

 

 

 

0 Kudos
44 Replies
Arjen_Markus
Honored Contributor I
2,591 Views

Your DLL "libdiscon.dll" should be accompanied by a so-called import library, "libdiscon.lib". Specify that import library with the link step. This should make the routine you want available via the DLL. To run the program that DLL will have to be found, one way is to copy it into the directory containing the executable.

There is no need for the !DEC$ compiler directives, unless perhaps you need global data from the DLL or vice versa, but that is an entirely different story.

0 Kudos
Dave12
Novice
2,572 Views

Thank you for the reply.

I don't compile the code myself and only get the DLL provided. In addition, I have access to a DYLIB file. However, this is probably the Linux equivalent of the DLL instead of the LIB file I am looking for.
Is it possible to generate the LIB file from the DLL?

0 Kudos
Arjen_Markus
Honored Contributor I
2,568 Views

The import library is a byproduct of the link step, so that should be available. Alternatives are that you load the DLL explicitly and get a pointer to the routine. That would involve some coding. I know there is a utility that can produce the missing import library. I know there is one in Cygwin, but this webpage might help: https://www.asawicki.info/news_1420_generating_lib_file_for_dll_library, even if it is old.

0 Kudos
Dave12
Novice
2,562 Views

I was able to create libdiscon.lib with the instructions in the link. In VS I added it under

Linker ->Input -> libdiscon.lib,

Linker -> Input -> Additional Library Dependencies -> (path to directory)

and under

Fortran -> Additional Include Directories -> (path to directory).


How can I now access the subroutine DISCON() in the code? I have found some results regarding C++. There, commands of the type #include are still needed.

0 Kudos
Arjen_Markus
Honored Contributor I
2,560 Views

You use it in just the same way as any routine from an ordinary library. (I may try to put a small example together.)

0 Kudos
Arjen_Markus
Honored Contributor I
2,552 Views

Here we are - two source files and a small batch file to create the program and the DLL+import library.

0 Kudos
Dave12
Novice
2,545 Views

Thank you, that is very helpful. Analogously I did the following in the module where I want to use the DLL:

module class_controller

    implicit none
    
    type :: model_controller
        logical   :: boolean_abort = .FALSE. 
        
    contains
        procedure :: use_controller
        procedure :: discon
      
    end type model_controller
    
        
    contains
    
    subroutine use_controller(this, swap)
      use, intrinsic :: iso_c_binding, only : c_float, c_int, c_char, c_null_char
     
      implicit none
     
      class(model_controller), intent(inout)       :: this
      real(kind = 8), dimension(78), intent(inout) :: swap
      real(c_float), dimension(78)                 :: swap_c_float
      real(c_float)                                :: ctrl_avrSWAP(78)                    
      integer(c_int)                               :: ctrl_aviFAIL                    
      character(kind=c_char,len=28)                :: ctrl_accINFILE
      character(kind=c_char,len=51)                :: ctrl_avcOUTNAME           
      character(kind=c_char,len=49)                :: ctrl_avcMSG    

      call discon(ctrl_avrSWAP, ctrl_aviFAIL, ctrl_accINFILE, ctrl_avcOUTNAME, ctrl_avcMSG)
    
    end subroutine use_controller
    
    subroutine discon(avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG)
    REAL(C_FLOAT),dimension(78),    INTENT(INOUT)   :: avrSWAP      
    INTEGER(C_INT),                 INTENT(INOUT)   :: aviFAIL      
    CHARACTER(KIND=C_CHAR, len = 28), INTENT(IN)    :: accINFILE    
    CHARACTER(KIND=C_CHAR, len = 20), INTENT(IN)     :: avcOUTNAME  
    CHARACTER(KIND=C_CHAR, len = 49), INTENT(INOUT) :: avcMSG  
    
    !DEC$ ATTRIBUTES DLLEXPORT :: DISCON
    end subroutine discon
    
end module class_controller

 I used the same inputs and outputs as in the called subroutine itself. The subroutine I want to use starts as follows:

SUBROUTINE DISCON(avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG) BIND (C, NAME='DISCON')
USE, INTRINSIC  :: ISO_C_Binding
USE             :: ROSCO_Types
USE             :: ReadSetParameters
USE             :: ControllerBlocks
USE             :: Controllers
USE             :: Constants
USE             :: Filters
USE             :: Functions
USE             :: ExtControl
USE             :: ROSCO_IO
USE             :: ZeroMQInterface

IMPLICIT NONE
! Enable .dll export
#ifndef IMPLICIT_DLLEXPORT
!DEC$ ATTRIBUTES DLLEXPORT :: DISCON
!GCC$ ATTRIBUTES DLLEXPORT :: DISCON
#endif

REAL(C_FLOAT),                  INTENT(INOUT)   :: avrSWAP(*)                       
INTEGER(C_INT),                 INTENT(INOUT)   :: aviFAIL                          
CHARACTER(KIND=C_CHAR),         INTENT(IN   )   :: accINFILE(NINT(avrSWAP(50)))     
CHARACTER(KIND=C_CHAR),         INTENT(IN   )   :: avcOUTNAME(NINT(avrSWAP(51)))    
CHARACTER(KIND=C_CHAR),         INTENT(INOUT)   :: avcMSG(NINT(avrSWAP(49)))        

 I receive the following error messages:

error #8262: For a type-bound procedure that has the PASS binding attribute, the first dummy argument must have the same declared type as the type being defined. [AVRSWAP]

error #6683: A kind type parameter must be a compile-time constant. [C_FLOAT]

error #6683: A kind type parameter must be a compile-time constant. [C_INT]

error #6683: A kind type parameter must be a compile-time constant. [C_CHAR]

error #6683: A kind type parameter must be a compile-time constant. [C_CHAR]

error #6683: A kind type parameter must be a compile-time constant. [C_CHAR]

 

It seems that the subroutine is read in correctly.

Do you the problem that causes the error messages?

0 Kudos
jimdempseyatthecove
Honored Contributor III
2,529 Views

Your subroutine discon is missing USE, INTRINSIC :: ISO_C_Binding, only : c_float, c_int, c_char, c_null_char

Also, the model_controller contained procedure declaration of discon has no explicit attribute of PASS nor NOPASS. The default is PASS. See: Passed-Object Dummy Arguments (intel.com)

 

In order to access any member variables (boolian_abort), it would be required to PASS the object pointer (which you may name THIS or any other suitable name). NOPASS can be used on contained procedures that do not reference member objects of the object being used to access the procedure.

 

EDIT: FWIW Unlike C++ where the this pointer to the object is passed as a hidden argument named this, Fortran requires an explicit declaration of the object pointer as the first argument to the procedure.

 

Jim Dempsey

 

0 Kudos
Dave12
Novice
2,493 Views

You are right. I have corrected the matter. Unfortunately, it still doesn't work to call the subroutine. It seems that the variables do not match. Could it be because the size of these is not yet allocated? Shouldn't they be assigned their size by the function call?
Below is the corrected code:

module class_controller

    implicit none
    
    type :: model_controller
        logical   :: boolean_abort = .FALSE.    
    contains
        procedure :: use_controller
        procedure :: discon
    end type model_controller
    contains
    
    subroutine use_controller(this, swap)
      use, intrinsic :: iso_c_binding, only : c_float, c_int, c_char, c_null_char
      implicit none
     
      class(model_controller), intent(inout)       :: this
      real(kind = 8), dimension(78), intent(inout) :: swap
      real(c_float), dimension(78)                 :: swap_c_float
      
      real(c_float), dimension(78)                 :: ctrl_avrSWAP                    
      integer(c_int)                               :: ctrl_aviFAIL                    
      character(kind=c_char,len=28)                :: ctrl_accINFILE
      character(kind=c_char,len=20)                :: ctrl_avcOUTNAME           
      character(kind=c_char,len=49)                :: ctrl_avcMSG    

      call discon(ctrl_avrSWAP, ctrl_aviFAIL, ctrl_accINFILE, ctrl_avcOUTNAME, ctrl_avcMSG)
    
    end subroutine use_controller
    
    subroutine discon(this, avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG)
    use, intrinsic :: iso_c_binding, only : c_float, c_int, c_char, c_null_char
    implicit none
    
    class(model_controller), intent(inout)          :: this
    REAL(C_FLOAT),dimension(78),    INTENT(INOUT)   :: avrSWAP     
    INTEGER(C_INT),                 INTENT(INOUT)   :: aviFAIL     
    CHARACTER(KIND=C_CHAR, len = 28), INTENT(IN)    :: accINFILE    
    CHARACTER(KIND=C_CHAR, len = 20), INTENT(IN)    :: avcOUTNAME  
    CHARACTER(KIND=C_CHAR, len = 49), INTENT(INOUT) :: avcMSG  
    
    !DEC$ ATTRIBUTES DLLEXPORT :: DISCON

    end subroutine discon
    
end module class_controller

 leading to:

error #6631: A non-optional actual argument must be present when invoking a procedure with an explicit interface. [AVCMSG]
error #6633: The type of the actual argument differs from the type of the dummy argument. [CTRL_AVIFAIL]
error #6634: The shape matching rules of actual arguments and dummy arguments have been violated. [CTRL_AVRSWAP]
error #8284: If the actual argument is scalar, the dummy argument shall be scalar unless the actual argument is of type character or is an element of an array that is not assumed shape, pointer, or polymorphic. [AVRSWAP] 2
error #6633: The type of the actual argument differs from the type of the dummy argument. [CTRL_ACCINFILE] 
error #7938: Character length argument mismatch. [CTRL_AVCOUTNAME]
error #6633: The type of the actual argument differs from the type of the dummy argument. [CTRL_AVRSWAP]

0 Kudos
Arjen_Markus
Honored Contributor I
2,488 Views

You forgot the argument "this" to the call to DISCON. Easy enough to overlook :).

0 Kudos
Dave12
Novice
2,479 Views

That's true, thank you!

The programme now runs without error message. When creating the project in the output window of Visual Studio, nothing like :

"Programm.exe" (Win32): "C:\Program Files (x86)\...\libdiscon.dll" is loaded . Symbols have been loaded"

is displayed. Would this be expected if the DLL was actually read in correctly?
I have placed the DLL in the working directory as well as in the folder where the EXE is created.

0 Kudos
Arjen_Markus
Honored Contributor I
2,475 Views

If the DLL was not loaded correctly, your program would not work. I am not sure when these messages are  displayed, but I can imagine that the pdb file was not present, so that Visual Studio would not know how to debug the routines in the DLL and therefore is silent. But that is utter divination and I would not count on it being even remotely pointing in the right direction.

0 Kudos
Steve_Lionel
Honored Contributor III
2,455 Views

The Visual Studio debugger will always tell you which DLLs have been loaded and whether or not symbols are available. This is merely informational. You won't see this running the program from outside the debugger.

0 Kudos
Dave12
Novice
2,422 Views

But as I am working in debug mode at the moment, so should the successful loading of the DLL already be indicated?
What Arjen_Markus says also makes sense to me, since an error would probably occur if the subroutine were not available.

0 Kudos
Dave12
Novice
2,417 Views

And an other question:

why do you propose that I use

!DEC$ ATTRIBUTES DLLEXPORT :: DISCON

instead of

!DEC$ ATTRIBUTES DLLIMPORT :: DISCON

Would't this be the commad to generate a DLL from a routine? I tried to use DLLIMPORT but it causes an error message. I would like to understand the difference in that case.

Thank you in advance

0 Kudos
Steve_Lionel
Honored Contributor III
2,406 Views

It did tell you the DLL was loaded.

DLLEXPORT is used when you are building a DLL and you want a particular procedure, variable or COMMON block to be visible to users of the DLL. It is this that triggers the creation of the import library (.lib) when you build the DLL.

DLLIMPORT is used to tell the compiler that the symbol you're referencing is coming from a DLL. This isn't required for procedures but is for data.

If you are building a DLL using modules, and have DLLEXPORT directives for things in that module, the compiler treats them as DLLIMPORT when the module is USEd. This can lead to informational messages when the DLL is linked about imported symbols already defined, but you can ignore these.

0 Kudos
Dave12
Novice
2,225 Views

Thank you for the explainations.

@Steve_Lionel, you said it told me that the DLL was loaded. Shouldn't that also be in the output window?
libdiscon.dll is not listed there. In addition, the DISCON function I want to use does not seem to work. Still I do not get any error messages.

0 Kudos
Steve_Lionel
Honored Contributor III
2,177 Views

@Steve_Lionel, you said it told me that the DLL was loaded. Shouldn't that also be in the output window?
libdiscon.dll is not listed there. In addition, the DISCON function I want to use does not seem to work. Still I do not get any error messages.


Since I don't know what you did see, I can't comment. Please show, don't describe. I built a small application with a DLL and did get a message about my DLL in the output window. (Line 6)

'Console20.exe' (Win32): Loaded 'D:\Projects\Console20\x64\Debug\Console20.exe'. Symbols loaded.
'Console20.exe' (Win32): Loaded 'C:\Windows\System32\ntdll.dll'. 
'Console20.exe' (Win32): Loaded 'C:\Windows\System32\kernel32.dll'. 
'Console20.exe' (Win32): Loaded 'C:\Windows\System32\KernelBase.dll'. 
'Console20.exe' (Win32): Loaded 'C:\Windows\System32\apphelp.dll'. 
'Console20.exe' (Win32): Loaded 'D:\Projects\Console20\Dll1.dll'. Symbols loaded.
'Console20.exe' (Win32): Loaded 'C:\Windows\System32\vcruntime140d.dll'. 
'Console20.exe' (Win32): Loaded 'C:\Windows\System32\ucrtbased.dll'. 
'Console20.exe' (Win32): Loaded 'C:\Program Files (x86)\Intel\oneAPI\compiler\2022.2.0\windows\redist\intel64_win\compiler\libifcoremdd.dll'. Module was built without symbols.
'Console20.exe' (Win32): Loaded 'C:\Program Files (x86)\Intel\oneAPI\compiler\2022.2.0\windows\redist\intel64_win\compiler\libifcoremdd.dll'. Module was built without symbols.
'Console20.exe' (Win32): Unloaded 'C:\Program Files (x86)\Intel\oneAPI\compiler\2022.2.0\windows\redist\intel64_win\compiler\libifcoremdd.dll'
'Console20.exe' (Win32): Loaded 'C:\Windows\System32\imagehlp.dll'. 
'Console20.exe' (Win32): Loaded 'C:\Windows\System32\ucrtbase.dll'. 
'Console20.exe' (Win32): Loaded 'C:\Program Files (x86)\Intel\oneAPI\compiler\2022.2.0\windows\redist\intel64_win\compiler\libmmd.dll'. Symbols loaded.

 

 

0 Kudos
Dave12
Novice
2,180 Views

The output window shows the following:

"DeSiO.exe" (Win32): "C:\Users\Documents\DeSiO\DeSiO\DeSiO\x64\Debug\DeSiO.exe" loaded. Symbols loaded.
"DeSiO.exe" (Win32): "C:\Windows\System32\ntdll.dll" loaded. 
"DeSiO.exe" (Win32): "C:\Windows\System32\kernel32.dll" loaded. 
"DeSiO.exe" (Win32): "C:\Windows\System32\KernelBase.dll" loaded. 
"DeSiO.exe" (Win32): "C:\Windows\System32\vcruntime140d.dll" loaded. 
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\compiler\libifcoremdd.dll" loaded. Module was built without symbols.
"DeSiO.exe" (Win32): "C:\Windows\System32\imagehlp.dll" loaded. 
"DeSiO.exe" (Win32): "C:\Windows\System32\ucrtbase.dll" loaded. 
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\mkl\mkl_intel_thread.dll" loaded. Module was built without symbols.
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\compiler\libmmdd.dll" loaded. Symbols loaded.
"DeSiO.exe" (Win32): "C:\Windows\System32\ucrtbased.dll" loaded. 
"DeSiO.exe" (Win32): "C:\Windows\System32\ucrtbased.dll" loaded. 
"DeSiO.exe" (Win32): "C:\Windows\System32\ucrtbased.dll" unloaded.
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\compiler\libmmd.dll" loaded. Symbols loaded.
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\mkl\mkl_core.dll" loaded. Module was built without symbols.
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\mkl\mkl_avx512.dll" loaded. Module was built without symbols.
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\mkl\mkl_vml_avx512.dll" loaded. Module was built without symbols.
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\mkl\libimalloc.dll" loaded. Module was built without symbols.
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\mkl\libimalloc.dll" unloaded.
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\mkl\libimalloc.dll" loaded. Module was built without symbols.
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\mkl\libimalloc.dll" unloaded.
"DeSiO.exe" (Win32): "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\redist\intel64_win\compiler\libiomp5md.dll" loaded. Symbols loaded.

So the libdiscon.dll does not appear here. However, when I remove the LIB from the folder, I get an error message. Could the error have occurred when creating the LIB from the DLL?
Another thing I wonder is why the commands associated with C programming such as "iso_c_binding" are used at all, when the called DLL was created in Fortran.

0 Kudos
Dave12
Novice
2,223 Views

Furthermore, my observation is that the programme does the same when I comment out the line which calls the DLL

 !DEC$ ATTRIBUTES DLLEXPORT :: DISCON

The only difference is that a programme.lib file is created.

0 Kudos
Reply