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

Incorrect working current directory for DLL

bohluly
Beginner
1,974 Views
Hi everybody,
I have a dll that is compiled withFORTRAN11, when it is loaded from C# program (not during debug, the problem is afterinstall the programs )C# program correctly load the DLL without any path and only by namebecauseboth of them are in the same directory.
but during the run ofFORTRANDLL, current path in the DLL is not same as position of that DLL is there and I can not load another file from the FORTRANDLL.
Is there any function to find the correct path (the directory of DLL), I cheked the "getcwd" and it gives the another path that is wrongcurrentpath.
Ihanks
0 Kudos
13 Replies
GVautier
New Contributor III
1,974 Views
Hello

A dll is searched first in the directory than contains the executable that wants to load it. So if your DLL is in the same directory it will be found.
But the getcwd function retrieves the current working directory that can be different from the DLL location directory.

If you want to retrieve the path of the DLL, you can use the GetModuleFileName API function that will return the full path of the DLL.


0 Kudos
bohluly
Beginner
1,974 Views
Thanks I checked theGetModuleFileName it is OK.

Usedfwin
CHARACTER*1024 ::
DLLPath
DLLPath = ''
N = GetModuleFileName(0, DLLPath, len(DLLPath))

0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,974 Views
GetModuleFileName with 0 handle returns the path to the executable that started the current process.

Current directory (getcwd) can be pretty much any directory, as set up by the program shortcut, or changed by the user in the UI.

OPEN statement, however, by default opens the file in the current directory.

In order to a) open the file in the same directory as the exe and b) not to change the current directory (which is generally a bad practice to do from code), find the first path and open file there explicitly:

[fortran]CHARACTER(MAX_PATH)         :: DLLPath   
integer::                      dirLen
   
