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

Calling a Fortran DLL in a Fortran poject

Dave12
Novice
4,902 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
Steve_Lionel
Honored Contributor III
1,584 Views

Please change the project property Linker > General > Show Progress to "Display all progress messages (/VERBOSE)", rebuild your project, ZIP the buildlog.htm in the Debug folder and attach the zip to a reply here. 

As for iso_c_binding, this intrinsic module is used by Fortran code and doesn't necessarily need to involve C. What evidence do you have that this is being used?

Can you just ZIP the whole solution folder and attach it here? (Do a Build > Clean Solution) first. There's a lot you haven't shown us.

0 Kudos
Dave12
Novice
1,575 Views

Enclosed you find the buildlog.htm-file. There is no solution folder generated.

Thank you for the support and best regards

 

0 Kudos
Steve_Lionel
Honored Contributor III
1,568 Views

As best as I can tell from the build log, no symbols were needed that would be found in libdiscon.lib. You got an error when you removed the library, as it is named explicitly in the project. I do see what looks to be the object file from compiling a module named DISCON_M - if this declares the routine DISCON then that's where it is being found and the DLL ignored.

A more definitive answer could be found if you enabled the link map (Linker > Debugging > Generate map file > Yes). The .map file will tell you exactly where it found DISCON.

0 Kudos
Dave12
Novice
1,560 Views

Hello Steve,

I included the module named DISCON_M as it was suggested (Link_to_other_Thread) to include a DLL written in C++. The naming might be a little bit confusing. I commented out the content of the related modules for the time being to eliminate duplicate names etc. as a source of errors. 

I try to read the function DISCON contained in the (fortran) DLL in the subroutine DISCON in the module class_controller.

Attached you find the .map file. I can not find anything there related to a libdiscon.lib or a discon.dll. What would be the expected correct result?

0 Kudos
Dave12
Novice
1,548 Views

Here the missing map-file

0 Kudos
Steve_Lionel
Honored Contributor III
1,549 Views

Attachment not found.

You won't see any references to DLLs in the link map, as those are resolved only when you run the program.

0 Kudos
Steve_Lionel
Honored Contributor III
1,533 Views
 0003:00140a60       discon                     00000001407fba60     DISCON_m.obj

Routine discon is being pulled from your module object, so there's no need for the DLL's library. Would you please attach the source for DISCON_M?

I assume that DISCON_M is also part of your DLL build, and that it has an ATTRIBUTES DLLEXPORT directive for it. The recommended practice here is to USE the module (requires the .mod to be in the include path), but to NOT have the module source in your executable build. The way I like to do this is to make the DLL project be a dependent of the main project - this will cause the Fortran build system to automatically add the DLL project's module path to compiles, and when linking, pulls in the export library.

0 Kudos
Dave12
Novice
1,531 Views

