共用方式為


System.Runtime.InteropServices.ICustomMarshaler 介面

本文提供此 API 參考文件的補充備註。

介面 ICustomMarshaler 提供用於處理方法呼叫的自定義包裝函式。

封送器作為新舊介面功能之間的橋樑。 自訂封送處理提供下列優點:

  • 它可讓設計成使用舊介面的用戶端應用程式,也能使用實作新介面的伺服器。
  • 它允許為新介面設計的用戶端應用程式能夠與實現舊介面的伺服器協同工作。

如果您有引入不同封送行為的介面,或以不同的方式向元件物件模型(COM)公開,您可以設計自訂封送處理器,而不是使用 Interop 封送處理器。 藉由使用自定義封送器,您可以將新的 .NET Framework 元件與現有 COM 元件之間的差異降到最低。

例如,假設您正在開發一個名稱為 INew 的受控介面。 當這個介面透過標準 COM 可呼叫包裝函式 (CCW) 公開給 COM 時,其方法與 Managed 介面相同,並使用 Interop 封送處理器內建的封送處理規則。 現在假設名為 IOld 的已知 COM 介面已經提供與 INew 介面相同的功能。 藉由設計自定義封送器,您可以提供一個非受控的 IOld 實作,該實作僅將呼叫委派給 INew 介面的受控實作。 因此,自訂封送處理器會作為受控與非受控介面之間的橋接。

備註

從 Managed 程式代碼呼叫僅限分派介面上的 Unmanaged 程式代碼時,不會叫用自定義封送器。

定義封送處理類型

在您建立自訂封送處理器之前,必須先定義要封送處理的受控和非受控介面。 這些介面通常會執行相同的函式,但會以不同的方式公開給 Managed 和 Unmanaged 物件。

Managed 編譯程式會從元數據產生 Managed 介面,產生的介面看起來像任何其他 Managed 介面。 下列範例顯示一般介面。

public interface INew
{
    void NewMethod();
}
Public Interface INew
    Sub NewMethod()
End Interface

您可以在介面定義語言 (IDL) 中定義 Unmanaged 類型,並使用Microsoft介面定義語言 (MIDL) 編譯程式加以編譯。 您可以在連結庫語句中定義 介面,併為其指派具有通用唯一標識碼 (UUID) 屬性的介面標識碼,如下列範例所示。

 [uuid(9B2BAADA-0705-11D3-A0CD-00C04FA35826)]
library OldLib {
     [uuid(9B2BAADD-0705-11D3-A0CD-00C04FA35826)]
     interface IOld : IUnknown
         HRESULT OldMethod();
}

MIDL 編譯程式會產生數個輸出檔案。 如果介面是在 Old.idl 中定義,輸出檔 Old_i.c 會 const 定義具有 介面識別碼 (IID) 的變數,如下列範例所示。

const IID IID_IOld = {0x9B2BAADD,0x0705,0x11D3,{0xA0,0xCD,0x00,0xC0,0x4F,0xA3,0x58,0x26}};

Old.h 檔案也由 MIDL 產生。 它包含介面的C++定義,可包含在您的C++原始程式碼中。

實作 ICustomMarshaler 介面

您的自定義封送處理器必須實作 ICustomMarshaler 介面,以提供適當的包裝器給執行階段。

下列 C# 程式代碼會顯示必須由所有自定義封送處理器實作的基底介面。

public interface ICustomMarshaler
{
    Object MarshalNativeToManaged(IntPtr pNativeData);
    IntPtr MarshalManagedToNative(Object ManagedObj);
    void CleanUpNativeData(IntPtr pNativeData);
    void CleanUpManagedData(Object ManagedObj);
    int GetNativeDataSize();
}
Public Interface ICustomMarshaler
     Function MarshalNativeToManaged( pNativeData As IntPtr ) As Object
     Function MarshalManagedToNative( ManagedObj As Object ) As IntPtr
     Sub CleanUpNativeData( pNativeData As IntPtr )
     Sub CleanUpManagedData( ManagedObj As Object )
     Function GetNativeDataSize() As Integer
