這點跟 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
沒有留言:
張貼留言