Thanks for taking a look at it. I would be surprised if the routine DISCON_M matters here, as I built it into the programme myself with this help (https://community.intel.com/t5/Intel-Fortran-Compiler/Integrate-C-DLL-in-Fortran-Code/m-p/1410919#M162620). 

Below is the routine as it is currently implemented. The commands are currently commented out as not to interfere with my current intention to read in the Fortran DLL. 

module DISCON_m
! Module to define Fortran interfaces of procedures to be consumed from C++ Dll(s) 

   use, intrinsic :: iso_c_binding, only : c_float, c_int, c_char
   use DllHelper_m, only : DllHelper_t

   private
   
   abstract interface 
      subroutine IDISCON ( avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG ) bind(C)
      ! Assumes the C++ function prototype is as follows:
      ! extern "C" void DISCON(float *, int *, const char *, const char *, char *)
         import :: c_float, c_int, c_char
         real(c_float),          intent(inout) :: avrSWAP(*)      
         integer(c_int),         intent(inout) :: aviFAIL        
         character(kind=c_char), intent(in)    :: accINFILE(*)      
         character(kind=c_char), intent(in)    :: avcOUTNAME(*)     
         character(kind=c_char), intent(inout) :: avcMSG(*)        
      end subroutine IDISCON  
   end interface
   
   ! Program variables
   type(DllHelper_t), save :: Controller
   procedure(IDISCON), pointer, save, protected, public :: DISCON => null()
   
   public :: SetupDISCON
   
contains

   subroutine SetupDISCON(boolean_abort)
   
      !use, intrinsic :: iso_c_binding, only : c_funptr, c_associated, c_f_procpointer
      !
      !type(c_funptr) :: pDISCON
      !integer :: irc
      !logical, intent(inout) :: boolean_abort
      !
      !call Controller%Load( "Controller.dll", irc )  !    Changed to the name of the actual DLL
      !if ( irc /= 0 ) then
      !  print*, 'Warning: failed to load Controller.dll...'
      !  boolean_abort = .TRUE.
      !  return
      !end if
      !
      !pDISCON = Controller%GetFunPtr( "DISCON" )
      !if ( .not. c_associated(pDISCON) ) then
      !   print*, 'Warning: failed to get the address of DISCON procedure'
      !   boolean_abort = .TRUE.
      !   return
      !end if
      !call c_f_procpointer( cptr=pDISCON, fptr=DISCON )
      !
      !if ( .not. associated(DISCON) ) then
      !   print*, 'Warning: DISCON is not associated.'
      !   boolean_abort = .TRUE.
      !   return
      !end if
      !
   end subroutine
   
end module

 

 

0 Kudos
Steve_Lionel
Honored Contributor III
1,524 Views

Ah, this is interesting. It would appear that you are dynamically loading the DLL in which you expect DISCON to be in. In that case, there is no link-time reference to the DLL at all. You have a procedure pointer DISCON in the module that you assign to the address of the DISCON routine from the DLL.

This is also why the debugger doesn't say that your DISCON.DLL is loaded, as it isn't until the program has already started execution, so the debugger has no idea it is there.

0 Kudos
Dave12
Novice
1,510 Views

Isn't the way the DLL is loaded determined by this command:?

!DEC$ ATTRIBUTES DLLEXPORT :: DISCON


Let me explain the context of the DLL more precisely: It is an opensource controller for wind turbines. It is also used in the opensource software openfast. The controller inputs and outputs are the same for all controllers and are called bladed type controllers.

The controller code can be found here: https://github.com/NREL/ROSCO/blob/main/ROSCO/src/DISCON.F90 . It seems the DLL is generated in line 35.

They wrote an interface module here: https://github.com/OpenFAST/openfast/blob/main/modules/servodyn/src/BladedInterface.f90 , which is used to link different controller types. The soubroutine "BladedDLL_Legacy_Procedure" (line 219) seems to me as some kind of a dummy subroutine.

From line 74 onwards, they start to statically load the controller. I have hope to be able to use some of this code. 

Due to the length of the code it is difficult to identify the important parts that I need for my intention.

0 Kudos
Steve_Lionel
Honored Contributor III
1,503 Views

The DLLEXPORT says to make that symbol available to users of the DLL. When the DLL is built, an export library (.LIB) is also created, but that's not the only way to access a DLL. You can also use the Windows API functions LoadLibrary and GetProcAddress to load it after the program starts running, which is what SetupDISCON would be doing. (There's an example of this in the Samples Bundle at https://software.intel.com/content/www/us/en/develop/download/776976.html under DLL\DynamicLoad.

You've commented out SetupDISCON so that it does nothing. If you want a DLL named discon.dll used here, uncomment the routine code, change 'controller.dll' to 'discon.dll' and it should do what you want.

0 Kudos
Dave12
Novice
1,498 Views

Thanks, you are right. I uncommented the parts again and adapted them to the DLL I currently want to use. I thought those code parts only work for DLLs that were generated from C++ code. There were kindly provided here: https://community.intel.com/t5/Intel-Fortran-Compiler/Integrate-C-DLL-in-Fortran-Code/m-p/1410919#M162620

Right now, I get the message that libdiscon.dll is loaded. Unfortunately, the code aborts when the subroutine DISCON(...) is actually called. I just want to make sure that the reason is not that the code expects a C++ DLL but gets a fortran DLL.

0 Kudos
Steve_Lionel
Honored Contributor III
1,497 Views

There's nothing language-specific about this part of the process. You say, "the code aborts", but don't say exactly what goes wrong (what error message, at what point it happens, etc.) When calling a routine in this fashion it is important that you pass the correct number/type of arguments that the called procedure expects. It is possible to debug a routine in a dynamically loaded DLL, but it requires some extra steps.

0 Kudos
Dave12
Novice
1,480 Views

That's good to know. I was thinking in that direction becuase it is commented on the module DISCON_m "! Module to define Fortran interfaces of procedures to be consumed from C++ Dll(s) "

Following I show all the code that runs right now to work with the DLL: 1. The provided routines to read the DLL:

 

module DISCON_m
! Module to define Fortran interfaces of procedures to be consumed from C++ Dll(s) 

   use, intrinsic :: iso_c_binding, only : c_float, c_int, c_char
   use DllHelper_m, only : DllHelper_t

   private
   
   abstract interface 
      subroutine IDISCON ( avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG ) bind(C)
      ! Assumes the C++ function prototype is as follows:
      ! extern "C" void DISCON(float *, int *, const char *, const char *, char *)
         import :: c_float, c_int, c_char
         real(c_float),          intent(inout) :: avrSWAP(*)      
         integer(c_int),         intent(inout) :: aviFAIL        
         character(kind=c_char), intent(in)    :: accINFILE(*)      
         character(kind=c_char), intent(in)    :: avcOUTNAME(*)     
         character(kind=c_char), intent(inout) :: avcMSG(*)        
      end subroutine IDISCON  
   end interface
   
   ! Program variables
   type(DllHelper_t), save :: controller
   procedure(IDISCON), pointer, save, protected, public :: DISCON => null()
   
   public :: SetupDISCON
   
contains

   subroutine SetupDISCON(boolean_abort)
   
      use, intrinsic :: iso_c_binding, only : c_funptr, c_associated, c_f_procpointer
      
      type(c_funptr) :: pDISCON
      integer :: irc
      logical, intent(inout) :: boolean_abort
      
      call Controller%Load( "libdiscon.dll", irc )  !    Changed to the name of the actual DLL
      if ( irc /= 0 ) then
        print*, 'Warning: failed to load libdiscon.dll...'
        boolean_abort = .TRUE.
        return
      end if
      
      pDISCON = Controller%GetFunPtr( "DISCON" )
      if ( .not. c_associated(pDISCON) ) then
         print*, 'Warning: failed to get the address of DISCON procedure'
         boolean_abort = .TRUE.
         return
      end if
      call c_f_procpointer( cptr=pDISCON, fptr=DISCON )
      
      if ( .not. associated(DISCON) ) then
         print*, 'Warning: DISCON is not associated.'
         boolean_abort = .TRUE.
         return
      end if
      
   end subroutine
   end module 
module DllHelper_m
! Purpose    : Helper module and type to work with Dlls on Windows
! Author     : FortranFan
! Reference  : Using Run-Time Dynamic Linking
!              https://docs.microsoft.com/en-us/windows/win32/dlls/using-run-time-dynamic-linking
! Description:
! This module defines a public type, DllHelper_t, to work with DLLs on Windows.  This type has
!  - Load method with an Dll name as input to load the DLL
!  - GetFunPtr to dispatch a C function pointer for an exported procedure in the Dll; the proc
!    name string is the input
!  - a finalizer that frees up the Dll when the object is destroyed.
!

   use, intrinsic :: iso_c_binding, only : c_char, c_funptr, c_null_funptr
   use IWINApi_m, only : HMODULE, NULL_HANDLE, BOOL, LoadLibrary, GetProcAddress, FreeLibrary

   private

   type, public :: DllHelper_t
      private
      character(kind=c_char, len=:), allocatable :: m_DllName
      integer(HMODULE) :: m_DllHandle = NULL_HANDLE
   contains
      final :: FreeDll
      procedure, pass(this) :: Load => LoadDll
      procedure, pass(this) :: GetFunPtr
   end type
   
contains
    
   subroutine FreeDll( this )
   ! Unload the DLL and free up its resources
      type(DllHelper_t), intent(inout) :: this
      
      ! Local variables
      integer(BOOL) :: iret
      
      if ( this%m_DllHandle /= NULL_HANDLE ) then
         iret = FreeLibrary( this%m_DllHandle )
      end if
      this%m_DllHandle = NULL_HANDLE
      
      return
        
   end subroutine 

   subroutine LoadDll( this, DllName, iret )
   ! Load the DLL and set up its handle

      class(DllHelper_t), intent(inout) :: this
      character(kind=c_char, len=*), intent(in) :: DllName
      integer(BOOL), intent(inout) :: iret

      iret = 0
      this%m_DllHandle = LoadLibrary( DllName )
      if ( this%m_DllHandle == NULL_HANDLE ) then
         iret = 1 !<-- TODO: replace with GetLastError
      end if
      
      return
        
   end subroutine 

   function GetFunPtr( this, ProcName ) result(FunPtr)
   ! Get the function pointer

      class(DllHelper_t), intent(inout) :: this
      character(kind=c_char, len=*), intent(in) :: ProcName
      ! Function result
      type(c_funptr) :: FunPtr

      FunPtr = GetProcAddress( this%m_DllHandle, ProcName )
      
      return
        
   end function 

end module 
module IWinAPI_m
! Purpose    : Interfaces toward Microsoft Windows API functions
! Author     : FortranFan
! Reference  : Microsoft Documentation
!
! Description:
! This module defines the interfaces and type aliases toward Windows APIs.
!
   
   use, intrinsic :: iso_c_binding, only : c_char, c_int, c_intptr_t, c_funptr

   implicit none

   !.. Public by default
   public

   !.. Mnemonics for types in Windows API functions
   integer(c_int), parameter :: HMODULE = c_intptr_t  !.. A handle to a module; base address in memory
   integer(HMODULE), parameter :: NULL_HANDLE = int( 0, kind=kind(NULL_HANDLE) )
   integer(c_int), parameter :: BOOL = c_int

   interface

      function LoadLibrary(lpLibName) bind(C, name='LoadLibraryA') result(RetVal)
      !DIR$ ATTRIBUTES STDCALL :: LoadLibrary
      ! https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya
      ! HMODULE LoadLibraryA( [in] LPCSTR lpLibFileName );

         import :: c_char, HMODULE

         !.. Argument list
         character(kind=c_char, len=1), intent(in) :: lpLibName(*)
         !.. Function result
         integer(HMODULE) :: RetVal

      end function LoadLibrary

      function FreeLibrary(lpLibHandle) bind(C, NAME='FreeLibrary') result(RetVal)
      !DIR$ ATTRIBUTES STDCALL :: FreeLibrary
      ! https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary
      ! BOOL FreeLibrary( [in] HMODULE hLibModule );

         import :: HMODULE, BOOL

         !.. Argument list
         integer(HMODULE), value :: lpLibHandle
         !.. Function result
         integer(BOOL) :: RetVal

      end function FreeLibrary

      function GetProcAddress(lpLibHandle, lpProcName) bind(C, name='GetProcAddress') result(RetVal)
      !DIR$ ATTRIBUTES STDCALL :: GetProcAddress
      ! https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress
      ! FARPROC GetProcAddress( [in] HMODULE hModule, [in] LPCSTR  lpProcName );

         import :: HMODULE, c_char, c_funptr

         !.. Argument list
         integer(HMODULE), intent(in), value      :: lpLibHandle
         character(kind=c_char,len=1), intent(in) :: lpProcName(*)
         !.. Function result
         type(c_funptr) :: RetVal

      end function GetProcAddress

   end interface

end module IWinAPI_m

 

2. The module I wrote to call the successfully loaded DLL:

 

module class_controller

    implicit none
    
    type :: model_controller
        logical   :: boolean_abort = .FALSE. 
        contains
        procedure :: use_controller
      
    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
      use DISCON_m, only : SetupDISCON, DISCON
      implicit none
     
      class(model_controller), intent(inout)       :: this
      real(kind = 8), dimension(117), intent(inout) :: swap
      real(c_float), dimension(117)                 :: swap_c_float
      real(c_float)                                :: ctrl_avrSWAP(117)                    
      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 SetupDISCON(this%boolean_abort) 
      if (this%boolean_abort .eqv. .TRUE.) then
        return
      end if
      
      ctrl_accINFILE      = c_char_"DISCON_DATA.IN" // c_null_char
      ctrl_avcOUTNAME     = c_char_"OUTNAME.txt" // c_null_char
      ctrl_avcMSG         = c_null_char
      ctrl_aviFAIL        = 0_c_int
    
      swap_c_float     = swap               ! converting input to C++ float values
      ctrl_avrSWAP     = swap_c_float       ! maybe unnecessary
      ctrl_avrSWAP(50) = 14.0_c_float       ! length of the path "ctrl_accINFILE" / No. of characters in the INFILE argument
      ctrl_avrSWAP(51) = 11.0_c_float       ! No. of characters in the OUTNAME argument
     
      ! Calling the actual controller function from the DLL
      call DISCON(ctrl_avrSWAP, ctrl_aviFAIL, ctrl_accINFILE, ctrl_avcOUTNAME, ctrl_avcMSG )
      swap = ctrl_avrSWAP 
    
    end subroutine use_controller
    
end module class_controller

 

and 3. the command to call the routine where it is needed in the code:

 

call the_model_controller%use_controller(swap)

 

 When 3. is executed the program aborts. I get the messages:

The Thread 0x565c ended with Code 2 (0x2).
The Thread 0x24c ended with Code 2 (0x2).
The Thread 0x6364 ended with Code 2 (0x2) geedet.
The Thread 0x316c ended with Code 2 (0x2).
The Thread 0x20 ended with Code 2 (0x2).
The Thread 0x3a90 ended with Code 2 (0x2).
The Programme "[15120] DeSiO.exe" ended with Code 2 (0x2).

0 Kudos
jimdempseyatthecove
Honored Contributor III
1,474 Views

When you build in Debug mode, have you stepped into the call the_model_controller%use_controller?

 

Does the error occur at the call DISCON?

If yes, then it is likely that the calling arguments are incorrect. In particular the contents of ctrl_avrSWAP. But check all arguments.

 

Jim Dempsey

 

0 Kudos
Dave12
Novice
1,472 Views

Yes I did this. The error occurs when DISCON is called. I will check the input arguments again, thank you for the suggestion.

0 Kudos
jimdempseyatthecove
Honored Contributor III
1,468 Views

Also, if arguments look correct, at the call DISCON open Disassembly window (and make in focus), perform Step Into. Note, you may have to repeat Step Into while arguments are placed on the stack and then once more at the assembly call instruction.

If you crash here, then it is likely that the procedure pointer DISCON is incorrect.

If the call gets into libdiscon.dll, perform a Step Out.

If this crashes, then this points bask to an argument error or code between the load dll and call corrupted the dll.

If this returns, then either the calling convention is wrong (unlikely for x64), or the code in the DISCON corrupted you application (abend will occur later). At this point, close the Disassembly window (back to source). And use the following:

1) Note the source and line in the source code.

