這點跟 Python 其實滿像的,Python 有 ctypes 可以做到類似的行為。script 中的 memory 跟 C/C++ 的 memory 無法互通,都必須透過轉換的方式讓 interpreter 可以順利看得懂 memory buffer。
C# 單一個 struct buffer 使用上都沒什麼問題,但是當你試著想要從 DLL 中撈回一串 struct array buffer時,就無法用直覺的方法完成這件事。因為 C# 在 DllImport 時的參數傳遞動了點手腳,所以如果直接將 C# 的 struct array 加 ref 傳至 DLL 可能會導致 crash 的產生。
1. 測試 C# dump struct buffer
測試一下 C# 在傳遞 struct buffer 的行為,將傳入的 struct address print 出來看看printf() 可以替換為 OutputDebugString() 至 DbgView 中看結果會比較方便
TestDLL.cpp
#define DLL_API __declspec(dllexport) typedef struct _Data { bool bData; int nData; char strData[256]; } Data; #ifdef __cplusplus extern "C" { #endif DLL_API bool Test_DumpAddress(Data* p) { printf("Test_DumpAddress() address=0x%p\n", p); return true; } DLL_API bool Test_DumpData(Data* d) { printf("Test_DumpData() address=0x%p bool=%d, int=%d, str=%s\n", d, d->bData, d->nData, d->strData); return true; } #ifdef __cplusplus } #endif
TestDLL.cs
class Program { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct Data { public bool bData; public int nData; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string strData; } [DllImport("TestDLL.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern void Test_DumpAddress(ref Data p); [DllImport("TestDLL.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern bool Test_DumpData(ref Data d); static void Main(string[] args) { // testing for dump single struct buffer Data sd = new Data(); sd.bData = false; sd.nData = 99; sd.strData = "Falldog"; Test_DumpData(ref sd); Test_DumpAddress(ref sd); // testing for dump struct buffer array Data[] sd_array = new Data[5]; for (int i = 0; i < 5; i++ ) { sd_array[i].bData = false; sd_array[i].nData = 100+i; sd_array[i].strData = "Falldog"; Test_DumpData(ref sd_array[i]); } Console.ReadLine(); // enter to end the process } }
Result :
[4660] Test_DumpData() address=0x0035EB74 bool=0, int=99, str=Falldog [4660] Test_DumpAddress() address=0x0035EB74 [4660] Test_DumpData() address=0x0035EB74 bool=0, int=100, str=Falldog [4660] Test_DumpData() address=0x0035EB74 bool=0, int=101, str=Falldog [4660] Test_DumpData() address=0x0035EB74 bool=0, int=102, str=Falldog [4660] Test_DumpData() address=0x0035EB74 bool=0, int=103, str=Falldog [4660] Test_DumpData() address=0x0035EB74 bool=0, int=104, str=Falldog
明顯看到 address 都是一樣的!在 C# 中明明是不一樣的變數,在傳入 DLL 後,address 都變一樣了,應該就是 C# 中間會產生一個 temp 的 buffer 來做轉換。
2. 將 DLL allocate 的 buffer load 至 C# 的 struct memory 中
以下為 C# 透過 DLL allocate 一塊 buffer,讓 DLL 填完後,在 C# 轉換為 C# 看得懂的 struct array buffer,所以傳下去的 buffer pointer type 為 IntPtr ,取出來後 再用 Marshal.PtrToStructure() 將 buffer 轉為 struct buffer,最後記得要將 DLL allocate 出來的 buffer free 掉,否則會 memory leak。TestDLL.cpp
#define DLL_API __declspec(dllexport) typedef struct _Data { bool bData; int nData; char strData[256]; } Data; #ifdef __cplusplus extern "C" { #endif DLL_API void Test_AllocData(int count, Data*& res) { res = new Data[count]; for(int i=0 ; i<count ; i++){ res[i].bData = true; res[i].nData = 500 + i; sprintf_s(res[i].strData, "Test_AllocData(%d)", i); } } DLL_API void Test_FreeData(Data* d) { if(d) delete [] d; } #ifdef __cplusplus } #endif
TestDLL.cs
class Program { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct Data { public bool bData; public int nData; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string strData; } [DllImport("TestDLL.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern void Test_AllocData(int size, ref IntPtr ptr); [DllImport("TestDLL.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern void Test_FreeData(IntPtr ptr); static void Main(string[] args) { // testing for retrive struct array buffer from DLL IntPtr binary = new IntPtr(); { Data[] ad = new Data[3]; Test_AllocData(3, ref binary); ad[0] = (Data)Marshal.PtrToStructure(binary + Marshal.SizeOf(typeof(Data)) * 0, typeof(Data)); ad[1] = (Data)Marshal.PtrToStructure(binary + Marshal.SizeOf(typeof(Data)) * 1, typeof(Data)); ad[2] = (Data)Marshal.PtrToStructure(binary + Marshal.SizeOf(typeof(Data)) * 2, typeof(Data)); Test_FreeData(binary); for (int i = 0; i < 3; i++) { Console.WriteLine(string.Format("@AllocData from DLL i={0} n={1} str={2}", i, ad[i].nData, ad[i].strData)); } } Console.ReadLine(); // enter to end the process } }
C# 的 DLL import 還有不少有趣的東西,只是因為接觸不多,沒時間好好來研究
Reference
沒有留言:
張貼留言