class CMyRegularDllApp : public CWinApp { public: CMyRegularDllApp(); // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CMyRegularDllApp) //}}AFX_VIRTUAL //{{AFX_MSG(CMyRegularDllApp) // NOTE - the ClassWizard will add and // remove member functions here. // DO NOT EDIT what you see in these blocks // of generated code ! //}}AFX_MSG DECLARE_MESSAGE_MAP() };//MyRegularDll.cpp:Defines the initialization routines for the DLL. //#include "stdafx.h" #include "MyRegularDll.h" // Note! // // If this DLL is dynamically linked against the MFC // DLLs, any functions exported from this DLL which // call into MFC must have the AFX_MANAGE_STATE macro // added at the very beginning of the function.// // For example: // // extern "C" BOOL PASCAL EXPORT ExportedFunction() // { // AFX_MANAGE_STATE(AfxGetStaticModuleState()); // // normal function body here // } // // It is very important that this macro appear in each // function, prior to any calls into MFC. This means that // it must appear as the first statement within the // function, even before any object variable declarations // as their constructors may generate calls into the MFC // DLL.BEGIN_MESSAGE_MAP(CMyRegularDllApp, CWinApp) //{{AFX_MSG_MAP(CMyRegularDllApp) // NOTE - the ClassWizard will add // and remove mapping macros here. // DO NOT EDIT what you see in these blocks // of generated code! //}}AFX_MSG_MAP END_MESSAGE_MAP() //////////////////////////////////////////////////////////// // CMyRegularDllApp construction CMyRegularDllApp::CMyRegularDllApp() { // TODO: add construction code here, // Place all significant initialization in InitInstance } 以上是AppWizard产生的含有主要代码的两个文件,各位可从中 看出和Non-MFC Dlls的区别。但要注意上面的AppWizard的提醒啊。 这次要讲的是最后一种动态连接库:Extension Dlls.再次说明, Extension Dll只被用MFC类库所编写的应用程序所调用.在这种动态 连接库中,你可以从MFC继承你所想要的、更适于你自己用的类,并 把它提供给你的应用程序。你也可随意的给你的应用程序提供MFC或 MFC继承类的对象指针。 Extension DLLs 和Regular DLLs不一样,它没有一个从CWinApp 继承而来的类的对象,所以,你必须为自己DllMain函数添加初始化 代码和结束代码.如下:#include "stdafx.h" #include <afxdllx.h>static AFX_EXTENSION_MODULE PROJNAMEDLL = { NULL, NULL };extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { if (dwReason == DLL_PROCESS_ATTACH) { TRACE0("PROJNAME.DLL Initializing!\n"); // Extension DLL one-time initialization AfxInitExtensionModule(PROJNAMEDLL, hInstance); // Insert this DLL into the resource chain new CDynLinkLibrary(Dll3DLL); } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0("PROJNAME.DLL Terminating!\n"); } return 1; // ok } 在上面代码中AfxInitExtensionMoudle函数捕捉此动态库模块 的run-time类及其对象,以便此后CDynLinkLibrary对象创建时使 用. 在初始化的时NEW一个CDynLinkLibrary对象的目的在于:它 能是Extension DLL想应用程序输出CRuntimeClass对象或资源. 如果此动态连接库被显式的调用,还必须在DLL_PROCESS_DETACH 选择项的执行代码上调用AfxTermEXtensonModule,这保证了当调 用进程与动态连接库分离是正确清理内存中的动态库模块。如果是 隐式的被调用,则此步不是必须的了。 以上是我对于编动态连接库的一点介绍,至于实际中选用那种类型 的动态库,则要根据实际编程所要实现的功能来决定。Non-MFC DLLS编 起来比较简单,建议各位要编的话从它开始。其他两种实现的功能则比 较强,编熟之后,不妨试一试。
(1)Windows的系统目录:\windows\system;
(2)DOS中path所指出的任何目录;
(3)程序所在的目录。
一、动态链接库的结构
动态链接库中定义有两种函数:导出函数(export function)和内部函数(internal function),导出函数可以被其它模块调用,内部函数只能在库内部使用。我们在用C++定制动态库文件时,需要编写的就是包含导出函数表的模块定义文件(.DEF)和实现导出函数功能的C++文件。下面以Sample.dll为例介绍DEF文件和实现文件的结构。
1.模块定义文件(.DEF)是一个或多个用于描述DLL属性的模块语句组成的文本文件,每个DEF文件至少必须包含以下模块定义语句:
·第一个语句必须是LIBRARY语句,指出DLL的名字;
·EXPORTS语句列出被导出函数的名字;
·可以使用DESCRIPTION语句描述DLL的用途(此句可选);
·“;”对一行进行注释(可选)。
2.实现文件 实现入口表函数的cpp文件中,包含DLL入口点处理的API函数和导出函数的代码。 二、创建Sample.dll 1.首先创建Sample.dll的工程,启动VC++5.0按以下步骤生成DLL工程: ·在选单中选择File\New\Project; ·在工程列表中选择Win32 Dynamic-Link Library; ·在Project Name中输入工程名:Sample; ·单击Location右边按钮,选择c:\sample目录; ·单击OK完成,至此已创建了Sample.dll的工程文件。 2.创建Sample.def文件: ·在选单中选择File\New\Text File; ·输入以下代码后保存文件名“Sample.def”: ;Sample.def ;指出DLL的名字Sample,链接器将这个名字放到DLL导入库中 LIBRARY Sample ;定义导出函数ShowMe()为例
EXPORTS ShowMe ;def文件结束 3.创建Sample.cpp .在选单中选择File\New\C++ Source File项 .输入以下代码后保存文件名“Sample.cpp” //Sample.cpp #include 〈windows.h〉 int ShowMe(void); //DllEntryPoint为DLL入口点函数,负责初试化并终止DLL BOOL WINAPI DllEntryPoint(HINSTANCE hDLL,DWORD dwReason,LPVOID Reserved) { switch(dwReason) { case DLL—PROCESS—ATTACH: { break; } case DLL-PROCESS-DETACH: { break; } } return TRUE; } int ShowMe(void) { //蜂鸣器响一下 MessageBeep((WORD)-1); MessageBox("你好!"); return 1; } 4.编译DLL文件 从Build选单中选择Build Sample.DLL,产生Sample.DLL文件,以后就可以随时调用了。 三、在应用程序中调用DLL文件 在应用程序中要首先装入DLL后才能调用导出表中的函数,例如用MFC创建基于对话框的工程Test,并在对话框上放置“Load”按钮,你就必须添加装载代码。 1.首先在TestDlg.cpp的首部添加变量设置代码: //设置全局变量gLibSample用于存储DLL句柄 HINSTANCE gLibSample=NULL; //第二个变量ShowMe是指向DLL库中ShowMe()函数的指针 typedef int( SHOWME)(void);
SHOWME ShowMe; 2.利用ClassWizard为“Load”按钮添加装载DLL的代码: Void CTestDlg::OnLoadButton() { //要添加的代码如下 if(gLibMyDLL!=NULL) { MessageBox("The Sample.DLL has already been load."); return; } //装载Sample.dll,未加路径,将在三个默认路径中寻找 gLibSample=LoadLibrary("SAMPLE.DLL"); //返回DLL中ShowMe()函数的地址 ShowMe=(SHOWME)GetProcAddress(gLibSample,"ShowMe"); } 3.只要DLL装载成功,在应用程序中就可以直接调用ShowMe()函数.
如节省内存、支持多语种等功能,而且,当DLL中的函数改变后,只要不是参数的改变
调用起的函数并不需要重新编译。这在编程时十分有用。至于其他妙处,各位在电脑
杂志、书籍中都能看到,我这里再说就是废话了.
这次小弟我所要讲的是如何在VC5.0中如何做自己的Win32 DLLs,各位要做自己的
动态连接库,首先要知道DLL在VC5.0中都有哪几种分类。VC支持三种DLL,它们是:
1.Non-MFC Dlls
2.Regular Dlls
3.Extension Dlls
Note:翻译措辞不当,故遇到术语是引用原词
Non-MFC DLL:指的是不用MFC的类库结构,直接用C语言写的DLL,其输出的函数一
般用的是标准C接口,并能被非MFC或MFC编写的应用程序所调用。
Regular DLL:和下述的Extension Dlls一样,是用MFC类库编写的。明显的特点是
在源文件里有一个继承CWinApp的类。其又可细分成静态连接到MFC和动态连接到MFC上
的。但静态连接到MFC的动态连接库只被VC的专业般和企业版所支持。
Extension DLL:用来实现从MFC所继承下来的类的重新利用,也就是说,用这种类
型的动态连接库,可以用来输出一个从MFC所继承下来的类。Extension DLL使用MFC的
动态连接版本所创建的,并且它只被用MFC类库所编写的应用程序所调用。
各位看到这里如果眼有点花或头有点晕,请别泄气,再看两遍,然后继续往下看,
定有收获。 这一节介绍Non-MFC DLLs的编写方法。下面是一个通用的
写法: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;
}
每一个DLL必须有一个入口点,这就象我们用C编写的应用程序一样,
必须有一个WINMAIN函数一样。 在这个示例中,DllMain是一个缺省的入口函数,你不需要编写自己
的DLL入口函数,并用linker的命令行的参数开关/ENTRY声明。用这个缺
省的入口函数就能使动态连接库被调用时得到正确的初始化,当然了,你
不要在初始化的时候填写使系统崩溃的代码了。
参数中,hMoudle是动态库被调用时所传递来的一个指向自己的句柄
(实际上,它是指向_DGROUP段的一个选择符)
ul_reason_for_call是一个说明动态库被调原因的标志。当进程或线程
装入或卸载动态连接库的时候,操作系统调用入口函数,并说明动态连接库
被调用的原因。它所有的可能值为:
DLL_PROCESS_ATTACH: 进程被调用
DLL_THREAD_ATTACH: 线程被调用
DLL_PROCESS_DETACH: 进程被停止
DLL_THREAD_DETACH: 线程被停止
lpReserved是一个被系统所保留的参数。
入口函数已经写了,盛下的也不难,你可以在文件中加入你所想要输
出的函数或变量或c++类或、或、或、?好象差部多了。look here!现在就
要加入一个新的输出函数了:
void _declspec(dllexport) JustSoSo()
{
MessageBox(NULL,"It's so easy!","Hahaha......",MB_OK);
}
要输出一个类也可以,如下:
class _declspec(dllexport) Easy
{
//add your class definition...
};
各位一定注意到在输出函数或类是我用到_declspec(dllexport),
这是VC提供的一个关键字,用它可在动态连接库中输出一个数据、
一个函数或一个类。用这个关键字可省你不少事,你不用在.DEF文件
中说明我要输出这个类、那个函数的 ?
前面讲到Non-MFC DLL的编法,现在讲讲调用DLL的方法。对DLL的
调用分为两种,一种是显式的调用,一种是隐式的调用。
所谓显式的调用,是指在应用程序中用LoadLibrary或MFC提供的
AfxLoadLibrary显式的将自己所做的动态连接库调近来,动态连接库
的文件名即是上两函数的参数,再用GetProcAddress()获取想要引入
的函数。自此,你就可以象使用如同本应用程序自定义的函数一样来
调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或
MFC提供的AfxLoadLibrary释放动态连接库。 隐式的调用则需要把产生动态连接库时产生的.LIB文件加入到应
用程序的工程中,想使用DLL中的函数时,只须说明以下,如下:说明
上篇的输出函数void JustSoSo(); _declspec(dllimport) void JustSoSo(); 隐式调用不需要调用LoadLibrary()和FreeLibrary(). 由此看来,隐式说明调用的方法比较简单,但DLL改变后,应用程序
须从新编译。并且,所有所调用的DLL在应用程序加载的同时被加载到内
存中,但应用程序调用的DLL比较多时,装入的过程十分慢。隐式的调用
则在应用程序不知道所要装入的DLL或隐式调用不成功,此时,允许用户
指定所要加载的动态连接库,比较灵活。
Regular DLL能够被所有支持DLL技术的语言所编写的应用程序
所调用。在这种动态连接库中,它必须有一个从CWinApp继承下来的
类,DllMain函数被MFC所提供,不用自己显式的写出来。下面是一个
例子:
// MyRegularDll.h:main header file for the MYREGULARDLL DLL
#include "resource.h" // main symbols
{
public:
CMyRegularDllApp();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyRegularDllApp)
//}}AFX_VIRTUAL
//{{AFX_MSG(CMyRegularDllApp)
// NOTE - the ClassWizard will add and
// remove member functions here.
// DO NOT EDIT what you see in these blocks
// of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};//MyRegularDll.cpp:Defines the initialization routines for the DLL.
//#include "stdafx.h"
#include "MyRegularDll.h"
// Note!
//
// If this DLL is dynamically linked against the MFC
// DLLs, any functions exported from this DLL which
// call into MFC must have the AFX_MANAGE_STATE macro
// added at the very beginning of the function.//
// For example:
//
// extern "C" BOOL PASCAL EXPORT ExportedFunction()
// {
// AFX_MANAGE_STATE(AfxGetStaticModuleState());
// // normal function body here
// }
//
// It is very important that this macro appear in each
// function, prior to any calls into MFC. This means that
// it must appear as the first statement within the
// function, even before any object variable declarations
// as their constructors may generate calls into the MFC
// DLL.BEGIN_MESSAGE_MAP(CMyRegularDllApp, CWinApp)
//{{AFX_MSG_MAP(CMyRegularDllApp)
// NOTE - the ClassWizard will add
// and remove mapping macros here.
// DO NOT EDIT what you see in these blocks
// of generated code!
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
////////////////////////////////////////////////////////////
// CMyRegularDllApp construction
CMyRegularDllApp::CMyRegularDllApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
以上是AppWizard产生的含有主要代码的两个文件,各位可从中
看出和Non-MFC Dlls的区别。但要注意上面的AppWizard的提醒啊。
这次要讲的是最后一种动态连接库:Extension Dlls.再次说明,
Extension Dll只被用MFC类库所编写的应用程序所调用.在这种动态
连接库中,你可以从MFC继承你所想要的、更适于你自己用的类,并
把它提供给你的应用程序。你也可随意的给你的应用程序提供MFC或
MFC继承类的对象指针。
Extension DLLs 和Regular DLLs不一样,它没有一个从CWinApp
继承而来的类的对象,所以,你必须为自己DllMain函数添加初始化
代码和结束代码.如下:#include "stdafx.h"
#include <afxdllx.h>static AFX_EXTENSION_MODULE PROJNAMEDLL = { NULL, NULL };extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("PROJNAME.DLL Initializing!\n"); // Extension DLL one-time initialization
AfxInitExtensionModule(PROJNAMEDLL,
hInstance); // Insert this DLL into the resource chain
new CDynLinkLibrary(Dll3DLL);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("PROJNAME.DLL Terminating!\n");
}
return 1; // ok
}
在上面代码中AfxInitExtensionMoudle函数捕捉此动态库模块
的run-time类及其对象,以便此后CDynLinkLibrary对象创建时使
用.
在初始化的时NEW一个CDynLinkLibrary对象的目的在于:它
能是Extension DLL想应用程序输出CRuntimeClass对象或资源.
如果此动态连接库被显式的调用,还必须在DLL_PROCESS_DETACH
选择项的执行代码上调用AfxTermEXtensonModule,这保证了当调
用进程与动态连接库分离是正确清理内存中的动态库模块。如果是
隐式的被调用,则此步不是必须的了。 以上是我对于编动态连接库的一点介绍,至于实际中选用那种类型
的动态库,则要根据实际编程所要实现的功能来决定。Non-MFC DLLS编
起来比较简单,建议各位要编的话从它开始。其他两种实现的功能则比
较强,编熟之后,不妨试一试。
第3步:实现属性和方法。在CtmpOcxCtrl类里添加一个私有变量m_MsgContext。Private: CString m_MsgContext; (2)属性实现方法:BSTR CTmpOcxCtrl::GetMsgContext() { return m_MsgContext.AllocSysString();} void CTmpOcxCtrl::SetMsgContext(LPCTSTR lpszNewValue) { if (m_MsgContext.Compare(lpszNewValue) != 0 ) { m_MsgContext = lpszNewValue; SetModifiedFlag(); }} (3) ShowMessage实现方法:void CTmpOcxCtrl::ShowMessage() { AfxMessageBox(m_MsgContext);} 第4步:编辑属性编辑框。(1)在资源编辑器,编辑属性对话框(2)给输入框控件添加关联变量,也是利用MFC Classwizards来实现,
(3) 在CtmpOcxCtrl类的DoPropExchange方法里添加如下代码,关联属性。PX_String(pPX,"MsgContext",m_MsgContext,"欢迎使用此Acitvex,呵呵"); 至此,除了事件之外,别的功能都实现了。 第5步,添加事件。在这里,我们添加一个当MsgContext属性被改变时的通知事件。实现很简单,请看如下步骤:在类CtmpOcxCtrl的MFC ClassWizards向导里的“Activex Event”页里,添加一个事件,
(2)在CTmpOcxCtrl类的SetMsgContext方法里,触发MsgContext值变的事件。 void CTmpOcxCtrl::SetMsgContext(LPCTSTR lpszNewValue) { if (m_MsgContext.Compare(lpszNewValue) != 0 ) { m_MsgContext = lpszNewValue; FireOnMsgChange(); SetModifiedFlag(); }} 最后编译运行,将自动注册此Ocx。