从 C# 调用 FORTRAN DLL 的受保护内存冲突
Posted
技术标签:
【中文标题】从 C# 调用 FORTRAN DLL 的受保护内存冲突【英文标题】:Protected Memory Violation calling FORTRAN DLL from C# 【发布时间】:2008-10-23 11:18:40 【问题描述】:我正在尝试调用从 FORTRAN 代码编译的旧版 dll。我是 Interop 的新手,但我已经阅读了一些关于它的文章,看起来我的案例应该相当简单。
我真正想调用的方法有一个复杂的方法签名,但我什至不能调用这个简单的 GetVersion 方法而不得到受保护的内存冲突。
这是我的 DllImport 代码:
[DllImport("GeoConvert.dll",
EntryPoint="_get_version@4",
CallingConvention=CallingConvention.StdCall)]
public static extern void GetGeoConvertVersion([MarshalAs(UnmanagedType.LPStr, SizeConst=8)]
ref string version);
这是 FORTRAN 代码:
SUBROUTINE GetVer( VRSION )
C
!MS$DEFINE MSDLL
!MS$IF DEFINED (MSDLL)
ENTRY Get_Version (VRSION)
!MS$ATTRIBUTES DLLEXPORT,STDCALL :: Get_Version
!MS$ATTRIBUTES REFERENCE :: VRSION
!MS$ENDIF
!MS$UNDEFINE MSDLL
C
CHARACTER*8 VRSION
C
VRSION = '1.0a_FhC'
C
RETURN
END
这是我失败的单元测试:
[Test]
public void TestGetVersion()
string version = "";
LatLonUtils.GetGeoConvertVersion(ref version);
StringAssert.IsNonEmpty(version);
这是我收到的错误消息:
System.AccessViolationException
Message: Attempted to read or write protected memory.
This is often an indication that other memory is corrupt.
我尝试过的其他事情:
使用默认编组 传递 char[] 而不是字符串(改为获取方法签名错误)【问题讨论】:
我对您仍在使用 Microsoft Fortran 编译器感到有些震惊。 (他们最后一个 Win32 Fortran 编译器是在 1994 年,不是吗?)我认识的大多数在 Windows 上开发 Fortran 的人都使用 DEC / Compaq / HP / Intel Visual Fortran 或 MinGW 派生的 gfortran。 另一方面,如果二进制代码有效,则没有理由重新编译。这可能是升级的强大动力。 【参考方案1】:...剪断... 好的,我让它工作了,问题是通过参考。我不知道为什么,但这有效: ...剪辑...
您需要通过引用传递,因为这是 FORTRAN 代码使用的语义。客户端代码正在传递一个缓冲区,FORTRAN 代码将写入该缓冲区以代替使用返回值。
...剪断... !MS$属性参考 :: VRSION ...剪辑...
您的 FORTRAN 代码中的此属性指定此参数是通过引用传递的。这意味着 FORTRAN 代码将写入该地址。如果 DllImport 也没有将其声明为 ref 值,则会出现访问冲突。
【讨论】:
为什么一开始就需要引用调用杂注? Microsoft Fortran 是否像其他 Fortran 编译器一样默认不使用引用调用? (还是按值调用仅适用于 DLL?) tflanagan,不完全是。通过引用传递失败,默认(按值)成功,即使 FORTRAN 明确要求引用。问题是一个 byte[] 已经是一个指针,所以通过 ref 传递一个 byte[] 是一个指向指针的指针,所以我遇到了内存访问错误。【参考方案2】:好的,我让它工作了,问题是通过 ref 传递的。我不知道为什么,但这有效:
[DllImport("GeoConvert.dll",
EntryPoint="_get_version@4",
CallingConvention=CallingConvention.StdCall)]
public static extern void GetGeoConvertVersion([MarshalAs(UnmanagedType.LPArray)]
byte[] version);
通过这个测试:
[Test]
public void TestGetVersion()
//string version = "";
byte[] version = new byte[8];
LatLonUtils.GetGeoConvertVersion(version);
char[] versionChars = System.Text.Encoding.ASCII.GetChars(version);
string versionString = new string(versionChars);
【讨论】:
我刚试过,效果很好:) 一条评论 - 如果你想用一行代码替换最后两行代码,你可以使用System.Text.Encoding.ASCII.GetString()
。有点清洁,但双向工作。感谢发布更新!它真的帮助了我。【参考方案3】:
您是否尝试过使用 StringBuilder?
将您的字符串创建为 StringBuilder 并将其传递给 dll 函数。
我不确定要使用什么 Marashlling 语句,也许默认值可能有效。
看看:Marshal C++ “string” class in C# P/Invoke
这是一篇好文章,可能也会有所帮助:Interop Marshalling
【讨论】:
【参考方案4】:由于我没有 FORTRAN 编译器,因此我无法尝试此解决方案,但我认为这对您有用:
[DllImport("GeoConvert.dll",
EntryPoint="_get_version@4",
CallingConvention=CallingConvention.StdCall,
CharSet=CharSet.Ansi)]
public static extern void GetGeoConvertVersion(StringBuilder version);
【讨论】:
【参考方案5】:谢谢大家,我一直在尝试将字符串从 c# 传递给 fortran dll 的子例程,这种方法是许多其他方法中唯一有效的方法
【讨论】:
以上是关于从 C# 调用 FORTRAN DLL 的受保护内存冲突的主要内容,如果未能解决你的问题,请参考以下文章