我正在使用C#和P / Invoke来访问DLL方法。该方法的定义如下:
[DllImport("userManager.dll")]
static extern int GetUsers(out IntPtr userList);
原始结构:
typedef struct user_list {
unsigned short NumUsers;
USER_LIST_ITEM List[VARLEN];
} USER_LIST
typedef struct user_list_item {
char name[260];
unsigned char address[256];
} USER_LIST_ITEM
我所做的结构布局如下:
[StructLayout(LayoutKind.Sequential)]
public class USER_LIST
{
public uint NumUsers;
[MarshalAs(UnmanagedType.ByValArray)]
public USER_LIST_ITEM [] List;
}
[StructLayout(LayoutKind.Sequential)]
public class USER_LIST_ITEM
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string address;
};
但是当我尝试解组它时,我收到了错误:
USER_LIST userList = new USER_LIST();
// Prepare pointer
IntPtr uList = Marshal.AllocHGlobal(Marshal.SizeOf(userList));
Marshal.StructureToPtr(userList, uList, false);
result = GetUsers(out uList);
Marshal.PtrToStructure(uList, userList); <--
运行时遇到了致命错误。错误的地址是0x79f82af6,在线程0x464上。错误代码是0xc0000005。此错误可能是CLR中的错误,也可能是用户代码的不安全或不可验证部分中的错误。此错误的常见来源包括COM-interop或PInvoke的用户封送错误,这可能会破坏堆栈。
我正确地获得了NumUsers属性,但是当解组数组时似乎发生了错误。有什么想法吗?
答案 0 :(得分:4)
如果在用作out
参数的结构中指定数组,则需要告诉编组程序该数组的长度。使用您的代码,封送器可能正在分配一个零长度数组或只使用null
,这会产生崩溃。遗憾的是,似乎无法将可变长度out
数组指定为结构的成员,因为MarshalAs.SizeParamIndex
仅适用于方法。你可能会使用MarshalAs.SizeConst
来指定一个大的,不变大小的数组,但通常你必须像这样解析(可能是被调用者分配的)返回缓冲区:
var count = Marshal.ReadInt32 (uList) ;
var users = new List<USER_LIST_ITEM> () ;
var ptr = (long)uList + 4 ;
for (int i = 0 ; i < count ; ++i)
{
users.Add (Marshal.PtrToStructure (typeof (USER_LIST_ITEM),
new IntPtr (ptr))) ;
ptr += Marshal.SizeOf (typeof (USER_LIST_ITEM)) ;
}
你必须特别注意对齐和填充以及32/64位问题。
答案 1 :(得分:3)
那是因为尚未分配List
。
您需要初始化所有字段。
我看到的另一个问题是:
IntPtr uList = Marshal.AllocHGlobal(Marshal.SizeOf(userList));
...
result = GetUsers(out uList);
您确定out
不应该是ref
吗?否则没有意义(不确定ref
是否正确)。
更新:再次查看你的代码,你应该这样做(并避免内存泄漏引起你的注意)。
IntPtr uList;
var result = GetUsers(out uList);
var userlist = (USER_LIST) Marshal.PtrToStructure(ulist, typeof(USER_LIST));
Marshal.FreeHGlobal(ulist); // pray here or shoot the author of the C function
再次更新:
您的p / invoke签名可能有误,或者您解释错误。
我猜它可能是这样的:
int GetUsers(USER_LIST* ulist);
而你所拥有的并不是一回事。
如果是这种情况,解决方案很简单。
将USER_LIST
更改为某个类(但保持顺序布局)并使用
// pinvoke sig
int GetUsers(USER_LIST ulist);
var ulist = new USER_LIST();
// initialize fields
var r = GetUsers(ulist);
- 或 -
通过ref
调用。
// pinvoke sig
int GetUsers(ref USER_LIST ulist);
var ulist = new USER_LIST();
// initialize fields
var r = GetUsers(ref ulist);
这样,您不必乱用手动编组,我不能再看到内存泄漏的可能性。
最终更新:
根据您发布的签名,看起来GetUsers
返回指向USER_LIST
列表的指针,返回值为count。那里的内存泄漏很好。
无论如何,我可能会在这里尝试一种不安全的方法,然后只是走过结果,并确保一切都被释放。 (我仍然认为你应该拍摄作者)。
答案 2 :(得分:1)
我认为您的原始代码可能不是那么错误。 您可能刚刚使用了wrong overload的Marshal.PtrToStructure。
你试过这个吗?
[DllImport("userManager.dll")]
static extern int GetUsers(out IntPtr userList);
[DllImport("userManager.dll")]
static extern void UMFree(IntPtr userList);
static void Main()
{
IntPtr userList; // no need to allocate memory in managed code;
GetUsers(out userList); // memory is allocated by native function
USER_LIST u = (USER_LIST)Marshal.PtrToStructure(userList, typeof(USER_LIST));
UMFree(userList);
}
使用不安全的代码:
public unsafe struct USER_LIST
{
public uint numUsers;
public USER_LIST_ITEM* list;
}
public unsafe struct USER_LIST_ITEM
{
public fixed byte name[260];
public fixed byte address[256];
}
class Program
{
[DllImport("userManager.dll")]
static unsafe extern int GetUsers(USER_LIST** userList);
[DllImport("userManager.dll")]
static unsafe extern int UMFree(USER_LIST* userList);
private static unsafe void Main()
{
USER_LIST* list;
GetUsers(&list);
UMFree(list);
}
}