DLLPath = '' 
N = GetModuleFileName(0, DLLPath, len(DLLPath))
!Strip the path up to the trailing backslash:
dirLen = index(Dllpath, "", .true.)
DLLPath = DLLPath(1:dirLen)
OPEN(42, file=TRIM(DLLPath)//"MyFile.txt")
...[/fortran]
0 Kudos
DavidWhite
Valued Contributor II
1,974 Views
Jugoslav,

once a DLL has been opened, is it possible to find out the directory it was loaded from?

As you say, GetModuleFileName returns the Executable (Excel) in my case. I would like to know which DLL(s) are actually being loaded at runtime, in part to check that the correct version of the code is in use.


Thanks,

David
0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,974 Views
Quoting David White
Jugoslav,

once a DLL has been opened, is it possible to find out the directory it was loaded from?

Yes, if you give GetModuleFileName a correct handle (HMODULE/HINSTANCE). There are two ways to do it:

  1. By writing a DllMain and storing its hinstDll argument into a common/module variable, and
  2. The following should also work. The slight drawback is that you must know your .dll name in advance (thus, won't reliably work in .e.g. a .lib):
    [fortran]integer(HANDLE):: hDll
    character(MAX_PATH):: path
    
    hDll = GetModuleHandle("MyDll.dll"//char(0))
    len = GetModuleFileName(hDll, path, len(path)) !Returns the full path now[/fortran]
0 Kudos
IanH
Honored Contributor III
1,974 Views
[fortran]MODULE DLLUtils
  USE IFWINTY  
  IMPLICIT NONE
  PRIVATE  
  PUBLIC :: GetThisDLLsFileName
  INTERFACE
    FUNCTION GetModuleHandleEx(dwFlags, lpModuleName, phModule)
    !DEC$ ATTRIBUTES DEFAULT, STDCALL, DECORATE, ALIAS:'GetModuleHandleExA' :: GetModuleHandleEx
      USE ISO_C_BINDING, ONLY: C_FUNPTR
      USE IFWINTY
      IMPLICIT NONE
      INTEGER(DWORD), INTENT(IN), VALUE :: dwFlags
      TYPE(C_FUNPTR), INTENT(IN), VALUE :: lpModuleName
      INTEGER(HANDLE), INTENT(OUT) :: phModule
      !DEC$ ATTRIBUTES REFERENCE :: phModule
      INTEGER(BOOL) :: GetModuleHandleEx      
    END FUNCTION GetModuleHandleEx
  END INTERFACE
  
  INTEGER(DWORD), PARAMETER :: GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS  &
      = 4_DWORD  
CONTAINS
  SUBROUTINE GetThisDLLsFileName(filename) 
  !DEC$ ATTRIBUTES DLLEXPORT :: GetThisDLLsFileName     
    USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_FUNLOC
    USE IFWIN    
    !----
    CHARACTER(:), INTENT(OUT), ALLOCATABLE :: filename    
    !----
    INTEGER(BOOL) :: rc    
    INTEGER(HANDLE) :: my_handle    
    INTEGER(DWORD) :: buf_len
    INTEGER(DWORD) :: str_len    
    !****      
    rc = GetModuleHandleEx(  &
        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,  &
        C_FUNLOC(AnExportedProcedure),  &
        my_handle )        
    ! should test rc, but I'm lazy...
    buf_len = 256_DWORD
    DO
      ALLOCATE(CHARACTER(buf_len) :: filename)
      str_len = GetModuleFilename(my_handle, filename, buf_len)
      IF (str_len < buf_len) THEN
        ! choppity by reallocation
        filename = filename(1:str_len)
        RETURN
      END IF      
      ! buffer too small, double and try again...
      buf_len = buf_len * 2
      DEALLOCATE(filename)
    END DO
  END SUBROUTINE GetThisDLLsFileName
    
  SUBROUTINE AnExportedProcedure() BIND(C)
  !DEC$ ATTRIBUTES DLLEXPORT :: AnExportedProcedure
  END SUBROUTINE
END MODULE DLLUtils
[/fortran]


and to test...

[fortran]PROGRAM DLLUtilsTest
  USE DLLUtils
  IMPLICIT NONE
  !---
  CHARACTER(:), ALLOCATABLE :: dll_name
  !****
  ! ifort bug requires this to be allocated on entry??
  ALLOCATE(CHARACTER(1) :: dll_name)  
  CALL GetThisDLLsFileName(dll_name)  
  PRINT "(A)", dll_name  
END PROGRAM DLLUtilsTest[/fortran]

might need /assume:realloc_lhs to work.
0 Kudos
netphilou31
New Contributor III
1,974 Views
Aother solution would be to add a DllMain routine to your project. This routine is called each time the DLL is loaded and attached to a process or a thread. The routine has the handle of the dll as the first argument so you can retrieve the path of the dll from the GetModuleFileName API function.

This the way I proceed frequently:
[fxfortran]      integer(4) function DllMain(hInst, ul_reason_being_called, lpReserved)
!DEC$ IF DEFINED(_X86_)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_DllMain@12' :: DllMain
!DEC$ ELSE
!DEC$ ATTRIBUTES STDCALL, ALIAS : 'DllMain' :: DllMain
!DEC$ ENDIF
      use DFWINA

      integer(4) hInst
      integer(4) ul_reason_being_called
      integer(4) lpReserved
C
      integer(4) hDLL
      common /DLL_Handle/ hDLL
C
      hDLL = hInst
C
      ul_reason_being_called = ul_reason_being_called
      lpReserved = lpReserved
C
      DllMain = 1
      return
C
      end
[/fxfortran]

Then in the main unit, you can retrieve the handle and call the GetModuleFileName API function, in the example below it is used to retrieve the full dll path in order to access the dll resources and particularly the dll version information:
[fxfortran]C
      integer(4) hDLL
      common /DLL_Handle/ hDLL
C
      character(len=256) DLL_Name, VERS_INFO
C
      integer(4) n
C
      n = GetModuleFileName(hDLL, DLL_Name, sizeof(DLL_Name))
      if (n /= 0) then
          call GetVersionInfoString(DLL_Name, VERS_INFO)
      else
          VERS_INFO = 'N/A'
      end if
[/fxfortran]

Note that "GetVersionInfoString" is a function I have created to retrieve the version information from the DLL resources thus it is not part of the Windows API.

Regards,

Phil.
0 Kudos
DavidWhite
Valued Contributor II
1,974 Views
Phil,

Tried your method in my situation where I am loading a DLL from Excel via VBA. Like the other methods I have tried, this still returns the name of the exe (Excel) in DLL_Name, rather than the DLL.

David
0 Kudos
netphilou31
New Contributor III
1,974 Views
David.
I am surprised that it doesn't work because I have tried the same thing, i.e. a Fortran dll declared in VBA to use some exported routines (in my example I was using IMSL routines to perform cubic spline interpolation in Excel). In my Fortran dll I have added the Dllmain function as above and when I call the GetModuleFileName using the "hInst" handle passed by argument in DllMain the results is the full path to the dll so I don't understand what is going wrong in your case.
Regards,
Phil.
0 Kudos
lklawrie
Beginner
1,974 Views
Here's how I've been doing it:

ghinstance=GetModuleHandle("EnergyPlusDLL.dll"C)
ret = GetModuleFileName (INT(ghInstance), szFullPath, len(szFullPath))


I am currently challenged, however, to get the Version info from 64 bit compiles, so if the above works for you I would be grateful for any assistance in working out the version info.

Linda
0 Kudos
netphilou31
New Contributor III
1,974 Views
Linda,

Here is the GetFileVersionInfo source code. I hope it will work in 64 bit (I do compile only 32 bit executables):
[fortran]      subroutine GetVersionInfoString(EXE_Name, VERSION_INFO)
      use DFWIN
      implicit none

      character(len=*), intent(in) ::  EXE_Name
      character(len=*), intent(out) :: VERSION_INFO

!     Retrieving the version information from an executable or dll

      integer(4)         Hndl
      integer(4)         nBytes
      integer(4)         rc
      character(len=256) StringInfo
      character(len=10)  CharSet

      integer(4)         uVersionLen, uTranslationLen
      integer(4)         lpstrVffInfo
      integer(4)         hMem
      integer(4)         i
      character(len=256) lpversion
      integer(1)         lpTranslation(256)
      integer(4)         lplpTranslation, lplpVersion

      pointer(lplpTranslation, lpTranslation)
      pointer(lplpVersion, lpversion)

      nBytes = GetFileVersionInfoSize(EXE_Name, loc(Hndl))

      if (nBytes /= 0) then

!         Allocating the required memory bloc
          hMem = GlobalAlloc(GMEM_MOVEABLE, int(nBytes))
          lpstrVffInfo = GlobalLock(hMem)

!         Retrieving the information bloc
          rc = GetFileVersionInfo(EXE_Name, Hndl, nBytes, lpstrVffInfo)

!         Retrieving the code page
          StringInfo = "\VarFileInfo\Translation"C
          rc = VerQueryValue(lpstrVffInfo, StringInfo, lplpTranslation, loc(uTranslationLen))

!         Retrieving the version information string
          write(CharSet,'(4z2.2)') lpTranslation(2),lpTranslation(1),lpTranslation(4),lpTranslation(3)
          StringInfo = '\StringFileInfo'//trim(CharSet)//'\FileVersion'C
          rc = VerQueryValue(lpstrVffInfo, StringInfo, lplpVersion, loc(uVersionLen))

!         Decoding
          if (rc /= 0) then
              rc = lstrcpy(VERSION_INFO, lpVersion)
              uVersionLen = min(uVersionLen,index(VERSION_INFO,char(0)))
              do i=uVersionLen,len(VERSION_INFO)
                 VERSION_INFO(i:i) = ' '
              end do
          else
              VERSION_INFO = 'N/A'
          end if

!         Freeing the allocated memory bloc
          rc = GlobalUnlock(hMem)
          rc = GlobalFree(hMem)

      else

!         Version information unavailable or not found
          VERSION_INFO = 'N/A'

      end if

      return
      end
[/fortran]
Regards,
0 Kudos
lklawrie
Beginner
1,974 Views
Thank you, Phill -- I will give this a try.

Linda
0 Kudos
Steven_L_Intel1
Employee
1,974 Views
The handles (hMem, etc.) should be declared INTEGER(HANDLE). The "lp" variables should be INTEGER(LPVOID).
0 Kudos
Reply