封送類(Marshaling classes)在.NET框架中扮演著至關重要的角色,尤其是在托管代碼與非托管代碼之間進行數據交換時。封送過程涉及到將托管環境中的對象轉換為非托管環境中可以理解的形式,并且反之亦然。這一過程確保了兩種不同類型的代碼能夠有效地通信和協作。
以下是封送類、結構和聯合
類型 | 描述 | 示例 |
---|---|---|
按值傳遞類 | 將具有整數成員的類傳遞為In/Out參數,與托管的情形相似。 | SysTime 示例 |
按值傳遞結構 | 將結構作為In參數傳遞。 | 結構示例 |
按引用傳遞結構 | 將結構作為In/Out參數傳遞。 | OSInfo 示例 |
具有內嵌結構(平展)的結構 | 傳遞非托管函數中表示內嵌結構的結構的類。此結構再托管的原型中將平展為一個的結構。 | FindFile 示例 |
具有指向另一結構的指針的結構 | 將包含指向第二結構的指針的結構作為成員傳遞。 | 結構示例 |
按值傳遞具有整數的結構數組 | 將僅包含整數的結構數組作為In/Out參數進行傳遞。可以更改數組的成員。 | 數組示例 |
按引用傳遞具有整數和字符串的結構數組 | 將包含整數和字符串的結構數組作為Out參數參數。被調用的函數為數組分配內存。 | OutArrayOfStructs 示例 |
具有值類型的聯合 | 傳遞具有值類型(整數和雙精度)的聯合。 | 聯合示例 |
具有混合類型的聯合 | 傳遞具有混合類型(整數和字符串)的聯合。 | 聯合示例 |
具有特定平臺的布局的結構 | 使用本機打包的傳遞類型。 | 平臺示例 |
結構中的null值 | 傳遞空引用(Visual Basic中為Nothing),而不傳遞對值類型的引用。 | HandleRef 示例 |
類的封送
當涉及到類的封送時,需要注意的是,在.NET Framework中,類是引用類型,而結構體是值類型。這意味著類實例通過引用傳遞,而結構體則是通過復制整個結構體的內容來傳遞。對于類而言,默認情況下它們只能通過COM互操作來進行封送,并總是作為接口被封送。
對于類而言,默認情況下它們只能通過COM互操作來進行封送,并總是作為接口被封送。具體來說:
-
向COM傳遞類:當托管類傳遞給COM時,互操作封送處理程序會自動使用COM代理包裝該類,并將由代理生成的類接口傳遞到COM方法調用。代理負責委托對類接口的所有調用返回給托管對象,并公開其他不由類顯式實現的接口,如
IUnknown
和IDispatch
。 -
向.NET代碼傳遞類:當接口傳遞回托管代碼時,互操作封送處理程序負責用適當的包裝器包裝接口,并將這個包裝器傳遞給托管方法。每個COM對象實例都有一個唯一的包裝器,無論該對象實現了多少個接口。例如,如果一個COM對象實現了五個不同的接口,則只有一個包裝器實例存在,它公開所有這五個接口。
封送類的默認行為
對于某些特定的.NET類型,如數組、布爾值、字符、委托、類、對象、字符串和結構等,默認的封送規則已經定義好了。這些規則決定了數據如何在托管和非托管內存之間傳遞。例如,.NET數組通常會被封送成指向數組元素本機表示形式的指針;而對于字符串,默認情況下會根據上下文選擇合適的編碼方式(如UTF-16, ANSI, UTF-8等),并且可以通過設置MarshalAs
屬性來指定更具體的封送選項。
自定義封送
盡管有默認的封送規則,但在很多實際應用場景下,開發者可能需要更加精細地控制封送過程。這時就可以利用MarshalAsAttribute
屬性來指定參數或字段應該怎樣被封送。例如,如果你想要將字符串作為以null結尾的UTF-8字符串發送,你可以這樣做:
[LibraryImport("somenativelibrary.dll")]
static extern int MethodA([MarshalAs(UnmanagedType.LPStr)] string parameter);
//或者
[LibraryImport("somenativelibrary.dll", StringMarshalling = StringMarshalling.Utf8)]
static extern int MethodB(string parameter);
示例:封送具有嵌套結構的類
假設我們有一個C++ DLL導出了一個名為MYPERSON3
的結構體,其中包含了另一個結構體MYPERSON
以及一個整數成員age
。要在C#中正確地封送這樣的結構體,我們可以定義相應的托管結構如下:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MyPerson
{public string first;public string last;
}[StructLayout(LayoutKind.Sequential)]
public struct MyPerson3
{public MyPerson person;public int age;
}
接著,我們需要為非托管函數創建一個托管原型,并確保正確地處理結構體的封送。如果我們知道函數接受的是按值傳遞的MYPERSON3
結構體,那么我們的C#聲明可能會像這樣:
private static class NativeMethods
{[DllImport("..\\LIB\\PinvokeLib.dll")]public static extern void TestStructInStruct3(MyPerson3 person3);
}
在這個例子中,MyPerson3
結構體會作為一個整體被復制到非托管堆棧上,然后傳遞給非托管函數。如果函數修改了結構體的內容,那么這些更改不會反映回原始的托管副本,除非我們將參數標記為ref
或out
,從而允許雙向的數據流動。
總結:
封送類涉及到了解托管與非托管邊界上的數據傳輸機制,包括但不限于上述提到的各種細節。正確地配置和管理這些細節可以幫助避免潛在的問題,確保應用程序之間的互操作性順暢無誤。