如何编写自定义封送处理程序,以使数据从本机流向托管流? [英] How do I write a custom marshaler which allows data to flow from native to managed?

查看:84
本文介绍了如何编写自定义封送处理程序,以使数据从本机流向托管流?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

尝试编写与此问题相关的自定义封送拆收器(

In attempting to write a custom marshaler related to this question (P/Invoke from C to C# without knowing size of array), I have come across something I cannot understand. This is the first ever custom marshaler that I have written so no doubt I'm missing something obvious due to my ignorance.

这是我的C#代码:

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace CustomMarshaler
{
    public class MyCustomMarshaler : ICustomMarshaler
    {
        static MyCustomMarshaler static_instance;

        public IntPtr MarshalManagedToNative(object managedObj)
        {
            if (managedObj == null)
                return IntPtr.Zero;
            if (!(managedObj is int[]))
                throw new MarshalDirectiveException("VariableLengthArrayMarshaler must be used on an int array.");

            int[] arr = (int[])managedObj;
            int size = sizeof(int) + arr.Length * sizeof(int);
            IntPtr pNativeData = Marshal.AllocHGlobal(size);
            Marshal.WriteInt32(pNativeData, arr.Length);
            Marshal.Copy(arr, 0, pNativeData + sizeof(int), arr.Length);
            return pNativeData;
        }

        public object MarshalNativeToManaged(IntPtr pNativeData)
        {
            int len = Marshal.ReadInt32(pNativeData);
            int[] arr = new int[len];
            Marshal.Copy(pNativeData + sizeof(int), arr, 0, len);
            return arr;
        }

        public void CleanUpNativeData(IntPtr pNativeData)
        {
            Marshal.FreeHGlobal(pNativeData);
        }

        public void CleanUpManagedData(object managedObj)
        {
        }

        public int GetNativeDataSize()
        {
            return -1;
        }

        public static ICustomMarshaler GetInstance(string cookie)
        {
            if (static_instance == null)
            {
                return static_instance = new MyCustomMarshaler();
            }
            return static_instance;
        }
    }
    class Program
    {
        [DllImport(@"MyLib.dll")]
        private static extern void Foo(
            [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyCustomMarshaler))]
            int[] arr
        );

        static void Main(string[] args)
        {
            int[] colorTable = new int[] { 1, 2, 3, 6, 12 };
            Foo(colorTable);
            foreach (int value in colorTable)
                Console.WriteLine(value);
        }
    }
}

另一方面是一个普通的本机DLL,它是用Delphi编写的.

On the other side is a trivial native DLL, written in Delphi as it happens.

library MyLib;

procedure Foo(P: PInteger); stdcall;
var
  i, len: Integer;
begin
  len := P^;
  Writeln(len);
  for i := 1 to len do begin
    inc(P);
    Writeln(P^);
    inc(P^);
  end;
end;

exports
  Foo;

begin
end.

这个想法是将数组传递给DLL,然后DLL打印出length字段和数组的值.本机代码还将数组的每个值加1.

The idea is that the array is passed to the DLL which then prints out the length field, and the values of the array. The native code also increments each value of the array by 1.

所以,我希望看到以下输出:

So, I expect to see this output:


5
1
2
3
6
12
2
3
4
7
13

但不幸的是,我看到了以下输出:

But unfortunately I see this output:


5
1
2
3
6
12
1
2
3
6
12

在调试器下,我可以看到MarshalNativeToManaged正在执行,并且它返回的值已经增加.但是这些递增的值找不到返回传递给Foo的对象的方式.

Under the debugger I can see that MarshalNativeToManaged is executing, and that the values that it returns have been incremented. But these incremented values don't find there way back into the object that is passed to Foo.

该如何解决?

推荐答案

很多年前,我遇到了类似的问题,发现关于Custom Marshaling的文档很少.我怀疑使用ICustomMarshaler从未真正起飞,因为在常规代码过程中始终可以使用手动封送来完成它.因此,从来没有真正需要任何高级自定义封送处理方案的文档.

I had a similar problem many years ago and found that there was very little documentation on Custom Marshaling. I suspect using ICustomMarshaler never really took off since it can always be done using manual marshaling in the course of your regular code. And so there was never really a need for any documentation of advanced custom marshaling scenarios.

无论如何,我想我会通过各种资源和大量的反复试验来弄清大多数Custom Marshaling的工作原理.

Anyway, through a variety of sources and much trial and error I think I teased out a practical understanding of how most of Custom Marshaling works.

在您的情况下,您已经为[In]编组正确设置了ManagedToNative方法,对于大多数[Out]编组正确设置了NativeToManaged方法,但是[In,Out]编组实际上有点棘手. [In,Out]封送实际上是就地封送.因此,在回退的途中,必须将数据封送回操作的[In]端所提供的同一实例.

In your case, you have set up the ManagedToNative method correctly for [In] marshaling and the NativeToManaged method correctly for most [Out] marshaling but [In, Out] marshaling is actually a bit trickier. [In, Out] marshaling is actually in-place marshaling. So on the way back out you must marshal the data back to the same instance that was provided in the [In] side of the operation.

这取决于使用引用类型还是值类型,调用是普通的pInvoke调用还是委托上的回调等,因此有一些小的变化.但是,思考需要在什么地方结束是关键

There are a number of small variations on this depending on whether using reference or value types, whether the call is a normal pInvoke call or a callback on a delegate, etc. But thinking about what needs to end up where is the key.

下面的代码变体以您想要的方式工作(对于.Net 2.0及更高版本,它似乎以相同的方式工作):

The following variation on your code works the way you want it to (and it seems to works the same way for .Net 2.0 and up):

        //This must be thread static since, in theory, the marshaled
    //call could be executed simultaneously on two or more threads.
    [ThreadStatic] int[] marshaledObject;

    public IntPtr MarshalManagedToNative(object managedObj)
    {
        if (managedObj == null)
            return IntPtr.Zero;
        if (!(managedObj is int[]))
            throw new MarshalDirectiveException("VariableLengthArrayMarshaler must be used on an int array.");

        //This is called on the way in so we must keep a reference to 
        //the original object so we can marshal to it on the way out.
        marshaledObject = (int[])managedObj;
        int size = sizeof(int) + marshaledObject.Length * sizeof(int);
        IntPtr pNativeData = Marshal.AllocHGlobal(size);
        Marshal.WriteInt32(pNativeData, marshaledObject.Length);
        Marshal.Copy(marshaledObject, 0, (IntPtr)(pNativeData.ToInt64() + sizeof(int)), marshaledObject.Length);
        return pNativeData;
    }

    public object MarshalNativeToManaged(IntPtr pNativeData)
    {
        if (marshaledObject == null)
            throw new MarshalDirectiveException("This marshaler can only be used for in-place ([In. Out]) marshaling.");

        int len = Marshal.ReadInt32(pNativeData);
        if (marshaledObject.Length != len)
            throw new MarshalDirectiveException("The size of the array cannot be changed when using in-place marshaling.");

        Marshal.Copy((IntPtr)(pNativeData.ToInt64() + sizeof(int)), marshaledObject, 0, len);

        //Reset to null for next call;
        marshalledObject = null;

        return marshaledObject;
    }

这篇关于如何编写自定义封送处理程序,以使数据从本机流向托管流?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