Platform Invoke in CLR (4)--不透明指针(IntPtr)和Marshal工具类

 IntPtr是托管环境中用来描述非托管环境中指针的类型。其所占内存大小由运行时的系统环境所决定(其实是因为在不同的系统环境中指针所占的字节数不一样,32位系统为4个字,64位系统为8个字节)。

个人认为IntPtr主要有两种用途:

  1. 作为不透明指针

这种情况下托管环境中不需要了解该指针的意义,仅仅保存在内存中,在需要时传送给非托管环境。

举个例子:假设我们用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);

这样的话会报内存访问违例错误。



posted @ 2012-11-17 22:06  self.refactoring  阅读(2237)  评论(0编辑  收藏  举报