我正在使用一个本机库,它将 IntPtr 返回到包含属性值的内存位置。我知道属性的类型是什么,并且我有一种方法可以从指针指向的内存中编组一个值(获取 IntPtr 和属性的类型)。此方法调用 Marshal.ReadInt32,或读取一系列字节并将它们转换为双精度,或使用 Marshal.PtrToStringUni 等读取字符串。我想为此方法编写一些单元测试,但不确定我该怎么做关于创建 IntPtr 以传递给该方法。我正在使用 NUnit,不能使用模拟框架。
3 回答
查看Marshal.Copy()的各种重载,这将允许您使用 IntPtr 初始化值。
如果我理解正确,你想对你的反序列化逻辑进行单元测试,给定一个 IntPtr。
如果是这种情况,您所要做的就是拥有序列化输出的不同组合的副本,以测试每个唯一路径。对于每个测试,获取可能的序列化实体的 IntPtr,调用您的方法并验证预期结果。
类似于获取指向内存区域的指针。将序列化的输出保存在一个字符串中,并从中获取一个 Intptr。GCHandle 看起来像你需要的东西。(虽然从未尝试过) http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.gchandle.tointptr.aspx
我对您的问题的阅读使我认为您要问的是:
如何创建模拟我正在进行的本机调用的函数,该调用将 IntPtr 返回到双精度或 unicode 字符串,以便我可以将该 IntPtr 传递给我想要测试的函数?
这样的事情可能会有所帮助:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
private static double myDouble = 3.14;
private static unsafe void TestPtrToDouble()
{
fixed(double* pDouble = &myDouble)
{
IntPtr intp = new IntPtr(pDouble);
double[] copy = new double[1];
Marshal.Copy(intp, copy, 0, 1);
Debug.Assert(copy[0] == myDouble);
}
}
private static char[] myString = { 'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'm', 'y', ' ', 's', 't', 'r', 'i', 'n', 'g' };
private static unsafe void TestPtrToUnicodeString()
{
fixed (char* pChar = &myString[0])
{
IntPtr intp = new IntPtr(pChar);
string copy = Marshal.PtrToStringUni(intp);
Debug.Assert(copy == "This is my string");
}
}
static void Main(string[] args)
{
TestPtrToUnicodeString();
TestPtrToDouble();
}
}
}
需要非常注意的是,您将无法返回从函数创建的模拟 IntPtr。即,您不能创建 P/Invoke 调用的模拟版本并期望一切正常。
.Net 运行时在内存中移动托管对象(垃圾收集过程的一部分)。该fixed
语句会阻止您获取地址的成员/对象发生这种情况。但仅限于块的生命周期。如果您从函数返回,则块结束,因此返回后指针值可能无效。即,您的 IntPtr 可能正在引用不再包含您要编组的数据的内存。
查看有关不安全代码和固定语句的 MSDN 文档。但是我在这里得到的内容应该会让您朝着正确的方向前进,为您的编组代码构建 NUnit 测试。
另请注意,此代码需要 /unsafe 编译器选项才能编译。如果您需要您的程序集在不支持不安全程序集的环境中运行,您必须在另一个程序集中拥有测试代码。但是由于您已经在使用 P/Invoke,我想情况并非如此。