问题
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