P/Invoke以指针形式传递结构体内的数组

栏目: C · 发布时间: 5年前

内容简介:在使用P/Invoke(平台调用)从.NET(例如C#)调用C++本机函数时,可以选择将数组以指针的形式传递。但如果数组是结构体中的成员时,就只能通过SafeArray或者按值传递数组的方式传递了,无法用指针的形式传递。那么问题来了,如果要在P/Invoke中使用一个带有动态数组成员的结构体,又不想用SafeArray,该如何实现呢?为了解决这个问题,可以设计专用于传递动态数组的结构。这个结构需要满足以下要求:一个能通过P/Invoke传递的基本数组结构C#代码如下所示:

在使用P/Invoke(平台调用)从.NET(例如C#)调用C++本机函数时,可以选择将数组以指针的形式传递。但如果数组是结构体中的成员时,就只能通过SafeArray或者按值传递数组的方式传递了,无法用指针的形式传递。那么问题来了,如果要在P/Invoke中使用一个带有动态数组成员的结构体,又不想用SafeArray,该如何实现呢?

为了解决这个问题,可以设计专用于传递动态数组的结构。这个结构需要满足以下要求:

  • 能作为其他对象的成员在.NET和本机函数之间传递
  • 能从.NET的IEnumerable接口转换,或者转换到C#数组
  • 正确实现.NET的IDisposable模式,使得:
    • 内存空间能够通过Dispose函数被显式释放
    • 在忘记调用Dispose的情况下由终结器释放内存
    • 不会尝试在.NET端释放在本机端创建的对象
  • 与C++标准库中的其他集合有相似的接口
  • 正确实现C++的析构函数,使得:
    • 析构函数调用时,通过C++端分配的内存空间能够正确释放,数组成员的析构函数能正确调用

一个能通过P/Invoke传递的基本数组结构C#代码如下所示:

[StructLayout(LayoutKind.Sequential)]
    public class UnmanagedArray
    {
        private readonly IntPtr data;
        private readonly int length;
    }

要实现与.NET IEnumerable<T>接口之间的互相转换,可以通过下面给出的FromIEnumerable和Cast成员函数实现。为了防止原始IEnumerable<T>中的元素被垃圾回收导致意外情况(例如将IEnumerable<UnmanagedArray>转换为UnmanagedArray时,垃圾回收和终结器会将原始数据中分配的本机内存释放),在一个静态字典中存储该原始IEnumerable<T>。

/// <summary>
        /// 用于保留转换为UnmanagedArray的原始对象,以防止其值被垃圾回收。
        /// </summary>
        private static Dictionary<IntPtr, object> originalVals = new Dictionary<IntPtr, object>();

        /// <summary>
        /// 该构造函数只用于将非托管对象转换为托管对象时由Marshal函数调用。
        /// 使用该构造函数的实例不会自动释放内存空间。
        /// </summary>
        private UnmanagedArray()
        {
            data = IntPtr.Zero;
            length = 0;
        }

        /// <summary>
        /// 以预先分配的地址空间初始化<see cref="UnmanagedArray"/>类
        /// </summary>
        /// <param name="data">数组的第一个元素的指针</param>
        /// <param name="length">数组的长度</param>
        private UnmanagedArray(IntPtr data, int length)
        {
            this.data = data;
            this.length = length;
        }

        public static UnmanagedArray FromIEnumerable<T>(IEnumerable<T> originalData)
        {
            int size;
            if (typeof(T) == typeof(string))
            {
                size = Marshal.SizeOf<IntPtr>();
            }
            else
            {
                size = Marshal.SizeOf<T>();
            }

            int length = originalData.Count();

            var data = Marshal.AllocHGlobal(size * length);
            int i = 0;
            if (typeof(T) == typeof(string))
            {
                foreach (T item in originalData)
                {
                    Marshal.WriteIntPtr(data + (size * i), Marshal.StringToHGlobalUni((string)(object)item));
                    ++i;
                }
            }
            else
            {
                foreach (var item in originalData)
                {
                    Marshal.StructureToPtr(item, data + (size * i), false);
                    ++i;
                }
            }

            originalVals[data] = originalData;

            return new UnmanagedArray(data, length, typeof(T));
        }

        public IEnumerable<T> Cast<T>()
        {
            for (int i = 0; i < length; ++i)
            {
                yield return Marshal.PtrToStructure<T>(data + (i * Marshal.SizeOf<T>()));
            }
        }

要实现.NET的IDisposable模式,首先令UnmanagedArray类实现IDisposable接口,并增加Dispose函数和终结器。由于P/Invoke功能不支持泛型,在此通过一个静态的类型字典来存储每个实例的类型。同时,构造函数需要进行修改以防止终结器尝试释放C++分配的内存。完整的C#中的UnmanagedArray类如下所示:

[StructLayout(LayoutKind.Sequential)]
    public class UnmanagedArray : IDisposable
    {
        private static Dictionary<IntPtr, Type> typeMap = new Dictionary<IntPtr, Type>();

        /// <summary>
        /// 用于保留转换为UnmanagedArray的原始对象,以防止其值被垃圾回收。
        /// </summary>
        private static Dictionary<IntPtr, object> originalVals = new Dictionary<IntPtr, object>();

        private readonly IntPtr data;
        private readonly int length;

        /// <summary>
        /// 该构造函数只用于将非托管对象转换为托管对象时由Marshal函数调用。
        /// 使用该构造函数的实例不会自动释放内存空间。
        /// </summary>
        private UnmanagedArray()
        {
            data = IntPtr.Zero;
            length = 0;
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 以预先分配的地址空间初始化<see cref="UnmanagedArray"/>类
        /// </summary>
        /// <param name="data">数组的第一个元素的指针</param>
        /// <param name="length">数组的长度</param>
        /// <param name="type">数组元素的类型</param>
        private UnmanagedArray(IntPtr data, int length, Type type)
        {
            this.data = data;
            this.length = length;
            typeMap[data] = type;
        }

        ~UnmanagedArray()
        {
            Dispose(false);
        }

        public static UnmanagedArray FromIEnumerable<T>(IEnumerable<T> originalData)
        {
            int size;
            if (typeof(T) == typeof(string))
            {
                size = Marshal.SizeOf<IntPtr>();
            }
            else
            {
                size = Marshal.SizeOf<T>();
            }

            int length = originalData.Count();

            var data = Marshal.AllocHGlobal(size * length);
            int i = 0;
            if (typeof(T) == typeof(string))
            {
                foreach (T item in originalData)
                {
                    Marshal.WriteIntPtr(data + (size * i), Marshal.StringToHGlobalUni((string)(object)item));
                    ++i;
                }
            }
            else
            {
                foreach (var item in originalData)
                {
                    Marshal.StructureToPtr(item, data + (size * i), false);
                    ++i;
                }
            }

            originalVals[data] = originalData;

            return new UnmanagedArray(data, length, typeof(T));
        }

        public IEnumerable<T> Cast<T>()
        {
            for (int i = 0; i < length; ++i)
            {
                yield return Marshal.PtrToStructure<T>(data + (i * Marshal.SizeOf<T>()));
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (typeMap.ContainsKey(data))
            {
                var type = typeMap[data];
                typeMap.Remove(data);

                originalVals.Remove(data);

                if (type == typeof(string))
                {
                    int size = Marshal.SizeOf<IntPtr>();
                    for (int i = 0; i < length; ++i)
                    {
                        Marshal.ZeroFreeGlobalAllocUnicode(
                            Marshal.ReadIntPtr(data + (size * i)));
                    }
                }
                else
                {
                    int size = Marshal.SizeOf(type);
                    for (int i = 0; i < length; ++i)
                    {
                        Marshal.DestroyStructure(data + (size * i), type);
                    }
                }

                Marshal.FreeHGlobal(data);
            }
        }
    }

在C++中对应的类代码如下。由于在使用P/Invoke时,.NET端的类对象会以指针的形式传递到C++代码中,所以C++侧的析构函数不会被调用,因此不必对析构函数做特殊处理。

template<typename T>
class Array
{
private:
	T *m_data;
	int m_length;

public:
	Array()
		: m_data(nullptr), m_length(0)
	{}

	Array(int _length)
		: m_data(new T[_length]), m_length(_length)
	{}

	~Array()
	{
		delete[] m_data;
	}
}

添加了复制构造函数、赋值操作符重载、迭代器接口和索引接口的C++类如下所示:

template<typename T>
class Array
{
private:
	T *m_data;
	int m_length;

public:
	Array()
		: m_data(nullptr), m_length(0)
	{}

	Array(int _length)
		: m_data(new T[_length]), m_length(_length)
	{}

	~Array()
	{
		delete[] m_data;
	}

	Array(const Array<T> &array)
		: m_data(new T[array.m_length]), m_length(array.m_length)
	{
		for (int i = 0; i < m_length; ++i)
		{
			m_data[i] = array.m_data[i];
		}
	}

	Array<T> &operator=(const Array<T> &array)
	{
		if (this != &array)
		{
			delete[] m_data;
			m_data = new T[array.m_length];
			m_length = array.m_length;

			for (int i = 0; i < m_length; ++i)
			{
				m_data[i] = array.m_data[i];
			}
		}

		return *this;
	}

	Array<T> &operator=(Array<T> &&array)
	{
		if (this != &array)
		{
			delete[] m_data;

			m_data = array.m_data;
			m_length = array.m_length;

			array.m_data = nullptr;
			array.m_length = 0;
		}

		return *this;
	}

	const T &operator[](size_t index) const
	{
		return m_data[index];
	}

	T &operator[](size_t index)
	{
		return m_data[index];
	}

	T *data()
	{
		return m_data;
	}

	T *begin()
	{
		return m_data;
	}

	T *end()
	{
		return m_data + m_length;
	}

	const T *begin() const
	{
		return m_data;
	}

	const T *end() const
	{
		return m_data + m_length;
	}

	int length() const
	{
		return m_length
	}
};

一个C++函数和对应的P/Invoke声明示例如下所示。由于.NET端不能正确释放C++端分配的内存,所以需要通过P/Invoke调用C++函数来释放空间。

class InputParam {
public:
    Array<int> ints;
    Array<int> doubles;
};

class OutputParam {
public:
    Array<int> ints;
    Array<double> doubles;
};

extern "C" __declspec(dllexport) void Foo(const InputParam &inputParam, const OutputParam *&outputParam);

extern "C" __declspec(dllexport) void FreeOutput(const OutputParam *paramToFree);
[StructLayout(LayoutKind.Sequential)]
public class InputParam {
    UnmanagedArray ints;
    UnmanagedArray doubles;
}

[StructLayout(LayoutKind.Sequential)]
public class OutputParam {
    UnmanagedArray ints;
    UnmanagedArray doubles;
}

public static extern void Foo(
    InputParam inputParam,
    [Out] out IntPtr outputParam);

public static extern void FreeOutput(
    IntPtr outputParam);

public void ExampleCall()
{
    InputParam a = ...;
    Foo(a, out IntPtr bPtr);
    var b = Marshal.PtrToStructure<OutputParam>(bPtr);
    FreeOutput(bPtr);
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Just My Type

Just My Type

Simon Garfield / Profile Books / 2010-10-21 / GBP 14.99

What's your type? Suddenly everyone's obsessed with fonts. Whether you're enraged by Ikea's Verdanagate, want to know what the Beach Boys have in common with easy Jet or why it's okay to like Comic Sa......一起来看看 《Just My Type》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具