c#調用C++的DLL找不到入口點以及衍生的相關問題

DllImport屬性詳解

API函數是構築Windows的基石, 是Windows編程的必備利器。每一種Windows應用程序開發工具都提供間接或者直接的方式調用Win32API,C#也不例外。使用Win32API的一個好處就是,我們可以實現更多的功能。

首先,要引入命名空間:using System.Runtime.InteropServices;

然後,聲明在程序中所要用到的API函數。注意方法體爲空。

DllImport屬性用於指定包含外部方法的實現的dll位置。

(1)DllImport屬性只能放在方法聲明上。

(2)DllImport具有單個定位參數:指定包含被導入方法的dll名稱的dllName參數。

(3)DllImport具有6個命名參數:

a、CallingConvention參數:指示入口點的調用約定,如果未指定CallingConvention,則使用默認值CallingConvention.Winapi;

b、CharSet參數:指示用在入口點種的字符集。如果未指定CharSet,則使用默認值CharSet.Auto;

c、EntryPoint參數:給出所聲明的方法在dll中入口點的名稱。如果未指定EntryPoint,則使用方法本身的名稱;

d、ExactSpelling參數:指示EntryPoint是否必須與指示的入口點的拼寫完全匹配。如果未指定ExactSpelling,則使用默認值false;

e、PreserveSig參數:指示方法的簽名應被應當被保留還是被轉換。當簽名被轉換時,它被轉換爲一個具有HRESULT返回值和該返回值的一個名爲retval的附加輸出參數簽名。如果未指定PreserveSig,則使用默認值false;

f、SetLastError參數:指示方法是否保留Win32上的錯誤,如果未指定SetLastError,則使用默認值false。

DllImport是一次性屬性類,而且用DllImport修飾的方法必須具有extern修飾符。

例子:

[DllImport("kernel32")]
private static extern void GetWindowsDirectory(StringBuilder WinDir,int count);
 
[DllImport("user32.dll",EntryPoint = "FlashWindow")]
private static extern bool FlashWindow(IntPtr hWnd,bool bInvert);
 
[DllImport("ws2_32.dll")]
private static extern int inet_addr(string cp);
 
[DllImport("IPHLPAPI.dll")]
private static extern int SendARP(Int32 DestIP, Int32 SrcIP, ref Int64 pMacAddr, ref Int32 PhyAddrLen);

c#程序調用C++的dll的時候,經常出現這樣的問題:

[c-sharp] view plain copy
  1. System.EntryPointNotFoundException:Unable to find an entry point named '函數名稱' in Dll 'c++ dll文件名'  

之前也遇到過這個問題,可是怎麼解決的就忘記了,這次遇到了,就寫下這個問題的原因。

這個是我在網上查資料找到的:http://www.cnblogs.com/tallman/archive/2009/03/07/735948.html

原因就是:c++源代碼中的函數在編譯成DLL後,函數的名稱就發生了改變:會在函數的前後產生一些字符。

我們能通過eXeScope軟件來查看c++編譯後的函數名稱是什麼,這裏要提下,eXeScope中文版本無法在x64的環境下使用,最好下英文版本。

 

例如:c++中的函數名是GetSvsSize,編譯後變成?GetSvsSize@@YA_NPA_WPAJ11111PAF@Z,這個時候,我們要是想調用這個函數,那麼應該這樣寫:

[c-sharp] view plain copy
  1. [DllImport(@"svsReader.dll", EntryPoint = "?GetSvsSize@@YA_NPA_WPAJ11111PAF@Z")]  

       我的問題就解決了,然後我就開始想這個問題:c++在編譯之後爲什麼要加上這些字符呢? 難道這是防止被Reflect?

在c#的代碼執行過程中,首先源代碼被編譯成託管模塊(分佈在各自的dll中),託管模塊裏面包括IL代碼、元數據、還有一些標誌(頭信息),那元數據裏面記錄了源代碼中定義的各種類型和成員等信息,所以c# reflect出來,裏面的類名,方法名都沒改變。


C++源碼如下: 
C++代碼 
  1. —————————————————a.h—————————————————  
  2. #ifdef A_EXPORTS  
  3. #define A_API __declspec(dllexport)  
  4. #else  
  5. #define A_API __declspec(dllimport)  
  6. #endif  
  7.   
  8. A_API int F(void);  
  9. —————————————————a.cpp—————————————————  
  10.   
  11. #include "stdafx.h"  
  12. #include "a.h"  
  13. BOOL APIENTRY DllMain( HANDLE hModule,  
  14.                        DWORD  ul_reason_for_call,  
  15.                        LPVOID lpReserved  
  16.  )  
  17. {  
  18. switch (ul_reason_for_call)  
  19. {  
  20. case DLL_PROCESS_ATTACH:  
  21. case DLL_THREAD_ATTACH:  
  22. case DLL_THREAD_DETACH:  
  23. case DLL_PROCESS_DETACH:  
  24. break;  
  25. }  
  26.     return TRUE;  
  27. }  
  28.   
  29. A_API int F(void)  
  30. {  
  31. MessageBox(NULL, "1212""1212", MB_OK);  
  32. return 0;  
  33. }  

