本文轉載自:http://blog.csdn.net/lhs198541/article/details/7593045 最近做的項目,需要在C# 中調用C++ 寫的DLL,因為C# 默認的編碼方式是Unicode,而調用的DLL規定只處理UTF8編碼格式的字符串,DLL中的輸入參數類型char*被我Marshal成byte[],輸出參數類型char**被我Marshal成了string(C++和C#之間的類型轉換請參閱相關資料),於是我就經歷了無數次用於接收時的string-->string(UTF8-->Unicode)和用於發送時的string-->byte[](Unicode-->UTF8)這樣頻繁的編碼轉換,期間多次出現中文亂碼,看來編碼轉換並非想象中那么簡單,今天花時間google一番,原來以前的理解確實不夠深,存在很多誤區,現整理如下: 結構體:typedef struct tagDownDepInfo
DOWNDEPINFO *pInfo=new DOWNDEPINFO[cou]; |
int CapGetDepList(DOWNDEPINFO ** pINFO)
這個定義是說里面的DOWNDEPINFO是CapGetDepList分配的還是外面分配的?
如果內部分配 定義ok
不是 應改成int CapGetDepList(DOWNDEPINFO *pINFO)
看你程序的用法,ms是下面這種
不說這個 你要知道這點 C#的數組的內存是manage的,而c++不是,所以當用ref傳出去時,不知道傳出去實際數組指針的大小,所以,internal unsafe static extern int CapGetDepList(ref tagDownDepInfo[] downinfo); 這種寫法有點問題的
一般,遇到ref數組之類的都得用IntPtr 如果數組是傳入的,嗯,這有點麻煩,要首先申請一塊對應的內存,調用Marshal.AllocHGlobal分配相應的內存,大小可以用Marshal.sizeof 自己調用Marshal.Copy 一個一個拷進去(我就是這么用的,沒找到好的方法)用后釋放內存
所以 我猜你的dll導入函數定義應為
[DllImport("Change.dll", CharSet = CharSet.Ansi)]
private static extern int CapGetDepList(IntPtr p);
用Marshal.PtrToStructure獲得執行結果
還有傳入指針,而沒有大小,是一個不怎么好的函數定義方式
[DllImport("Change.dll", CharSet = CharSet.Ansi)]
private static extern int CapGetDepList(IntPtr p);
可能的一些步驟為
IntPtr[] ptArray = new IntPtr[1];
ptArray[0] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(tagDownDepInfo)) * 100);
IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)) * 1);
Marshal.Copy(ptArray, 0, pt, 1);
然后
CapGetDepList(pt);
拿到數據
ps:你可以說下寫這個接口的人,定義個數組盡然沒有數組大小的參數,還有只需要1維的搞個2維的,
那2維變3維啊哈(can 參考監控軟件MEMORY中相應方法,注意該例子為結構體數組指針)
取數據方法
for (int i = 0; i < size; i++)
{
tagDownDepInfo tagInfo = (tagDownDepInfo)Marshal.PtrToStructure((IntPtr)((UInt32)ptArray[0] + i * s), typeof(tagDownDepInfo));
}
Marshal.FreeHGlobal(ptArray[0]);
Marshal.FreeHGlobal(pt);
總結::
typedef struct tagDownDepInfo
{
char DPCODE1[6]; //一級部門
char DPCODE2[6]; //二級部門
char DPCODE3[8]; //三級部門
char DPCODE4[8]; //四級部門
char DPNAME1[60]; //
char DPNAME2[60]; //
char DPNAME3[60]; //
char DPNAME4[60]; //
}DOWNDEPINFO, FAR *PDOWNDEPINFO;
int CapGetDepList(DOWNDEPINFO ** pINFO)
c++中使用方法:
DOWNDEPINFO *pInfo=new DOWNDEPINFO[cou];
memset(pInfo, 0, sizeof(DOWNDEPINFO)*cou);
int nFlag=ccFun.CapGetDepList(&pInfo);
c#中調用方法:
[DllImport("Change.dll", CharSet = CharSet.Ansi)]
private static extern int CapGetDepList(IntPtr p);
可能的一些步驟為
IntPtr[] ptArray = new IntPtr[1];
ptArray[0] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(tagDownDepInfo)) * 100);
IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)) * 1);
Marshal.Copy(ptArray, 0, pt, 1);
然后
CapGetDepList(pt);
for (int i = 0; i < size; i++)
{
tagDownDepInfo tagInfo = (tagDownDepInfo)Marshal.PtrToStructure((IntPtr)((UInt32)ptArray[0] + i * s), typeof(tagDownDepInfo));
}
Marshal.FreeHGlobal(ptArray[0]);
Marshal.FreeHGlobal(pt);
C#調用c++dll文件是一件很麻煩的事情,首先面臨的是數據類型轉換的問題,相信經常做c#開發的都和我一樣把學校的那點c++底子都忘光了吧(語言特性類)。
網上有一大堆得轉換對應表,也有一大堆的轉換實例,但是都沒有強調一個更重要的問題,就是c#數據類型和c++數據類型占內存長度的對應關系。
如果dll文件中只包含一些基礎類型,那這個問題可能可以被忽略,但是如果是組合類型(這個叫法也許不妥),如結構體、類類型等,在其中的成員變量的長度的申明正確與否將決定你對dll文件調用的成敗。
如有以下代碼,其實不是dll文件的源碼,而是廠商給的c++例子代碼
c++中的結構體申明
typedef struct
{
unsigned char Port;
unsigned long Id;
unsigned char Ctrl;
unsigned char pData[8];
}HSCAN_MSG;
c++中的函數申明(一個c++程序引用另一個c++的dll文件)
extern "C" int _stdcall HSCAN_SendCANMessage(unsigned char nDevice,unsigned char nPort,HSCAN_MSG *msg,int nLength);
c++中的調用:
....
HSCAN_MSG msg[100];
.....
HSCAN_SendCANMessage(m_nDevice,m_nPort,msg,nFrames);
由上述代碼可見,msg是個結構體的數組。
下面是我的c#的代碼
c#結構體申明:(申明成)
[StructLayout(LayoutKind.Sequential)]
public struct HSCAN_MSG
{
// UnmanagedType.ByValArray, [MarshalAs(UnmanagedType.U1)]這個非常重要,就是申明對應類型和長度的
[MarshalAs(UnmanagedType.U1)]
public byte Port;
[MarshalAs(UnmanagedType.U4)]
public uint nId;
[MarshalAs(UnmanagedType.U1)]
public byte nCtrl;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] pData;
};
c#函數申明
[DllImport("HS2106API.dll")]
public static extern int HSCAN_SendCANMessage(
byte nDevice, byte nPort, HSCAN_MSG[] pMsg, int nLength);
C#函數調用
HSCAN_MSG[] msg = new HSCAN_MSG[1]; //發送緩沖區大小可根據需要設置;
for (int yy = 0; yy < msg.Length; yy++)
{
msg[yy] = new HSCAN_MSG();
}
//...結構體中的成員的實例化略
HSCAN_SendCANMessage(0x0, 0x0, msg, 1)
那些只能用指針不能用結構體和類的地方
c++中的結構體申明
typedef struct
{
unsigned char Port;
unsigned long Id;
unsigned char Ctrl;
unsigned char pData[8];
}HSCAN_MSG;
c++中的函數申明(一個c++程序引用另一個c++的dll文件)
extern "C" int _stdcall HSCAN_SendCANMessage(unsigned char nDevice,unsigned char nPort,HSCAN_MSG *msg,int nLength);
c#中的結構體申明:
[StructLayout(LayoutKind.Sequential)]
public struct HSCAN_MSG
{
[MarshalAs(UnmanagedType.U1)]
public byte Port;
/// <summary>
/// 節點標識,nEFF=1 時(擴展幀),為29 位nEFF=0(標准幀)時,為11 位;
/// </summary>
[MarshalAs(UnmanagedType.U4)]
public uint nId;
[MarshalAs(UnmanagedType.U1)]
public byte nCtrl;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] pData;
};
c#函數的調用:包含使用指針IntPtr替代結構體數組和讀取IntPtr的方法
HSCAN_MSG[] msg1 = new HSCAN_MSG[10];
for (int i = 0; i < msg1.Length; i++)
{
msg1[i] = new HSCAN_MSG();
msg1[i].pData = new byte[8];
}
IntPtr[] ptArray = new IntPtr[1];
ptArray[0] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(HSCAN_MSG)) * 10);
IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(HSCAN_MSG)));
Marshal.Copy(ptArray, 0, pt, 1);
int count = HSCAN_ReadCANMessage(0x0, 0,pt, 10);
textBoxStatus.Text += "\r\n" + "讀取0口:" + count.ToString() + "幀數據";
for (int j = 0; j < 10; j++)
{
msg1[j] =
(HSCAN_MSG)Marshal.PtrToStructure((IntPtr)((UInt32)pt+ j * Marshal.SizeOf(typeof(HSCAN_MSG)))
, typeof(HSCAN_MSG));
textBoxStatus.Text += "\r\n收到0口" + Convert.ToByte(msg1[j].pData[0]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[1]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[2]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[3]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[4]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[5]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[6]).ToString()
+ "|" + Convert.ToByte(msg1[j].pData[7]).ToString();
}