用VC++5.0制作DLL經驗一二
北京大唐電信軟件中心
周志杰
---- 本文'一'、'二'兩部分適用于對DLL的基本制作方法已經了解,或手頭有關于DLL制作方法的書籍的讀者.對于初次接觸DLL制作的讀者,建議您先按'三'中的步驟建立一個自己的DLL并在另一個應用程序中成功的調用它之后再閱讀'一'、'二'.
---- DLL可以分為兩個不同的類別:用C/C++(不用對象)編寫的基于API的傳統DLL和基于MFC對象的DLL.
一.兩種類型的比較:
---- 1. 基于MFC的DLL限制在使用MFC的編譯器中.
---- 基于API的DLL可以從一種編譯器移植到另一種編譯器.
---- 2. 基于MFC的DLL可以由制作向導構建框架,制作簡單
---- 基于API的DLL的制作向導只為你制作了一個空的DLL工程,工程的維護和代碼的編寫全部需要手工完成(這一點在VC 6.0中有了改進)
---- 3. 基于MFC的DLL不適用與制作讀取二進制文件的DLL
---- (無法正確讀取與DOS應用程序共享的二進制文件)
---- 基于API的DLL可以正確讀取在DOS環境中創建的二進制文件.
---- 造成該結果的原因其實很簡單:
---- 在讀取二進制文件的過程中通常會使用"結構(體)",基于MFC的DLL要加載MFC,它要求"結構成員位對齊"的比特位是8位,而且你無法方便的通過選中"Project- >Setting- >C/C++"選項卡中的"Code Generation"再修改"Struct member alignment"來使其變為1位(在VC5.0中即使改變了,在編譯時該改變也會被忽略.不過這一不足在VC6.0中已經得到了改進,讀者有興趣的話可參見筆者《在VC6.0中讀取二進制文件的驚喜》一文).
---- 而基于API的DLL則可以通過以上的方法方便的實現.
二.在實際制作與使用中的一點經驗:
---- 1.制作DLL的目的之一是共享資源/代碼.所謂"共享"當然不應該僅僅是幾個VC++制作的應用程序可以使用,但是在與其他編程語言協作時,有些問題是需要注意的.
---- 在制作DLL時,VC++對函數的省缺聲明是"__cedcl",也就是說,如果你在聲明你的函數時不作特殊聲明的話,你制作的DLL將只能被C/C++調用,如果你想用其他開發語言(比如VB5.0)調用它就會報錯,即使調用方法完全正確.
---- 那么該怎么辦呢,你一定已經猜到了---不要用省缺的聲明方式---一個很好的選擇是使用"WINAPI"來聲明你的函數.它可以把你的DLL中的函數聲明成WINDOWS API供其他程序調用(當然也包括C/C++制作的程序).
---- 2.建議你在制作DLL的同時制作包括導出函數原型聲明的.H文件雖然這不是必須的但是若你的DLL是被C/C++調用,.H文件和.LIB文件可以為使用你的DLL的開發人員省去不少精力,當你需要修改/升級你的DLL時更是如此.DLL的C/C++使用者只要在工程中引入.H和.LIB文件可以象使用自己編寫的函數一樣方便的使用DLL中的函數,不必再使用存儲DLL句柄、聲明函數型指針、LoadLibrary、GetProcAddress那樣繁復的調用方式.
---- 3.雖然你不必編寫.DEF文件就能制作出基于API的.DLL文件.但是制作.DEF文件并不難,至少你有捷徑可以走.有一個很簡單的方法你不妨一試:
---- 例如,你用基于API的方法制作了一個DLL工程文件并為其編寫了.CPP和.H文件,你可以保存并關閉該工程,然后在另一個目錄中創建一個與其同名的基于MFC的DLL工程.好了,現在你已經知道怎么做了---將該目錄中的.DEF文件移動過去就可以了.省下的工作就是再次打開基于API的DLL工程,并將.DEF文件加入工程,將你的導出函數的函數名加到EXPORTS之后,再重新編譯工程就OK了.
---- 4.DLL文件的省缺名稱是與工程名一致的(也是在.DEF文件中LIBRARY 之后的名字),不要試圖在制作完畢之后通過簡單的修改.DLL文件的文件名來改變它,這會導致使用該DLL的應用程序錯誤.
三.一個例子:
---- DLL中定義有兩種函數:
---- 導出函數(exportfunction): 可以被其他模塊調用
---- 內部函數(internalfunction): 只能在DLL內部使用
---- 創建一個基于API的DLL.本例只定義了導出函數.
---- 1.在FILE- >NEW- >PROJECTS中選擇"WIN32 Dynamic-Link Library"在Project Name中輸入 "a"按OK
---- 2.在FILE- >NEW- >FILES中選擇C++ SOURCE FILE,在FILE中輸入a.cpp,按OK
---- 在FILE- >NEW- >FILES中選擇TEXT文件,在FILE中輸入a.h,按OK
---- 在FILE- >NEW- >FILES中選擇TEXT文件,在FILE中輸入a.def,按OK
---- 3.源文件:
//---------------------------
//a.cpp
#include < windows >
WINAPI int add(int a,int b)
{ return (a+b);
}
//---------------------------
//a.h
WINAPI int add(int a,int b);
//---------------------------
//a.def
LIBRARY "aaa" ;指出DLL的名字
DESCRIPTION 'aaa Windows Dynamic Link Library'
;描述DLL的用途(此句可選)
EXPORTS add ;導出函數的名字
四.調用DLL的方法:
---- 1.通常我們在調用DLL時所需的DLL文件必須位于以下三個目錄之一:
---- (1)Windows的系統目錄:\windows\system;
---- (2)DOS中path所指出的任何目錄;
---- (3)程序所在的目錄;
---- 同時應注意管理好你的.lib文件和.h和文件
---- 2.建立一個工程,簡單起見可建立一個控制臺應用程序.
---- 3.在工程中引入a.lib:
---- (1)如果你的a.lib放在VC標準的LIB文件夾中.
單擊Project- >Project Settings...
在link選卡的object/library modules中加上a.lib即可
---- (2)如果你的a.lib不是放在VC標準的LIB文件夾中
單擊Project- >Add to Project- >files...
找到a.lib文件,按OK
< pre >
4.//------------------------
//call_a.cpp
#include< stdio.h >
#include "c:/a/a.h"
void main(void)
{ int c=0;
c=add(1,2);
printf("1+2=%d",c);
}
//本程序在VC5.0下調試通過
 |
