繁体   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