C#源碼函數原型聲明: 

[DllImport("a.dll")] 
public extern static int F(); 
調用後提示找不到入口點 

在命令行用dumpbin /exports 看函數名: 

dumpbin /exports a.dll 
函數名不是"F"?而是"?F@@YAHXZ" 

C#函數聲明寫成: 

C#代碼 
  1. [DllImport("a.dll",EntryPoint="?F@@YAHXZ")]  
  2. public extern static int F();  

這樣調用成功! 

原因:在C++函數聲明時要將 extern "C" 添加在 DLL 函數聲明之前 

主要注意包含 DllImport 的代碼行。此代碼行根據參數值通知編譯器,使之聲明位於 User32.dll 中的函數並將簽名中出現的所有字符串(如參數或返回值)視爲 Unicode 字符串。如果缺少 EntryPoint 參數,則默認值爲函數名。另外,由於 CharSet 參數指定 Unicode,因此公共語言運行庫將首先查找稱爲 MessageBoxW(有 W 是因爲 Unicode 規範)的函數。如果運行庫未找到此函數,它將根據調用約定查找 MessageBox 以及相應的修飾名。受支持的調用約定只有 __cdecl 和 __stdcall。 
當調用用戶定義的 DLL 中所包含的函數時,,如下所示: 
// The function declaration in SampleDLL.h file 
extern "C" SAMPLEDLL_API int fnSampleDLL(void); 

Dumpbin.exe位於 VS的安裝目錄\VC\bin下,如果點擊dumpbin.exe提示 
出現mspdb80.dll無法找到的情況,是因爲VC\Bin\下沒有 “msobj80.dll,mspdb80.dll,mspdbcore.dll,mspdbsrv.exe”這四個文件(在VS2005中並沒有這四個文件),解決的方法: 
1>直接從Common7\IDE\下複製這四個文件到VC\Bin\下即可解決 
2>添加系統變量 (Path),這樣:我的電腦->屬性->高級->環境變量->系統變量,在path中添加C:\Program Files\Microsoft Visual Studio 8\Common7\IDE;,注意結尾最後用“;”隔開! 
這樣在用ml編譯就不會出現mspdb80.dll文件找不到的錯誤了 

DLL(動態庫)導出函數名亂碼含義  

DLL(動態庫)導出函數名亂碼含義  
C++編譯時函數名修飾約定規則:    
  __stdcall調用約定:    
  1、以"?"標識函數名的開始,後跟函數名;   
  
  2、函數名後面以"@@YG"標識參數表的開始,後跟參數表;  
   
  3、參數表以代號表示:    
  X--void
  D--char
  E--unsigned char
  F--short
  H--int
  I--unsigned int
  J--long
  K--unsigned long
  M--float
  N--double
  _N--bool
  ....    
  PA--表示指針,後面的代號表明指針類型,如果相同類型的指針連續出現,以"0"代替,一個"0"代表一次重複;    
  4、參數表的第一項爲該函數的返回值類型,其後依次爲參數的數據類型,指針標識在其所指數據類型前;    
   
  5、參數表後以"@Z"標識整個名字的結束,如果該函數無參數,則以"Z"標識結束。    
  其格式爲"?functionname@@YG*****@Z"或"?functionname@@YG*XZ",例如    
                      int Test1(char *var1, unsigned long)-----"?Test1@@YGHPADK@Z"                      

                     void Test2()-----"?Test2@@YGXXZ"
     
  __cdecl調用約定:    
  規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的"@@YG"變爲"@@YA"。    
  __fastcall調用約定:    
  規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的"@@YG"變爲"@@YI"。   

  如果要用DEF文件輸出一個"C++"類,則把要輸出的數據和成員的修飾名都寫入.def模塊定義文件    
  所以...   通過def文件來導出C++類是很麻煩的,並且這個修飾名是不可避免的


C++編譯器的命名規則是這樣的:
因爲c++支持函數名重載,所以編譯器會根據自己的規則對函數名進行篡改,防止命名發生衝突。
解決辦法是在你dll的.cpp 和.h頭文件中在函數前 加關鍵字_stdcall
或者在.def文件中直接指定導出的函數名
這樣你再用depends或者exescope 看dll導出函數時就不會出現名字改編的問題了


C#中DllImport用法彙總

C++ 動態庫導出函數名亂碼及解決

dll 導出函數名的那些事



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章