互操作 C#/C 问题:AccessViolationException
并感谢您提供的任何帮助的建议。
我在 C 中有这个简单的函数:
__declspec(dllexport) Point* createPoint (int x, int y) {
Point *p;
p = (Point*) malloc(sizeof(Point));
p->x = x;
p->y=y;
return p;
}
Point 是一个非常简单的结构,有两个 int 字段,x 和 y。
我想从 C# 调用这个函数。
我使用这段代码:
[DllImport("simpleC.dll", EntryPoint = "createPoint", CallingConvention = CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.LPStruct)]
public static extern Point createPoint(int x, int y);
Point p = Wrapper.createPoint(1, 2);
但在运行时我有一个 AccessViolationException
。详细观察异常,我发现异常是从 Marshal.CoTaskMemFree(IntPtr)
方法抛出的。
看来这个方法无法释放C malloc分配的内存。
我做错了什么?
真的很感谢。
and thanks in advice for any help.
i have this trivial function in C:
__declspec(dllexport) Point* createPoint (int x, int y) {
Point *p;
p = (Point*) malloc(sizeof(Point));
p->x = x;
p->y=y;
return p;
}
Point is a very simple struct with two int fields, x and y.
I would like calling this function from C#.
I use this code:
[DllImport("simpleC.dll", EntryPoint = "createPoint", CallingConvention = CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.LPStruct)]
public static extern Point createPoint(int x, int y);
Point p = Wrapper.createPoint(1, 2);
But at runtime I have an AccessViolationException
. Watching exception in detail, I found that exception is thrown from Marshal.CoTaskMemFree(IntPtr)
method.
It seems that this method is unable to free memory allocated by C malloc.
What am i doing wrong?
Really thanks.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
CoTaskMemFree
不能用于释放由malloc
分配的内存(因为它们使用不同的分配器)。根据 MSDN,“运行时始终使用 CoTaskMemFree 方法来释放内存如果您正在使用的内存不是通过 CoTaskMemAlloc 方法分配的,则必须使用 IntPtr 并使用适当的方法手动释放内存。”此外,Adam Nathan 指出“UnmanagementType. LPStruct 仅支持一种特定情况:将 System.Guid 值类型视为具有额外间接级别的非托管 GUID...您可能应该远离 UnmanagedType.LPStruct。
有两种可能的解决方案:
IntPtr
并使用 Marshal.ReadInt32 读取结构体的字段,或使用 Marshal.PtrToStructure 将数据复制到托管结构,或使用不安全代码将IntPtr
值转换为Point *
。 C 库需要公开一个释放内存的destroyPoint(Point *)
方法。void getPoint(int x, int y, Point *)
。这让 C# 分配结构,而 C 方法只需填充数据值。 (大多数 Win32 API 都是这样定义的)。最后一点:除非您的方法使用 SetLastError< /a> Win32 API,您无需在 P/Invoke 属性上指定
SetLastError = true
。CoTaskMemFree
cannot be used to free memory allocated bymalloc
(because they use different allocators). According to MSDN, "The runtime always uses the CoTaskMemFree method to free memory. If the memory you are working with was not allocated with the CoTaskMemAlloc method, you must use an IntPtr and free the memory manually using the appropriate method."Additionally, Adam Nathan notes that "UnmanagedType.LPStruct is only supported for one specific case: treating a System.Guid value type as an unmanaged GUID with an extra level of indirection. ... You should probably just stay away from UnmanagedType.LPStruct."
There are two possible solutions:
IntPtr
and use Marshal.ReadInt32 to read the fields of the struct, or use Marshal.PtrToStructure to copy the data to a managed struct, or use unsafe code to cast theIntPtr
value to aPoint *
. The C library will need to expose adestroyPoint(Point *)
method that frees the memory.void getPoint(int x, int y, Point *)
. This lets C# allocate the struct, and the C method simply fills in the data values. (Most of the Win32 APIs are defined this way).One final note: Unless your method uses the SetLastError Win32 API, you don't need to specify
SetLastError = true
on your P/Invoke attribute.由于您没有释放“p”的代码,所以很难说。然而,malloc() 和 free() 一起工作的方式可能与 C# 管理内存的方式完全不同。由于 C# 有垃圾收集(我相信),它很可能使用完全不同的内存管理系统。
无论如何,正确的解决方案是,如果您使用库创建对象,那么也应该使用它来销毁它。实现一个“destroyPoint”函数,该函数释放 C 库中的内存,将其导入到 C# 代码中,然后从那里调用它来销毁 C 库创建的对象。
作为一般设计/编码规则,每个“创建”函数都应该有一个匹配的“释放/销毁/删除”函数。不说别的,它可以轻松确保所有创建的项目都得到正确销毁。
Since you don't have the code that frees "p", it is hard to say. However it is likely that the way malloc() and free() work together is completely different to the way C# manages memory. Since C# has garbage collection (I believe) it is likely that it uses a completely different memory management system.
In any case, the correct solution is that if you use your library to create an object, you should also use it to destroy it. Implement a "destroyPoint" function that frees the memory in your C library, import it to the C# code, and call it from there to destroy the objects created by your C library.
As a general design/coding rule, every "create" function should have a matching "free/destroy/delete" function. Apart from nothing else, it makes it easy to ensure that all created items get properly destroyed.
C# 端如何定义 Point 类型?
它必须是不安全的,或者您需要返回一个空指针(IntPtr)。 GC 无法计算来自外部的引用(此处为分配的内存),因此您的代码不能期望通过 GC 管理外部分配的内存。
一种替代方法是保留静态引用以避免垃圾收集,如果您需要在应用程序运行时持久保留该对象。
How is the Point type defined on the C# side?
It has to be unsafe, or you need to return a void pointer (IntPtr). The GC is not able to count references from outside (here the allocated memory), thus your code can not expect to manage externally allocated memory via the GC.
One alternative is to keep a static reference to avoid a Garbage collection, if you need to keep the object persistently during the runtime of your application.