End Interface

介面 ICustomMarshaler 包含提供轉換支援、清理支援,以及要封送處理之數據的相關資訊的方法。

作業類型 ICustomMarshaler 方法 說明
轉換 (從原生程式代碼轉換為 Managed 程式代碼) MarshalNativeToManaged 將原生數據的指標封送處理至 Managed 物件。 這個方法會傳回一個自訂運行時可呼叫封裝器(RCW),它可以封送處理作為參數傳遞的非受控介面。 封送處理器應該傳回該類型的自定義 RCW 實例。
轉換(從托管程式碼到原生程式碼) MarshalManagedToNative 將受控物件封送為指向原生數據的指標。 這個方法會傳回自訂 COM 可呼叫包裝函式(CCW),可封送處理當作自變數的受控介面。 封送處理器應該傳回該類型的自定義CCW實例。
清除 (原生程式代碼) CleanUpNativeData 可讓封送處理器清除MarshalManagedToNative 方法所傳回的原生數據(CCW)。
管理程式碼的清理 CleanUpManagedData 可讓封送處理器清除由MarshalNativeToManaged方法傳回的受控資料(RCW)。
資訊(關於原生代碼) GetNativeDataSize 傳回要封送處理的非受控數據的大小。

轉換

ICustomMarshaler.MarshalNativeToManaged

將原生數據的指標封送處理至 Managed 物件。 這個方法會傳回一個自訂運行時可呼叫封裝器(RCW),它可以封送處理作為參數傳遞的非受控介面。 封送處理器應該傳回該類型的自定義 RCW 實例。

ICustomMarshaler.MarshalManagedToNative

將受控物件封送為指向原生數據的指標。 這個方法會傳回自訂 COM 可呼叫包裝函式(CCW),可封送處理當作自變數的受控介面。 封送處理器應該傳回該類型的自定義CCW實例。

清理

ICustomMarshaler.CleanUpNativeData

可讓封送處理器清除MarshalManagedToNative 方法所傳回的原生數據(CCW)。

ICustomMarshaler.CleanUpManagedData

可讓封送處理器清除由MarshalNativeToManaged方法傳回的受控資料(RCW)。

大小資訊

ICustomMarshaler.GetNativeDataSize

傳回要封送處理的非受控數據的大小。

備註

如果自定義封送處理器在從原生到受控的封送處理過程中,或在清理過程中,呼叫了任何會設定最後一個 P/Invoke 錯誤的方法,則 Marshal.GetLastWin32Error()Marshal.GetLastPInvokeError() 所返回的值將代表封送處理或清理呼叫中的該次呼叫。 當將自定義的封送處理器與 P/Invokes 一起使用,並將 DllImportAttribute.SetLastError 設定為 true 時,這可能會導致錯誤被忽略。 若要保留最後一個 P/Invoke 錯誤,請使用 Marshal.GetLastPInvokeError() 實作中的 Marshal.SetLastPInvokeError(Int32)ICustomMarshaler 方法。

實作 GetInstance 方法

除了實作 ICustomMarshaler 介面外,自定義封送處理器還必須實作一個稱為 staticGetInstance 方法,該方法需接受 String 作為參數,並具有 ICustomMarshaler 的返回型別。 這個 static 方法是由 Common Language Runtime 的 COM Interop 層呼叫,以具現化自定義封送器實例。 傳遞至 GetInstance 的字串是一個 cookie,該方法可用來自訂傳回的封送處理器。 下列範例顯示最少但完整的 ICustomMarshaler 實作。

public class NewOldMarshaler : ICustomMarshaler
{
    public static ICustomMarshaler GetInstance(string pstrCookie)
        => new NewOldMarshaler();