2) Issue Step Out

3) if no crash/abend goto step 1)

4) if crash, place break point at source and line of last Step Out

5) Issue Restart (and continue past call to DISCON)

6) Step through code to locate locality of error.

 

Note, while performing 6) you may find a CALL that fails. At this point, place a break on that CALL, Restart, at this break, Step In. Then perform steps 1:6 to refine the search. At some point you should have an ah-ha moment.

 

Jim Dempsey

0 Kudos
Dave12
Novice
1,464 Views

To show you what happened I attach some screenshots.

The DLL libdiscon.dll is successfully loaded, as it can be seen in the first image (highlighted). Then in jumps into the assembly call instruction(?). I highlighted the step in image 1 and 2. In picture 3 I show what happens if I step in this call command.

So, considering the DLL is not damaged, the arguments are not correct?

Step 1Step 1Step 2Step 2Step 3Step 3

0 Kudos
jimdempseyatthecove
Honored Contributor III
1,462 Views

The call into DISCON succeeded (last screenshot)

What happens with the Step Out (this runs the code in DISCON through and including the ret)?

Jim Dempsey

0 Kudos
Dave12
Novice
1,439 Views

If I Step Out at the point, that I showed in the last screenshot no error occurs.

I followed the procedure of Stepping In into the "call" commands and Stepping out after. This worked for some of the calls. In the following screenshots I show at which Step Out the programm aborts:

