程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 通過P/Invoke返回Struct和String Array

通過P/Invoke返回Struct和String Array

編輯:關於.NET

P/Invoke提供了方便的.NET和c++ dll交互接口,通過P/Invoke可以將native的對象轉化成managed object,從而享受.NET帶來的種種便利.

但是,假如dll中返回的參數,不是形如int, double, bool這樣可以直接轉化為.NET類型的對象,又該如 何使用P/Invoke呢?

比如我有這樣一個接口:

 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的結構體

1     [StructLayout(LayoutKind.Sequential)]

2     public class GroupList

3     {

4         String GroupName;

5         int UserCount;

6         String[] UserNames;

7     }

然後寫一個如下的函數,試圖通過對Attribute的修飾來達到獲取返回的結構體的目的.

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. 但是,我們可以手動寫一個轉換:

 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.所以,我們通過 groupCount = Marshal.ReadInt32 (groupListPtr)  把它讀出來

int

第二個member是group*.那就可以用 groupPtr = Marshal.ReadIntPtr(groupListPtr, 4);

IntPtr

讀出來.注意這裡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*)的 接口. 當然,解析的過程還是一樣的.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved