Fortran调用C++ DLL运行时触发访问违例,求参数排查方案
嘿,看了你的代码,这个运行时访问违例的问题根源很明确——Fortran和C++的接口参数完全没匹配上,尤其是第一个字符串数组的处理错得比较典型。我来帮你一步步排查修正:
解决Fortran调用C++ DLL的访问违例问题
1. 核心错误:字符串数组参数类型完全不兼容
C++的Initialize函数第一个参数是char *DllNames[],这本质是指向C风格字符串(char*)的指针数组——每个元素都是一个指向NULL终止字符串的独立指针。
但你的Fortran抽象接口里定义的是:
CHARACTER(KIND=C_CHAR), INTENT(IN ), DIMENSION(9) :: PDLLNames
这是一个连续的字符块数组(即使指定了长度,也只是多个长字符串连续存储),和C++需要的char*[]指针数组完全不是一回事,这是导致内存访问错误的首要原因。
修正接口定义
把Fortran抽象接口里的Initialize子例程改成如下形式,用TYPE(C_PTR)对应C的指针类型:
ABSTRACT INTERFACE SUBROUTINE Initialize(PDLLNames, NumberOfDLLs, PInputfile, Poutputfile, CtrlVersion, InitState) BIND(C, NAME='Initialize') USE, INTRINSIC :: ISO_C_Binding IMPLICIT NONE TYPE(C_PTR), INTENT(IN), DIMENSION(*) :: PDLLNames ! 对应C的char*[] INTEGER(C_INT), INTENT(IN) :: NumberOfDLLs TYPE(C_PTR), INTENT(IN) :: PInputfile ! 对应C的char* TYPE(C_PTR), INTENT(INOUT) :: Poutputfile ! 对应C的char* TYPE(C_PTR), INTENT(IN) :: CtrlVersion ! 对应C的char* INTEGER(C_INT), INTENT(IN) :: InitState END SUBROUTINE Initialize ! 其他接口保持不变 SUBROUTINE MainDll(InputSignals, OutputSignals, PErrorMessage) BIND(C, NAME='MainDll') USE, INTRINSIC :: ISO_C_Binding IMPLICIT NONE REAL(C_DOUBLE), INTENT(IN ) :: InputSignals (*) REAL(C_DOUBLE), INTENT( OUT) :: OutputSignals (*) CHARACTER(KIND=C_CHAR), INTENT( OUT) :: PErrorMessage (*) END SUBROUTINE MainDll END INTERFACE
这里额外加了NAME属性,确保Fortran调用的函数名和C++导出的完全一致,避免编译器名字修饰的坑。
2. 字符串变量的C指针转换
Fortran的字符变量需要转换成C兼容的NULL终止字符串指针才能传递给C++,你之前直接传递Fortran字符指针的方式不符合C的调用约定。需要做以下调整:
修正调用代码部分
! Variables for dll interface PROCEDURE(Initialize), BIND(C), POINTER :: Initialize_proc INTEGER(C_INT) :: NumberOfDLLs=9, InitState, i CHARACTER(KIND=C_CHAR, LEN=56), TARGET :: MainDll, DLLInputfile, DLLOutputfile, StateControllerName CHARACTER(KIND=C_CHAR, LEN=56), TARGET, DIMENSION(9) :: DLLname TYPE(C_PTR), DIMENSION(9) :: C_DLLNames ! 存储每个DLL名称的C指针 TYPE(C_PTR) :: C_Inputfile, C_Outputfile, C_CtrlVersion ! 关键:给所有字符串添加C_NULL_CHAR终止符(C++依赖这个识别字符串边界) DLLInputfile = TRIM(DLLInputfile) // C_NULL_CHAR DLLOutputfile = TRIM(DLLOutputfile) // C_NULL_CHAR StateControllerName = TRIM(StateControllerName) // C_NULL_CHAR DO i = 1, 9 DLLname(i) = TRIM(DLLname(i)) // C_NULL_CHAR C_DLLNames(i) = C_LOC(DLLname(i)) ! 获取每个字符串的C兼容指针 END DO ! 转换其他字符串为C指针 C_Inputfile = C_LOC(DLLInputfile) C_Outputfile = C_LOC(DLLOutputfile) C_CtrlVersion = C_LOC(StateControllerName) ! 加载DLL时也要确保路径是NULL终止的 module_handle = LoadLibrary(TRIM(MainDll) // C_NULL_CHAR) ! 建议加个错误检查:如果module_handle是C_NULL_PTR,说明DLL加载失败 IF (module_handle == C_NULL_PTR) THEN PRINT *, "Failed to load DLL!" STOP END IF proc_address = GetProcAddress(module_handle, C_CHAR_'Initialize' // C_NULL_CHAR ) IF (proc_address == C_NULL_PTR) THEN PRINT *, "Failed to find Initialize function!" STOP END IF ! 调用DLL函数 CALL C_F_PROCPOINTER(proc_address, Initialize_proc) CALL Initialize_proc(C_DLLNames, NumberOfDLLs, C_Inputfile, C_Outputfile, C_CtrlVersion, InitState)
3. 额外需要检查的细节
- 位数匹配:确保Fortran程序和C++ DLL的编译位数一致(都是32位或都是64位),位数不匹配会直接导致访问违例。
- 返回值处理:C++函数返回
RESULT类型,但你Fortran接口定义的是SUBROUTINE。如果RESULT是整数类型,需要把Fortran接口改成FUNCTION并接收返回值,比如:
调用时:FUNCTION Initialize(...) BIND(C, NAME='Initialize') RESULT(Res) USE, INTRINSIC :: ISO_C_Binding IMPLICIT NONE INTEGER(C_INT) :: Res ! 请根据C++的RESULT实际类型调整 ! ... 参数定义 END FUNCTION InitializeRes = Initialize_proc(...) - 参数INTENT匹配:确认C++函数中
OutputFile是否真的会修改内容,如果只是输出路径,可能INTENT(IN)就足够,避免不必要的内存权限问题。
内容的提问来源于stack exchange,提问作者Eik Herbsleb
相关产品推荐
相关产品推荐