1122In the call depicted in the first screenshot are several further call commands. The one that aborts the programm is the one I show in the second screenshot. So Stepping In is possible, if I Step Out, the error occurs. I do not see any further error messages than those is the output window. Is it possible to determine the cause of the error based on the memory address or other information at this point?

0 Kudos
jimdempseyatthecove
Honored Contributor III
1,430 Views

>>...second screenshot. So Stepping In is possible, if I Step Out, the error occurs...

So you step in and repeat the steps 1:6

 

1) Note the source and line in the source code.

2) Issue Step Out

3) if no crash/abend goto step 1)

4) if crash, place break point at source and line of last Step Out

5) Issue Restart (and continue past call to DISCON)

6) Step through code to locate locality of error.

 

Note, while performing 6) you may find a CALL that fails. At this point, place a break on that CALL, Restart, at this break, Step In. Then perform steps 1:6 to refine the search. At some point you should have an ah-ha moment.

 

Note, this process is recursive in nature. You need to drill down call levels to the procedure that errors out.  Due to the error not being a Fortran reported error, this indicates it occurs in an external DLL. Check the arguments to see if they are trash. IOW either:

a) DISCON returned trash

b) An argument error on call to DISCON caused DISCON to "crater" (overwrite) code or other data.

c) DISCON closes a resource but the code following the call to DISCON assumes the resource is still available.

d) some other reason not apparent at this time

 

Jim Dempsey

0 Kudos
Reply