将数组移交给 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.AccessViolationException
与 Marshal.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的主要内容,如果未能解决你的问题,请参考以下文章