C#調用C語言的API時一般把void *指針轉換成IntPtr,但這經常遠遠不夠的。在C語言中void *是個萬金油,尤其是一些老的c語言程序,所有的參數就一個void*指針,里面包羅萬象,然后在程序中來一個switch,甚至多個switch來處理不同的參數。最近筆者就碰到了這個問題,不得不來研究一下怎么把void *指針轉換成IntPtr。
1.void *指針到IntPtr的簡單轉化。
c語言函數原型:
int SetConfig(int type, void *p);
這里假設p的所傳遞的參數式是結構體A:
struct A { wchar_t osdbuffer[100]; unsigned short ix; unsigned short iy; };
那么在C#中原型可以定義如下:
int SetConfig(int type, IntPtr p);
結構體A
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct A { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] public string osdbuffer; public ushort ix; //顯示坐標x public ushort iy; //顯示坐標y }
注意這里的CharSet,它由c中wchar_t決定的,如果c程序編譯時使用Unicode,這里就用CharSet.Unicode,否則使用CharSet.Ansi。關於字符串的編碼問題如果不懂可以去網上查一下。至於怎么知道C語言是用Unicode還是Ansi編譯,我是經過調用它的API測試出來的,調用成功了就說明他的編碼和我的調用代碼一致。
這里還有一個很重要的問題,那就是內存在編譯時的分配問題。一般默認情況下,內存的分配是4byte的整數倍,在這里我省略了,但為了便於理解,補充一下。結構體A完整一點的定義:(注意Pack的值)
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode,Pack = 4)] public struct A { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] public string osdbuffer; public ushort ix; //顯示坐標x public ushort iy; //顯示坐標y }
c語言是用的是非托管代碼,c#使用的是托管代碼,c#的調用代碼如下:
A s_a = new A();
int lenght = Marshal.SizeOf(s_a); IntPtr pA= Marshal.AllocHGlobal(lenght); Marshal.StructureToPtr(s_a, pA, true); int type = 1;
int ret = SetConfig( type, pA); Marshal.FreeHGlobal(pA);
2.void *指針到IntPtr的復雜轉化。
在這里結構體A變得復雜一點,如果它內部包含一個指向另一個結構體B的指針
struct A { wchar_t osdbuffer[100]; unsigned short ix; unsigned short iy;、 B *pB; }; struct B { wchar_t title[20]; };
在C#中你要做的也就稍微復雜一點,也就是說你不但要為A分配內存,也要為B分配內存
B s_b = new B();
//賦值省略
int lenght1 = Marshal.SizeOf(s_b); IntPtr pB= Marshal.AllocHGlobal(lenght1); Marshal.StructureToPtr(s_b, pB, true); A s_a = new A();
s_a.pB = pB;
//其他賦值
//
int lenght2 = Marshal.SizeOf(s_a); IntPtr pA= Marshal.AllocHGlobal(lenght2); Marshal.StructureToPtr(s_a, pA, true); int type = 1;
int ret = SetConfig( type, pA); Marshal.FreeHGlobal(pB); Marshal.FreeHGlobal(pA);
萬變不離其宗,只要掌握了原理,不管void *指針傳遞的參數有多么復雜,都可以搞定。