在C#裡調用C++的dll時需要注意的一些問題

來源:互聯網
上載者:User
原文來自:http://hi.baidu.com/cityhacker/blog/item/419ed50af30a9e1595ca6b9d.html2009-11-19 12:21 在c#裡調用C++的dll,遇到了一些頭疼的問題: 

C++裡標頭檔定義形勢如下:

typedef void (*CALLBACKFUN1W)(wchar_t*, void* pArg);
typedef void (*CALLBACKFUN1A)(char*, void* pArg);

bool BIOPRINT_SENSOR_API dllFun1(CALLBACKFUN1 pCallbackFun1, void* pArg);

在其中一個匯入的dll方法裡,有一個回呼函數的參數

[DllImport("test.dll", EntryPoint = "dllFunc1", CharSet = CharSet.Unicode)]
   public static extern bool dllFunc1([MarshalAs(UnmanagedType.FunctionPtr)] CallbackFunc1 pCallbackFunc1 , IntPtr pArg);

回呼函數在C#裡定義成委託如下:
public delegate void CallbackFunc1(StringBuilder strName, IntPtr pArg);

調試運行,報錯。
有時是直接出錯退出,資訊如下:
Buffer overrun detected!

Program:
...

A buffer overrun has been detected which has corrupted the program's internal state. The program cannot safely continue execution and must now be terminated.

有時則能運行起來,但會拋出異常:
System.AccessViolationException: 嘗試讀取或寫入受保護的記憶體。這通常指示其他記憶體已損壞。

幾經周折,覓得答案,原來是要指定 調用方式,如下就OK了:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
   public delegate void CallbackFunc1(IntPtr hWnd, IntPtr pArg);

而系統預設為 CallingConvention.StdCall。

程式終於不報錯了,但是又出現結果不對了
定義成如下時,strName在方法中的值,只有一個字元,
public delegate void CallbackFunc1(StringBuilder strName, IntPtr pArg);

後來改為:
public delegate void CallbackFunc1([MarshalAs(UnmanagedType.LPWStr)] StringBuilder strName, IntPtr pArg);

OK了,strName帶出來的值完整了,參數類型定義成 string 或者 StringBuilder 都無所謂

還可以用 IntPtr ,或者 char* 都行(用char* 得加 unsafe)

char* 類型的,得到值後,可迴圈至'\0'得到整個字串
IntPtr 類型的,可以用Marshal.Copy出來,如:
Marshal.Copy(IntPtr_source, toBytes, 0, 1024);

如果報
“嘗試讀取或寫入受保護的記憶體。這通常指示其他記憶體已損壞。”
異常,
還有可能是因為C++和C#的參數類型對應問題,

如:
bool __declspec(dllimport) getImage(unsigned char** ppImage, int& nWidth, int& nHeight);

對應成
[DllImport("test.dll")]
public static extern bool getImage(IntPtr ppImage, ref int nWidth, ref int nHeight);

時,
則該方法在調用前,要對傳入的ppImage分配空間,如下
IntPtr pImage = Marshal.AllocHGlobal(iWidth * iHeight);
這種方法不推薦,因為是帶出結果來,一般這種指標不確定需要分配多大空間的。

正確的要對應成下面這樣:
[DllImport("test.dll")]
public static extern bool getImage(ref IntPtr ppImage, ref int nWidth, ref int nHeight);

調用時只要定義就行了:
IntPtr pImage = new IntPtr();
int refWidth = 0, refHeight = 0;

getImage(ref pImage, ref refWidth, ref refHeight);

總結,凡是雙針指型別參數,可以用 ref IntPtr
而對於 int*, int&, 則都可用 ref int 對應

另外,提一下自訂訊息的響應

   public const int WM_USER = 0x0400;
   public const int WM_TEST_MSG = (WM_USER + 0x100);

C# 要響應 dll 的自訂 訊息,則要重寫 WinForm的DefWndProc方法。

protected override void DefWndProc(ref Message m)

{

switch (m.Msg)
    {

      case WM_TEST_MSG:
      {

       }
      break;

default:
      base.DefWndProc(ref m);
      break;
    }

}

訊息發送是通過 Windows 提供的 API 函數 SendMessage 來實現的,它的原型定義:

[DllImport("User32.dll",EntryPoint="SendMessage")]
private static extern int SendMessage(
       IntPtr hWnd,      // handle to destination window
       uint Msg,         // message
       uint wParam,      // first message parameter
       uint lParam       // second message parameter
);