回復人: zhugw |
2007-4-2 17:57:39 |
Re:Re:用visual c++制作dll全過程
µ¼³öÎĵµÄÚÈÝ: d:\Program Files\GoldenSection Notes\export\Notes.csv
--------------------------------------------------------------------------------
Lesson19 動態庫
2006-7-1 21:26:53
--------------------------------------------------------------------------------
動態鏈接庫
自從微軟推出第一個版本的Windows操作系統以來,動態鏈接庫(DLL)一直是Windows操作系統的基礎。
動態鏈接庫通常都不能直接運行,也不能接收消息。它們是一些獨立的文件,其中包含能被可執行程序或其它DLL調用來完成某項工作的函數。只有在其它模塊調用動態鏈接庫中的函數時,它才發揮作用。
Windows API中的所有函數都包含在DLL中。其中有3個最重要的DLL,Kernel32.dll,它包含用于管理內存、進程和線程的各個函數;User32.dll,它包含用于執行用戶界面任務(如窗口的創建和消息的傳送)的各個函數;GDI32.dll,它包含用于畫圖和顯示文本的各個函數。
C:\WINDOWS\system32
靜態庫和動態庫
靜態庫:函數和數據被編譯進一個二進制文件(通常擴展名為.LIB)。在使用靜態庫的情況下,在編譯鏈接可執行文件時,鏈接器從庫中復制這些函數和數據并把它們和應用程序的其它模塊組合起來創建最終的可執行文件(.EXE文件),不用和應用程序一起發布,但是應用程序比較大,靜態庫發生變化后應用程序必須重新編譯。
在使用動態庫的時候,往往提供兩個文件:一個引入庫和一個DLL。引入庫包含被DLL導出的函數和變量的符號名,DLL包含實際的函數和數據。在編譯鏈接可執行文件時,只需要鏈接引入庫,DLL中的函數代碼和數據并不復制到可執行文件中,在運行的時候,再去加載DLL,訪問DLL中導出的函數。
使用動態鏈接庫的好處
可以采用多種編程語言來編寫。
增強產品的功能。
提供二次開發的平臺。
簡化項目管理。
可以節省磁盤空間和內存。
有助于資源的共享。
有助于實現應用程序的本地化。
動態鏈接庫被多個進程訪問
動態鏈接庫加載的兩種方式
隱式鏈接
顯示加載
D:\Program Files\Microsoft Visual Studio\VC98\Bin\DUMPBIN.EXE -exports dllname.dll 查看動態庫的導出函數
D:\Program Files\Microsoft Visual Studio\VC98\Bin\DUMPBIN.EXE -imports file.exe 查看應用程序使用的dll和函數
D:\Program Files\Microsoft Visual Studio\Tools\DEPENDS.EXE 查看應用程序使用的dll和函數
D:\Program Files\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT 建立vc的環境信息,每次執行只對當前命令窗口有效
@echo off
rem
rem Root of Visual Developer Studio Common files.
set VSCommonDir=D:\PROGRA~1\MICROS~2\Common
rem
rem Root of Visual Developer Studio installed files.
rem
set MSDevDir=D:\PROGRA~1\MICROS~2\Common\msdev98
rem
rem Root of Visual C++ installed files.
rem
set MSVCDir=D:\PROGRA~1\MICROS~2\VC98
rem
rem VcOsDir is used to help create either a Windows 95 or Windows NT specific path.
rem
set VcOsDir=WIN95
if "%OS%" == "Windows_NT" set VcOsDir=WINNT
rem
echo Setting environment for using Microsoft Visual C++ tools.
rem
if "%OS%" == "Windows_NT" set PATH=%MSDevDir%\BIN;%MSVCDir%\BIN;%VSCommonDir%\TOOLS\%VcOsDir%;%VSCommonDir%\TOOLS;%PATH%
if "%OS%" == "" set PATH="%MSDevDir%\BIN";"%MSVCDir%\BIN";"%VSCommonDir%\TOOLS\%VcOsDir%";"%VSCommonDir%\TOOLS";"%windir%\SYSTEM";"%PATH%"
set INCLUDE=%MSVCDir%\ATL\INCLUDE;%MSVCDir%\INCLUDE;%MSVCDir%\MFC\INCLUDE;%INCLUDE%
set LIB=%MSVCDir%\LIB;%MSVCDir%\MFC\LIB;%LIB%
set VcOsDir=
set VSCommonDir=
編寫DllOne.dll
輸出文件
查看導出函數(沒有導出函數)
使用 _declspec(dllexport) 導出函數
產生 .lib文件(引入庫文件)
在使用動態庫的時候,往往提供兩個文件:一個引入庫和一個DLL。引入庫包含被DLL導出的函數和變量的符號名,DLL包含實際的函數和數據。在編譯鏈接可執行文件時,只需要鏈接引入庫,DLL中的函數代碼和數據并不復制到可執行文件中,在運行的時候,再去加載DLL,訪問DLL中導出的函數。
查看導出函數1
ordinal 序號、RVA函數地址
name 由于c++實現函數重載進行名字改編(名字粉碎)
查看導出函數2
上面為導入函數,下面為導出函數,DllOne.dll沒有導入函數,.exe有導入和導出函數
vc調用dll如下(必須被導出的函數才能被客戶端程序調用)
//動態庫代碼如下
//導出Dll函數,需要在每個函數前增加
_declspec(dllexport) int add(int a, int b)
{
return a + b;
}
_declspec(dllexport) int subtract(int a, int b)
{
return a - b ;
}
提供.dll,.lib文件給客戶端程序
release版本文件比較小
編譯前是可以把debug,release目錄刪除的,編譯后重新建立文件夾
可以編譯成debug,release版本
debug版本文件比較大
客戶端調用dll
在工程setting中取得輸入庫文件(當然要本項目能找到.lib文件,拷貝到當前目錄)
如果沒有找到輸入庫文件
如果工程沒有設置.lib文件(報錯)
定義了輸入庫,沒有什么函數將報錯(函數沒有定義)
所以1.要設置工程的.lib文件,2.聲明調用函數
/*---------------------------------------------------------
CVCDDllTestDlg.cpp中聲明如下
動態庫函數聲明
1.函數聲明可以不要extern關鍵字(變量什么必須要extern關鍵字)
int add(int a, int b);
int subtract(int,int);
2.
extern int add(int a, int b);
extern int subtract(int,int);
3.使用導入關鍵字(生成效率更高的代碼,比較常用)
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int,int);
*/
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int,int);
void CVCDllTestDlg::OnButtonAdd()
{
CString str;
str.Format("10 + 10 = %d",add(10,10));
MessageBox(str);
}
void CVCDllTestDlg::OnButtonSubtract()
{
CString str;
str.Format("50 - 10 = %d",subtract(50,10));
AfxMessageBox(str);
}
編譯程序,把動態庫拷貝到相應的目錄(發行軟件時就不需要.lib文件了,但是動態庫是必須的文件,
可以在path中設置,或者拷貝到當前目錄,說明.lib文件只是動態庫文件的函數名稱映射表,鏈接程序要讀此文件完成鏈接工作,
真正的函數和數據在dll中,所以必須要一起發布dll)
調用程序當然可以把dll函數什么放在頭文件中(VCDllTestDlg.h : header file)
調用者也可以把函數什么寫在頭文件中
//DllOne.h
_declspec(dllimport) int add(int a,int b);
_declspec(dllimport) int subtract(int a,int b);
在使用到的地方包含此頭文件
或者動態庫的開發者定義頭文件,調用者自己使用
當前目錄是D:\Project\Study\SunXinC++\Lesson19\VCDllTest
#include "..\DllOne\DllOne.h"
..表示當前目錄的上級目錄
#include "..\\DllOne\\DllOne.h"
兩個\\也是一樣的效果
dll開發者統一提供頭文件(他才知道函數的定義)
//DllOne.h
//沒有定義則定義為導入函數(客戶程序包含此文件時沒有定義則聲明為導入)
#ifndef DLLONE_API
#define DLLONE_API _declspec(dllimport)
#endif
//函數聲明(沒有包含在宏定義中,都要執行)
DLLONE_API int add(int a,int b);
DLLONE_API int subtract(int a,int b);
dll代碼實現定義如下
//動態庫實現文件,已經定義了
#define DLLONE_API _declspec(dllexport)
#include "DllOne.h"
/*DllOne.h展開時
沒有定義則定義為導入函數(客戶程序包含此文件時沒有定義則聲明為導入)
#ifndef DLLONE_API(現在定義了,則什么也不做)
#define DLLONE_API _declspec(dllimport)
#endif
//函數聲明(這里總是要被執行)
DLLONE_API int add(int a,int b);
DLLONE_API int subtract(int a,int b);
*/
//定義時可以不要DLLONE_API,在聲明中已經有了
int add(int a, int b)
{
return a + b;
}
int subtract(int a, int b)
{
return a - b ;
}
/*
DLLONE_API int add(int a, int b)
{
return a + b;
}
DLLONE_API int subtract(int a, int b)
{
return a - b ;
}
*/
調用者包含時#include "..\DllOne\DllOne.h"展開為
導出整個類(訪問權限依然受類的控制)
class DLLONE_API MyClass
{
public:
void pubDisplay(int x, int y); //公有
private:
void PriDisplay(int x, int y); //私有
};
實現
#include <Windows.h>
#include <stdio.h>
void MyClass::pubDisplay(int x, int y)
{
//include Windows.h
HWND hwnd = GetForegroundWindow(); //調用者窗口
HDC hdc = GetDC(hwnd); //得到窗口設備
char buffer[20];
memset(buffer,0,20);
sprintf(buffer,"x=%d,y=%d",x,y);//<stdio.h>格式化
TextOut(hdc,0,0,buffer,strlen(buffer));//輸出
ReleaseDC(hwnd,hdc);
}
void MyClass::PriDisplay(int x, int y)
{
//私有函數不能被調用,即使它被導出了
}
客戶端調用
void CVCDllTestDlg::OnButtonExportClass()
{
MyClass obj;
//obj.PriDisplay(10,10); 私有不能被訪問
obj.pubDisplay(10,10);
}
導出函數(私有函數不能被調用,即使它被導出了)
導入函數
//只是導出需要的函數
class MyClass
{
public:
DLLONE_API void pubDisplay(int x, int y); //公有,導出
//void DLLONE_API pubDisplay(int x, int y);//公有,導出也可以
private:
void PriDisplay(int x, int y); //私有,不導出
}
;
查看導出
沒有導出不能被訪問
按理說沒有導出整個類,不能構造對象,但是c++沒有限制,只要有public的函數被導出,就能調用
但是,如果導出的是private的成員函數,也不能被調用
如果構造函數為private,則即使導出也不能構造對象
class DLLONE_API MyClass
{
public:
void pubDisplay(int x, int y);
private:
MyClass()
{
}
}
;
調用約定
默認的調用約定為 _cdecl,和沒有什么是一樣的
DLLONE_API int _cdecl add(int a,int b); == DLLONE_API int add(int a,int b);
注意導出和導入的聲明要匹配 extern "C" 必須為大寫,有了extern "C"就不能導出類和成員函數,只能導出全局函數
導出 導入
_declspec(dllexport) int _stdcall add(int a,int b) _declspec(dllimport) int _stdcall add(int a,int b)
_declspec(dllexport) int add(int a,int b) _declspec(dllimport) int add(int a,int b)
_declspec(dllexport) int _cdecl add(int a,int b) _declspec(dllimport) int _cdecl add(int a,int b)
extern "C" _declspec(dllexport) int _stdcall add(int a,int b) extern "C"_declspec(dllimport) int _stdcall add(int a,int b)
在c++中,相同的編譯器,不管用那一種聲明,只要導出導入相同都可以調用dll中函數
調用約定 結果
_declspec(dllexport) int add(int a,int b) ?add@@YAHHH@Z
_declspec(dllexport) int _cdecl add(int a,int b) ?add@@YAHHH@Z
extern "C" _declspec(dllexport) int add(int a,int b) add
extern "C" _declspec(dllexport) int _cdecl add(int a,int b) add
_declspec(dllexport) int _stdcall add(int a,int b) ?add@@YGHHH@Z
extern "C" _declspec(dllexport) int _stdcall add(int a,int b) _add@8
dephi,pb,vb等語言必須用標準調用約定,pascal調用約定定義導出函數,__stdcall 函數的參數被從右到左推送到堆棧上,被調用函數在返回之前從堆棧中彈出這些參數。
_stdcall 標準調用約定,即pascal調用約定,或者winapi調用約定
沒有/默認,和_cdecl為c調用約定,由調用者負責壓堆,調用完成出棧
extern "C" 只是解決c++和c的名字改編方案,不是調用約定的改變,如果使用了調用約定,那么名稱依然會改編
此時用模塊定義文件可以解決名字改編問題,同時如果沒有聲明調用約定或者聲明為_cdecl為c調用約定,要給pb,dephi調用
一樣要使用_stdcall標準調用約定,模塊定義文件中依然可以導出類成員函數,只要寫函數名稱就可以了,不一定要導出整個類
當然導出和導入的聲明必須一致
extern "C"的使用不能導出類,因為c語言中沒有class,當不管是按c導出還是c++導出,調用約定都要考慮
_cdecl
See Also
Argument Passing and Naming Conventions | C++ Keywords
Microsoft Specific
This is the default calling convention for C and C++ programs. Because the stack is cleaned up by the caller, it can do vararg functions. The __cdecl calling convention creates larger executables than __stdcall, because it requires each function call to include stack cleanup code. The following list shows the implementation of this calling convention.
Element Implementation
Argument-passing order Right to left
Stack-maintenance responsibility Calling function pops the arguments from the stack
Name-decoration convention Underscore character (_) is prefixed to names
Case-translation convention No case translation performed
Place the __cdecl modifier before a variable or a function name. Because the C naming and calling conventions are the default, the only time you need to use __cdecl is when you have specified the /Gz (stdcall) or /Gr (fastcall) compiler option. The /Gd compiler option forces the __cdecl calling convention.
Example
In the following example, the compiler is instructed to use C naming and calling conventions for the system function:
// Example of the __cdecl keyword on function
_CRTIMP int __cdecl system(const char *);
// Example of the __cdecl keyword on function pointer
typedef BOOL (__cdecl *funcname_ptr)(void * arg1, const char * arg2, DWORD flags, ...);
_stdcall
See Also
Argument Passing and Naming Conventions | C++ Keywords
Microsoft Specific
The __stdcall calling convention is used to call Win32 API functions. The callee cleans the stack, so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function prototype.
return-type __stdcall function-name[(argument-list)]
The following list shows the implementation of this calling convention.
Element Implementation
Argument-passing order Right to left.
Argument-passing convention By value, unless a pointer or reference type is passed.
Stack-maintenance responsibility Called function pops its own arguments from the stack.
Name-decoration convention An underscore (_) is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list. Therefore, the function declared as int func( int a, double b ) is decorated as follows: _func@12
Case-translation convention None
The /Gz compiler option specifies __stdcall for all functions not explicitly declared with a different calling convention.
Functions declared using the __stdcall modifier return values the same way as functions declared using __cdecl.
Example
In the following example, use of __stdcall results in all WINAPI function types being handled as a standard call:
// Example of the __stdcall keyword
#define WINAPI __stdcall
// Example of the __stdcall keyword on function pointer
typedef BOOL (__stdcall *funcname_ptr)(void * arg1, const char * arg2, DWORD flags, ...);
See Also
Argument Passing and Naming Conventions | C++ Keywords
C++ Keywords
See Also
Lexical Conventions | C++ Operators
Keywords are predefined reserved identifiers that have special meanings. They cannot be used as identifiers in your program. The following keywords are reserved for Microsoft C++. Names with leading underscores are Microsoft extensions.
__abstract 2 __alignof __asm __assume
__based __box 2 __cdecl __declspec
__delegate 2 __event __except __fastcall
__finally __forceinline __gc 2 __hook 3
__identifier __if_exists __if_not_exists __inline
__int8 __int16 __int32 __int64
__interface __leave __m64 __m128
__m128d __m128i __multiple_inheritance __nogc 2
__noop __pin 2 __property 2 __raise
__sealed 2 __single_inheritance __stdcall __super
__try_cast 2 __try/__except,__try/__finally __unhook 3 __uuidof
__value 2 __virtual_inheritance __w64 bool
break case catch char
class const const_cast continue
default delete deprecated 1 dllexport 1
dllimport 1 do double dynamic_cast
else enum explicit extern
false float for friend
goto if inline int
long mutable naked 1 namespace
new noinline 1 noreturn 1 nothrow 1
novtable 1 operator private property 1
protected public register reinterpret_cast
return selectany 1 short signed
sizeof static static_cast struct
switch template this thread 1
throw true try typedef
typeid typename union unsigned
using declaration,
using directive uuid 1 virtual void
volatile __wchar_t, wchar_t while ?
Microsoft Specific
In Microsoft C++, identifiers with two leading underscores are reserved for compiler implementations. Therefore, the Microsoft convention is to precede Microsoft-specific keywords with double underscores. These words cannot be used as identifier names.
Microsoft extensions are enabled by default. To ensure that your programs are fully portable, you can disable Microsoft extensions by specifying the ANSI-compatible /Za command-line option (compile for ANSI compatibility) during compilation. When you do this, Microsoft-specific keywords are disabled.
When Microsoft extensions are enabled, you can use the Microsoft-specific keywords in your programs. For ANSI compliance, these keywords are prefaced by a double underscore. For backward compatibility, single-underscore versions of all the double-underscored keywords except __except, __finally, __leave, and __try are supported. In addition, __cdecl is available with no leading underscore.
The __asm keyword replaces C++ asm syntax. asm is reserved for compatibility with other C++ implementations, but not implemented. Use __asm.
The __based keyword has limited uses for 32-bit target compilations.
函數必須導出(_declspec(dllexport) 或者模塊定義文件.def)
在函數前必須有_stdcall關鍵字
_declspec(dllexport) int add(int a,int b) ?add@@YAHHH@Z
_declspec(dllexport) int _cdecl add(int a,int b) ?add@@YAHHH@Z
_declspec(dllexport) int _stdcall add(int a,int b)?add@@YGHHH@Z
函數名稱都有?......pb中認為是非法字符
extern "C" _declspec(dllexport) int add(int a,int b) add
Function int add(int a, int b) Library "DllOne.dll"
extern "C" _declspec(dllexport) int _cdecl add(int a,int b) add
Function int add(int a, int b) Library "DllOne.dll"
extern "C" _declspec(dllexport) int _stdcall add(int a,int b) _add@8
add可以
subtract錯誤
所以必須要用stdcall調用約定
_declspec(dllexport) int _stdcall add(int a,int b) ?add@@YGHHH@Z
但是函數名稱被改編了
extern "C" _declspec(dllexport) int _stdcall add(int a,int b) _add@8
可以調用,但是前臺調用函數不方便
怎么解決?--模塊定義文件.def
動態庫定義
int _stdcall add(int a, int b)
{
return a + b;
}
int _stdcall subtract(int a, int b)
{
return a - b;
}
模塊定義文件(使用記事本定義DllTwo.def并且導入到工程中)
LIBRARY DllTwo
EXPORTS
add
subtract
Function int add(int a, int b) Library "DllTwo.dll"http://stdcall .def
Function int subtract(int a, int b) Library "DllTwo.dll"http://stdcall .def
int a,b,Result
a = 20
b = 10
Result = add(a,b) //stdcall .def ok
MessageBox('提示a+b',String(a) + "+" + String(b) + " = " + String(result))
Result = subtract(a,b) //stdcall .def ok
MessageBox('提示a-b',String(a) + "-" + String(b) + " = " + String(result))
使用模塊定義文件也導出了函數(_stdcall導出,函數名稱沒有改編,是我想要的結果,pb可以調用了,但是c++怎么調用???)
copy模塊定義文件到工程目錄,工程設置DllTwo.lib
聲明函數(不是_stdcall導入),不能調用
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int,int);
void CVCDllTestDlg::OnButtonAdd()
{
CString str;
str.Format("10 + 10 = %d",add(10,10));
MessageBox(str);
}
void CVCDllTestDlg::OnButtonSubtract()
{
CString str;
str.Format("50 - 10 = %d",subtract(50,10));
AfxMessageBox(str);
}
調用約定必須要一致才能調用(當然也可以提供.h給客戶端使用)
_declspec(dllimport) int _stdcall add(int a, int b);
_declspec(dllimport) int _stdcall subtract(int,int);
模塊定義文件怎么導出類(????),目前不知道,可以用模塊定義文件導出全局函數,dllexport導出類結合使用
1.def
LIBRARY DllTwo
EXPORTS
add
subtract
2.dll
//.def中
int _stdcall add(int a, int b)
{
return a + b;
}
//.def中
int _stdcall subtract(int a, int b)
{
return a - b;
}
//_declspec(dllexport)
class _declspec(dllexport) MyClass
{
public:
void _stdcall display();
};
void MyClass::display()
{
}
3.使用(別忘了.lib文件)
_declspec(dllimport) int _stdcall add(int a, int b);
_declspec(dllimport) int _stdcall subtract(int,int);
class _declspec(dllimport) MyClass
{
public:
void _stdcall display();
};
隱式鏈接
將可執行文件鏈接到 DLL
為隱式鏈接到 DLL,可執行文件必須從 DLL 的提供程序獲取下列各項:
包含導出函數和/或 C++ 類的聲明的頭文件(.H 文件)。
要鏈接的導入庫(.LIB files)。(生成 DLL 時鏈接器創建導入庫。)
實際的 DLL(.DLL 文件)。
使用 DLL 的可執行文件必須包括頭文件,此頭文件包含每個源文件中的導出函數(或 C++ 類),而這些源文件包含對導出函數的調用。從編碼的角度講,導出函數的函數調用與任何其他函數調用一樣。若要生成調用可執行文件,必須與導入庫鏈接。如果使用的是外部生成文件,請指定導入庫的文件名,此導入庫中列出了要鏈接到的其他對象 (.OBJ) 文件或庫。
操作系統在加載調用可執行文件時,必須能夠定位 .DLL 文件。
顯式鏈接
將可執行文件鏈接到 DLL在顯式鏈接下,應用程序必須進行函數調用以在運行時顯式加載 DLL。為顯式鏈接到 DLL,應用程序必須:
調用 LoadLibrary(或相似的函數)以加載 DLL 和獲取模塊句柄。
調用 GetProcAddress,以獲取指向應用程序要調用的每個導出函數的函數指針。由于應用程序是通過指針調用 DLL 的函數,編譯器不生成外部引用,故無需與導入庫鏈接。
使用完 DLL 后調用 FreeLibrary。
例如:
typedef UINT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT);
...
HINSTANCE hDLL; // Handle to DLL
LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer
DWORD dwParam1;
UINT uParam2, uReturnVal;
hDLL = LoadLibrary("MyDLL");
if (hDLL != NULL)
{
lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL,
"DLLFunc1");
if (!lpfnDllFunc1)
{
// handle the error
FreeLibrary(hDLL);
return SOME_ERROR_CODE;
}
else
{
// call the function
uReturnVal = lpfnDllFunc1(dwParam1, uParam2);
}
}
LoadLibrary
進程調用 LoadLibrary(或 AfxLoadLibrary)以顯式鏈接到 DLL。如果成功,函數將指定的 DLL 映射到調用進程的地址空間中并返回此 DLL 的句柄,該句柄可與用于顯式鏈接的其他函數(如 GetProcAddress 和 FreeLibrary)一起使用。
LoadLibrary 嘗試使用用于隱式鏈接的同一搜索序列來定位 DLL。如果系統無法找到 DLL 或者入口點函數返回 FALSE,LoadLibrary 將返回 NULL。如果對 LoadLibrary 的調用所指定的 DLL 模塊已映射到調用進程的地址空間中,則函數僅返回 DLL 的句柄并遞增模塊的引用數。
如果 DLL 有入口點函數,則操作系統在調用 LoadLibrary 的進程上下文中調用此函數。如果由于以前調用了 LoadLibrary 但沒有相應地調用 FreeLibrary 函數而導致 DLL 已經附加到進程,則不會調用此入口點函數。
加載擴展 DLL 的 MFC 應用程序應使用 AfxLoadLibrary 而不是 LoadLibrary。AfxLoadLibrary 在調用 LoadLibrary 之前處理線程同步。AfxLoadLibrary 的接口(函數原型)與 LoadLibrary 相同。
如果出于某種原因 Windows 無法加載 DLL,進程可以嘗試從錯誤恢復。例如,進程可通知用戶所發生的錯誤,并讓用戶指定 DLL 的其他路徑。
安全說明???如果代碼將在 Windows NT 4 或 Windows 2000 上運行,請務必要指定任何 DLL 的完整路徑名。
GetProcAddress
顯式鏈接到 DLL 的進程調用 GetProcAddress 來獲取 DLL 導出函數的地址。使用返回的函數指針調用 DLL 函數。GetProcAddress 將(由 LoadLibrary、AfxLoadLibrary 或 GetModuleHandle 返回的)DLL 模塊句柄和要調用的函數名或函數的導出序號用作參數。
由于是通過指針調用 DLL 函數并且沒有編譯時類型檢查,需確保函數的參數是正確的,以便不會超出在堆棧上分配的內存和不會導致訪問沖突。確保類型安全的一種方法是查看導出函數的函數原型,并創建函數指針的匹配 typedef。例如:
typedef UINT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT);
...
HINSTANCE hDLL; // Handle to DLL
LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer
DWORD dwParam1;
UINT uParam2, uReturnVal;
hDLL = LoadLibrary("MyDLL");
if (hDLL != NULL)
{
lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL,
"DLLFunc1");
if (!lpfnDllFunc1)
{
// handle the error
FreeLibrary(hDLL);
return SOME_ERROR_CODE;
}
else
{
// call the function
uReturnVal = lpfnDllFunc1(dwParam1, uParam2);
}
}
調用 GetProcAddress 時指定所需函數的方式取決于 DLL 的生成方式。
僅當要鏈接到的 DLL 是用模塊定義 (.DEF) 文件生成的,并且序號在 DLL 的 .DEF 文件的 EXPORTS 節中與函數一起列出時,才能獲取導出序號。如果 DLL 具有許多導出函數,則相對于使用函數名,使用導出序號調用 GetProcAddress 的速度稍快一些,因為導出序號是 DLL 導出表的索引。使用導出序號,GetProcAddress 可直接定位函數,而不是將指定名稱與 DLL 導出表中的函數名進行比較。但是,僅當有權控制 .DEF 文件中導出函數的序號分配時,才應使用導出序號調用 GetProcAddress。
FreeLibrary
The FreeLibrary function decrements the reference count of the loaded dynamic-link library (DLL). When the reference count reaches zero, the module is unmapped from the address space of the calling process and the handle is no longer valid.
BOOL FreeLibrary(
HMODULE hModule
);
Parameters
hModule [in] Handle to the loaded DLL module. The LoadLibrary or GetModuleHandle function returns this handle.
Return Values
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Windows 用來定位 DLL 的搜索路徑
請參見
DLL
在顯式鏈接和隱式鏈接下,Windows 都首先搜索一組預安裝的 DLL,如性能庫 (KERNEL32.DLL) 和安全庫 (USER32.DLL)。Windows 然后按下列順序搜索 DLL:
當前進程的可執行模塊所在的目錄。
當前目錄。
Windows 系統目錄。GetSystemDirectory 函數檢索此目錄的路徑。
Windows 目錄。GetWindowsDirectory 函數檢索此目錄的路徑。
PATH 環境變量中列出的目錄。
注意未使用 LIBPATH 環境變量。
//動態加載dll,工程中去掉DllTwo.lib
void CVCDllTestDlg::OnButtonAdd()
{
//不區分大小寫,.dll可以省略
HMODULE hmodule = LoadLibrary("DllTwo.dll");
if (hmodule == NULL)
{
MessageBox("加載dll錯誤!");
return;
}
/*windef.h
#define CALLBACK _stdcall
#define WINAPI _stdcall
#define WINAPIV _cdecl
#define APIENTRY WINAPI
#define APIPRIVATE _stdcall
#define PASCAL _stdcall
*/
//導出使用_stdcall,調用時必須匹配,也可以使用上面的宏定義
typedef int (_stdcall*TypeAddProc)(int a, int b);
TypeAddProc add = NULL;
//函數名稱大小寫敏感,必須轉換為函數指針類型
add = (TypeAddProc)GetProcAddress(hmodule,"add");
if (add == NULL)
{ //釋放
FreeLibrary(hmodule);
MessageBox("獲得函數地址錯誤!");
return;
}
CString str;
str.Format("10 + 10 = %d",add(10,10));
MessageBox(str);
//釋放
FreeLibrary(hmodule);
}
調用是也可以直接用導出的發生名字改編的名稱調用
ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"?add@@YAHHH@Z");
也可以用序號調MAKEINTRESOURCE
ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
FARPROC GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName
);
The MAKEINTRESOURCE macro converts an integer value to a resource type compatible with the resource-management functions. This macro is used in place of a string containing the name of the resource.
LPTSTR MAKEINTRESOURCE
(
WORD wInteger
);
一個完整的導出文件(def文件也可以導出類)
//.def中
int _stdcall add(int a, int b)
{
return a + b;
}
//.def中
int _stdcall subtract(int a, int b)
{
return a - b;
}
//_declspec(dllexport)
/*class _declspec(dllexport) MyClass
{
public:
int _stdcall display();
};
int MyClass::display()
{
return 100;
}
*/
//在.def中導出
class MyClass
{
public:
int _stdcall displayOne();
int _stdcall displayTwo();
int _stdcall displayThree();
};
int MyClass::displayOne()
{
return 100;
}
int MyClass::displayTwo()
{
return 200;
}
int MyClass::displayThree()
{
return 300;
}
導出模塊定義文件
LIBRARY DllTwo
EXPORTS
add
subtract
displayOne
displayTwo
displayThree
產生mapfile
模塊定義文件也可以使用mapfile中名稱
class MyClass
{
public:
int _stdcall displayOne();
int _stdcall displayTwo();
int _stdcall displayThree();
};
int MyClass::displayOne()
{
return 1;
}
int MyClass::displayTwo()
{
return 2;
}
int MyClass::displayThree()
{
return 3;
}
導出
LIBRARY DllTwo
EXPORTS
add
subtract
?displayOne@MyClass@@QAGHXZ
displayTwo
?displayThree@MyClass@@QAGHXZ
調用
_declspec(dllimport) int _stdcall add(int a, int b);
_declspec(dllimport) int _stdcall subtract(int a, int b);
class _declspec(dllimport) MyClass
{
public:
int _stdcall displayOne();
int _stdcall displayTwo();
int _stdcall displayThree();
};
void CVCDllTestDlg::OnButtonAdd()
{
CString str;
str.Format("10 + 10 = %d",add(10,10));
MessageBox(str);
}
void CVCDllTestDlg::OnButtonSubtract()
{
CString str;
str.Format("50 - 10 = %d",subtract(50,10));
AfxMessageBox(str);
}
void CVCDllTestDlg::OnButtonExportClass()
{
MyClass obj ;
CString str;
str.Format(".def導出類成員函數displayOne = %d",obj.displayOne());
AfxMessageBox(str);
str.Format(".def導出類成員函數displayTwo = %d",obj.displayTwo());
AfxMessageBox(str);
str.Format(".def導出類成員函數displayThree = %d",obj.displayThree());
AfxMessageBox(str);
}
參見
MSDN DLL介紹
Visual C++ 概念:添加功能
DLL
動態鏈接庫 (DLL) 是作為共享函數庫的可執行文件。動態鏈接提供了一種方法,使進程可以調用不屬于其可執行代碼的函數。函數的可執行代碼位于一個 DLL 中,該 DLL 包含一個或多個已被編譯、鏈接并與使用它們的進程分開存儲的函數。DLL 還有助于共享數據和資源。多個應用程序可同時訪問內存中單個 DLL 副本的內容。
動態鏈接與靜態鏈接的不同之處在于:動態鏈接允許可執行模塊(.dll 文件或 .exe 文件)僅包含在運行時定位 DLL 函數的可執行代碼所需的信息。在靜態鏈接中,鏈接器從靜態鏈接庫獲取所有被引用的函數,并將庫同代碼一起放到可執行文件中。
使用動態鏈接代替靜態鏈接有若干優點。DLL 節省內存,減少交換操作,節省磁盤空間,更易于升級,提供售后支持,提供擴展 MFC 庫類的機制,支持多語言程序,并使國際版本的創建輕松完成。
本文章族提供有關編程 DLL 的詳細信息。
本節內容
應用程序和 DLL 之間的差異 描述應用程序和 DLL 之間的基本差異。
使用 DLL 的優點 描述動態鏈接的優點。
DLL 類型 討論如何決定在項目中使用何種 DLL。
Win16 DLL 與 Win32 DLL 之間的差異 描述 Win16 DLL 和 Win32 DLL 之間的更改。
DLL 常見問題 提供有關 DLL 的常見問題解答。
將可執行文件鏈接到 DLL 描述與 DLL 的顯式鏈接和隱式鏈接。
初始化 DLL 討論當 DLL 加載時必須執行的 DLL 初始化代碼(如分配內存)。
運行時庫行為 描述運行時庫如何執行 DLL 啟動序列。
LoadLibrary 和 AfxLoadLibrary 討論如何使用 LoadLibrary 和 AfxLoadLibrary 顯式鏈接到 DLL。
GetProcAddress 討論如何使用 GetProcAddress 獲取 DLL 中導出函數的地址。
FreeLibrary 和 AfxFreeLibrary 討論當不再需要 DLL 模塊時如何使用 FreeLibrary 和 AfxFreeLibrary。
Windows 用來定位 DLL 的搜索路徑 描述 Windows 操作系統用來定位系統上的 DLL 的搜索路徑。
動態鏈接到 MFC 的規則 DLL 的模塊狀態 描述動態鏈接到 MFC 的規則 DLL 的模塊狀態。
擴展 DLL 解釋通常實現從現有 Microsoft 基礎類庫類派生的可重用類的 DLL。
創建純資源 DLL 討論只包含資源(如圖標、位圖、字符串和對話框等)的純資源 DLL。
MFC 應用程序中的本地化資源:附屬 DLL 提供對附屬 DLL 的增強支持,該功能有助于創建針對多種語言進行本地化的應用程序。 導入和導出 描述如何將公共符號導入應用程序或從 DLL 導出函數。
Active 技術和 DLL 使對象服務器得以在 DLL 內完全實現。
DLL 中的自動化 描述“MFC DLL 向導”中的“自動化”選項提供的內容。
MFC DLL 命名約定 討論 MFC 中包含的 DLL 和庫如何遵循結構化命名約定。
從 Visual Basic 應用程序調用 DLL 函數 描述如何從 Visual Basic 應用程序中調用 DLL 函數。
相關章節
將 MFC 用作 DLL 的一部分 描述規則 DLL,它使您可以將 MFC 庫作為 Windows 動態鏈接庫的一部分來使用。 MFC 的 DLL 版本 描述如何將 MFCxx.DLL 和 MFCxxD.DLL(其中 x 是 MFC 版本號)共享動態鏈接庫用于 MFC 應用程序和擴展 DLL。 添加功能 提供有關下列內容的主題鏈接:描述有關 Visual C++ 庫的概念信息和討論各種編碼技術和方法。