以下為我學習編寫DLL的一些總結,其中包括一些來自互聯網包括一些例子),感謝作者的幫忙。下面提到的例子都經過編程測試通過。
VC中編寫Non MFC DLL的方法總結
一、
LIB: extern "C" int add(int x,int y); //聲明為C編譯、連接方式的外部函數
1.1 在程序中如何引入lib.h:
1)#pragma comment( lib, "..\\debug\\libTest.lib" ) //指定與靜態庫一起連接
2)VC中 tools、options、directories、library files菜單或選項,填入庫文件路徑
引入lib.h後,就可以直接使用ADD函數。
DLL:extern "C" int __declspec(dllexport)add(int x, int y);//聲明函數add為DLL的導出函數。
1.2 DLL內的函數分為兩種:
(1)DLL導出函數,可供應用程序調用;
(2) DLL內部函數,只能在DLL程序使用,應用程序無法調用它們
引入Dll後,不能馬上調用Add數,要分為幾個步驟來實現調用函數:
1.3首先引入實例:
1.3.1 生產DLL
/* 文件名:lib.h 在該頭文件裡面聲明導出函數變量及類*/
#ifndef LIB_H
#define LIB_H
/*必須把要被外部調用的函數,聲明成導出函數*/
extern "C" int __declspec(dllexport)add(int x, int y);//函數
extern int globalVar;//把變量聲明為外部的可以導出的變量
class __declspec(dllexport)testClass;//聲明該類為導出類
#endif
/*classTest.h 在該頭文件裡面聲明類*/
#include <stdio.h>
class testClass
{
public:
testClass();
void coutData();
protected:
private:
int a;
};
/* 文件名:lib.cpp 可以在任意地方定義導出變量函數類,但必須包括聲明所在的頭文件*/
#include "lib.h"
#include <stdio.h>
#include "classTest.h"
int globalVar = 10;
int add(int x, int y)
{
return x + y;
}
testClass::testClass()
{
printf("output the class ! init...\n");
}
void testClass::coutData()
{
a = 20;
printf("the class'var is a = %d\n",a);
}
/* Lib def: */
;LIBRARY "testdll" 用;表示注釋
LIBRARY testdll //庫的名稱
EXPORTS
;use DATA to define the export VAR
globalVar DATA //導出變量
add @ 1 //導出函數
1.3.2 調用:
/*方法一:動態調用,loadLibrary -- GetProcAddress--FreeLibrary方式*/
#include <stdio.h>
#include <windows.h>
typedef int(*lpAddFun)(int, int); //宏定義函數指針類--------------1)
int main(int argc, char *argv[])
{
HINSTANCE hDll; //DLL句柄 -------3)
lpAddFun addFun; //函數指針 ------2)
hDll = LoadLibrary("..\\Debug\\dllTest.dll"); --------3)
if (hDll != NULL)
{
addFun = (lpAddFun)GetProcAddress(hDll, "add"); ----4)
// addFun = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1));//直接引用導出函數的序號,在def文件裡面已經定義好各個導出函數的符號
if (addFun != NULL)
{
int result = addFun(2, 3);
printf("%d", result);
}
FreeLibrary(hDll);----------5)
}
return 0;
}
1).語句typedef int ( * lpAddFun)(int,int)定義了一個與add函數接受參數類型和返回值均相同的函數指針類型.如。net框架中的委托一樣。
2).在main函數中定義了lpAddFun的實例addFun
3).在函數main中定義了一個DLL HINSTANCE句柄實例hDll,通過Win32 Api函數LoadLibrary動態加載了DLLaddFun。經由函數指針addFun進行了對DLL中add函數的調用
4).在函數main中通過Win32 Api函數GetProcAddress得到了所加載DLL模塊中函數add的地址並賦給了addFun。經由函數指針addFun進行了對DLL中add函數的調用
5).應用工程使用完DLL後,在函數main中通過Win32 Api函數FreeLibrary
釋放了已經加載的DLL模塊
/*方法二:靜態調用方式*/
#include "..\\testdll\\classTest.h" //調用導出類時,要把該類所在的頭文件也包含進去,有沒有其他方法使其不需要的呢?
#pragma comment(lib,"..\\debug\\testdll.lib")
extern "C" int __declspec(dllimport)add(int x, int y);//注意要用dllimport表示導入函數,dllexport表示到處函數符號.
extern int __declspec(dllimport)globalVar;//用dllimport導入函數
class __declspec(dllimport) testClass;
int main()
{
int result=add(2,3);
printf("%d\n",result);
printf("the global var is : %d\n",globalVar);
testClass test;
test.coutData();
return 0;
}
PS:注意若要導出的是類,則必須把包含該類的頭文件及該頭文件所包含的所有頭文件)都加到調用程序中。還有另一種方法,在下面轉載哪一段有說明
二、
如果要被其他語言編寫的調用,比如VB等,則要用_stdcall修飾。
例子:
/* lib .h */
#ifndef LIB_H
#define LIB_H
int _stdcall add(int x,int y);
#endif
/* lib.cpp*/
#include "lib.h"
#include "windows.h"
#include "stdio.h"
// lib.cpp : 采用_stdcall修飾導出函數
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
printf("\nprocess attach of dll");
break;
case DLL_THREAD_ATTACH:
printf("\nthread attach of dll");
break;
case DLL_THREAD_DETACH:
printf("\nthread detach of dll");
break;
case DLL_PROCESS_DETACH:
printf("\nprocess detach of dll");
break;
}
return TRUE;
}
//用任何一種方法輸出函數時,確保用_stdcall調用約定, _stdcall 調用約定是用來調用Win32 API函數。
//_stdcall 以相反的順序(從右到左) 把參數推入棧中
int _stdcall add(int x,int y)
{
return x + y;
}
/* lib def */
; lib.def : 導出DLL函數
LIBRARY LIB
EXPORTS
add @ 1
調用:
#include "stdafx.h"
#include "windows.h"
typedef int (_stdcall * lpAddFun)(int,int);
int main(int argc, char* argv[])
{
HINSTANCE hDll;
lpAddFun addFun;
hDll = LoadLibrary("..\\Debug\\dllTest.dll");
if (hDll != NULL)
{
addFun = (lpAddFun)GetProcAddress(hDll,"add");
//或addFun = (lpAddFun)GetProcAddress(hDll,MAKEINTRESOURCE(1));
//MAKEINTRESOURCE直接使用導出文件中的序號
if(addFun!=NULL)
{
int result = addFun(2,3);
printf("\ncall add in dll:%d",result);
}
FreeLibrary(hDll);
}
return 0;
}
以下部分為轉載:
關於DLL的函數:
動態鏈接庫中定義有兩種函數:導出函數(export function)和內部函數(internal function)。
導出函數可以被其它模塊調用,內部函數在定義它們的DLL程序內部使用。
輸出函數的方法有以下幾種:
1、傳統的方法
在模塊定義文件的EXPORT部分指定要輸入的函數或者變量。語法格式如下:
entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]
其中:
entryname是輸出的函數或者數據被引用的名稱;
internalname同entryname;
@ordinal表示在輸出表中的順序號(index);
NONAME僅僅在按順序號輸出時被使用不使用entryname);
DATA表示輸出的是數據項,使用DLL輸出數據的程序必須聲明該數據項為_declspec(dllimport)。
上述各項中,只有entryname項是必須的,其他可以省略。
對於“C”函數來說,entryname可以等同於函數名;但是對“C++”函數成員函數、非成員函數)
來說,entryname是修飾名。可以從.map映像文件中得到要輸出函數的修飾名,或者使用
DUMPBIN /SYMBOLS得到,然後把它們寫在.def文件的輸出模塊。DUMPBIN是VC提供的一個工具。
如果要輸出一個“C++”類,則把要輸出的數據和成員的修飾名都寫入.def模塊定義文件。
2、在命令行輸出
對鏈接程序LINK指定/EXPORT命令行參數,輸出有關函數。
3、使用MFC提供的修飾符號_declspec(dllexport)
在要輸出的函數、類、數據的聲明前加上_declspec(dllexport)的修飾符,表示輸出。__declspec
(dllexport)在C調用約定、C編譯情況下可以去掉輸出函數名的下劃線前綴。extern "C"使得在C++中
使用C編譯方式成為可能。在“C++”下定義“C”函數,需要加extern “C”關鍵詞。用extern "C"來
指明該函數使用C編譯方式。輸出的“C”函數可以從“C”代碼裡調用。
例如,在一個C++文件中,有如下函數:
extern "C" {void __declspec(dllexport) __cdecl Test(int var);}
其輸出函數名為:Test
MFC提供了一些宏,就有這樣的作用。
AFX_CLASS_IMPORT:__declspec(dllexport)
AFX_API_IMPORT:__declspec(dllexport)
AFX_DATA_IMPORT:__declspec(dllexport)
AFX_CLASS_EXPORT:__declspec(dllexport)
AFX_API_EXPORT:__declspec(dllexport)
AFX_DATA_EXPORT:__declspec(dllexport)
AFX_EXT_CLASS: #ifdef _AFXEXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
AFX_EXT_API:#ifdef _AFXEXT
AFX_API_EXPORT
#else
AFX_API_IMPORT
AFX_EXT_DATA:#ifdef _AFXEXT
AFX_DATA_EXPORT
#else
AFX_DATA_IMPORT
像AFX_EXT_CLASS這樣的宏,如果用於DLL應用程序的實現中,則表示輸出因為_AFX_EXT被定義,通
常是在編譯器的標識參數中指定該選項/D_AFX_EXT);如果用於使用DLL的應用程序中,則表示輸入
_AFX_EXT沒有定義)。
要輸出整個的類,對類使用_declspec(_dllexpot);要輸出類的成員函數,則對該函數使用
_declspec(_dllexport)。如:
class AFX_EXT_CLASS CTextDoc : public CDocument
{
…
}
extern "C" AFX_EXT_API void WINAPI InitMYDLL();
這幾種方法中,最好采用第三種,方便好用;其次是第一種,如果按順序號輸出,調用效率會高些;
最次是第二種。
六、模塊定義文件(.DEF)
模塊定義文件(.DEF)是一個或多個用於描述DLL屬性的模塊語句組成的文本文件,每個DEF文件至少必
須包含以下模塊定義語句:
* 第一個語句必須是LIBRARY語句,指出DLL的名字;
* EXPORTS語句列出被導出函數的名字;將要輸出的函數修飾名羅列在EXPORTS之下,這個名字必須與
定義函數的名字完全一致,如此就得到一個沒有任何修飾的函數名了。
* 可以使用DESCRIPTION語句描述DLL的用途(此句可選);
* ";"對一行進行注釋(可選)。
七、DLL程序和調用其輸出函數的程序的關系
1、dll與進程、線程之間的關系
DLL模塊被映射到調用它的進程的虛擬地址空間。
DLL使用的內存從調用進程的虛擬地址空間分配,只能被該進程的線程所訪問。
DLL的句柄可以被調用進程使用;調用進程的句柄可以被DLL使用。
DLL使用調用進程的棧。
2、關於共享數據段
DLL定義的全局變量可以被調用進程訪問;DLL可以訪問調用進程的全局數據。使用同一DLL的每一個
進程都有自己的DLL全局變量實例。如果多個線程並發訪問同一變量,則需要使用同步機制;對一個
DLL的變量,如果希望每個使用DLL的線程都有自己的值,則應該使用線程局部存儲(TLS,Thread
Local Strorage)。
在程序裡加入預編譯指令,或在開發環境的項目設置裡也可以達到設置數據段屬性的目的.必須給
這些變量賦初值,否則編譯器會把沒有賦初始值的變量放在一個叫未被初始化的數據段中。