簡體   English   中英

C#marshal非托管指針返回類型

[英]C# marshal unmanaged pointer return type

我有一個非托管庫,它有這樣的功能:

type* foo();

foo基本上通過Marshal.AllocHGlobal在托管堆上分配非托管type的實例。

我有一個托管版本的type 它不是blittable但我在成員上設置了MarshalAs屬性,因此我可以使用Marshal.PtrToStructure來獲取它的托管版本。 但是不得不用額外的簿記來調用foo來調用Marshal.PtrToStructure有點煩人。

我希望能夠在C#方面做這樣的事情:

[DllImport("mylib", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPStruct)]
type* foo();

讓C#的marshaller處理幕后轉換,就像它對函數參數一樣。 我以為我應該能夠這樣做因為在托管堆上分配了type 但也許我不能? 有沒有辦法讓C#的內置編組器為我處理返回類型的非托管到托管轉換,而無需手動調用Marshal.PtrToStructure

如果在.NET端, type被聲明為類而不是結構,則自定義封送器工作正常。 這在UnmanagedType枚舉中有明確說明:

與MarshalAsAttribute.MarshalType或MarshalAsAttribute.MarshalTypeRef字段一起使用時,指定自定義封送器類。 MarshalAsAttribute.MarshalCookie字段可用於將其他信息傳遞給自定義封送程序。 您可以在任何引用類型上使用此成員

這是一些應該正常工作的示例代碼

[[DllImport("mylib", CallingConvention = CallingConvention.Cdecl)]
[return : MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef= typeof(typeMarshaler))]
private static extern type Foo();

private class typeMarshaler : ICustomMarshaler
{
    public static readonly typeMarshaler Instance = new typeMarshaler();

    public static ICustomMarshaler GetInstance(string cookie) => Instance;

    public int GetNativeDataSize() => -1;

    public object MarshalNativeToManaged(IntPtr nativeData) => Marshal.PtrToStructure<type>(nativeData);

    // in this sample I suppose the native side uses GlobalAlloc (or LocalAlloc)
    // but you can use any allocation library provided you use the same on both sides
    public void CleanUpNativeData(IntPtr nativeData) => Marshal.FreeHGlobal(nativeData);

    public IntPtr MarshalManagedToNative(object managedObj) => throw new NotImplementedException();
    public void CleanUpManagedData(object managedObj) => throw new NotImplementedException();
}

[StructLayout(LayoutKind.Sequential)]
class type
{
    /* declare fields */
};

當然,將非托管結構聲明更改為類可能會產生深刻的影響(可能並不總是會引發編譯時錯誤),尤其是在您有大量現有代碼的情況下。

另一種解決方案是使用Roslyn來解析代碼,提取所有類似Foo的方法,並為每個方法生成一個額外的.NET方法。 我會這樣做的。

type* foo()

這是一個非常笨拙的函數簽名,很難在C或C ++程序中正確使用,並且當你進行pinvoke時永遠不會變得更好。 內存管理是最大的問題,您希望與編寫此代碼的程序員合作以使其更好。

您的首選簽名應類似於int foo(type* arg, size_t size) 換句話說,調用者提供內存並且本機函數填充它。 大小參數是避免內存損壞所必需的,當類型的版本更改並變大時是必需的。 通常包括在一個類型的領域。 int返回值對於返回錯誤代碼非常有用,因此您可以正常失敗。 除了使其安全之外,它還更加高效,因為根本不需要內存分配。 您只需傳遞一個局部變量即可。

...通過Marshal.AllocHGlobal在托管堆上分配非托管類型的實例

不,這是內存管理假設變得非常危險的地方。 從來沒有托管堆,本機代碼沒有合適的方式來調用CLR。 你不能假設它使用了相當於Marshal.AllocHGlobal()的東西。 本機代碼通常使用malloc()來分配存儲,用於分配的堆是它鏈接的CRT的實現細節。 只有CRT的free()函數才能保證可靠地釋放它。 你不能自己打電話給()。 跳到底部,看看為什么AllocHGlobal()似乎是正確的。

有一些函數簽名強制pinvoke marshaller釋放內存,它通過調用Marshal.FreeCoTaskMem()來實現。 請注意,這不等於Marshal.AllocHGlobal(),它使用不同的堆。 它假定編寫本機代碼以支持互操作並使用CoTaskMemAlloc(),它使用專用於COM互操作的堆。

這不是輕浮但我有MarshalAs屬性設置......

這是一個堅韌不拔的細節,解釋了為什么你必須讓它變得尷尬。 pinvoke marshaller不想解決這個問題,因為它必須編組副本,並且存在太多自動釋放對象及其成員的存儲的風險。 使用[MarshalAs]是不必要的,並且不會使代碼更好,只需將返回類型更改為IntPtr 准備傳遞給Marshal.PtrToStructure()以及您需要的任何內存釋放功能。


我必須談談Marshal.AllocHGlobal()似乎是正確的原因。 它不像以前那樣,但在最近的Windows和VS版本中已經改變了。 Win8和VS2012有一個很大的設計變化。 操作系統不再創建Marshal.AllocHGlobal和Marshal.AllocCoTaskMem分配的單獨堆。 它現在是一個單獨的堆,默認進程堆(GetProcessHeap()返回它)。 VS2012中包含的CRT也有相應的變化,它現在也使用GetProcessHeap()而不是使用HeapCreate()創建自己的堆。

變化很大,沒有廣泛宣傳。 微軟並沒有發布我所知道的任何動機,我認為基本原因是WinRT(又名UWP),大量的內存管理骯臟,讓C ++,C#和Javascript代碼無縫協同工作。 這對於必須編寫互操作代碼的每個人來說都非常方便,您現在可以假設Marshal.FreeHGlobal()完成了工作。 或者Marshal.FreeCoTaskMem()就像pinvoke marshaller使用的那樣。 或者像本機代碼一樣使用free(),沒有區別了。

但也是一個重大的風險,你不能再認為代碼在你的開發機器上運行良好並且必須在Win7上重新測試時沒有錯誤。 如果你猜錯了發布功能,你會得到一個AccessViolationException。 更糟糕的是,你還需要支持XP或Win2003,沒有崩潰,但你會默默地泄漏內存。 當它發生時很難處理,因為你無法在不改變原生代碼的情況下取得成功。 最好早點把它弄好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM