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

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

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

比如我有这样一个接口:

Code

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

 

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

声明一个带Attribute的结构体

Code

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

 

Code

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

 

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

Code

关于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*)的接口. 当然,解析的过程还是一样的. :)

 

posted @ 2009-03-03 17:55  magicdlf  阅读(2645)  评论(2编辑  收藏  举报