再轉貼一篇相關文章:

C#中調用Windows API的要點

在.Net Framework SDK文檔中,關於調用Windows API的指示比較零散,並且其中稍全面一點的是針對Visual Basic .net講述的。本文將C#中調用API的要點彙集如下,希望給未在C#中使用過API的朋友一點協助。另外如果安裝了Visual Studio .net的話,在C:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Samples\Technologies\Interop\PlatformInvoke\WinAPIs\CS目錄下有大量的調用API的例子。
  一、調用格式
  using System.Runtime.InteropServices; //引用此名稱空間,簡化後面的代碼
  ...
  //使用DllImportAttribute特性來引入api函數,注意聲明的是空方法,即方法體為空白。
  [DllImport("user32.dll")]
  public static extern ReturnType FunctionName(type arg1,type arg2,...);
  //調用時與調用其他方法並無區別
  可以使用欄位進一步說明特性,用逗號隔開,如:
  [ DllImport( "kernel32", EntryPoint="GetVersionEx" )]
  DllImportAttribute特性的公用欄位如下:
  1、CallingConvention 指示向非託管實現傳遞方法參數時所用的 CallingConvention 值。
  CallingConvention.Cdecl : 調用方清理堆棧。它使您能夠調用具有 varargs 的函數。
  CallingConvention.StdCall : 被呼叫者清理堆棧。它是從Managed 程式碼調用非託管函數的預設約定。
  2、CharSet 控制調用函數的名稱版本及指示如何向方法封送 String 參數。
  此欄位被設定為 CharSet 值之一。如果 CharSet 欄位設定為 Unicode,則所有字串參數在傳遞到非託管實現之前都轉換成 Unicode 字元。這還導致向 DLL EntryPoint 的名稱中追加字母“W”。如果此欄位設定為 Ansi,則字串將轉換成 ANSI 字串,同時向 DLL EntryPoint 的名稱中追加字母“A”。
  大多數 Win32 API 使用這種追加“W”或“A”的約定。如果 CharSet 設定為 Auto,則這種轉換就是與平台有關的(在 Windows NT 上為 Unicode,在 Windows 98 上為 Ansi)。CharSet 的預設值為 Ansi。CharSet 欄位也用於確定將從指定的 DLL 匯入哪個版本的函數。
  CharSet.Ansi 和 CharSet.Unicode 的名稱匹配規則大不相同。對於 Ansi 來說,如果將 EntryPoint 設定為“MyMethod”且它存在的話,則返回“MyMethod”。如果 DLL 中沒有“MyMethod”,但存在“MyMethodA”,則返回“MyMethodA”。
  對於 Unicode 來說則正好相反。如果將 EntryPoint 設定為“MyMethod”且它存在的話,則返回“MyMethodW”。如果 DLL 中不存在“MyMethodW”,但存在“MyMethod”,則返回“MyMethod”。如果使用的是 Auto,則匹配規則與平台有關(在 Windows NT 上為 Unicode,在 Windows 98 上為 Ansi)。如果 ExactSpelling 設定為 true,則只有當 DLL 中存在“MyMethod”時才返回“MyMethod”。
  3、EntryPoint 指示要調用的 DLL 進入點的名稱或序號。
  如果你的方法名不想與api函數同名的話,一定要指定此參數,例如:
  [DllImport("user32.dll",CharSet="CharSet.Auto",EntryPoint="MessageBox")]
  public static extern int MsgBox(IntPtr hWnd,string txt,string caption, int type);
  4、ExactSpelling 指示是否應修改非託管 DLL 中的進入點的名稱,以與 CharSet 欄位中指定的 CharSet 值相對應。如果為 true,則當 DllImportAttribute.CharSet 欄位設定為 CharSet 的 Ansi 值時,向方法名稱中追加字母 A,當 DllImportAttribute.CharSet 欄位設定為 CharSet 的 Unicode 值時,向方法的名稱中追加字母 W。此欄位的預設值是 false。
  5、PreserveSig 指示託管方法簽名不應轉換成返回 HRESULT、並且可能有一個對應於傳回值的附加 [out, retval] 參數的非託管簽名。
  6、SetLastError 指示被呼叫者在從屬性化方法返回之前將調用 Win32 API SetLastError。 true 指示調用方將調用 SetLastError,預設為 false。運行時封送拆收器將調用 GetLastError 並緩衝返回的值,以防其被其他 API 呼叫重寫。使用者可通過調用 GetLastWin32Error 來檢索錯誤碼。

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.