Call specific C++ DLL from Fortran

那年仲夏 提交于 2021-01-24 11:10:31

问题


I received a DLL made in C++, and I'm building a Fortran program to call the C++ DLL. My compiler (gfortran) shows no warnings, but it crashes during runtime with the following description:

    forrtl: severe (157): Program Exception - access violation
    Image              PC        Routine            Line        Source
    MainDLL_v10.dll    0B7A6B01  Unknown               Unknown  Unknown
    MainDLL_v10.dll    0B7A1BEF  Unknown               Unknown  Unknown
    ...

I guess something is wrong with my call arguments. The C++ DLL includes the following:

    #include <DllClasses.h>
   ...
   extern "C"
   {
   ...
     __declspec(dllexport) RESULT __cdecl Initialize(char *DllNames[], int NumberOfDlls, char *InputFile,  char *OutputFile, char *CtrlVersion, int InitState)
     {
     ...
     }
   ...
   } // extern C

My Fortran program is written like this. Here is a part of my code before CONTAINS.

    ABSTRACT INTERFACE
        SUBROUTINE Initialize(PDLLNames, NumberOfDLLs, PInputfile, Poutputfile, CtrlVersion, InitState) BIND(C)
            USE, INTRINSIC :: ISO_C_Binding
            IMPLICIT NONE
            !DEC$ ATTRIBUTES C :: Initialize
            CHARACTER(KIND=C_CHAR), INTENT(IN   ), DIMENSION(9) :: PDLLNames    
            INTEGER(C_INT),         INTENT(IN   )               :: NumberOfDLLs   
            CHARACTER(KIND=C_CHAR), INTENT(IN   )               :: PInputfile  
            CHARACTER(KIND=C_CHAR), INTENT(INOUT)               :: Poutputfile 
            CHARACTER(KIND=C_CHAR), INTENT(IN   )               :: CtrlVersion 
            INTEGER(C_INT),         INTENT(IN   )               :: InitState
        END SUBROUTINE Initialize
        SUBROUTINE MainDll(InputSignals, OutputSignals, PErrorMessage) BIND(C)
            USE, INTRINSIC :: ISO_C_Binding
            IMPLICIT NONE
            !DEC$ ATTRIBUTES C :: MainDll
            REAL(C_DOUBLE),          INTENT(IN   )   :: InputSignals   (*) 
            REAL(C_DOUBLE),          INTENT(  OUT)   :: OutputSignals  (*)
            CHARACTER(KIND=C_CHAR),  INTENT(  OUT)   :: PErrorMessage  (*)
        END SUBROUTINE MainDll
    END INTERFACE

And here is a part of my Fortran code in a procedure.

        ! Variables for dll interface
        PROCEDURE(Initialize), BIND(C), POINTER                 :: Initialize_proc
        INTEGER(C_INT)                                          :: NumberOfDLLs=9, InitState
        CHARACTER(KIND=C_CHAR, LEN=56), TARGET                  :: MainDll, DLLInputfile, DLLOutputfile, StateControllerName  
        CHARACTER(KIND=C_CHAR, LEN=56), TARGET, DIMENSION(9)    :: DLLname
        CHARACTER(KIND=C_CHAR, LEN=56), POINTER                 :: PoInputfile, PoOutputfile, PoStateControllerName    
        CHARACTER(KIND=C_CHAR, LEN=56), POINTER, DIMENSION(9)   :: PoDLLname(:)           
        PoInputfile => DLLInputfile
        PoOutputfile => DLLOutputfile
        PoStateControllerName => StateControllerName
        PoDLLname(1:) => DLLname(1:9)
    ...    
        ! Load DLL
        module_handle = LoadLibrary(MainDll // C_NULL_CHAR)
        proc_address = GetProcAddress( module_handle, C_CHAR_'Initialize' // C_NULL_CHAR )
        ! Call Initialize function in DLL
        CALL C_F_PROCPOINTER(proc_address, Initialize_proc)        
        CALL Initialize_proc(PoDLLname, NumberOfDLLs, PoInputfile, PoOutputfile, PoStateControllerName, InitState)

回答1:


The characteristics of your C functions and that described by the associated Fortran interface bodies do not match.

When an interoperable procedure is called (one that has BIND(C)) in Fortran, scalar arguments without the VALUE attribute are passed by reference to the C++ function. If you want the argument to be passed by value, you need to add the VALUE attribute on the Fortran side.

For example, in the C++ fragment:

__declspec(dllexport) RESULT __cdecl Initialize(... int NumberOfDlls

NumberOfDlls is pass by value, but the Fortran shows:

SUBROUTINE Initialize(... NumberOfDLLs, ...) BIND(C)
  ...
  INTEGER(C_INT), INTENT(IN) :: NumberOfDLLs   

No value attribute - the Fortran argument corresponds to a C++ parameter of int *NumberOfDlls. For pass by value, use a Fortran argument declaration of:

  INTEGER(C_INT), INTENT(IN), VALUE :: NumberOfDLLs   

Your Fortran interface also includes compiler directives (!DEC$ ATTRIBUTES...) for a family of compilers (represented today by Intel Fortran) other than gfortran. That directive would change the behaviour of the arguments on the Fortran side for that family of compilers, possibly in a way that matches the C function. But gfortran does not understand that directive - it considers it to be just a comment.

gfortran has its own equivalent directives, but the use of directives like that reflects a time when the language standard had no C interoperability support.

Since compilers started supporting Fortran 2003, you are better off using the standard language features to manage interoperability - add VALUE to the arguments that need it (InitState too), and delete the directives.

Not shown in your question are the specifics around the Windows API's LoadLibrary and GetProcAddress. I am assuming they have been described correctly for all compilers that might be used.

There is a suggestion in the way the arguments are being prepared in Fortran (lots of pointers being used - why??) of other misunderstandings around the requirements for calling C code.



来源:https://stackoverflow.com/questions/64281930/call-specific-c-dll-from-fortran

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!