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的時候,經常出現這樣的問題:
- 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,這個時候,我們要是想調用這個函數,那麼應該這樣寫:
- [DllImport(@"svsReader.dll", EntryPoint = "?GetSvsSize@@YA_NPA_WPAJ11111PAF@Z")]
我的問題就解決了,然後我就開始想這個問題:c++在編譯之後爲什麼要加上這些字符呢? 難道這是防止被Reflect?
在c#的代碼執行過程中,首先源代碼被編譯成託管模塊(分佈在各自的dll中),託管模塊裏面包括IL代碼、元數據、還有一些標誌(頭信息),那元數據裏面記錄了源代碼中定義的各種類型和成員等信息,所以c# reflect出來,裏面的類名,方法名都沒改變。
C++源碼如下:
- —————————————————a.h—————————————————
- #ifdef A_EXPORTS
- #define A_API __declspec(dllexport)
- #else
- #define A_API __declspec(dllimport)
- #endif
- A_API int F(void);
- —————————————————a.cpp—————————————————
- #include "stdafx.h"
- #include "a.h"
- BOOL APIENTRY DllMain( HANDLE hModule,
- DWORD ul_reason_for_call,
- LPVOID lpReserved
- )
- {
- switch (ul_reason_for_call)
- {
- case DLL_PROCESS_ATTACH:
- case DLL_THREAD_ATTACH:
- case DLL_THREAD_DETACH:
- case DLL_PROCESS_DETACH:
- break;
- }
- return TRUE;
- }
- A_API int F(void)
- {
- MessageBox(NULL, "1212", "1212", MB_OK);
- return 0;
- }
C#源碼函數原型聲明:
[DllImport("a.dll")]
public extern static int F();
調用後提示找不到入口點
在命令行用dumpbin /exports 看函數名:
dumpbin /exports a.dll
函數名不是"F"?而是"?F@@YAHXZ"
C#函數聲明寫成:
- [DllImport("a.dll",EntryPoint="?F@@YAHXZ")]
- 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導出函數時就不會出現名字改編的問題了