C#引用c++DLL結構體數組注意事項(數據發送與接收時)


本文轉載自: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一番,原來以前的理解確實不夠深,存在很多誤區,現整理如下:
  (一)、Encoding和CharSet
  為什么先提這兩個,實屬問題之源。在C#中包裝DLL的時候,DllImportAttribute當中的選項CharSet着實讓我糊塗了很久,MSDN曰:規定封送字符串應使用何種字符集,其中枚舉值有Ansi和Unicode,我真不知道到底改選哪一個。於是乎, google一番,Encoding這棵救命草被我找到,同時也釋疑了不少疑惑。
  首先,字符集不同於編碼,以前總將它們混為一談,CharSet是字符集,Encoding是編碼。字符集是字符的集合,規定這個集合里有哪些字符,每個字符都有一個整數編號(只是編號不是編碼);而編碼是用來規定字符編號如何與二進制交互,每個“字符”分別用一個字節還是多個字節存儲。啊嗚,原來這樣,那我這里接觸到的Ansi、Unicode、UTF8等等等等究竟是怎么回事呢,借此機會,一探究竟!^_^
  (二)、Ansi、Unicode、UTF8、bala bala
   提到字符集,有ASCII、GB2312、GBK、GB18030、BIG5、JIS等等多種,與此相對應的編碼方式為ASCII、GB2312、GBK、GB18030、BIG5、JIS(囧,難怪糊塗如我般的人如此多),但是Unicode字符集卻有多種編碼方式:UTF-8、 UTF-7、UTF-16、 UnicodeLittle、UnicodeBig。原來如此,字符集與編碼原來是這個樣子。ˇˍˇ|||
  那Ansi又是什么呢?
  Ansi:系統編碼的發展經歷了三個階段,ASCIIàAnsi(不同國家語言本地化)àUnicode(標准化),原來Ansi編碼也好,Ansi字符集也好,都是指本地化的東西,在簡體中文系統下,ANSI 編碼代表 GB2312 編碼,Windows下自帶的記事本程序,默認的就是ANSI編碼。呵呵,那偶就去試試吧,輸入“anhui合肥”(不含引號),保存編碼方式選“ANSI”,查看,哦,9個字節,明白了,原來Ansi編碼保留了對ASCII編碼的兼容,當遇到ASCII字符時,采用單字節存儲,當遇到非ASCII編碼時,采用雙字節表示(GB2312編碼)。
  (三)、DllImportAttribute中的CharSet枚舉值選擇
  對字符集和編碼的概念清楚了以后,終於可以研究C#中調用非托管的DLL的方法咯。從托管應用程序去調用非托管代碼,如果CharSet=Unicode,則DLL中的接口函數將出現的所有字符串(包括參數和返回值)視為Unicode字符集,Ansi一樣的道理。真不錯,了解到這里總算有點撥雲見日的感覺了!O(∩_∩)O
  好像目前需要了解的知識點都差不多了,終於可以開始來解決我的問題了, (*^__^*)。回顧整理一下,第一:我需要調用非托管代碼,第二:非托管代碼只處理UTF8編碼格式的字符,第三,萬惡的中文亂碼,接口函數簽名中,無論參數還是返回值,接收到或是發送出的字符串都含有中文。呵呵,看來上面的知識應該可以解決這個問題了:首先接收字符串,因為接收到的是UTF8編碼格式,CharSet屬性肯定要設置成Unicode了,接收后要正確顯示中文,在當前系統中,需要將編碼方式轉換成GB2312,即Encoding.Default;另外關於發送字符數組,因為無論是C#中默認的Unicode編碼方式,還是DLL處理時規定的UTF8編碼方式,都是Unicode字符集的一種編碼方式,所以CharSet也要設置成Unicode,只是要在發送前需要將字符數組轉換一下編碼方式。以下附帶兩個簡單的函數實現。
// 轉換接收到的字符串
public string UTF8ToUnicode(string recvStr)
{
byte[] tempStr = Encoding.UTF8.GetBytes(recvNotify);
byte[] tempDef = Encoding.Convert(Encoding.UTF8, Encoding.Default, tempStr);
    string msgBody = Encoding.Default.GetString(tempDef);
    return msgBody;
}
// 轉換要發送的字符數組
public byte[] UnicodeToUTF8(string sendStr)
{
    string tempStr = Encoding.UTF8.GetString(sendStr);
    byte[] msgBody = Encoding.UTF8.GetBytes(tempUTF8);
    return msgBody;
}
         總結一下,本文因項目完成的需要,難免存在局限性,只討論了兩種情況:Unicode編碼的字符串轉UTF8格式的字符數組,以及UTF8格式的字符串轉Unicode格式的字符串,上面兩個方法均通過測試,至此總算解決了中文亂碼的問題。平台調用的知識點很多,只有真正掌握必需的基礎知識和平台調用的原理,才能做到活學活用,我要繼續努力。

結構體: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#中的寫法是:
結構體: [StructLayout(LayoutKind.Sequential)]
  internal struct tagDownDepInfo
  {
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
  public string DPCODE1; //一級部門
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
  public string DPCODE2; //二級部門
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
  public string DPCODE3; //三級部門
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
  public string DPCODE4; //四級部門
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)]
  public string DPNAME1; //
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)]
  public string DPNAME2; //
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)]
  public string DPNAME3; //
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)]
  public string DPNAME4; //
  }
函數: [DllImport("Change.dll", CharSet = CharSet.Ansi)]
  internal unsafe static extern int CapGetDepList(ref tagDownDepInfo[] downinfo);
調用: tagDownDepInfo[] downinfo = new tagDownDepInfo[100];//現已知道有100條數據
  int ret = Class1.CapGetDepList(ref downinfo);
結果:返回正確,但downinfo 卻變成了1行,這是為什么呢?請大家指教謝謝!在線等!分數不多,請諒解!

 

DOWNDEPINFO *pInfo=new DOWNDEPINFO[cou];
memset(pInfo, 0, sizeof(DOWNDEPINFO)*cou);
int nFlag=ccFun.CapGetDepList(&pInfo); DOWNDEPINFO *pInfo=new DOWNDEPINFO[cou];
memset(pInfo, 0, sizeof(DOWNDEPINFO)*cou);
int nFlag=ccFun.CapGetDepList(&pInfo);

 

 

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();
  }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM