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);
}

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

查看所有标签

猜你喜欢:

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

软件研发之道

软件研发之道

Jim McCarthy、Michele McCarthy / 赵 俐、石华耀 / 人民邮电出版社 / 2011-8 / 45.00元

Jim McCarlthy等的《软件研发之道 微软开发团队的经验法则》叙述 了微软Visual C++开发团队的故事,通过作者的总结和归纳告诉读者如何 构建一个优秀的软件开发团队,如何在一段时间内成功地交付一个软件。 作为升级版,作者将言简意赅的法则扩展到57条,包括了软件研发及营销 的全部内容,相信每一位项目经理都会对此书爱不释手。 《软件研发之道 微软开......一起来看看 《软件研发之道》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

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

HTML 编码/解码

SHA 加密
SHA 加密

SHA 加密工具