首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

P/Invoke 進階實戰:手動封送巢狀結構體與字串陣列返回值

     編輯:關於.NET

挑戰:非定長結構體無法自動封送

C++ DLL 返回巢狀結構體:

struct group { char* groupName; int userCount; char** userNames; };
struct groupList { int count; group* groups; };
extern "C" DLLEXP groupList* getGroupList();

groups 數量不定,.NET 自動封送無法處理指向變長陣列的指標。標準 MarshalAs 僅適用定長結構。

解決方案:IntPtr 手動解析

  1. DLL 導入宣告返回 IntPtr(相當於 void*)
  2. 定義對應 .NET 類別 Group
  3. 編寫 parseGroupList 逐層解析記憶體
[DllImport("mydll.dll")]
public static extern IntPtr getGroupList();

class Group {
    public string Name;
    public List UserList = new List();
}

static List parseGroupList(IntPtr groupListPtr) {
    var ret = new List();
    int groupCount = Marshal.ReadInt32(groupListPtr); // count
    
    for (int i = 0; i < groupCount; i++) {
        var group = new Group();
        // group* groups 陣列起始位址 (offset 4)
        IntPtr groupPtr = Marshal.ReadIntPtr(groupListPtr, 4);
        // group 結構體:groupName (offset 0), userCount (offset 4), userNames (offset 8)
        group.Name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(groupPtr, 0));
        int userCount = Marshal.ReadInt32(groupPtr, 4);
        IntPtr usernameListPtr = Marshal.ReadIntPtr(groupPtr, 8);
        
        for (int j = 0; j < userCount; j++) {
            IntPtr usernamePtr = Marshal.ReadIntPtr(usernameListPtr, j * IntPtr.Size);
            group.UserList.Add(Marshal.PtrToStringAnsi(usernamePtr));
        }
        ret.Add(group);
    }
    return ret;
}

關鍵技術點

  • IntPtr.Size 自動適配 32/64 位元指標大小
  • Marshal.ReadInt32/ReadIntPtr 指定偏移量讀取
  • PtrToStringAnsi 處理 C 風格字串

記憶體管理最佳實踐

切勿直接返回結構體指標! 應設計 GroupListAlloc/GroupListFree/GetGroupList(GroupList*) 三件套:

  • 呼叫端負責分配/釋放緩衝區
  • DLL 僅填充資料
  • 避免跨模組堆損壞、記憶體洩漏

參考資源

字串陣列封送經典文章:Marshaling Array of Strings

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved