将数组移交给 C# 中动态加载的 C++ DLL 时出现 System.AccessViolationException

Posted

技术标签:

【中文标题】将数组移交给 C# 中动态加载的 C++ DLL 时出现 System.AccessViolationException【英文标题】:System.AccessViolationException when handing an array over to a dynamically loaded c++ DLL in C# 【发布时间】:2020-06-01 16:23:32 【问题描述】:

我在 C# 程序中动态加载最初用 C++ 编写的 DLL,并将数组作为参数传递,如下所示:

// That's only for being able to load the DLL dynamically during runtime
[DllImport(@"C:\Windows\System32\kernel32.dll", EntryPoint = "LoadLibrary")]
public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string dllToLoad);

[DllImport(@"C:\Windows\System32\kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

[DllImport(@"C:\Windows\System32\kernel32.dll", EntryPoint = "FreeLibrary")]
public static extern bool FreeLibrary(IntPtr hModule);

// Delegate with function signature for the DISCON function
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.U4)]
delegate void DisconDelegate(float[] arr);

static void Main(string[] args)
   // Load DLL
   IntPtr _dllhandle = IntPtr.Zero;
   DisconDelegate _discon = null;
   string dllPath = @"D:\myProject\Trivial_discon.dll";

    _dllhandle = LoadLibrary(dllPath);
    var discon_handle = GetProcAddress(_dllhandle, "DISCON");
   _discon = (DisconDelegate)Marshal.GetDelegateForFunctionPointer(discon_handle, typeof(DisconDelegate));

   // create the array and change it after its initialization
   float[] arr = new float[]  5, 6, 7 ;
   arr[0] = 7;

   _discon(arr);


请注意,我在初始化后再次更改arr[0] = 7; 数组的条目。这将返回以下错误:System.AccessViolationException H结果=0x80004003 Message=试图读取或写入受保护的内存。这通常表明其他内存已损坏。 来源= 堆栈跟踪:

但是,如果我离开 arr[0] = 7;,它会起作用。

所以我想知道:为什么在这种情况下初始化之后更改数组的条目会有问题?我该如何解决这个问题,即如何在初始化后更改数组的条目并仍然能够将其作为参数传递给 DLL?

【问题讨论】:

查看 pinvoke 示例:pinvoke.net/default.aspx/kernel32.LoadLibrary。您在 LoadLibrary 中缺少一个 Marshal。 @jdweng:我在示例中添加了元帅,但这还没有解决问题。不过,感谢您的评论! _discon_handle 的值是多少?句柄无效或您需要运行 c# AS ADMIN。要以 ADMIN 身份运行,请创建 VS 的快捷方式。然后右键单击快捷方式并选择以管理员身份运行。 在 c# 中,字符串是一个类,其中每个字符是一个或两个字节。在 c 语言中,字符串是一个以 '\0' 结尾的 byte[]。所以使用 MarshalAs(UnmanagedType.LPStr) 将 c# 字符串转换为 c 语言字符串。 GetProcAddress 需要是 ANSI。请参阅:pinvoke.net/default.aspx/kernel32.GetProcAddress 【参考方案1】:

您需要将数组从托管内存转换为非托管内存。尝试关注

       static void Main(string[] args)
        

            Data data = new Data();
            data.arr = new float[]  5, 6, 7 ;
            IntPtr arrPtr = Marshal.AllocHGlobal(data.arr.Length * sizeof(float));
            Marshal.StructureToPtr(data, arrPtr, true);
        
        [StructLayout(LayoutKind.Sequential)]
        public struct Data
        

            public float[] arr;
        

【讨论】:

这似乎还不是解决方案,因为我现在得到异常 System.AccessViolationExceptionMarshal.StructureToPtr(data, arrPtr, true); 一致,而不是与 DLL 调用 _discon(arrPtr) 一致(即下一个)。 您使用的是最新发布的代码吗?我对代码进行了更新并测试了现在发布的内容,没有出现异常。 我再次测试了您的代码。似乎如果我在打开 Visual Studio 后运行一次它,它就可以工作。如果我第二次运行它,我会再次得到异常 System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.' 它在进一步运行时也会抛出这个异常。 我怀疑返回值有问题,因为它运行了一次。该函数不返回任何内容,那么为什么要返回: [return: MarshalAs(UnmanagedType.U4)] IntPtr 是 4 个字节,看起来不错。函数是否返回值? 即使_discon(arr); 被注释掉也会出现这种奇怪的行为。该函数不返回任何值,但它改变了arr的值,该值应用作“输出”。

以上是关于将数组移交给 C# 中动态加载的 C++ DLL 时出现 System.AccessViolationException的主要内容,如果未能解决你的问题,请参考以下文章

C#中动态加载和卸载DLL

C#总结动态加载C++动态链接库

嵌入式单声道:将数组从 C# DLL 返回/复制到 C++

将字节数组从 C# 传递到 C++ DLL 作为 char*

在 C# 中从 C++ .DLL 加载值始终返回 true

在 C# 中动态加载和使用 DLL