从 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 的受保护内存冲突的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Derived 内部调用 Base 实例中的受保护成员函数?

C++ 从 dll 调用 FORTRAN 子例程

使用 fortran dll 的线程安全 C# 服务

C# 中实例成员的受保护访问? [复制]

C#访问派生类中的受保护成员[重复]

C#:基类中的受保护方法;无法使用来自另一个类的派生类对象进行访问[重复]