如何通过P/Invoke返回Struct和String Array

本文介绍如何使用P/Invoke处理.NET与C++ DLL交互中的复杂结构体,特别是当DLL返回包含指针和不定长数组的结构体时的手动转换方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

P/Invoke提供了方便的.NET和c++ dll交互接口,通过P/Invoke可以将native的对象转化成managed object,从而享受.NET带来的种种便利.

但是,假如dll中返回的参数,不是形如int, double, bool这样可以直接转化为.NET类型的对象,又该如何使用P/Invoke呢?

比如我有这样一个接口:

ContractedBlock.gif ExpandedBlockStart.gif Code
 1 #ifdef DLLPROJECT
 2 #define DLLEXP __declspec(dllexport)
 3 #else
 4 #define DLLEXP __declspec(dllimport)
 5 #endif
 6 
 7 struct group
 8 {
 9     char* groupName;
10     int userCount;
11     char** userNames;
12 };
13 
14 struct groupList
15 {
16     int count;
17     group* groups;
18 };
19 
20 extern "C"
21 {
22     DLLEXP groupList* getGroupList();
23 }

getGroupList返回一个嵌套struct的结构体,如何在.NET中获取该对象呢?

 

如果查阅MSDN,通常会得到这样的答案:

声明一个带Attribute的结构体

ContractedBlock.gif ExpandedBlockStart.gif Code
1     [StructLayout(LayoutKind.Sequential)]
2     public class GroupList
3     {
4         String GroupName;
5         int UserCount;
6         String[] UserNames;
7     }

然后写一个如下的函数,试图通过对Attribute的修饰来达到获取返回的结构体的目的.

 

ContractedBlock.gif ExpandedBlockStart.gif Code
1     [DllImport("mydll.dll")]
2     [return: MarshalAs(UnmanagedType.LPStruct)]
3     public static extern void getGroupList();

 假如你正在采用类似的方法解决问题,基本上你会得到一个Memory Corrupt的错误信息. 或许有人要说,结构体/String数组不应该作为返回值传递,而是应该放到参数中,由getGroupList来为参数赋值. 的确,有很多这样调用的例子,网上能搜到一大把,可惜的是,这样的方法只适用于定长的结构. 比如,不包括的struct,或者是定长的String数组. MSDN上有很多类似的例子,请看这里.

 

既然MSDN上已经有成功的例子了,那我这里要说明的是什么呢? 注意struct groupList中,groups的个数是不确定的,它是一个指像group数组的指针. .NET在Marshal的时候自然不知道如何将这样的结构体转换成.NET Object. 但是,我们可以手动写一个转换:

ContractedBlock.gif ExpandedBlockStart.gif Code
 1     [DllImport("mydll.dll")]
 2     public static extern IntPtr getGroupList();
 3 
 4     class Group
 5     {
 6         public string Name;
 7         public List<string> UserList;
 8         public Group()
 9         {
10             UserList = new List<string>();
11         }
12     }
13 
14     static List<Group> parseGroupList(IntPtr groupListPtr)
15     {
16         List<Group> ret = new List<Group>();
17         int groupCount = Marshal.ReadInt32(groupListPtr);
18         for (int i = 0; i < groupCount; i++)
19         {
20             Group group = new Group();
21             IntPtr groupPtr = Marshal.ReadIntPtr(groupListPtr, 4);
22             group.Name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(groupPtr));
23             int userCount = Marshal.ReadInt32(groupPtr, 4);
24             IntPtr usernameListPtr = Marshal.ReadIntPtr(groupPtr, 8);
25             for (int j = 0; j < userCount; j++)
26             {
27                 IntPtr usernamePtr = Marshal.ReadIntPtr(usernameListPtr, j * 4);
28                 string name = Marshal.PtrToStringAnsi(usernamePtr);
29                 group.UserList.Add(name);
30             }
31             ret.Add(group);
32         }
33         return ret;
34     }
35 
36     static void Main(string[] args)
37     {
38         IntPtr groupList = getGroupList();
39         parseGroupList(groupList);
40     }
41 

关于IntPtr,可以在网上找一些相关的信息,这里,只要把它想象成c++中的void*类型即可. 在Main中,我们读到了一个IntPtr类型的groupList,即指向dll返回结构体的指针. 然后,在parseGroupList中,我们一步一步地解析这个指针.

struct groupList的第一个member是int count.所以,我们通过

int groupCount = Marshal.ReadInt32(groupListPtr)  把它读出来

第二个member是group*.那就可以用

IntPtr groupPtr = Marshal.ReadIntPtr(groupListPtr, 4);

读出来.注意这里4这个参数表示位移,我们之前已经读到一个int了,所以要位移4bytes.

以此类推,如此我们可以把c++中的结构体,转换成.NET中的List<Group>类型. 全归功于Marshal的强大功能.

 

总结

以上的方法,可以读取任何的结构体,关于如何解析字符串数组,可以看code project上的经典文章

http://www.codeproject.com/KB/cs/marshalarrayofstrings.aspx

我就是看了这篇文章后受到启发,把它扩展应用到返回struct上的.

最后还要提一下,通常情况下,还是把结构体放在返回值里,原因一,返回值要留给ErrorCode用;原因二,这样的写法通常会忘记释放内存(注意,groupList是在dll中用malloc分配的,还需要在同一个dll中free掉).一个更好的做法是设计一组GroupListAlloc/GroupListFree/int GetGroupList(GroupList*)的接口. 当然,解析的过程还是一样的. :)

 

转载于:https://www.cnblogs.com/magicdlf/archive/2009/03/03/PInvoke.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值