IntPtr是托管环境中用来描述非托管环境中指针的类型。其所占内存大小由运行时的系统环境所决定(其实是因为在不同的系统环境中指针所占的字节数不一样,32位系统为4个字,64位系统为8个字节)。
个人认为IntPtr主要有两种用途:
- 作为不透明指针
这种情况下托管环境中不需要了解该指针的意义,仅仅保存在内存中,在需要时传送给非托管环境。
举个例子:假设我们用C++实现了一个Http服务器,然而我们需要为提供C#接口启动和停止服务。
C++代码:
class HttpService
{
private:
int _port;
public:
HttpService()
{
}
void start( int port)
{
_port = port;
printf(“Service Started at port:%d!“,_port);
}
void stop()
{
printf(“Service Stopped!“);
}
};
_declspec(dllexport) void* _stdcall StartHttpService(int port)
{
HttpService* pService = new HttpService();
pService->start(port);
return pService;
}
_declspec(dllexport) void _stdcall StopHttpService(void* ptr)
{
HttpService* pService = (HttpService*) ptr;
pService->stop();
}
C#包装类代码:
[DllImport(“native.dll“, EntryPoint = “StartHttpService“, CharSet = CharSet.Ansi)]
public static extern IntPtr StartHttpService(int port);
[DllImport(“native.dll“, EntryPoint = “StopHttpService“, CharSet = CharSet.Ansi)]
public static extern void StopHttpService(IntPtr ptr);
C#调用代码:
Console.WriteLine(“StartHttpService Method“);
IntPtr servicePtr = NativeWrapper.StartHttpService(11245);
Console.WriteLine(“StopHttpService Method“);
NativeWrapper.StopHttpService(servicePtr);
此处调用代码启动完Http服务器后,将获得的服务器指针保存下来,在需要停止服务器时将指针传送给非托管环境。
2. 和Marshal类配合对非托管内存进行操作
Marshal类是.NET类库提供的一个用于和非托管内存交互的方法集。通过该类提供的方法,我们可以实现非托管内存的拷贝,托管类型与非托管类型的转换等。这些个场景中,IntPtr无疑是对所要操作的非托管内存块起标识作用。
这里举两个从非托管内存中拷贝数据到托管内存中,并转换为托管类型的例子。
Sample 1:获取一个在非托管环境中动态构造的结构体
C++代码:
// We need to write a address to specific memory identifier an address.
// So we need a Person**(A pointer whose value point to another pointer) here as the parameter.
_declspec(dllexport) void _cdecl GetPerson(Person** result)
{
*result = new Person();
(*result)->name = L“Jensen_From_Native_Get_Person“;
}
C#封装类代码:
[DllImport(“native.dll“, EntryPoint = “GetPerson“, CallingConvention = CallingConvention.Cdecl)]
public static extern void GetPerson(out IntPtr pPerson); // We need to pass the address of the IntPtr struct here,so use out keyword.
C#调用代码:
IntPtr pPerson;
Console.WriteLine(“GetPerson Method“);
NativeWrapper.GetPerson(out pPerson); // We need to pass the address of the IntPtr struct here,so use out keyword.
// We have got the pointer to the native memory,
// then we need to use marshal class to copy the data represented by the pointer to managed heap.
Person personFromNative = (Person)Marshal.PtrToStructure(pPerson, typeof(Person));
Sample 2: 获取一个在非托管环境中动态构造的结构体数组
C++代码:
// We need to write a address(Point to an pointer array) to specific memory identifier an address.
// So we need a Person***(A pointer whose value point to another pointer array) here as the parameter.
_declspec(dllexport) void _cdecl GetPersonArray(Person*** result,int* count)
{
*result = new Person*[2];
(*result)[0] = new Person();
(*result)[0]->name = L”Jensen_From_Native_Get_Person_Array_1″;;
(*result)[1] = new Person();
(*result)[1]->name = L”Jensen_From_Native_Get_Person_Array_2″;
*count = 2;
}
C#封装类代码:
[DllImport(“native.dll”, EntryPoint = “GetPersonArray”,CallingConvention = CallingConvention.Cdecl)]
public static extern void GetPersonArray(out IntPtr person,out int count); // We need to pass the address of the IntPtr struct here and do not care what the pointer represent,so use out keyword.
C#调用代码:
Console.WriteLine(“GetPersonArray Method“);
IntPtr pPerson = IntPtr.Zero;
int count;
NativeWrapper.GetPersonArray(out pPerson, out count);
// pointer pPerson have stored the address of the Person pointer array.
// Then we need to use Marshal class to copy the person pointer from the unmanaged memory to managed memory.
IntPtr[] nativeResult = new IntPtr[count];
Marshal.Copy(pPerson, nativeResult, 0, count);
// We got the Person pointer in the managed enviorment
// Then we need to use Marshal class to copy the data represented by the pointer to the managed memory.
foreach (IntPtr ptr in nativeResult)
{
Person innerResult = (Person)Marshal.PtrToStructure(ptr, typeof(Person));
}
以上两个例子都是通过C++申请内存,然后在托管环境中使用C++构造的数据。貌似PInvoke自动封送服务不能把非托管环境中申请的内存同步到托管环境中。比如在第一个例子中不使用IntPtr,而使用如下代码:
C++代码不变:
C#封装类:
[StructLayout(LayoutKind.Explicit, Pack = 8, CharSet = CharSet.Ansi)]
public class CPerson
{
[FieldOffset(0)]
[MarshalAs(UnmanagedType.LPWStr)]
public string Name;
[FieldOffset(8)]
public double Weight;
[FieldOffset(16)]
public int Age;
}
[DllImport(“native.dll“, EntryPoint = “GetPerson“, CallingConvention = CallingConvention.Cdecl)]
public static extern void GetPerson(out CPerson pPerson);
C#调用代码:
CPerson cPerson;
Console.WriteLine(“GetPerson Method“);
NativeWrapper.GetPerson(out cPerson);
这样的话会报内存访问违例错误。