DLL
静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
对动态链接库,我们还需建立如下概念:
(1)DLL 的编制与具体的编程语言及编译器无关
(2)动态链接库随处可见
kernel32.dll中的函数主要处理内存管理和进程调度;
user32.dll中的函数主要控制用户界面; MessageBox
gdi32.dll中的函数则负责图形方面的操作。
(3)VC动态链接库的分类
VC++支持三种DLL,Non-MFC DLL、MFC Regular DLL、MFC Extension DLL。
非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;
MFC规则DLL 包含一个继承自CWinApp的类,但其无消息循环;
MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。
静态链接库
对静态链接库的讲解不是本文的重点,但是在具体讲解DLL之前,通过一个静态链接库的例子可以快速地帮助我们建立“库”的概念。
new一个名称为libTest的static library工程,并新建lib.h和lib.cpp两个文件,lib.h和lib.cpp的源代码如下:
//文件:lib.h
#ifndef
LIB_H
#define
LIB_H
extern
"C" int add(int x,int y);//声明为C编译、连接方式的外部函数
#endif
//文件:lib.cpp
#include
"lib.h"
int
add(int x,int y)
{
return x +
y;
}
编译这个工程就得到了一个.lib文件,这个文件就是一个函数库,它提供了add的功能。将头文件和.lib文件提交给用户后,用户就可以直接使用其中的add函数了。标准Turbo C2.0中的C库函数(我们用来的scanf、printf、memcpy、strcpy等)就来自这种静态库。
在libTest工程所在的工作区内new一个libCall工程。libCall工程仅包含一个main.cpp文件:
#include <stdio.h>
#include
"..\lib.h"
#pragma
comment( lib, "..\\debug\\libTest.lib" )
//指定与静态库一起连接
int
main(int argc, char* argv[])
{
printf( "2 + 3
= %d", add( 2, 3 )
);
}
静态链接库的调用就是这么简单,或许我们每天都在用。#pragma comment( lib , "..\\debug\\libTest.lib" )的意思是指本文件生成的.obj文件应与libTest.lib一起连接。如果不用#pragma comment指定,则可以直接在VC++中设置添加的libTest.lib文件的路径。
库的调试与查看
通常有比上述做法更好的调试途径,那就是将库工程和应用工程(调用库的工程)放置在同一VC工作区,只对应用工程进行调试,在应用工程调用库中函数的语句处设置断点,执行后按下F11,这样就单步进入了库中的函数。
非MFC DLL
Win32 Dynamic-Link Library:
/* 文件名:lib.h*/
#ifndef
LIB_H
#define
LIB_H
extern
"C" int __declspec(dllexport)add(int x,
int y);
#endif
/* 文件名:lib.cpp*/
#include
"lib.h"
int
add(int x, int y)
{
return x +
y; }
#include
<stdio.h>
#include
<windows.h>
typedef
int(*lpAddFun)(int, int); //宏定义函数指针类型
int
main(int argc, char *argv[])
{
HINSTANCE
hDll;
//DLL句柄
lpAddFun
addFun; //函数指针
hDll =
LoadLibrary("..\\Debug\\dllTest.dll");
if
(hDll !=
NULL)
{
addFun
= (lpAddFun)GetProcAddress(hDll, "add");
if
(addFun != NULL)
{
int result = addFun(2,
3);
printf("%d",
result);
}
FreeLibrary(hDll);
}
return
0;
}
声明导出函数
DLL中导出函数的声明有两种方式:
在函数声明中加上__declspec(dllexport)
另外一种方式是采用模块定义(.def) 文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。
; lib.def : 导出DLL函数
LIBRARY dllTest
EXPORTS
add @ 1
//可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n
DLL的调用方式
LoadLibrary-GetProcAddress-FreeLibrary
“DLL加载-DLL函数地址获取-DLL释放” 这种调用方式称为DLL的动态调用。
动态调用方式的特点是完全由编程者用 API 函数加载和卸载 DLL,程序员可以决定 DLL 文件何时加载或不加载,显式链接在运行时决定加载哪个 DLL 文件。
静态调用方式简单实用,将编译dllTest工程所生成的.lib和.dll文件拷入dllCall工程所在的路径,dllCall执行下列代码:
#pragma comment(lib,"dllTest.lib")
//.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息
extern
"C" __declspec(dllimport) add(int x,int
y);
int
main(int argc, char* argv[])
{
int result =
add(2,3);
printf("%d",result);
return
0; }
(1)告诉编译器与DLL相对应的.lib文件所在的路径及文件名,#pragma comment(lib,"dllTest.lib")就是起这个作用。
(2)声明导入函数,extern "C" __declspec(dllimport) add(int x,int y)语句中的__declspec(dllimport)发挥这个作用。
静态调用方式不再需要使用系统API来加载、卸载DLL以及获取DLL中导出函数的地址。这是因为,当程序员通过静态链接方式编译生成应用程序时,应用程序中调用的与.lib文件中导出符号相匹配的函数符号将进入到生成的EXE 文件中,.lib文件中所包含的与之对应的DLL文件的文件名也被编译器存储在 EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows将根据这些信息发现并加载DLL,然后通过符号名实现对DLL 函数的动态链接。这样,EXE将能直接通过函数名调用DLL的输出函数,就象调用程序内部的其他函数一样。
DllMain函数
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:
}
return TRUE;
}
DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DLLMain函数也被调用。ul_reason_for_call指明了被调用的原因。原因共有4种,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和 THREAD_DETACH。
APIENTRY被定义为__stdcall,它意味着这个函数以标准Pascal的方式进行调用,也就是WINAPI方式;
进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识,只有在特定的进程内部有效,句柄代表了DLL模块在进程虚拟空间中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同可以替换使用,这就是函数参数hModule的来历。
hDll =
LoadLibrary("..\\Debug\\dllTest.dll");
if (hDll != NULL)
{
addFun =
(lpAddFun)GetProcAddress(hDll,
MAKEINTRESOURCE(1));//MAKEINTRESOURCE使用导出文件中的序号
if (addFun !=
NULL)
{
int result = addFun(2, 3);
printf("\ncall add in
dll:%d", result);}
FreeLibrary(hDll);
}直接通过.def文件中为add函数指定的顺序号访问add函数
__stdcall约定
如果通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为__stdcall方式,WINAPI都采用这种方式,而C/C++缺省的调用方式却为__cdecl。_若采用C编译方式(在C++中需将函数声明为extern "C"),__stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数;而 __cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
#define CALLBACK
__stdcall //这就是传说中的回调函数
#define WINAPI __stdcall //这就是传说中的WINAPI
#define WINAPIV __cdecl
#define APIENTRY WINAPI //DllMain的入口就在这里
#define APIPRIVATE
__stdcall
#define PASCAL
__stdcall
在lib.h中,应这样声明add函数:
int __stdcall add(int x,
int y);
typedef int(__stdcall
*lpAddFun)(int, int);
若在lib.h中将函数声明为__stdcall调用,而应用工程中仍使用typedef int (* lpAddFun)(int,int),运行时将发生错误(因为类型不匹配,在应用工程中仍然是缺省的__cdecl调用)。那段话实际上已经给出了错误的原因,即“This
is usually a result of…”。
DLL导出变量
DLL定义的全局变量可以被调用进程访问;DLL也可以访问调用进程的全局数据,我们来看看在应用工程中引用DLL中变量的例子
/* 文件名:lib.h*/
#ifndef
LIB_H
#define LIB_H
extern int
dllGlobalVar;
#endif
/* 文件名:lib.cpp */
#include
"lib.h" #include
<windows.h> int
dllGlobalVar;
BOOL
APIENTRY DllMain(HANDLE hModule, DWORD
ul_reason_for_call, LPVOID
lpReserved)
{ switch
(ul_reason_for_call)
{
DLL_PROCESS_ATTACH:DLL_THREAD_ATTACH:DLL_THREAD_DETACH:DLL_PROCESS_DETACH: }
return TRUE;}
/*LIBRARY
"dllTest"*/
EXPORTS
dllGlobalVar
CONSTANT; 或dllGlobalVar
DATA GetGlobalVar
#include
<stdio.h>
#pragma
comment(lib,"dllTest.lib")
extern int dllGlobalVar;
int main(int argc, char
*argv[])
{ printf("%d ",
*(int*)dllGlobalVar); *(int*)dllGlobalVar =
1; printf("%d ",
*(int*)dllGlobalVar); return
0; }
* (int*)dllGlobalVar采用这种方式引用DLL全局变量时。千万不要进行这样的赋值操作:dllGlobalVar = 1,其结果是dllGlobalVar指针的内容发生变化,程序中以后再也引用不到DLL中的全局变量了。
#include
<stdio.h>
#pragma
comment(lib,"dllTest.lib")
extern int
_declspec(dllimport) dllGlobalVar; //用_declspec(dllimport)导入
int
main(int argc, char *argv[])
{ printf("%d
",
dllGlobalVar); dllGlobalVar =
1; printf("%d
", dllGlobalVar); return
0; }
通过_declspec(dllimport)方式导入的就是DLL中全局变量本身而不再是其地址了,笔者建议在一切可能的情况下都使用这种方式。
DLL导出类
DLL中定义的类可以在应用工程中使用。
/*point.h*/
#ifndef
POINT_H
#define
POINT_H
#ifdef
DLL_FILE
class
_declspec(dllexport) point //导出类point
#else
class
_declspec(dllimport) point //导入类point
#endif
{public:float
y;float x;point();point(float x_coordinate, float
y_coordinate);};
#endif
/*point.cpp*/
#ifndef
DLL_FILE
#define
DLL_FILE
#endif
#include
"point.h"
//类point的缺省构造函数
point::point()
{x
= 0.0; y =
0.0;}
//类point的构造函数
point::point(float
x_coordinate, float y_coordinate)
{x
=
x_coordinate; y = y_coordinate;}
类的引用:
#include
"..\circle.h"
#pragma
comment(lib,"dllTest.lib");
int
main(int argc, char *argv[])
{circle
c; point
p(2.0,
2.0); c.SetCentre(p); c.SetRadius(1.0);
printf("area:%f
girth:%f", c.GetArea(), c.GetGirth());
return
0;}
由此可见,应用工程中几乎可以看到DLL中的一切,包括函数、变量以及类,这就是DLL所要提供的强大能力。只要DLL释放这些接口,应用程序使用它就将如同使用本工程中的程序一样!
MFC规则DLL
(1)静态链接到MFC 的规则DLL
静态链接到MFC的规则DLL与MFC库(包括MFC扩展 DLL)静态链接,将MFC库的代码直接生成在.dll文件中。在调用这种DLL的接口时,MFC使用DLL的资源。因此,在静态链接到MFC 的规则DLL中不需要进行模块状态的切换。使用这种方法生成的规则DLL其程序较大,也可能包含重复的代码。
(2)动态链接到MFC 的规则DLL
动态链接到MFC 的规则DLL 可以和使用它的可执行文件同时动态链接到 MFC DLL 和任何MFC扩展 DLL。在使用了MFC共享库的时候,MFC使用主应用程序的资源句柄来加载资源模板。这样,当DLL和应用程序中存在相同ID的资源时(即所谓的资源重复问题),系统可能不能获得正确的资源。因此,对于共享MFC DLL的规则DLL,我们必须进行模块切换以使得MFC能够找到正确的资源模板。
MFC规则DLL的创建
我们来一步步讲述使用MFC向导创建MFC规则DLL的过程,选择project的类型为MFC
AppWizard(dll)。
一个简单的MFC规则DLL
属于静态链接到MFC 的规则DLL。在DLL中添加对话框的方式与在MFC应用程序中是一样的。
第一组文件:CWinApp继承类的声明与实现
/*
RegularDll.h : main header file for the REGULARDLL
DLL*/
#if
!defined(AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_)
#define
AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_
#if
_MSC_VER > 1000
#pragma
once
#endif
// _MSC_VER > 1000
#ifndef
__AFXWIN_H__
#error
include 'stdafx.h' before including this file for
PCH
#endif
#include
"resource.h" // main symbols
class
CRegularDllApp : public CWinApp
{ public: CRegularDllApp(); DECLARE_MESSAGE_MAP() };
#endif
/*
RegularDll.cpp : Defines the initialization routines
for the DLL.*/
#include
"stdafx.h"
#include
"RegularDll.h"
#ifdef
_DEBUG
#define
new DEBUG_NEW
#undef
THIS_FILE
static
char THIS_FILE[] = __FILE__;
#endif
BEGIN_MESSAGE_MAP(CRegularDllApp,
CWinApp)
END_MESSAGE_MAP()
CRegularDllApp::CRegularDllApp(){}
CRegularDllApp
theApp;
在这一组文件中定义了一个继承自CWinApp的类CRegularDllApp,并同时定义了其的一个实例theApp。但是MFC规则DLL并不是MFC应用程序,它所继承自CWinApp的类不包含消息循环。这是因为,MFC规则DLL不包含CWinApp::Run 机制,主消息泵仍然由应用程序拥有。如果DLL 生成无模式对话框或有自己的主框架窗口,则应用程序的主消息泵必须调用从DLL 导出的函数来调用PreTranslateMessage成员函数。
另外,MFC规则DLL与MFC 应用程序中一样,需要将所有 DLL中元素的初始化放到InitInstance 成员函数中。
第二组文件自定义对话框类声明及实现
这一部分的编程与一般的应用程序根本没有什么不同,我们照样可以利用MFC类向导来自动为对话框上的控件添加事件。MFC类向导照样会生成类似ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton)的消息映射宏。
第三组文件 DLL中的资源文件
#define
IDD_DLL_DIALOG 1000
#define
IDC_HELLO_BUTTON 1000
在MFC规则DLL中使用资源也与在MFC应用程序中使用资源没有什么不同。
第四组文件 MFC规则DLL接口函数
#include
"StdAfx.h"
#include "DllDialog.h"
extern "C"
__declspec(dllexport) void
ShowDlg(void)
{
CDllDialog
dllDialog;
dllDialog.DoModal();
}
与非MFC DLL完全相同,我们可以使用__declspec(dllexport)声明或在.def中引出的方式导出MFC规则DLL中的接口。
MFC规则DLL的调用
下面是“调用DLL”按钮单击事件的消息处理函数:
void
CRegularDllCallDlg::OnCalldllButton()
{
typedef
void (*lpFun)(void);
HINSTANCE
hDll; //DLL句柄
hDll
= LoadLibrary("RegularDll.dll");
if
(NULL==hDll)
{ MessageBox("DLL加载失败"); }
lpFun
addFun; //函数指针
lpFun
pShowDlg =
(lpFun)GetProcAddress(hDll,"ShowDlg");
if
(NULL==pShowDlg)
{ MessageBox("DLL中函数寻找失败"); }
pShowDlg();
}
我们照样可以在EXE程序中隐式调用MFC规则DLL,只需要将DLL工程生成的.lib文件和.dll文件拷入当前工程所在的目录
RegularDllCallDlg.cpp文件的顶部添加:
#pragma
comment(lib,"RegularDll.lib")
void
ShowDlg(void);
void
CRegularDllCallDlg::OnCalldllButton()
{ ShowDlg(); }
共享MFC DLL的规则DLL的模块切换
应用程序进程本身及其调用的每个DLL模块都具有一个全局唯一的HINSTANCE句柄,它们代表了DLL或EXE模块在进程虚拟空间中的起始地址。进程本身的模块句柄一般为0x400000,而DLL模块的缺省句柄为0x10000000。如果程序同时加载了多个DLL,则每个DLL模块都会有不同的 HINSTANCE。应用程序在加载DLL时对其进行了重定位。
共享MFC DLL(或MFC扩展DLL)的规则DLL涉及到HINSTANCE句柄问题,HINSTANCE句柄对于加载资源特别重要。EXE和DLL都有其自己的资源,而且这些资源的ID可能重复,应用程序需要通过资源模块的切换来找到正确的资源。如果应用程序需要来自于DLL的资源,就应将资源模块句柄指定为 DLL的模块句柄;如果需要EXE文件中包含的资源,就应将资源模块句柄指定为EXE的模块句柄。
尤其值得特别注意,在DLL和EXE中对话框使用了相同的资源ID=2000,在DLL和EXE工程的resource.h中分别有如下的宏:
//DLL中对话框的ID #define
IDD_DLL_DIALOG 2000
//EXE中对话框的ID #define
IDD_EXE_DIALOG 2000
#include
"StdAfx.h"
#include
"SharedDll.h"
void
ShowDlg(void)
{
CDialog
dlg(IDD_DLL_DIALOG);
dlg.DoModal(); }
而为应用工程主对话框的“调用DLL”的单击事件添加如下消息处理函数:
void
CSharedDllCallDlg::OnCalldllButton()
{ ShowDlg(); }
产生这个问题的根源在于应用程序与MFC规则DLL共享MFC DLL(或MFC扩展DLL)的程序总是默认使用EXE的资源,我们必须进行资源模块句柄的切换,其实现方法有三:
在DLL接口函数中使用:AFX_MANAGE_STATE(AfxGetStaticModuleState());
我们将DLL中的接口函数ShowDlg改为:
void
ShowDlg(void)
{ AFX_MANAGE_STATE(AfxGetStaticModuleState()); CDialog
dlg(IDD_DLL_DIALOG); dlg.DoModal(); }
AFX_MODULE_STATE*
AFXAPI AfxGetStaticModuleState(
);
该函数的功能是在栈上(这意味着其作用域是局部的)创建一个AFX_MODULE_STATE类(模块全局数据也就是模块状态)的实例,对其进行设置,并将其指针pModuleState返回。
AFX_MODULE_STATE类的原型如下:
class
AFX_MODULE_STATE : public
CNoTrackObject
{
public:
#ifdef
_AFXDLL
AFX_MODULE_STATE(BOOL
bDLL, WNDPROC pfnAfxWndProc, DWORD
dwVersion);
AFX_MODULE_STATE(BOOL
bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,BOOL
bSystem);
#else
AFX_MODULE_STATE(BOOL
bDLL);
#endif
~AFX_MODULE_STATE();
CWinApp*
m_pCurrentWinApp;
HINSTANCE
m_hCurrentInstanceHandle;
HINSTANCE
m_hCurrentResourceHandle;
LPCTSTR
m_lpszCurrentAppName; }
AFX_MODULE_STATE类利用其构造函数和析构函数进行存储模块状态现场及恢复现场的工作。
AFX_MANAGE_STATE是一个宏,其原型为:AFX_MANAGE_STATE( AFX_MODULE_STATE*
pModuleState
)
该宏用于将pModuleState设置为当前的有效模块状态。当离开该宏的作用域时(也就离开了pModuleState所指向栈上对象的作用域),先前的模块状态将由AFX_MODULE_STATE的析构函数恢复。
在DLL接口函数中使用: AfxGetResourceHandle(); AfxSetResourceHandle(HINSTANCE
xxx);
AfxGetResourceHandle用于获取当前资源模块句柄,而AfxSetResourceHandle则用于设置程序目前要使用的资源模块句柄。
我们将DLL中的接口函数ShowDlg改为:
void
ShowDlg(void)
{ HINSTANCE save_hInstance =
AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
CDialog
dlg(IDD_DLL_DIALOG);
dlg.DoModal();
AfxSetResourceHandle(save_hInstance); }
通过AfxGetResourceHandle和AfxSetResourceHandle的合理变更,我们能够灵活地设置程序的资源模块句柄,而方法一则只能在DLL接口函数退出的时候才会恢复模块句柄。
extern
CSharedDllApp theApp; //需要声明theApp外部全局变量
void
ShowDlg(void)
{
HINSTANCE save_hInstance =
AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
CDialog
dlg(IDD_DLL_DIALOG); dlg.DoModal();
AfxSetResourceHandle(save_hInstance);
CDialog
dlg1(IDD_DLL_DIALOG);
dlg1.DoModal();
}相继为DLL中的对话框和EXE中的对话框。
由应用程序自身切换
void
ShowDlg(void)
{ CDialog
dlg(IDD_DLL_DIALOG); dlg.DoModal(); }
而将应用程序的OnCalldllButton函数改为:
void
CSharedDllCallDlg::OnCalldllButton()
{ HINSTANCE
exe_hInstance =
GetModuleHandle(NULL);
//或者HINSTANCE
exe_hInstance =
AfxGetResourceHandle();
//获取DLL模块句柄 HINSTANCE
dll_hInstance =
GetModuleHandle("SharedDll.dll");
AfxSetResourceHandle(dll_hInstance);
ShowDlg();
AfxSetResourceHandle(exe_hInstance);
ShowDlg(); //此时显示的是EXE的对话框
} 相继为DLL中的对话框和EXE中的对话框。
MFC扩展DLL
MFC扩展DLL主要强调对MFC进行功能扩展。因此,如果DLL的目标不是增强MFC的功能,其与应用程序的接口也不是MFC,请不要将DLL建立为MFC扩展DLL。
使用Visual C++向导生产MFC扩展DLL时,MFC向导会自动增加DLL的入口函数DllMain:
extern
"C" int APIENTRY
DllMain(HINSTANCE
hInstance, DWORD dwReason, LPVOID
lpReserved)
{ UNREFERENCED_PARAMETER(lpReserved);
if (dwReason ==
DLL_PROCESS_ATTACH)
{
TRACE0("MFCEXPENDDLL.DLL
Initializing!\n");
if (!AfxInitExtensionModule(MfcexpenddllDLL,
hInstance))
return
0;
new
CDynLinkLibrary(MfcexpenddllDLL);
}
else
if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("MFCEXPENDDLL.DLL
Terminating!\n");
AfxTermExtensionModule(MfcexpenddllDLL); }
return
1; }上述代码完成MFC扩展DLL的初始化和终止处理。
MFC扩展DLL的创建
下面我们将在MFC扩展DLL中导出一个按钮类CSXButton。
MFC中包含一些宏,这些宏在DLL和调用DLL的应用程序中被以不同的方式展开,这使得在DLL和应用程序中,使用统一的一个宏就可以表示出输出和输入的不同意思:
//
for data
#ifndef
AFX_DATA_EXPORT
#define
AFX_DATA_EXPORT __declspec(dllexport)
#endif
#ifndef
AFX_DATA_IMPORT
#define
AFX_DATA_IMPORT __declspec(dllimport)
#endif
//
for classes
#ifndef
AFX_CLASS_EXPORT
#define
AFX_CLASS_EXPORT __declspec(dllexport)
#endif
#ifndef
AFX_CLASS_IMPORT
#define
AFX_CLASS_IMPORT __declspec(dllimport)
#endif
//
for global APIs
#ifndef
AFX_API_EXPORT
#define
AFX_API_EXPORT __declspec(dllexport)
#endif
#ifndef
AFX_API_IMPORT
#define
AFX_API_IMPORT __declspec(dllimport)
#endif
#ifndef
AFX_EXT_DATA
#ifdef
_AFXEXT
#define
AFX_EXT_CLASS
AFX_CLASS_EXPORT
#define
AFX_EXT_API
AFX_API_EXPORT
#define
AFX_EXT_DATA
AFX_DATA_EXPORT
#define AFX_EXT_DATADEF
#else
#define
AFX_EXT_CLASS
AFX_CLASS_IMPORT
#define
AFX_EXT_API
AFX_API_IMPORT
#define
AFX_EXT_DATA
AFX_DATA_IMPORT
#define AFX_EXT_DATADEF
#endif
#endif
导出一个类,直接在类声明头文件中使用AFX_EXT_CLASS即可,以下是导出CSXButton类的例子:
#ifndef
_SXBUTTON_H
#define
_SXBUTTON_H
#define
SXBUTTON_CENTER -1
class
AFX_EXT_CLASS CSXButton : public CButton
{
public:
CSXButton();
private:
BOOL
m_bUseOffset;
CPoint
m_pointImage;
//{{AFX_VIRTUAL(CSXButton)
public:
virtual
void DrawItem(LPDRAWITEMSTRUCT
lpDrawItemStruct);
//}}AFX_VIRTUAL
public:
virtual
~CSXButton();
protected:
//{{AFX_MSG(CSXButton)
afx_msg
LRESULT OnGetText(WPARAM wParam, LPARAM
lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
#endif
把SXBUTTON.CPP文件直接添加到工程,编译工程,得到“mfcexpenddll.lib”和“mfcexpenddll.dll”两个文件。如果我们想用.lib文件导出这些符号是非常困难的,我们需要在工程中生成.map文件,查询.map文件的符号,然后将其一一导出。打开 DLL工程的settings选项,再选择Link,勾选其中的产生MAP文件(Generate mapfile)就可以产生.map文件了。
0001:00000380
?HasImage@CSXButton@@QAEHXZ 10001380 f i
SXBUTTON.OBJ
0001:000003d0
??0CSXButton@@QAE@XZ
100013d0 f SXBUTTON.OBJ
0001:00000500
??_GCSXButton@@UAEPAXI@Z
10001500 f i SXBUTTON.OBJ
0001:00000570
??_ECSXButton@@UAEPAXI@Z
10001570 f i SXBUTTON.OBJ
0001:00000630
??1CSXButton@@UAE@XZ
10001630 f SXBUTTON.OBJ
0001:00000700
?_GetBaseMessageMap@CSXButton@@KGPBUAFX_MSGMAP@@XZ
10001700
f SXBUTTON.OBJ
0001:00000730
?GetMessageMap@CSXButton@@MBEPBUAFX_MSGMAP@@XZ
10001730 f
SXBUTTON.OBJ
0001:00000770
?Redraw@CSXButton@@AAEXXZ 10001770 f i
SXBUTTON.OBJ
0001:000007d0
?SetIcon@CSXButton@@QAEHIHH@Z 100017d0
f SXBUTTON.OBJ
……………………………………………………………………..//省略
MFC扩展DLL的调用
在DLL所在工作区新增一个dllcall工程,它是一个基于对话框的MFC EXE程序。在其中增加两个按钮SXBUTTON1、SXBUTTON2,并设置其属性为“Owner draw”
在工程中添加两个ICON资源:IDI_MSN_ICON(MSN的图标)、IDI_REFBAR_ICON(Windows的系统图标)。
修改“calldllDlg.h”头文件:
#include
"..\..\mfcexpenddll\SXBUTTON.h"
//包含dll的导出类头文件
#pragma
comment(lib,"mfcexpenddll.lib")
//隐式链接dll
class
CCalldllDlg : public CDialog
{ public: CCalldllDlg(CWnd*
pParent = NULL); CSXButton
m_button1;
CSXButton
m_button2; ...}
修改“calldllDlg.cpp”文件使得m_button1、m_button2成员变量与对话框上的按钮控件建立关联:
void
CCalldllDlg::DoDataExchange(CDataExchange*
pDX)
{ CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCalldllDlg)
DDX_Control(pDX,
IDC_BUTTON2, m_button2);
DDX_Control(pDX,
IDC_BUTTON1, m_button1);
//}}AFX_DATA_MAP
}
修改BOOL CCalldllDlg::OnInitDialog()函数,在其中增加对两个按钮设置ICON的代码:
BOOL
CCalldllDlg::OnInitDialog()
{ CDialog::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0)
== IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX
< 0xF000);
CMenu*
pSysMenu = GetSystemMenu(FALSE);
if
(pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if
(!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING,
IDM_ABOUTBOX, strAboutMenu);
} }
SetIcon(m_hIcon,
TRUE);
SetIcon(m_hIcon,
FALSE);
m_button1.SetIcon(IDI_MSN_ICON,16,16);
m_button2.SetIcon(IDI_REFBAR_ICON,16,16);
return
TRUE;
}
我们也可以在OnInitDialog()函数中添加如下代码实现m_button1、m_button2与IDC_BUTTON1、IDC_BUTTON2关联:
m_button1.SubclassDlgItem(IDC_BUTTON1,
this); m_button2.SubclassDlgItem(IDC_BUTTON2, this);
但是,DDX_Control与按钮类的SubclassDlgItem成员函数不能同时存在,否则程序会出错。
#define AUDIOREADER_EXPORTS
CPP文件
#ifdef AUDIOREADER_EXPORTS
#define AUDIOREADER_API __declspec(dllexport)
#else
#define AUDIOREADER_API __declspec(dllimport)
#endif
class AUDIOREADER_API CAudioReader
{}
创建DLL时的工程属性很重要
如果将工程属性设置为MFC扩展DLL,那么函数参数就不能为CString,应该为(LPSTR)(LPCTSTR)CString(即char* char[])

浙公网安备 33010602011771号