    public Object MarshalNativeToManaged(IntPtr pNativeData) => throw new NotImplementedException();
    public IntPtr MarshalManagedToNative(Object ManagedObj) => throw new NotImplementedException();
    public void CleanUpNativeData(IntPtr pNativeData) => throw new NotImplementedException();
    public void CleanUpManagedData(Object ManagedObj) => throw new NotImplementedException();
    public int GetNativeDataSize() => throw new NotImplementedException();
}

套用 MarshalAsAttribute

若要使用自定義封送處理器,您必須將 MarshalAsAttribute 屬性套用至要封送處理的參數或欄位。

您也必須將 UnmanagedType.CustomMarshaler 列舉值傳遞至 MarshalAsAttribute 建構函式。 此外,您必須使用下列其中一個具名參數來指定 MarshalType 欄位:

  • MarshalType (必要):自定義封送器之組件限定名稱。 名稱應該包含自定義封送器命名空間和類別。 如果自定義封送器未在所使用的元件中定義,您必須指定其定義所在的元件名稱。

    備註

    您可以使用 MarshalTypeRef 欄位,而不是 MarshalType 欄位。 MarshalTypeRef 會採用更容易指定的類型。

  • MarshalCookie (選擇性):傳遞至自定義封送器的Cookie。 您可以使用 Cookie 以提供其他資訊給編排器。 例如,如果使用相同的封送處理器來提供一些包裝函式,Cookie 會識別特定的包裝函式。 Cookie 會傳遞至 GetInstance 的 Marshaller 方法。

MarshalAsAttribute 屬性會識別自定義封送處理器,以便啟動適合的封裝器。 Common Language Runtime 的 Interop 服務接著會檢查屬性,並在第一次需要封送引數(參數或欄位)時建立自訂封送處理器。

運行時間接著會在自定義封送器上呼叫 MarshalNativeToManagedMarshalManagedToNative 方法,以啟動正確的包裝函式來處理呼叫。

使用自訂的序列化器

當自定義封送器完成時,您可以使用它做為特定類型的自定義包裝函式。 下列範例顯示 IUserData 受控介面的定義:

interface IUserData
{
    void DoSomeStuff(INew pINew);
}
Public Interface IUserData
    Sub DoSomeStuff(pINew As INew)
End Interface

在下列範例中,IUserData 介面使用 NewOldMarshaler 自訂封送器,讓非受控用戶端應用程式能將 IOld 介面傳遞至 DoSomeStuff 方法。 方法的 DoSomeStuff 受控版本描述會採用 INew 介面,如上一個範例所示,而非受控版本 DoSomeStuff 則使用 IOld 介面指標,如下列範例所示。

[uuid(9B2BAADA-0705-11D3-A0CD-00C04FA35826)]
library UserLib {
     [uuid(9B2BABCD-0705-11D3-A0CD-00C04FA35826)]
     interface IUserData : IUnknown
         HRESULT DoSomeStuff(IUnknown* pIOld);
}

匯出受管理定義的 IUserData 所產生的類型庫,會給出此範例中顯示的非受管理定義,而不是標準定義。 MarshalAsAttribute 屬性應用於 INew 自變數在受管理定義的 DoSomeStuff 方法中,表示自變數使用自定義的封送處理器,如下列範例所示。

using System.Runtime.InteropServices;
Imports System.Runtime.InteropServices
interface IUserData
{
    void DoSomeStuff(
        [MarshalAs(UnmanagedType.CustomMarshaler,
         MarshalType="NewOldMarshaler")]
    INew pINew
    );
}
Public Interface IUserData
    Sub DoSomeStuff( _
        <MarshalAs(UnmanagedType.CustomMarshaler, _
        MarshalType := "MyCompany.NewOldMarshaler")> pINew As INew)
End Interface

在上述範例中,提供給 MarshalAsAttribute 屬性的第一個參數是 UnmanagedType.CustomMarshaler 列舉值 UnmanagedType.CustomMarshaler

第二個參數是 MarshalType 字段,其提供自定義封送器之元件限定名稱。 此名稱是由自定義封送器 (MarshalType="MyCompany.NewOldMarshaler") 的命名空間和類別所組成。