【问题标题】:Protected Memory Violation calling FORTRAN DLL from C#从 C# 调用 FORTRAN DLL 的受保护内存冲突
【发布时间】: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。
  • 另一方面,如果二进制代码有效,则没有理由重新编译。这可能是升级的强大动力。

标签: c# .net interop fortran dllimport


【解决方案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 的子例程,这种方法是许多其他方法中唯一有效的方法

      【讨论】:

        猜你喜欢
        • 2014-03-16
        • 2019-11-30
        • 1970-01-01
        • 2010-10-09
        • 2013-03-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-05
        相关资源
        最近更新 更多