win32 DLL 学习总结
生活随笔
收集整理的這篇文章主要介紹了
win32 DLL 学习总结
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
DLL的開發(fā)與調(diào)用(一)——創(chuàng)建導出函數(shù)的Win32 DLL
http://www.cnblogs.com/Pickuper/articles/2053745.htmlVisual C++6.0 中可實現(xiàn)的DLL
? ? ? ? ?Visual C++6.0 支持自動生成Win32 DLL和MFC AppWizard DLL兩種,其中Win32 DLL不使用MFC類庫,其導出的函數(shù)是標準的C接口,能夠被非MFC和MFC的應用程序調(diào)用,應用范圍更廣泛。所以下面就介紹Win32 DLL的開發(fā)。
創(chuàng)建導出函數(shù)的Win32 DLL
1、啟動Visual C++6.0,利用AppWizard創(chuàng)建一個“Win32 Dynamic-Link Library”類型的工程,工程名為SayHello。采用默認設置,即創(chuàng)建一個Win32 DLL的空項目。
2、為DLL工程添加頭文件SayHello.h和源文件SayHello.cpp。在頭文件SayHello.h中,聲明DLL的導出函數(shù)Say和Sum,分別用來顯示"Hello,World!"和求和。聲明代碼如下:
//SayHello.h
//
/*
extern "C"修飾詞的作用是使C++編譯器以C語言的方式對這個函數(shù)進行處理,以便供其他語言所用。
*/
extern "C" void _declspec(dllexport)Say(char* szWords,int nLen); ? ? ? ? ? ? ? ? ? ? ? ? ? //聲明Say導出函數(shù)
extern "C" float _declspec(dllexport)Sum(float fNum1,float fNum2); ? //聲明Sum導出函數(shù)
在源文件SayHello.cpp中添加函數(shù)Say和Sum的實現(xiàn)代碼,代碼如下:
//SayHello.cpp
//
#include <string.h>
#include "SayHello.h"
void Say(char* szWords,int nLen)
{
? ? strcpy(szWords,"Hello,World!");
? ? strcat(szWords,"\0");
}
float Sum(float fNum1,float fNum2)
{
? ? return fNum1+fNum2;
}
3、【F7】鍵編譯生成DLL。此時在工程的Debug文件夾下生成實際代碼文件SayHello.dll和導入庫文件SayHello.lib。
4、從DLL中導出函數(shù)有兩種方法,一種是使用_declspec(dllexport)關(guān)鍵字,如SayHello.h中所示;一種是添加.def文件(值得注意的是,添加的文件類型是文本文件,且名稱應輸入SayHello.def),代碼如下:
;SayHello.def
;
LIBRARY "SayHello"
DESCRIPTION "導出DLL中的函數(shù)"
EXPORTS
? ? Say ?@1
? ? Sum ?@2
5、加載DLL分為靜態(tài)加載和動態(tài)加載。動態(tài)加載(運行時動態(tài)鏈接,也叫顯示鏈接)DLL是通過LoadLibrary、GetProcAddress和FreeLibrary這3個API函數(shù)進行的。調(diào)用如下:
typedef void(*SAY)(char*,int);
SAY Say;
typedef float(*SUM)(float,float);
SUM Sum;
HINSTANCE hdll;
hdll=LoadLibrary("..\\..\\SayHello\\Debug\\SayHello.dll");
if(hdll!=NULL)
{
? ? //GetProcAddress函數(shù)獲得獲得獲得DLL導出函數(shù)地址
? ? Say=(SAY)GetProcAddress(hdll,"Say");
? ? Sum=(SUM)GetProcAddress(hdll,"Sum");
}
else
{
? ? AfxMessageBox("無法加載DLL!");
? ? return;
}
UpdateData(TRUE);
const int Len=20;
char p[Len];
Say(p,Len);
m_strDispHello.Format("%s",p);
m_fResult=Sum(m_fNum1,m_fNum2);
UpdateData(FALSE);
FreeLibrary(hdll);
靜態(tài)加載(加載時動態(tài)鏈接,也叫隱式鏈接)DLL是由編譯系統(tǒng)完成對DLL的加載和應用程序結(jié)束時對DLL的卸載,需要將DLL的引用庫文件(.lib)與應用程序進行靜態(tài)鏈接。調(diào)用如下:
#pragma comment(lib,"SayHello.lib")
extern "C" _declspec(dllimport) void Say(char* szWords,int nLen);?
extern "C" _declspec(dllimport) float Sum(float fNum1,float fNum2);
此時就使用Say和Sum函數(shù)了。
========
Win32 動態(tài)鏈接(dll)簡單示例
http://blog.csdn.net/weiwenhp/article/details/8710811dll(dynamic link library)動態(tài)鏈接庫相當于是把一些函數(shù)或者類啊編譯成源碼.不過它不可執(zhí)行.只是當被其他exe或dll調(diào)用到時才被加載到內(nèi)存中.像windows那些API都是放到一些dll文件中.比如kernel32.dll,它包含管理內(nèi)存,進程,線程的一些函數(shù).User32.dll包含用于執(zhí)行用戶界面任務的函數(shù).
而當我們寫代碼要用到dll中的函數(shù)時,在編譯階段一般只要個lib文件,里面有dll中的函數(shù)和類的描述信息,但沒有實現(xiàn)代碼信息.
DLL的創(chuàng)建
下面來看一個創(chuàng)建dll的簡單示例
創(chuàng)建 Win32 Project-->application type選DLL.
project名字就取DllTest.創(chuàng)建好項目后我們會看到自動生成了.dllmain.cpp和DllTest.cpp,前一個文件不用去動它.
我們就在DllTest.cpp文件中添加如下內(nèi)容
_declspec(dllexport) int multiply(int one , int two) //返回兩數(shù)相乘的積 { return one*two; }
編譯下這個項目.你會在目錄下面看到DllTest.dll 和 DllTest.lib 這兩文件.等會其他項目中要用它倆.
DLL的使用
新建一個簡單的Win32 console application 項目.把上面的DllTest.dll和DllTest.lib兩文件拷到項目目錄下.再添加如下代碼
#include <iostream> using namespace std; #pragma comment(lib, "./DllTest.lib") int multiply( int one , int two) ; //函數(shù)聲明,函數(shù)定義最終是去調(diào)用DllTest.dll中的代碼了.//另外最好是寫成這樣_declspec(dllimport) int multiply( int one , int two) int main() {int ret = multiply( 4,5);cout<<ret; //20return 0; }
當然了,如果你嫌#pragma comment(lib, "./DllTest.lib")這樣寫麻煩,也不不寫,而是在項目的property page -->Linker -->Input -->Additional Dependencies里面敲入DllTest.lib
Dll創(chuàng)建示例2(帶類的dll)
上面是比較簡單的再來看個復雜點的.
跟前面一樣還是一樣先創(chuàng)建一個win32 dll項目名為DllTest.然后添加class Arwen.
/Arwen.h中內(nèi)容/
#pragma once
#include <iostream>
#define DLL_API _declspec(dllimport)
class DLL_API Arwen{
public:
int age;
void Fun();
};
?
//Arwen.cpp中內(nèi)容/
#include "StdAfx.h"
#include "Arwen.h"
#define DLL_API _declspec(dllexport)
void Arwen::Fun()
{
std::cout<<"my age is "<<age;
}?
使用DLL
新建一個win32 console application ,把DllTest.dll和Dll.lib拷貝過去.另外把頭文件Arwen.h也拷過去.
#include "Arwen.h"
#pragma comment(lib, "./DllTest.lib")
int main()
{
Arwen an;
an.age = 25;
an.Fun();
?return 0;
}
?動態(tài)加載DLL
前面講的是靜態(tài)加載DLL,現(xiàn)在瞧下怎么動態(tài)加載.
#include <windows.h>
typedef int( *pFun) (int a, int b); ?//定義一個函數(shù)指針類型
void main()
{
? HINSTANCE hInt = LoalLibrary( _T( "../debug/DllTest.dll") ); ?//動態(tài)加載
? pFun mulitplyFun = (pFun) GetProcAddress( hInt , (LPCSTR) MAKEINTRESOURCE(2)); //函數(shù)序列號是通過工具dumpbin查到的
}?
工具dumpbin的使用.
1.先找到vsvar32.bat文件,目錄是在: 安裝目錄\VC\bin\vcvars32.bat.然后在cmd里面執(zhí)行它
2.先切換到dll文件所在目錄,假如這里是DllTest.dll,然后執(zhí)行命令dumpbin - exports DllTest.dll
3.得到所以導出函數(shù)信息,其中ordianl那一列指函數(shù)序列號 , name那一列則是編譯之后函數(shù)的名字,比之前的函數(shù)名多了些前綴后綴.
========
win32 dll簡單例子
http://blog.csdn.net/rem2002/article/details/1744978
一。顯示鏈接dll
編寫dll
FILE->Visal C++項目: Win32項目->應用程序設置: 選擇 DLL(D) 選項 并勾選 導出符號,將 h,cpp文件修改如下:
MyDll.h
? ?//Mydll.h
? ?#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
extern "C" MYDLL_API int fun(int mode); //自己寫的 extern "C" 不可少
extern "C" MYDLL_API int fun2(int a,int b);?
MyDll.cpp
#include "stdafx.h"
#include "MyDll.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;
}
MYDLL_API int fun(int mode) //自己寫的
{
? ??
? ? return mode*mode;
}
MYDLL_API int fun2(int a,int b) //自己寫的
{
? ? int d = (a>b?(a-b):(b-a));
? ? return d;
}
編寫測試程序:testDll
采用win32控制臺生成的執(zhí)行程序進行測試 (注: 屬性->C/C++:預處理器->預處理器定義 ?加宏:MYDLL_EXPORTS)
因為MyDll.h中定義了宏 #define MYDLL_API __declspec(dllexport)
#include <iostream>
#include <Windows.h>
typedef int (*PFNMYDLL)(int);//聲明函數(shù)原型
typedef int (*HHH)(int,int);?
using namespace std;
void main()
{
? ? HMODULE hModule = ::LoadLibrary("MyDll.dll");//加載DLL庫
? ? PFNMYDLL newfun = (PFNMYDLL)::GetProcAddress(hModule,"fun");//取得fun函數(shù)的地址
? ? int i = newfun(4);
? ? printf("The result is %d ",i);
? ? HHH newfun2 = (HHH)::GetProcAddress(hModule,"fun2");//取得fun函數(shù)的地址
? ? int d = newfun2(6,4);
? ? printf("the 6,4 is: %d ",d);
? ? int c = newfun2(7,19);
? ? printf("the 7,19 is:%d ",c);
? ? ::FreeLibrary(hModule);
}
?
二.隱式鏈接
[cpp] view plain copy
#ifdef MYDLL_EXPORTS ?
#define MYDLL_API __declspec(dllexport) ?
#else ?
#define MYDLL_API __declspec(dllimport) ?
#endif ?
??
class MYDLL_API MyDll ?
{ ?
public: ?
? ? MyDll(void); ?
? ? ~MyDll(void); ?
? ? void setValue(int value); ?
? ? int getValue(); ?
??
private: ?
? ? int m_nValue; ?
}; ?
?
?使用 dll 代碼
[cpp] view plain copy
#include <stdlib.h> ?
#include <stdio.h> ?
#include <windows.h> ?
#include "MyDll.h" ?
#pragma comment(lib,"MyDll.lib") ?
??
void main() ?
{ ?
??
?MyDll myDll; ?
?myDll.setValue(20); ?
?int i = myDll.getValue(); ?
??
?printf("%d",i); ?
} ?
?
?以下為轉(zhuǎn)貼
三。導出并顯式鏈接一組C++成員函數(shù)
這里有兩個問題。第一是C++成員函數(shù)名是經(jīng)過修飾的(即使指定extern "C"標記也是這樣);第二是C++不允許將指向成員函數(shù)的指針轉(zhuǎn)換成其它類型。這兩個問題限制了C++類的顯式鏈接。下面介紹兩種方法來解決這個問題:①用虛函數(shù)表的方法,這也是COM使用的方法;②用GetProcAddress直接調(diào)用。
1.虛函數(shù)表方法:
使用到的 dll 頭文件 MyDll.h
[cpp] view plain copy
#ifdef MYDLL_EXPORTS ?
#define MYDLL_API __declspec(dllexport) ?
#else ?
#define MYDLL_API __declspec(dllimport) ?
#endif ?
??
class MYDLL_API MyDll ?
{ ?
public: ?
? ? MyDll(void); ?
? ? MyDll(int i); ?
? ? virtual ~MyDll(void); ?
? ? virtual void setValue(int value); ?
? ? virtual int getValue(); ?
??
private: ?
? ? int m_nValue; ?
}; ?
?
使用 dll 的代碼
[cpp] view plain copy
#include <stdlib.h> ?
#include <stdio.h> ?
#include <string> ?
#include <windows.h> ?
??
#include "MyDll.h" ?
??
typedef MyDll* (*pCreateA)(); ?
typedef MyDll* (*pCreateA1)(int); ?
??
void main() ?
{ ?
? ? HMODULE hModule; ?
? ? ??
? ? hModule = ::LoadLibrary("MyDll");//加載DLL庫 ?
??
? ? pCreateA pCreate = (pCreateA)GetProcAddress(hModule, TEXT("CreateMyDll")); ?
??
? ? MyDll* a = (pCreate)(); ?
? ? a->setValue(20); ?
? ? printf("one:%d/n",a->getValue()); ? ?
??
? ? pCreateA1 pCreate1 = (pCreateA1)GetProcAddress(hModule, TEXT("CreateMyDll1")); ?
??
? ? MyDll* b = (pCreate1)(50); ?
? ? printf("two:%d/n",b->getValue()); ?
??
? ? ::FreeLibrary(hModule); ?
??
? ? getchar(); ?
? ? return; ?
} ??
dll 項目
MyDll.h 即使用到的 dll 頭文件
MyDll.cpp
[cpp] view plain copy
#include "MyDll.h" ?
??
MyDll::MyDll(void) ?
:m_nValue(0) ?
{ ?
} ?
??
MyDll::MyDll(int i) ?
{ ?
? ? m_nValue = i; ?
} ?
??
MyDll::~MyDll(void) ?
{ ?
? ? m_nValue = 0; ?
} ?
??
void MyDll::setValue(int value) ?
{ ?
? ? m_nValue = value; ?
} ?
??
int MyDll::getValue() ?
{ ?
? ? return m_nValue; ?
} ?
Inst.cpp
[c-sharp] view plain copy
#include "MyDll.h" ?
??
extern "C" __declspec(dllexport) MyDll* CreateMyDll() ?
{ ?
? ? return new MyDll(); ?
} ?
extern "C" __declspec(dllexport) MyDll* CreateMyDll1(int i) ?
{ ?
? ? return new MyDll(i); ?
} ?
?
這個方法的使用得用戶可以很容易地為你的程序制作插件。它的缺點是創(chuàng)建對象的內(nèi)存必須在dll中分配.
直接使用GetProcAddress進行顯式鏈接
這個方法的關(guān)鍵在于將GetProcAddress函數(shù)返回的FARPROC類型轉(zhuǎn)化為C++中指向成員函數(shù)的指針。幸運的是,通過C++的unio和模板機制,這個目標可以很容易地實現(xiàn)。我們要做的只是定義如下的函數(shù):
template<class Src , class Dest>
Dest force_cast(Src src){
?union{
? Dest d;
? Src s;
?} convertor;
convertor.s = Src;
?return convertor.d;
}
上面的函數(shù)允許我們在任何類型間進行轉(zhuǎn)換,比reinterpret_cast更加有效。例如,我們定義一種指針類型:
typedef void (A::*PSetNum)(int);
我們可以將FARPROC類型的指針fp轉(zhuǎn)化成PSetNum:
PSetNum psn = force_cast<PSetNum>(fp);
找到了將FARPROC轉(zhuǎn)化成成員函數(shù)指針的方法以后,我們要考慮如何將C++成員函數(shù)以更加友好的名字導出。這可以通過一個.def文件來實現(xiàn)。
第一步是找到待導出函數(shù)經(jīng)過修飾的函數(shù)名,這可以通過查看map file或者匯編代碼來實現(xiàn)。然后在.def文件中指定導出函數(shù)的新的函數(shù)名:
EXPORTS
?ConstructorOfA1 = ??0A@@QAE@XZ ? ? ? ?PRIVATE
?ConstructorOfA2 = ??0A@@QAE@H@Z ? ? ? PRIVATE
?SetValueOfA ? ? ? = ?SetNum@A@@UAEXH@Z ?PRIVATE
?GetValueOfA ? ? ? = ?GetNum@A@@UAEHXZ ? PRIVATE ?
下面是調(diào)用這些成員函數(shù)的方法:
typedef void (A::*PfnConstructorOfA1)();
typedef void (A::*PfnConstructorOfA2)(int);
typedef void (A::*PfnDestructorOfA)();
typedef void (A::*PfnSetNumOfA)(int);
typedef int ?(A::*PfnGetNumOfA)();
A* a1 = (A*)_alloca(sizeof(A));
PfnConstructorOfA1 pfnConsA =
? ? ?force_cast<PfnConstructorOfA1>(GetProcAddress(hMod, TEXT("ConstructorOfA1")));
(a1->*pfnConsA)();
PfnSetNumOfA pfnSetNumA =
? ? ? ? ? force_cast<PfnSetNumOfA>(GetProcAddress(hMod, TEXT("SetNumOfA")));
(a1->*pfnSetNumA)(1); ? ??
PfnGetNumOfA pfnGetNumA =
? ? ? ? ? force_cast<PfnGetNumOfA>(GetProcAddress(hMod, TEXT("GetNumOfA")));
_tprintf(TEXT("Value of m_nNum in a is %d/n"),(a1->*pfnGetNumA)());
?注意這里使用了alloca從棧中分配內(nèi)存,你也可以使用malloc從堆中分配內(nèi)存。但是不能使用C++的new操作符,因為能過new來分配內(nèi)存編譯器會自動插入對constructor的調(diào)用。但我們要的是顯式鏈接,所以必須避免這種情況。隨之產(chǎn)生的結(jié)果是我們只能顯式地去調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。
========
動態(tài)鏈接庫-Win32 DLL的創(chuàng)建和使用
http://www.cnblogs.com/because/archive/2012/02/18/2357109.html摘要
? ? ? ?利用Visual C++6.0創(chuàng)建和使用DLL(Dynamic-Link Library).
概述
在實際編程時,我們可以把完成某種功能的函數(shù)放在一個動態(tài)鏈接庫中,然后給其他程序調(diào)用。
WinAPI中所有的函數(shù)都包含在3個最重要的DLL中。
Kernel32.dll
它包含那些用于管理內(nèi)存、進程和線程的函數(shù),例如CreateThread函數(shù);
User32.dll
它包含那些用于執(zhí)行用戶界面任務的函數(shù),例如CreateWindow函數(shù);
GDI32.dll
它包含那些用于畫圖和顯示文本的函數(shù)。
用法
?新建一個Win32 Console Application工程:
以MathLib為工程名稱新建Win32 Dynamic-Link Library的空工程,
添加C++ Source File 源文件到工程中,命名為MathLib.c
添加以下代碼:
復制代碼
?1 #define MATH_API _declspec(dllexport)
?2 #include "MathLib.h"
?3 int add(int a,int b)
?4 {
?5 ? ? return a+b;
?6 }
?7 int subtract(int a,int b)
?8 {
?9 ? ? return a-b;
10 }
復制代碼
添加C/C++ Header File 頭文件到工程中,命名為MathLib.h
復制代碼
1 #ifdef MATH_API
2 #else
3 ? ? #define ? MATH_API _declspec(dllimport)
4 #endif
5 MATH_API int add(int a,int b);
6 MATH_API int subtract(int a,int b);
復制代碼
編譯后生成MathLib.dll和MathLib.lib兩個動態(tài)鏈接庫文件。
測試
隱式調(diào)用
?
新建MFC AppWizard[exe]可執(zhí)行工程DllTest,用于測試剛才新建動態(tài)鏈接庫MathLib的功能。
復制MathLib.dll,MathLib.lib,MathLib.h到當前工程,
在DllTestDlg.cpp中添加頭文件引用:
#include "MathLib.h"
?
添加MathLib.h頭文件至工程,
在Project->Setting->Link->object/library modules:添加MathLib.lib
添加一個按鈕Add到Dialogue中,在Add按鈕的響應函數(shù)中添加以下代碼:
1 void CDllTestDlg::OnBtnMath()?
2 {
3 ? ? // TODO: Add your control notification handler code here
4 ? ? CString res;
5 ? ? res.Format("10+2=%d",add(10,2));
6 ? ? MessageBox(res);
7 }
復制代碼
編譯運行程序,
成功運行MathLib中的加法功能。
工程文件:
========
windows程序設計之調(diào)用動態(tài)鏈接庫DLL DLL的調(diào)用約定
http://www.cnblogs.com/llz5023/archive/2012/12/30/2839682.html1、動態(tài)鏈接庫英文為DLL,是Dynamic Link Library 的縮寫形式,DLL是一個包含可由多個程序同時使用的代碼和數(shù)據(jù)的庫,DLL不是可執(zhí)行文件。動態(tài)鏈接提供了一種方法,使進程可以調(diào)用不屬于其可執(zhí)行代碼的函數(shù)。函數(shù)的可執(zhí)行代碼位于一個 DLL 中,該 DLL 包含一個或多個已被編譯、鏈接并與使用它們的進程分開存儲的函數(shù)。DLL 還有助于共享數(shù)據(jù)和資源。多個應用程序可同時訪問內(nèi)存中單個DLL 副本的內(nèi)容。DLL 是一個包含可由多個程序同時使用的代碼和數(shù)據(jù)的庫。
2、操作實例,C語言咧調(diào)用系統(tǒng)的kernel32.dll中的GlobalMemoryStatusEx函數(shù)
? ? ? ? ? ?typedef ? void(WINAPI* ? FunctionGlobalMemoryStatusEx)(LPMEMORYSTATUS);//聲明函數(shù)指針模型
? ? ? ? ? ? HMODULE ? hModule;//Dll句柄
? ? ? ? ? ? FunctionGlobalMemoryStatusEx ? GlobalMemoryStatusEx;//函數(shù)指針模型聲明函數(shù)變量
? ? ? ? ? ? MEMORYSTATUS status;
? ? ? ? ? ? status.dwLength = sizeof(status);
? ? ? ? ? ? //GlobalMemoryStatus(&status);
? ? ? ? ? ? hModule ? = ? LoadLibrary("kernel32.dll");//調(diào)試時hModule為0x10000000,載入動態(tài)鏈接庫dll,返回它的句柄
? ? ? ? ? ? if(NULL==hModule)//判斷載入是否成功
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //error.
? ? ? ? ? ? ? ? MessageBox(hwndDlg,TEXT("載入指定的動態(tài)鏈接庫dll失敗"),TEXT("error"),MB_OK);
? ? ? ? ? ? ? ? return 0;
? ? ? ? ? ? }
? ? ? ? ? ? //調(diào)用GetProcAddress API根據(jù)dll句柄,和dll的聲明的函數(shù)名獲取函數(shù)指針
? ? ? ? ? ? GlobalMemoryStatusEx ? =(FunctionGlobalMemoryStatusEx)GetProcAddress(hModule,"GlobalMemoryStatusEx");
? ? ? ? ? ? if(NULL==GlobalMemoryStatusEx)//判斷獲取是否成功
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //error
? ? ? ? ? ? ? ? MessageBox(hwndDlg,TEXT("error2"),TEXT("error2"),MB_OK);
? ? ? ? ? ? ? ? return 0;
? ? ? ? ? ? }
? ? ? ? ? ? //獲取成功,然后可以直接用函數(shù)指針來調(diào)用函數(shù),函數(shù)名就是函數(shù)指針,C語言應該都懂
? ? ? ? ? ? GlobalMemoryStatusEx(&status);//調(diào)用函數(shù)
? ? ? ? ? ? FreeLibrary(hModule);//用完了要釋放dll
3、第二步已經(jīng)說名了怎么動態(tài)調(diào)用DLL,我們還要注意一點,DLL的調(diào)用約定
dll有__cdecl __stdcall WINAPI 等不同的調(diào)用約定,也就是參數(shù)的壓棧順序等,暫時不用關(guān)心,只要保證調(diào)用的時候和dll中的調(diào)用約定一樣就可以。
//否則會報錯:The value of ESP was not properly saved across a function call. ?This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.?
如上面的列子,如果我把typedef ? void(WINAPI* ? FunctionGlobalMemoryStatusEx)(LPMEMORYSTATUS);//聲明函數(shù)指針模型,改成:
typedef ? void(__cdecl* ? FunctionGlobalMemoryStatusEx)(LPMEMORYSTATUS););//聲明函數(shù)指針模型
運行時會報錯誤:
image?
由此,在聲明函數(shù)原型指針時要注意寫對調(diào)用約定,如果不知道,那么換著調(diào)試看那個對。
4、說明一下調(diào)用約定(Calling Convention)相關(guān)的(其他地方拷貝來的)
調(diào)用約定用來處理決定函數(shù)參數(shù)傳送時入棧和出棧的順序(由調(diào)用者還是被調(diào)用者把參數(shù)彈出棧),以及編譯器用來識別函數(shù)名稱的名稱修飾約定等問題。在Microsoft VC++ 6.0中定義了下面幾種調(diào)用約定,我們將結(jié)合匯編語言來一一分析它們:
4.1、__cdecl
__cdecl是C/C++和MFC程序默認使用的調(diào)用約定,也可以在函數(shù)聲明時加上__cdecl關(guān)鍵字來手工指定。采用__cdecl約定時,函數(shù)參數(shù)按照從右到左的順序入棧,并且由調(diào)用函數(shù)者把參數(shù)彈出棧以清理堆棧。因此,實現(xiàn)可變參數(shù)的函數(shù)只能使用該調(diào)用約定。由于每一個使用__cdecl約定的函數(shù)都要包含清理堆棧的代碼,所以產(chǎn)生的可執(zhí)行文件大小會比較大。__cdecl可以寫成_cdecl。
下面將通過一個具體實例來分析__cdecl約定:
在VC++中新建一個Win32 Console工程,命名為cdecl。其代碼如下:
int __cdecl Add(int a, int b); //函數(shù)聲明
void main()
{
Add(1,2); //函數(shù)調(diào)用
}
int __cdecl Add(int a, int b) //函數(shù)實現(xiàn)
{
return (a + b);
}
函數(shù)調(diào)用處反匯編代碼如下:
;Add(1,2);
push 2 ;參數(shù)從右到左入棧,先壓入2
push 1 ;壓入1
call @ILT+0(Add) (00401005) ;調(diào)用函數(shù)實現(xiàn)
add esp,8 ;由函數(shù)調(diào)用清棧
4.2、__stdcall
__stdcall調(diào)用約定用于調(diào)用Win32 API函數(shù)。采用__stdcal約定時,函數(shù)參數(shù)按照從右到左的順序入棧,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的棧,函數(shù)參數(shù)個數(shù)固定。由于函數(shù)體本身知道傳進來的參數(shù)個數(shù),因此被調(diào)用的函數(shù)可以在返回前用一條ret n指令直接清理傳遞參數(shù)的堆棧。__stdcall可以寫成_stdcall。
還是那個例子,將__cdecl約定換成__stdcall:
int __stdcall Add(int a, int b)
{
return (a + b);
}
函數(shù)調(diào)用處反匯編代碼:
; Add(1,2);
push 2 ;參數(shù)從右到左入棧,先壓入2
push 1 ;壓入1
call @ILT+10(Add) (0040100f) ;調(diào)用函數(shù)實現(xiàn)
函數(shù)實現(xiàn)部分的反匯編代碼:
;int __stdcall Add(int a, int b)
push ebp
mov ebp,esp
sub esp,40h
push ebx
push esi
push edi
lea edi,[ebp-40h]
mov ecx,10h
mov eax,0CCCCCCCCh
rep stos dword ptr [edi]
;return (a + b);
mov eax,dword ptr [ebp+8]
add eax,dword ptr [ebp+0Ch]
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 8 ;清棧
4.3、__fastcall
__fastcall約定用于對性能要求非常高的場合。__fastcall約定將函數(shù)的從左邊開始的兩個大小不大于4個字節(jié)(DWORD)的參數(shù)分別放在ECX和EDX寄存器,其余的參數(shù)仍舊自右向左壓棧傳送,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的堆棧。__fastcall可以寫成_fastcall。
依舊是相類似的例子,此時函數(shù)調(diào)用約定為__fastcall,函數(shù)參數(shù)個數(shù)增加2個:
int __fastcall Add(int a, double b, int c, int d)
{
return (a + b + c + d);
}
函數(shù)調(diào)用部分的匯編代碼:
;Add(1, 2, 3, 4);
push 4 ;后兩個參數(shù)從右到左入棧,先壓入4
mov edx,3 ;將int類型的3放入edx
push 40000000h ;壓入double類型的2
push 0
mov ecx,1 ;將int類型的1放入ecx
call @ILT+0(Add) (00401005) ;調(diào)用函數(shù)實現(xiàn)
函數(shù)實現(xiàn)部分的反匯編代碼:
; int __fastcall Add(int a, double b, int c, int d)
push ebp
mov ebp,esp
sub esp,48h
push ebx
push esi
push edi
push ecx
lea edi,[ebp-48h]
mov ecx,12h
mov eax,0CCCCCCCCh
rep stos dword ptr [edi]
pop ecx
mov dword ptr [ebp-8],edx
mov dword ptr [ebp-4],ecx
;return (a + b + c + d);
fild dword ptr [ebp-4]
fadd qword ptr [ebp+8]
fiadd dword ptr [ebp-8]
fiadd dword ptr [ebp+10h]
call __ftol (004011b8)
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 0Ch ;清棧
關(guān)鍵字__cdecl、__stdcall和__fastcall可以直接加在要輸出的函數(shù)前,也可以在編譯環(huán)境的Setting...->C/C++->Code Generation項選擇。它們對應的命令行參數(shù)分別為/Gd、/Gz和/Gr。缺省狀態(tài)為/Gd,即__cdecl。當加在輸出函數(shù)前的關(guān)鍵字與編譯環(huán)境中的選擇不同時,直接加在輸出函數(shù)前的關(guān)鍵字有效。
========
DLL中導出函數(shù)(函數(shù)名及其調(diào)用約定)
http://www.cnblogs.com/leijiangtao/p/4797585.html最近簡單研究了一下dll的導出函數(shù),整理了一下
1.導出函數(shù)名的問題
dll導出函數(shù)最簡單的語法是
void__declspec(dllexport) fun();
由于它默認的是c++的調(diào)用約定cdecl,因此導出的函數(shù)就變成了
?fun@@YAXXZ
如果直接取函數(shù)名fun,就會找不到函數(shù),有兩種方法可以解決這個問題:用C的編譯方式和def文件
① ? ? ?用C的編譯方式
在導出函數(shù)前聲明extern “C”,即:
extern “C” void__declspec(dllexport) fun();
加入extern “C”是告訴編譯器,用C的編譯方式生成文件,不需要加入?yún)?shù)作為修飾
② ? ? ?Def文件
在project中建立一個def文件,寫入
LIBRARY ? "testDLL"// testDLL是project的名字
EXPORTS ? ? ? ? ? ?//輸出
? fun ? ? ? ? ? ? ?//函數(shù)名(也可以帶序號的輸出函數(shù)名fun@1)
extern “C” void__declspec(dllexport) 和在def文件中導出函數(shù)的作用是一樣的,因此沒必要都寫在工程中。
Ps,如果導出的函數(shù)名帶一些修飾,如:?fun@@YAXXZ,用GetProcAddress()函數(shù)直接調(diào)用“?fun@@YAXXZ”也是可以找到函數(shù)的。
2. 修飾函數(shù)的關(guān)鍵字
stdcall cdecl fastcall thiscall naked call
這些調(diào)用約定決定了:
? ? ? ? ? 參數(shù)傳遞次序
? ? ? ? ? 調(diào)用堆棧由誰(調(diào)用函數(shù)或被調(diào)用函數(shù))清理
? ? ? ? ? 導出函數(shù)名
導出函數(shù)的調(diào)用約定和使用這個函數(shù)時聲明的調(diào)用約定必須一致,否則程序會崩潰。
在C和C++中默認的調(diào)用約定是__cdecl,上面函數(shù)完整的修飾就是:
void__declspec(dllexport) __cdeclfun();
但是windows系統(tǒng)用的回調(diào)函數(shù)一般都是_stdcall。
下面是各個調(diào)用約定詳細的解釋:
_stdcall
是Pascal方式清理C方式壓棧,通常用于Win32 Api中,函數(shù)采用從右到左的壓棧方式, 自己在退出時清空堆棧。VC將函數(shù)編譯后會在函數(shù)名前面加上下劃線前綴,在函數(shù)名后加上"@"和參數(shù)的字節(jié)數(shù)。
int f(void *p) -->> _f@4(在外部匯編語言里可以用這個名字引用這個函數(shù))?
__cdecl
C調(diào)用約定(即用__cdecl關(guān)鍵字說明)(The C default calling convention)按從右至左的順序壓參數(shù)入棧,由調(diào)用者把參數(shù)彈出棧。對于傳送參數(shù)的內(nèi)存棧是由調(diào)用者來維護的(正因為如此,實現(xiàn)可變參數(shù)vararg的函數(shù)(如printf)只能使用該調(diào)用約定)。
另外,在函數(shù)名修飾約定方面也有所不同。 _cdecl是C和C++程序的缺省調(diào)用方式。每一個調(diào)用它的函數(shù)都包含清空堆棧的代碼,所以產(chǎn)生的可執(zhí)行文件大小會比調(diào)用_stdcall函數(shù)的大。函數(shù)采用從右到左的壓棧方式。VC將函數(shù)編譯后會在函數(shù)名前面加上下劃線前綴。
_fastcall
調(diào)用的主要特點就是快,因為它是通過寄存器來傳送參數(shù)的(實際上,它用ECX和EDX傳送前兩個雙字(DWORD)或更小的參數(shù),剩下的參數(shù)仍舊自右向左壓棧傳送,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的內(nèi)存棧),
在函數(shù)名修飾約定方面,它和前兩者均不同。__fastcall方式的函數(shù)采用寄存器傳遞參數(shù),VC將函數(shù)編譯后會在函數(shù)名前面加上"@"前綴,在函數(shù)名后加上"@"和參數(shù)的字節(jié)數(shù)。
thiscall
僅僅應用于“C++”成員函數(shù)。this指針存放于CX/ECX寄存器中,參數(shù)從右到左壓。thiscall不是關(guān)鍵詞,因此不能被程序員指定。
naked call
當采用1-4的調(diào)用約定時,如果必要的話,進入函數(shù)時編譯器會產(chǎn)生代碼來保存ESI,EDI,EBX,EBP寄存器,退出函數(shù)時則產(chǎn)生代碼恢復這些寄存器的內(nèi)容。
(這些代碼稱作 prolog and epilog code,一般,ebp,esp的保存是必須的).?
但是naked call不產(chǎn)生這樣的代碼。naked call不是類型修飾符,故必須和_declspec共同使用。?
另外,關(guān)鍵字 __stdcall、__cdecl和__fastcall可以直接加在要輸出的函數(shù)前。它們對應的命令行參數(shù)分別為/Gz、/Gd和/Gr。缺省狀態(tài)為/Gd,即__cdecl。
========
總結(jié)
以上是生活随笔為你收集整理的win32 DLL 学习总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 安全编程学习总结
- 下一篇: 自己写编译器学习总结