您的当前位置:首页正文

dll编程C++

2024-03-11 来源:好走旅游网
1 2 VC++动态链接库编程之MFC规则DLL

2005-10-09 10:52作者:宋宝华出处:作者授权转载责任编辑:方舟 POPO专区天极软件专题专区精选 到天极软件“读编交流区”畅所欲言 Google专区 Flash MX 视频教程PPT动画演示教程特洛伊木马专区3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

QQ专区 QQ挂机 了解Web2.0Photoshop视频教程Excel动画教程集网页设计视频教程防火墙应用专区照片处理数字暗房Word动画演示教程Windows Vista专区 注册表应用专区黑客知识教程专区Windows API开发专区网络编程专区VB数据库编程专区图像处理与多媒体编程

第4节我们对非MFC DLL进行了介绍,这一节将详细地讲述MFC规则DLL的创建与使用技巧。

另外,自从本文开始连载后,收到了一些读者的e-mail。有的读者提出了一些问题,笔者将在本文的最后一次连载中选取其中的典型问题进行解答。由于时间的关系,对于读者朋友的来信,笔者暂时不能一一回复,还望海涵!由于笔者的水平有限,文中难免有错误和纰漏,也热诚欢迎读者朋友不吝指正!

5. MFC规则DLL

5.1 概述

MFC规则DLL的概念体现在两方面:

(1) 它是MFC的

“是MFC的”意味着可以在这种DLL的内部使用MFC;

(2) 它是规则的

“是规则的”意味着它不同于MFC扩展DLL,在MFC规则DLL的内部虽然可以使用MFC,但是其与应用程序的接口不能是MFC。而MFC扩展DLL与应用程序的接口可以是MFC,可以从MFC扩展DLL中导出一个MFC类的派生类。

Regular DLL能够被所有支持DLL技术的语言所编写的应用程序调用,当然也包括使用MFC的应用程序。在这种动态连接库中,包含一个从CWinApp继承下来的类,DllMain函数则由MFC自动提供。

Regular DLL分为两类:

(1)静态链接到MFC 的规则DLL

静态链接到MFC的规则DLL与MFC库(包括MFC扩展 DLL)静态链接,将MFC库的代码直接生成在.dll文件中。在调用这种DLL的接口时,MFC使用DLL的资源。因此,在静态链接到MFC 的规则DLL中不需要进行模块状态的切换。

38 39 40 41 42 43 44 45 46 47 48 49 50 51

使用这种方法生成的规则DLL其程序较大,也可能包含重复的代码。

(2)动态链接到MFC 的规则DLL

动态链接到MFC 的规则DLL 可以和使用它的可执行文件同时动态链接到 MFC DLL 和任何MFC扩展 DLL。在使用了MFC共享库的时候,默认情况下,MFC使用主应用程序的资源句柄来加载资源模板。这样,当DLL和应用程序中存在相同ID的资源时(即所谓的资源重复问题),系统可能不能获得正确的资源。因此,对于共享MFC DLL的规则DLL,我们必须进行模块切换以使得MFC能够找到正确的资源模板。

我们可以在Visual C++中设置MFC规则DLL是静态链接到MFC DLL还是动态链接到MFC DLL。如图8,依次选择Visual C++的project -> Settings -> General菜单或选项,在Microsoft Foundation Classes中进行设置。

图8 设置动态/静态链接MFC DLL

52 53 54 55 56

5.2 MFC规则DLL的创建

我们来一步步讲述使用MFC向导创建MFC规则DLL的过程,首先新建一个project,如图9,选择project的类型为MFC AppWizard(dll)。点击OK进入如图10所示的对话框。

图9 MFC DLL工程的创建

57

图10所示对话框中的1区选择MFC DLL的类别。

58 59 60 61 62 63 64 65 66 67 68

2区选择是否支持automation(自动化)技术, automation 允许用户在一个应用程序中操纵另外一个应用程序或组件。例如,我们可以在应用程序中利用 Microsoft Word 或Microsoft Excel的工具,而这种使用对用户而言是透明的。自动化技术可以大大简化和加快应用程序的开发。

3区选择是否支持Windows Sockets,当选择此项目时,应用程序能在 TCP/IP 网络上进行通信。 CWinApp派生类的InitInstance成员函数会初始化通讯端的支持,同时工程中的StdAfx.h文件会自动include 头文件。

添加socket通讯支持后的InitInstance成员函数如下:

BOOL CRegularDllSocketApp::InitInstance() {

if (!AfxSocketInit()) {

AfxMessageBox(IDP_SOCKETS_INIT_FAILED); return FALSE; }

return TRUE; }

69 70

4区选择是否由MFC向导自动在源代码中添加注释,一般我们选择“Yes,please”。

71 72 73 74 5.3 一个简单的MFC规则DLL

这个DLL的例子(属于静态链接到MFC 的规则DLL)中提供了一个如图11所示的对话框。

图11 MFC规则DLL例子

75 76 77 78 79 80

在DLL中添加对话框的方式与在MFC应用程序中是一样的。 在图11所示DLL中的对话框的Hello按钮上点击时将MessageBox一个“Hello,pconline的网友”对话框,下面是相关的文件及源代码,其中删除了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 construction

CRegularDllApp::CRegularDllApp() { }

///////////////////////////////////////////////////////////////////////////// // The one and only CRegularDllApp object

CRegularDllApp theApp; 81 82 83 84 85 86 87 88 89 90

分析:

在这一组文件中定义了一个继承自CWinApp的类CRegularDllApp,并同时定义了其的一个实例theApp。乍一看,您会以为它是一个MFC应用程序,因为MFC应用程序也包含这样的在工程名后添加“App”组成类名的类(并继承自CWinApp类),也定义了这个类的一个全局实例theApp。

我们知道,在MFC应用程序中CWinApp取代了SDK程序中WinMain的地位,SDK程序WinMain所完成的工作由CWinApp的三个函数完成:

virtual BOOL InitApplication( ); virtual BOOL InitInstance( ); virtual BOOL Run( ); //传说中MFC程序的“活水源头” 91 92 93 94 95 96 97 98 99 100

但是MFC规则DLL并不是MFC应用程序,它所继承自CWinApp的类不包含消息循环。这是因为,MFC规则DLL不包含CWinApp::Run机制,主消息泵仍然由应用程序拥有。如果DLL 生成无模式对话框或有自己的主框架窗口,则应用程序的主消息泵必须调用从DLL 导出的函数来调用PreTranslateMessage成员函数。

另外,MFC规则DLL与MFC 应用程序中一样,需要将所有 DLL中元素的初始化放到InitInstance 成员函数中。

第二组文件 自定义对话框类声明及实现

#if !defined(AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_) #define AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_

#if _MSC_VER > 1000 #pragma once

#endif // _MSC_VER > 1000 // DllDialog.h : header file

///////////////////////////////////////////////////////////////////////////// // CDllDialog dialog

class CDllDialog : public CDialog {

// Construction public:

CDllDialog(CWnd* pParent = NULL); // standard constructor enum { IDD = IDD_DLL_DIALOG }; protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected:

afx_msg void OnHelloButton(); DECLARE_MESSAGE_MAP() };

#endif

// DllDialog.cpp : implementation file

#include \"stdafx.h\" #include \"RegularDll.h\" #include \"DllDialog.h\" #ifdef _DEBUG

#define new DEBUG_NEW #undef THIS_FILE

static char THIS_FILE[] = __FILE__; #endif

///////////////////////////////////////////////////////////////////////////// // CDllDialog dialog

CDllDialog::CDllDialog(CWnd* pParent /*=NULL*/) : CDialog(CDllDialog::IDD, pParent) {}

void CDllDialog::DoDataExchange(CDataExchange* pDX) {

CDialog::DoDataExchange(pDX); }

BEGIN_MESSAGE_MAP(CDllDialog, CDialog)

ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton) END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////// // CDllDialog message handlers

void CDllDialog::OnHelloButton() {

MessageBox(\"Hello,pconline的网友\}

101 102 103 104 105 106 107 108

分析:

这一部分的编程与一般的应用程序根本没有什么不同,我们照样可以利用MFC类向导来自动为对话框上的控件添加事件。MFC类向导照样会生成类似

ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton)的消息映射宏。

第三组文件 DLL中的资源文件

//{{NO_DEPENDENCIES}}

// Microsoft Developer Studio generated include file.

// Used by RegularDll.rc //

#define IDD_DLL_DIALOG 1000

#define IDC_HELLO_BUTTON 1000

109 110 111 112 113 114 115

分析:

在MFC规则DLL中使用资源也与在MFC应用程序中使用资源没有什么不同,我们照样可以用Visual C++的资源编辑工具进行资源的添加、删除和属性的更改。

第四组文件 MFC规则DLL接口函数

#include \"StdAfx.h\" #include \"DllDialog.h\"

extern \"C\" __declspec(dllexport) void ShowDlg(void) {

CDllDialog dllDialog;

dllDialog.DoModal(); } 116 117 118 119 120 121 122 123 124 125 126 127

分析:

这个接口并不使用MFC,但是在其中却可以调用MFC扩展类CdllDialog的函数,这体现了“规则”的概类。

与非MFC DLL完全相同,我们可以使用__declspec(dllexport)声明或在.def中引出的方式导出MFC规则DLL中的接口。 5.4 MFC规则DLL的调用

笔者编写了如图12的对话框MFC程序(下载本工程)来调用5.3节的MFC规则DLL,在这个程序的对话框上点击“调用DLL”按钮时弹出5.3节MFC规则DLL中的对话框。

图12 MFC规则DLL的调用例子

128 129

下面是“调用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(); } 130 131 132 133 134 135 136

上述例子中给出的是显示调用的方式,可以看出,其调用方式与第4节中非MFC DLL的调用方式没有什么不同。

我们照样可以在EXE程序中隐式调用MFC规则DLL,只需要将DLL工程生成的.lib文件和.dll文件拷入当前工程所在的目录,并在RegularDllCallDlg.cpp文件(图12所示对话框类的实现文件)的顶部添加:

#pragma comment(lib,\"RegularDll.lib\") void ShowDlg(void); 137 138

并将void CRegularDllCallDlg::OnCalldllButton() 改为:

void CRegularDllCallDlg::OnCalldllButton() {

ShowDlg(); }

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

5.5 共享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的模块句柄。

这次我们创建一个动态链接到MFC DLL的规则DLL(下载本工程),在其中包含如图13的对话框。

图13 DLL中的对话框

155 156 157

另外,在与这个DLL相同的工作区中生成一个基于对话框的MFC程序,其对话框与图12完全一样。但是在此工程中我们另外添加了一个如图14的对话框。

图14 EXE中的对话框

158 159 160 161 162

图13和图14中的对话框除了caption不同(以示区别)以外,其它的都相同。

尤其值得特别注意,在DLL和EXE中我们对图13和图14的对话框使用了相同的资源ID=2000,在DLL和EXE工程的resource.h中分别有如下的宏:

//DLL中对话框的ID

#define IDD_DLL_DIALOG 2000

//EXE中对话框的ID

#define IDD_EXE_DIALOG 2000

163 164 165

与5.3节静态链接MFC DLL的规则DLL相同,我们还是在规则DLL中定义接口函数ShowDlg,原型如下:

#include \"StdAfx.h\" #include \"SharedDll.h\"

void ShowDlg(void) {

CDialog dlg(IDD_DLL_DIALOG); //打开ID为2000的对话框 dlg.DoModal(); }

166 167

而为应用工程主对话框的“调用DLL”的单击事件添加如下消息处理函数:

void CSharedDllCallDlg::OnCalldllButton() { ShowDlg(); } 168 169 170 171 172 173 174 175 176

我们以为单击“调用DLL”会弹出如图13所示DLL中的对话框,可是可怕的事情发生了,我们看到是图14所示EXE中的对话框! 惊讶?

产生这个问题的根源在于应用程序与MFC规则DLL共享MFC DLL(或MFC扩展DLL)的程序总是默认使用EXE的资源,我们必须进行资源模块句柄的切换,其实现方法有三:

方法一 在DLL接口函数中使用:

AFX_MANAGE_STATE(AfxGetStaticModuleState()); 177 178

我们将DLL中的接口函数ShowDlg改为:

void ShowDlg(void) {

//方法1:在函数开始处变更,在函数结束时恢复

//将AFX_MANAGE_STATE(AfxGetStaticModuleState());作为接口函数的第一//条语句进行模块状态切换

AFX_MANAGE_STATE(AfxGetStaticModuleState());

CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框 dlg.DoModal(); }

179 180 181 182 183

这次我们再点击EXE程序中的“调用DLL”按钮,弹出的是DLL中的如图13的对话框!嘿嘿,弹出了正确的对话框资源。

AfxGetStaticModuleState是一个函数,其原型为:

AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( ); 184 185 186 187 188

该函数的功能是在栈上(这意味着其作用域是局部的)创建一个AFX_MODULE_STATE类(模块全局数据也就是模块状态)的实例,对其进行设置,并将其指针pModuleState返回。

AFX_MODULE_STATE类的原型如下:

// AFX_MODULE_STATE (global data for a module)

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;

… //省略后面的部分 }

189 190 191 192 193 194 195 196

AFX_MODULE_STATE类利用其构造函数和析构函数进行存储模块状态现场及恢复现场的工作,类似汇编中call指令对pc指针和sp寄存器的保存与恢复、中断服务程序的中断现场压栈与恢复以及操作系统线程调度的任务控制块保存与恢复。

许多看似不着边际的知识点居然有惊人的相似!

AFX_MANAGE_STATE是一个宏,其原型为:

AFX_MANAGE_STATE( AFX_MODULE_STATE* pModuleState ) 197 198 199 200

该宏用于将pModuleState设置为当前的有效模块状态。当离开该宏的作用域时(也就离开了pModuleState所指向栈上对象的作用域),先前的模块状态将由AFX_MODULE_STATE的析构函数恢复。

201 202

方法二 在DLL接口函数中使用:

AfxGetResourceHandle();

AfxSetResourceHandle(HINSTANCE xxx);

203 204 205 206 207

AfxGetResourceHandle用于获取当前资源模块句柄,而AfxSetResourceHandle则用于设置程序目前要使用的资源模块句柄。

我们将DLL中的接口函数ShowDlg改为:

void ShowDlg(void) {

//方法2的状态变更

HINSTANCE save_hInstance = AfxGetResourceHandle(); AfxSetResourceHandle(theApp.m_hInstance);

CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框 dlg.DoModal();

//方法2的状态还原

AfxSetResourceHandle(save_hInstance); }

208 209 210 211

通过AfxGetResourceHandle和AfxSetResourceHandle的合理变更,我们能够灵活地设置程序的资源模块句柄,而方法一则只能在DLL接口函数退出的时候才会恢复模块句柄。方法二则不同,如果将ShowDlg改为:

extern CSharedDllApp theApp; //需要声明theApp外部全局变量

void ShowDlg(void) {

//方法2的状态变更

HINSTANCE save_hInstance = AfxGetResourceHandle(); AfxSetResourceHandle(theApp.m_hInstance);

CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框 dlg.DoModal();

//方法2的状态还原

AfxSetResourceHandle(save_hInstance);

//使用方法2后在此处再进行操作针对的将是应用程序的资源

CDialog dlg1(IDD_DLL_DIALOG); //打开ID为2000的对话框 dlg1.DoModal(); }

212 213 214 215 216 217 218 219 220 221

在应用程序主对话框的“调用DLL”按钮上点击,将看到两个对话框,相继为DLL中的对话框(图13)和EXE中的对话框(图14)。

方法三 由应用程序自身切换

资源模块的切换除了可以由DLL接口函数完成以外,由应用程序自身也能完成(下载本工程)。

现在我们把DLL中的接口函数改为最简单的:

void ShowDlg(void) { CDialog dlg(IDD_DLL_DIALOG); //打开ID为2000的对话框 dlg.DoModal(); } 222 223

而将应用程序的OnCalldllButton函数改为:

void CSharedDllCallDlg::OnCalldllButton() {

//方法3:由应用程序本身进行状态切换 //获取EXE模块句柄

HINSTANCE exe_hInstance = GetModuleHandle(NULL);

//或者HINSTANCE exe_hInstance = AfxGetResourceHandle(); //获取DLL模块句柄

HINSTANCE dll_hInstance = GetModuleHandle(\"SharedDll.dll\"); AfxSetResourceHandle(dll_hInstance); //切换状态 ShowDlg(); //此时显示的是DLL的对话框

AfxSetResourceHandle(exe_hInstance); //恢复状态

//资源模块恢复后再调用ShowDlg

ShowDlg(); //此时显示的是EXE的对话框 }

224 225 226

方法三中的Win32函数GetModuleHandle可以根据DLL的文件名获取DLL的模块句柄。如果需要得到EXE模块的句柄,则应调用带有Null参数的GetModuleHandle。

227 228 229 230 231 232 233 234 235

方法三与方法二的不同在于方法三是在应用程序中利用AfxGetResourceHandle和AfxSetResourceHandle进行资源模块句柄切换的。同样地,在应用程序主对话框的“调用DLL”按钮上点击,也将看到两个对话框,相继为DLL中的对话框(图13)和EXE中的对话框(图14)。

在下一节我们将对MFC扩展DLL进行详细分析和实例讲解,欢迎您继续关注本系列连载VC++动态链接库编程之MFC扩展 DLL

2005-10-10 08:52作者:宋宝华出处:天极网责任编辑:方舟

POPO专区天极软件专题专区精选 到天极软件“读编交流区”畅所欲言 Google专区 Flash MX 视频教程PPT动画演示教程特洛伊木马专区236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251

QQ专区 QQ挂机 了解Web2.0Photoshop视频教程Excel动画教程集网页设计视频教程防火墙应用专区照片处理数字暗房Word动画演示教程Windows Vista专区 注册表应用专区黑客知识教程专区Windows API开发专区网络编程专区VB数据库编程专区图像处理与多媒体编程

前文我们对非MFC DLL和MFC规则DLL进行了介绍,现在开始详细分析DLL的最后一种类型――MFC扩展DLL。

6.1概论

MFC扩展DLL与MFC规则DLL的相同点在于在两种DLL的内部都可以使用MFC类库,其不同点在于MFC扩展DLL与应用程序的接口可以是MFC的。MFC扩展DLL的含义在于它是MFC的扩展,其主要功能是实现从现有MFC库类中派生出可重用的类。MFC扩展DLL使用MFC 动态链接库版本,因此只有用共享MFC 版本生成的MFC 可执行文件(应用程序或规则DLL)才能使用MFC扩展DLL。

从前文可知,MFC规则DLL被MFC向导自动添加了一个CWinApp的对象,而MFC扩展DLL则不包含该对象,它只是被自动添加了DllMain 函数。对于MFC扩展DLL,开发人员必须在DLL的DllMain函数中添加初始化和结束代码。

从下表我们可以看出三种DLL对DllMain入口函数的不同处理方式:

DLL类型 非 MFC DLL MFC规则 DLL MFC扩展 DLL

入口函数

编程者提供DllMain函数

CWinApp对象的InitInstance 和 ExitInstance MFC DLL向导生成DllMain 函数

252 253 254 255 256 257 258

对于MFC扩展DLL,系统会自动在工程中添加如下表所示的宏,这些宏为DLL和应用程序的编写提供了方便。像AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA这样的宏,在DLL和应用程序中将具有不同的定义,这取决于_AFXEXT宏是否被定义。这使得在DLL和应用程序中,使用统一的一个宏就可以表示出输出和输入的不同意思。在DLL中,表示输出(因为_AFXEXT被定义,通常是在编译器的标识参数中指定/D_AFXEXT);在应用程序中,则表示输入(_AFXEXT没有定义)。

定义

AFX_CLASS_IMPORT AFX_API_IMPORT AFX_DATA_IMPORT AFX_CLASS_EXPORT AFX_API_EXPORT AFX_DATA_EXPORT

AFX_EXT_CLASS

AFX_EXT_API

AFX_EXT_DATA

259

260

__declspec(dllexport) __declspec(dllexport) __declspec(dllexport) __declspec(dllexport) __declspec(dllexport) __declspec(dllexport) #ifdef _AFXEXT AFX_CLASS_EXPORT #else

AFX_CLASS_IMPORT #ifdef _AFXEXT AFX_API_EXPORT #else

AFX_API_IMPORT #ifdef _AFXEXT AFX_DATA_EXPORT #else

AFX_DATA_IMPORT

261

262

263 264 265 266 267 268 269

6.2 MFC扩展DLL导出MFC派生类

在这个例子中,我们将产生一个名为“ExtDll”的MFC扩展DLL工程,在这个DLL中导出一个对话框类,这个对话框类派生自MFC类CDialog。

使用MFC向导生成MFC扩展DLL时,系统会自动添加如下代码:

static AFX_EXTENSION_MODULE ExtDllDLL = { NULL, NULL }; extern \"C\" int APIENTRY

DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved ) {

// Remove this if you use lpReserved

UNREFERENCED_PARAMETER( lpReserved );

//说明:lpReserved是一个被系统所保留的参数,对于隐式链接是一个非零值,对于显式链接值是零

if (dwReason == DLL_PROCESS_ATTACH) {

TRACE0( \"EXTDLL.DLL Initializing!\\n\" ); // Extension DLL one-time initialization

if ( !AfxInitExtensionModule( ExtDllDLL, hInstance )) return 0;

// Insert this DLL into the resource chain new CDynLinkLibrary( ExtDllDLL ); }

else if (dwReason == DLL_PROCESS_DETACH) {

TRACE0( \"EXTDLL.DLL Terminating!\\n\" );

// Terminate the library before destructors are called AfxTermExtensionModule( ExtDllDLL ); }

return 1; // ok }

270 271 272 273 274 275 276

这一段代码含义晦涩,我们需要对其进行解读:

(1)上述代码完成MFC扩展DLL的初始化和终止处理;

(2)初始化期间所创建的 CDynLinkLibrary 对象使MFC扩展 DLL 可以将 DLL中的CRuntimeClass 对象或资源导出到应用程序;

277 278 279 280 281 282 283 284 285

(3)AfxInitExtensionModule函数捕获模块的CRuntimeClass 结构和在创建 CDynLinkLibrary 对象时使用的对象工厂(COleObjectFactory 对象);

(4)AfxTermExtensionModule函数使 MFC 得以在每个进程与扩展 DLL 分离时(进程退出或使用AfxFreeLibrary卸载DLL时)清除扩展 DLL;

(5)第一条语句static AFX_EXTENSION_MODULE ExtDllDLL = { NULL, NULL };定义了一个AFX_EXTENSION_MODULE类的静态全局对象,AFX_EXTENSION_MODULE的定义如下:

struct AFX_EXTENSION_MODULE {

BOOL bInitialized; HMODULE hModule; HMODULE hResource;

CRuntimeClass* pFirstSharedClass;

COleObjectFactory* pFirstSharedFactory; };

286 287 288 289 290

由AFX_EXTENSION_MODULE的定义我们可以更好的理解(2)、(3)、(4)点。

在资源编辑器中添加一个如图15所示的对话框,并使用MFC类向导为其添加一个对应的类CExtDialog,系统自动添加了ExtDialog.h和ExtDialog.cpp两个头文件。

图15 MFC扩展DLL中的对话框

291 292

修改ExtDialog.h中CExtDialog类的声明为:

class AFX_EXT_CLASS CExtDialog : public CDialog {

public:

CExtDialog( CWnd* pParent = NULL ); enum { IDD = IDD_DLL_DIALOG };

protected:

virtual void DoDataExchange( CDataExchange* pDX ); DECLARE_MESSAGE_MAP() };

293 294 295 296 297 298 299 300 301 302

这其中最主要的改变是我们在class AFX_EXT_CLASS CExtDialog语句中添加了“AFX_EXT_CLASS”宏,则使得DLL中的CExtDialog类被导出。

6.3 MFC扩展DLL的加载

6.3.1 隐式加载

我们在6.2工程所在的工作区中添加一个LoadExtDllDlg工程,用于演示MFC扩展DLL的加载。在LoadExtDllDlg工程中添加一个如图16所示的对话框,这个对话框上包括一个“调用DLL”按钮。

图16 MFC扩展DLL调用工程中的对话框

303 304

在与图16对应对话框类实现文件的头部添加:

// LoadExtDllDlg.cpp : implementation file //

#include \"..\\ExtDialog.h\"

#pragma comment( lib, \"ExtDll.lib\" )

而“调用DLL”按钮的单击事件的消息处理函数为:

void CLoadExtDllDlg::OnDllcallButton() {

CExtDialog extDialog; extDialog.DoModal(); }

305 306 307 308

当我们单击“调用DLL”的时候,弹出了如图15的对话框。

为提供给用户隐式加载(MFC扩展DLL一般使用隐式加载,具体原因见下节),MFC扩

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 展DLL需要提供三个文件:

(1)描述DLL中扩展类的头文件;

(2)与动态链接库对应的.LIB文件;

(3)动态链接库.DLL文件本身。

有了这三个文件,应用程序的开发者才可充分利用MFC扩展DLL。

6.3.2 显示加载

显示加载MFC扩展DLL应使用MFC全局函数AfxLoadLibrary而不是WIN32 API中的LoadLibrary。AfxLoadLibrary 最终也调用了 LoadLibrary这个API,但是在调用之前进行了线程同步的处理。

AfxLoadLibrary 的函数原型与 LoadLibrary完全相同,为:

HINSTANCE AFXAPI AfxLoadLibrary( LPCTSTR lpszModuleName ); 326 327 328

与之相对应的是,MFC 应用程序应使用AfxFreeLibrary 而非FreeLibrary 卸载MFC扩展DLL。AfxFreeLibrary的函数原型也与 FreeLibrary完全相同,为:

BOOL AFXAPI AfxFreeLibrary( HINSTANCE hInstLib ); 329 330

如果我们把上例中的“调用DLL”按钮单击事件的消息处理函数改为:

void CLoadExtDllDlg::OnDllcallButton() {

HINSTANCE hDll = AfxLoadLibrary( \"ExtDll.dll\" ); if(NULL == hDll) {

AfxMessageBox( \"MFC扩展DLL动态加载失败\" ); return; }

CExtDialog extDialog; extDialog.DoModal(); AfxFreeLibrary(hDll); }

331 332

则工程会出现link错误:

LoadExtDllDlg.obj : error LNK2001: unresolved external symbol \"__declspec(dllimport) public: virtual __thiscall

CExtDialog::~CExtDialog(void)\" (__imp_??1CExtDialog@@UAE@XZ)

LoadExtDllDlg.obj : error LNK2001: unresolved external symbol

\"__declspec(dllimport) public: __thiscall CExtDialog::CExtDialog(class CWnd *)\" (__imp_??0CExtDialog@@QAE@PAVCWnd@@@Z)

333 334 335 336 337 338 339 340 341 342 343 344

提示CExtDialog的构造函数和析构函数均无法找到!是的,对于派生MFC类的MFC扩展DLL,当我们要在应用程序中使用DLL中定义的派生类时,我们不宜使用动态加载DLL的方法。

6.4 MFC扩展DLL加载MFC扩展DLL

我们可以在MFC扩展DLL中再次使用MFC扩展DLL,但是,由于在两个DLL中对于AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA宏的定义都是输出,这会导致调用的时候出现问题。

我们将会在调用MFC扩展DLL的DLL中看到link错误:

error LNK2001: unresolved external symbol …....... 345 346 347

因此,在调用MFC扩展DLL的MFC扩展DLL中,在包含被调用DLL的头文件之前,需要临时重新定义AFX_EXT_CLASS的值。下面的例子显示了如何实现:

//临时改变宏的含义“输出”为“输入”

#undef AFX_EXT_CLASS #undef AFX_EXT_API #undef AFX_EXT_DATA

#define AFX_EXT_CLASS AFX_CLASS_IMPORT #define AFX_EXT_API AFX_API_IMPORT #define AFX_EXT_DATA AFX_DATA_IMPORT

//包含被调用MFC扩展DLL的头文件

#include \"CalledDLL.h\"

//恢复宏的含义为输出

#undef AFX_EXT_CLASS #undef AFX_EXT_API #undef AFX_EXT_DATA

#define AFX_EXT_CLASS AFX_CLASS_EXPORT #define AFX_EXT_API AFX_API_EXPORT

#define AFX_EXT_DATA AFX_DATA_EXPORT 348 349 350 351 352

6.5 MFC扩展DLL导出函数和变量

MFC扩展DLL导出函数和变量的方法也十分简单,下面我们给出一个简单的例子。

我们在MFC向导生成的MFC扩展DLL工程中添加gobal.h和global.cpp两个文件:

//global.h:MFC扩展DLL导出变量和函数的声明

extern \"C\" {

int AFX_EXT_DATA total; //导出变量

int AFX_EXT_API add( int x, int y ); //导出函数 }

//global.cpp:MFC扩展DLL导出变量和函数定义

#include \"StdAfx.h\" #include \"global.h\"

extern \"C\" int total; int add(int x,int y) {

total = x + y; return total; }

353 354

编写一个简单的控制台程序来调用这个MFC扩展DLL:

#include #include

//AFX_EXT_DATA、AFX_EXT_API宏的定义在afxver_.h头文件中

#pragma comment ( lib, \"ExtDll.lib\" ) #include \"..\\global.h\"

int main(int argc, char* argv[]) {

cout << add(2,3) << endl; cout << total; return 0; }

355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380

运行程序,在控制台上看到:

5

5

另外,在Visual C++下建立MFC扩展DLL时,MFC DLL向导会自动生成.def文件。因此,对于函数和变量,我们除了可以利用AFX_EXT_DATA、AFX_EXT_API宏导出以外,在.def文件中定义导出也是一个很好的办法。与之相比,在.def文件中导出类却较麻烦。通常需要从工程生成的.map文件中获得类的所有成员函数被C++编译器更改过的标识符,并且在.def文件中导出这些“奇怪”的标识符。因此,MFC扩展DLL通常以AFX_EXT_CLASS宏直接声明导出类。

6.6 MFC扩展DLL的应用

上述各小节所举MFC扩展DLL的例子均只是为了说明某方面的问题,没有真实地体现“MFC扩展” 的内涵,譬如6.2派生自CDialog的类也不具备比CDialog更强的功能。MFC扩展DLL的真实内涵体现在它提供的类虽然派生自MFC类,但是提供了比MFC类更强大的功能、更丰富的接口。下面我们来看一个具体的例子(单击此处下载本工程)。

我们知道static控件所对应的CStatic类不具备设置背景和文本颜色的接口,这使得我们不能在对话框或其它用户界面上自由灵活地修改static控件的颜色风格,因此我们需要一个提供了SetBackColor和SetTextColor接口的CStatic派生类CMultiColorStatic。

这个类的声明如下:

class AFX_EXT_CLASS CMultiColorStatic : public CStatic {

// Construction

public:

CMultiColorStatic();

virtual ~CMultiColorStatic(); // Attributes protected:

CString m_strCaption; COLORREF m_BackColor; COLORREF m_TextColor; // Operations public:

void SetTextColor( COLORREF TextColor ); void SetBackColor( COLORREF BackColor ); void SetCaption( CString strCaption );

// Generated message map functions protected:

afx_msg void OnPaint(); DECLARE_MESSAGE_MAP() };

381 382 383

在这个类的实现文件中,我们需要为它提供WM_PAINT消息的处理函数(这是因为颜色的设置依赖于WM_PAINT消息):

BEGIN_MESSAGE_MAP(CMultiColorStatic, CStatic)

//{{AFX_MSG_MAP(CMultiColorStatic)

ON_WM_PAINT() //为这个类定义WM_PAINT消息处理函数 //}}AFX_MSG_MAP END_MESSAGE_MAP()

384 385

下面是这个类中的重要成员函数:

//为CMultiColorStatic类添加“设置文本颜色”接口

void CMultiColorStatic::SetTextColor( COLORREF TextColor ) {

m_TextColor = TextColor; //设置文字颜色 }

//为CMultiColorStatic类添加“设置背景颜色”接口

void CMultiColorStatic::SetBackColor( COLORREF BackColor ) {

m_BackColor = BackColor; //设置背景颜色 }

//为CMultiColorStatic类添加“设置标题”接口

void CMultiColorStatic::SetCaption( CString strCaption ) {

m_strCaption = strCaption; }

//重画Static,颜色和标题的设置都依赖于这个函数

void CMultiColorStatic::OnPaint() {

CPaintDC dc(this); // device context for painting CRect rect;

GetClientRect( &rect );

dc.SetBkColor( m_BackColor ); dc.SetBkMode( TRANSPARENT );

CFont *pFont = GetParent()->GetFont();//得到父窗体的字体 CFont *pOldFont;

pOldFont = dc.SelectObject( pFont );//选用父窗体的字体 dc.SetTextColor( m_TextColor );//设置文本颜色

dc.DrawText( m_strCaption, &rect, DT_CENTER );//文本在Static中央 dc.SelectObject( pOldFont ); }

386 387 388 389

为了验证CMultiColorStatic类,我们制作一个基于对话框的应用程序,它包含一个如图17所示的对话框。该对话框上包括一个static控件和三个按钮,这三个按钮可分别把static控件设置为“红色”、“蓝色”和“绿色”。

图17 扩展的CStatic类调用演示

390 391 392 393

下面看看应如何编写与这个对话框对应的类。

包含这种Static的对话框类的声明如下:

#include \"..\\MultiColorStatic.h\"

#pragma comment ( lib, \"ColorStatic.lib\" )

// CCallDllDlg dialog

class CCallDllDlg : public CDialog {

public:

CCallDllDlg(CWnd* pParent = NULL); // standard constructor enum { IDD = IDD_CALLDLL_DIALOG };

CMultiColorStatic m_colorstatic; //包含一个CMultiColorStatic的实例 protected:

virtual void DoDataExchange(CDataExchange* pDX);//DDX/DDV support

HICON m_hIcon;

// Generated message map functions //{{AFX_MSG(CCallDllDlg)

virtual BOOL OnInitDialog();

afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnRedButton(); afx_msg void OnBlueButton(); afx_msg void OnGreenButton(); //}}AFX_MSG

DECLARE_MESSAGE_MAP() };

394 395

下面是这个类中与使用CMultiColorStatic相关的主要成员函数:

void CCallDllDlg::DoDataExchange(CDataExchange* pDX) {

CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CCallDllDlg)

DDX_Control(pDX, IDC_COLOR_STATIC, m_colorstatic); //使m_colorstatic与IDC_COLOR_STATIC控件关联 //}}AFX_DATA_MAP }

BOOL CCallDllDlg::OnInitDialog() { …

// TODO: Add extra initialization here

// 初始static控件的显示

m_colorstatic.SetCaption(\"最开始为黑色\"); m_colorstatic.SetTextColor(RGB(0,0,0));

return TRUE; // return TRUE unless you set the focus to a control }

//设置static控件文本颜色为红色

void CCallDllDlg::OnRedButton() {

m_colorstatic.SetCaption( \"改变为红色\" );

m_colorstatic.SetTextColor( RGB( 255, 0, 0 ) ); Invalidate( TRUE ); //导致发出WM_PAINT消息 }

//设置static控件文本颜色为蓝色

void CCallDllDlg::OnBlueButton() {

m_colorstatic.SetCaption( \"改变为蓝色\" );

m_colorstatic.SetTextColor( RGB( 0, 0, 255 ) ); Invalidate( TRUE ); //导致发出WM_PAINT消息 }

//设置static控件文本颜色为绿色

void CCallDllDlg::OnGreenButton() {

m_colorstatic.SetCaption( \"改变为绿色\" ); m_colorstatic.SetTextColor( RGB(0,255,0) ); Invalidate( TRUE ); //导致发出WM_PAINT消息 }

396 397 398 399 400

至此,我们已经讲解完成了所有类型的动态链接库,即非MFC DLL、MFC规则DLL和MFC扩展DLL。下一节将给出DLL的三个工程实例,与读者朋友们共同体会DLL的应用范围和使用方法。VC++动态链接库编程之DLL典型实例

2005-10-11 08:32作者:宋宝华出处:天极网责任编辑:方舟

POPO专区天极软件专题专区精选 到天极软件“读编交流区”畅所欲言 Google专区 Flash MX 视频教程PPT动画演示教程特洛伊木马专区401 402 403 404 405 406 407 408 409 410 411

QQ专区 QQ挂机 了解Web2.0Photoshop视频教程Excel动画教程集网页设计视频教程防火墙应用专区照片处理数字暗房Word动画演示教程Windows Vista专区 注册表应用专区黑客知识教程专区Windows API开发专区网络编程专区VB数据库编程专区图像处理与多媒体编程

动态链接库DLL实现了库的共享,体现了代码重用的思想。我们可以把广泛的、具有共性的、能够多次被利用的函数和类定义在库中。这样,在再次使用这些函数和类的时候,就不再需要重新添加与这些函数和类相关的代码。具有共性的问题大致有哪些呢?笔者归纳如下:

(1)通用的算法

图像处理、视频音频解码、压缩与解压缩、加密与解密通常采用某些特定的算法,这些算法较固定且在这类程序中往往经常被使用。

(2)纯资源DLL

412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451

我们可以从DLL中获取资源,对于一个支持多种语言的应用程序而言,我们可以判断操作系统的语言,并自动为应用程序加载与OS对应的语言。这是多语言支持应用程序的一般做法。

(3)通信控制DLL

串口、网口的通信控制函数如果由DLL提供则可以使应用程序轻松不少。在工业控制、modem程序甚至socket通信中,经常使用通信控制DLL。

本节将给出DLL的三个典型应用实例。

7.1 算法DLL

我们直接用读者的一个提问作为例子。

宋宝华先生,您好!

我在dev.yesky.com上看到你连载的《VC++动态链接库编程》,觉得非常好。我以前主要是用Delphi的,C/C++学过,对Win32和VCL比较熟悉,但是没有接触过VC++,对MFC很陌生。这段时间和一个同学合作做光学成像的计算机模拟,用到傅立叶变换,手里面有例程是VC++写的。我们的界面是用Delphi开发,需要将其傅立叶变换功能提出做一个DLL供Delphi调用。苦于不懂MFC,试了很多方法,都不成功,最后只得采用折衷方案,简单修改一下程序,传一个参数进去,当作exe来调用,才没有耽搁后续进程。

……

谢谢!

礼!

某某

学习过较高级别数学(概率统计与随机过程)、信号与线性系统及数字信号处理的读者应该知道,傅立叶变换是一种在信号分析中常用的算法,用于时域和频域的相互转换。FFT变换算法通用而有共性,我们适宜把它集成在一个DLL中。

随后,这位读者提供了这样的一个函数:

/* 函数名称:FFT() * 参数: * complex * TD - 指向时域数组的指针 * complex * FD - 指向频域数组的指针 * r -2的幂数,即迭代次数 * 返回值: 无。

* 说明:该函数用来实现快速傅立叶变换 */

void FFT(complex * TD, complex * FD, int r) {

LONG count; // 傅立叶变换点数 int i,j,k; // 循环变量 int bfsize,p; // 中间变量 double angle; // 角度

complex *W,*X1,*X2,*X; count = 1 << r; //傅立叶变换点数

// 分配运算所需存储器

W = new complex[count / 2]; X1 = new complex[count]; X2 = new complex[count];

// 计算加权系数

for(i = 0; i < count / 2; i++) {

angle = -i * PI * 2 / count;

W[i] = complex (cos(angle), sin(angle)); }

// 将时域点写入X1

memcpy(X1, TD, sizeof(complex) * count);

// 采用蝶形算法进行快速傅立叶变换

for(k = 0; k < r; k++) {

for(j = 0; j < 1 << k; j++) {

bfsize = 1 << (r-k);

for(i = 0; i < bfsize / 2; i++) {

p = j * bfsize;

X2[i + p] = X1[i + p] + X1[i + p + bfsize / 2];

X2[i + p + bfsize / 2] = (X1[i + p] - X1[i + p + bfsize / 2]) * W[i * (1<X = X1; X1 = X2; X2 = X; }

// 重新排序

for(j = 0; j < count; j++) {

p = 0;

for(i = 0; i < r; i++) {

if (j&(1<p+=1<<(r-i-1); } }

FD[j]=X1[p]; }

// 释放内存

delete W; delete X1; delete X2; }

452 453 454 455 456 457 458 459 460

既然有了FFT这个函数,我们要把它做在DLL中,作为DLL的一个接口将是十分简单的,其步骤如下:

(1)利用MFC向导建立一个非MFC DLL;

(2)在工程中添加fft.h和fft.cpp两个文件;

fft.h的源代码为:

#ifndef FFT_H #define FFT_H #include

using namespace std;

extern \"C\" void __declspec(dllexport) __stdcall FFT(complex * TD, complex * FD, int r);

#define PI 3.1415926 #endif

fft.cpp的源代码为:

/* 文件名:fft.cpp */

#include \"fft.h\"

void __stdcall FFT(complex * TD, complex * FD, int r) {

…//读者提供的函数代码 }

461 462 463 464 465 466 467 468 469 470 471 472 473

在任何编程语言中使用Win32 API LoadLibrary都可以加载这个DLL,而使用GetProcAddress(hDll, \"FFT\")则可以获得函数FFT的地址,读者所提到的Delphi当然也不例外。

这个DLL中有两点需要注意:

(1)使用extern \"C\"修饰函数声明,否则,生成的DLL只能供C++调用;

(2)使用__stdcall修饰函数声明及定义,__stdcall是Windows API的函数调用方式。 7.2纯资源DLL

我们在应用程序中产生如图18所示的资源(对话框),单击此处下载本工程。

图18 中文对话框

474 475 476 477

在与这个应用程序相同的工作区里利用MFC向导建立两个简单的DLL,把应用工程中的资源全选后分别拷贝到ChineseDll和EngLishDll,在EnglishDll工程的资源文件中搜索下面的语句:

/////////////////////////////////////////////////////////////////////////////

// Chinese (P.R.C.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) #ifdef _WIN32

LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED #pragma code_page(936) #endif //_WIN32

478 479

将其改为:

///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32

LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

#pragma code_page(1252) #endif //_WIN32

480 481

并将其中所有的中文翻译为英文。这个DLL为我们提供了如图19所示的对话框资源。

图19英文对话框

482 483

修改应用工程的InitInstance()函数,在

CResourceDllCallDlg dlg; m_pMainWnd = &dlg;

int nResponse = dlg.DoModal();

484 485

之前(即对话框显示之前)添加如下代码:

//获取操作系统的语言 WORD wLangPID = PRIMARYLANGID( GetSystemDefaultLangID() ); if( LANG_CHINESE == wLangPID ) {

hLanguageDll = LoadLibrary( \"ChineseDll.dll\" ); //加载中文资源 } else {

hLanguageDll = LoadLibrary( \"EnglishDll.dll\" ); //加载英文资源 }

if( NULL == hLanguageDll ) {

AfxMessageBox( \"Load DLL failure\" ); return FALSE; }

AfxSetResourceHandle( hLanguageDll ); //设置当前的资源句柄

486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508

这样的应用程序将具有自适应性质,在中文OS中显示中文资源,在英文OS中则显示英文资源。7.3通信控制DLL

我们在这里举一个串口通信类的例子。

也许您需要了解一点串口通信的背景知识,其实串口到处都看得到,譬如PC机的COM口即为串行通讯口(简称串口)。如图20,打开Windows的设备管理器,我们看到了COM口。

在Windows系统,需通过DCB(Device Control Block)对串口进行配置。利用Windows API GetCommState函数可以获取串口当前配置;利用SetCommState函数则可以设置串口通讯的参数。

串行通信通常按以下四步进行:

(1)打开串口;

(2)配置串口;

(3)数据传送;

(4)关闭串口。

图20 PC的串口

509 510

由此可见,我们需要给串口控制DLL提供如下四个接口函数:

//打开指定的串口,其参数port为端口号

BOOL ComOpen(int port); //在这个函数里使用默认的参数设置串口

//将打开的串口关闭

void ComClose(int port);

//将串口接收缓冲区中的数据放到buffer中

int GetComData(char *buf, int buf_len);

//将指定长度的数据发送到串口

int SendDataToCom(LPBYTE buf,int buf_Len);

511 512

下面给出了DLL接口的主要源代码框架:

//com.h:com类通信接口

class AFX_EXT_CLASS com {

public:

ComOpen(int port) {

513 514

… }

int SendDataToCom(LPBYTE buf,int buf_Len) { … }

int GetComData(char *buf, int buf_len) { … }

void ComClose() { … } }

我们编写一控制台程序来演示DLL的调用:

#include #include

using namespace std;

#include

#include \"com.h\" //包含DLL中导出类的头文件 int main(int argc, char *argv[]) { try {

char str[] = \"com_class test\"; com com1;

com1.ComOpen (1);

for(int i=0; i<100; i++) //以同步方式写com的buffer {

Sleep(500);

com1.SendDataToCom (str,strlen(str)); }

com1.ComClose (); }

catch(exception &e) {

cout << e.what() << endl; }

return 0;

} 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557

DLL的编写与调用方法及主要应用皆已讲完,在下一节里,我们将看到比较“高深”的主题――DLL木马。曾几何时,DLL木马成为了病毒的一种十分重要的形式,是DLL的什么特性使得它能够成为一种病毒?下一节我们将揭晓谜底。VC++动态链接库编程之DLL木马 2005-10-25 09:10作者:宋宝华出处:天极网责任编辑:方舟

从前文可知,DLL在程序编制中可作出巨大贡献,它提供了具共性代码的复用能力。但是,正如一门高深的武学,若被掌握在正义之侠的手上,便可助其仗义江湖;但若被掌握在邪恶之徒的手上,则必然在江湖上掀起腥风血雨。DLL正是一种这样的武学。DLL一旦染上了魔性,就不再是正常的DLL程序,而是DLL木马,一种恶贯满盈的病毒,令特洛伊一夜之间国破家亡。

DLL木马的原理

DLL木马的实现原理是编程者在DLL中包含木马程序代码,随后在目标主机中选择特定目标进程,以某种方式强行指定该进程调用包含木马程序的DLL,最终达到侵袭目标系统的目的。

正是DLL程序自身的特点决定了以这种形式加载木马不仅可行,而且具有良好的隐藏性:

(1)DLL程序被映射到宿主进程的地址空间中,它能够共享宿主进程的资源,并根据宿主进程在目标主机的级别非法访问相应的系统资源;

(2)DLL程序没有独立的进程地址空间,从而可以避免在目标主机中留下\"蛛丝马迹\",达到隐蔽自身的目的。

DLL木马实现了\"真隐藏\",我们在任务管理器中看不到木马\"进程\",它完全溶进了系统的内核。与\"真隐藏\"对应的是\"假隐藏\",\"假隐藏\"木马把自己注册成为一个服务。虽然在任务管理器中也看不到这个进程,但是\"假隐藏\"木马本质上还具备独立的进程空间。\"假隐藏\"只适用于Windows9x的系统,对于基于WINNT的操作系统,通过服务管理器,我们可以发现系统中注册过的服务。

DLL木马注入其它进程的方法为远程线程插入。

远程线程插入技术指的是通过在另一个进程中创建远程线程的方法进入那个进程的内存地址空间。将木马程序以DLL的形式实现后,需要使用插入到目标进程中的远程线程将该木马DLL插入到目标进程的地址空间,即利用该线程通过调用Windows API LoadLibrary函数来加载木马DLL,从而实现木马对系统的侵害。

DLL木马注入程序

这里涉及到一个非常重要的Windows API――CreateRemoteThread。与之相比,我们所习惯使用的CreateThread API函数只能在进程自身内部产生一个新的线程,而且被创建的

558 559 560 561 562 新线程与主线程共享地址空间和其他资源。而CreateRemoteThread则不同,它可以在另外的进程中产生线程!CreateRemoteThread有如下特点:

(1)CreateRemoteThread较CreateThread多一个参数hProcess,该参数用于指定要创建线程的远程进程,其函数原型为:

HANDLE CreateRemoteThread(

HANDLE hProcess, //远程进程句柄

LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );

563 564 565 566 567 568 569 570 571

(2)线程函数的代码不能位于我们用来注入DLL木马的进程所在的地址空间中。也就是说,我们不能想当然地自己写一个函数,并把这个函数作为远程线程的入口函数;

(3)不能把本进程的指针作为CreateRemoteThread的参数,因为本进程的内存空间与远程进程的不一样。

以下程序由作者Shotgun的DLL木马注入程序简化而得(单击此处下载,在经典书籍《Windows核心编程》中我们也可以看到类似的例子),它将d盘根目录下的troydll.dll插入到ID为4000的进程中:

#include #include #include

void CheckError ( int, int, char *); //出错处理函数

PDWORD pdwThreadId;

HANDLE hRemoteThread, hRemoteProcess;

DWORD fdwCreate, dwStackSize, dwRemoteProcessId; PWSTR pszLibFileRemote=NULL;

void main(int argc,char **argv) {

int iReturnCode;

char lpDllFullPathName[MAX_PATH]; WCHAR pszLibFileName[MAX_PATH]={0};

dwRemoteProcessId = 4000;

strcpy(lpDllFullPathName, \"d:\\\roydll.dll\");

//将DLL文件全路径的ANSI码转换成UNICODE码

iReturnCode = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, lpDllFullPathName, strlen(lpDllFullPathName), pszLibFileName, MAX_PATH);

CheckError(iReturnCode, 0, \"MultByteToWideChar\"); //打开远程进程

hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD | //允许创建线程 PROCESS_VM_OPERATION | //允许VM操作 PROCESS_VM_WRITE, //允许VM写 FALSE, dwRemoteProcessId );

CheckError( (int) hRemoteProcess, NULL, \"Remote Process not Exist or Access Denied!\");

//计算DLL路径名需要的内存空间

int cb = (1 + lstrlenW(pszLibFileName)) * sizeof(WCHAR);

pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);

CheckError((int)pszLibFileRemote, NULL, \"VirtualAllocEx\"); //将DLL的路径名复制到远程进程的内存空间

iReturnCode = WriteProcessMemory(hRemoteProcess, pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);

CheckError(iReturnCode, false, \"WriteProcessMemory\"); //计算LoadLibraryW的入口地址

PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT(\"Kernel32\")), \"LoadLibraryW\");

CheckError((int)pfnStartAddr, NULL, \"GetProcAddress\"); //启动远程线程,通过远程线程调用用户的DLL文件

hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL);

CheckError((int)hRemoteThread, NULL, \"Create Remote Thread\"); //等待远程线程退出

WaitForSingleObject(hRemoteThread, INFINITE); //清场处理

if (pszLibFileRemote != NULL) {

VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE); }

if (hRemoteThread != NULL) {

CloseHandle(hRemoteThread ); }

if (hRemoteProcess!= NULL) {

CloseHandle(hRemoteProcess);

} }

//错误处理函数CheckError()

void CheckError(int iReturnCode, int iErrorCode, char *pErrorMsg) {

if(iReturnCode==iErrorCode) {

printf(\"%s Error:%d\\n\\n\ //清场处理

if (pszLibFileRemote != NULL) {

VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE); }

if (hRemoteThread != NULL) {

CloseHandle(hRemoteThread ); }

if (hRemoteProcess!= NULL) {

CloseHandle(hRemoteProcess); }

exit(0); } }

572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590

从DLL木马注入程序的源代码中我们可以分析出DLL木马注入的一般步骤为:

(1)取得宿主进程(即要注入木马的进程)的进程ID dwRemoteProcessId;

(2)取得DLL的完全路径,并将其转换为宽字符模式pszLibFileName;

(3)利用Windows API OpenProcess打开宿主进程,应该开启下列选项:

a.PROCESS_CREATE_THREAD:允许在宿主进程中创建线程;

b.PROCESS_VM_OPERATION:允许对宿主进程中进行VM操作;

c.PROCESS_VM_WRITE:允许对宿主进程进行VM写。

(4)利用Windows API VirtualAllocEx函数在远程线程的VM中分配DLL完整路径宽字符所需的存储空间,并利用Windows API WriteProcessMemory函数将完整路径写入该存储空间;

591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 (5)利用Windows API GetProcAddress取得Kernel32模块中LoadLibraryW函数的地址,这个函数将作为随后将启动的远程线程的入口函数;

(6)利用Windows API CreateRemoteThread启动远程线程,将LoadLibraryW的地址作为远程线程的入口函数地址,将宿主进程里被分配空间中存储的完整DLL路径作为线程入口函数的参数以另其启动指定的DLL;

(7)清理现场。

DLL木马的防治

从DLL木马的原理和一个简单的DLL木马程序中我们学到了DLL木马的工作方式,这可以帮助我们更好地理解DLL木马病毒的防治手段。

一般的木马被植入后要打开一网络端口与攻击程序通信,所以防火墙是抵御木马攻击的最好方法。防火墙可以进行数据包过滤检查,我们可以让防火墙对通讯端口进行限制,只允许系统接受几个特定端口的数据请求。这样,即使木马植入成功,攻击者也无法进入到受侵系统,防火墙把攻击者和木马分隔开来了。

对于DLL木马,一种简单的观察方法也许可以帮助用户发现之。我们查看运行进程所依赖的DLL,如果其中有一些莫名其妙的DLL,则可以断言这个进程是宿主进程,系统被植入了DLL木马。\"道高一尺,魔高一丈\",现如今,DLL木马也发展到了更高的境界,它们看起来也不再\"莫名其妙\"。在最新的一些木马里面,开始采用了先进的DLL陷阱技术,编程者用特洛伊DLL替换已知的系统DLL。特洛伊DLL对所有的函数调用进行过滤,对于正常的调用,使用函数转发器直接转发给被替换的系统DLL;对于一些事先约定好的特殊情况,DLL会执行一些相应的操作。

本文给出的只是DLL木马最简单情况的介绍,读者若有兴趣深入研究,可以参考其它资料VC++动态链接库编程之读者反馈与答复

2005-10-25 14:07作者:宋宝强出处:天极网责任编辑:方舟

POPO专区天极软件专题专区精选 到天极软件“读编交流区”畅所欲言 Google专区 Flash MX 视频教程PPT动画演示教程特洛伊木马专区621 622 623 624 625 626 627 628

QQ专区 QQ挂机 了解Web2.0Photoshop视频教程Excel动画教程集网页设计视频教程防火墙应用专区照片处理数字暗房Word动画演示教程Windows Vista专区 注册表应用专区黑客知识教程专区Windows API开发专区网络编程专区 1.关于文章的获取

VB数据库编程专区图像处理与多媒体编程 许多读者发来e-mail询问本系列文章的相关事宜,如: (1) 是否已出版? (2) 哪里可以下载打包版?

629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672

(3) 哪里可以下载笔者的其它文章?

还有一些读者对日前笔者在天极网发表的《C语言嵌入式系统编程修炼之道》非常喜爱,给予了热情洋溢的赞扬,询问笔者能否继续创作嵌入式编程方面的文章。

对于这些问题,统一作答如下:

(1)本系列文章暂时尚未出版;

(2)您可以在天极网软件频道下载笔者的多数拙作。另外,我也将不定期将这些文章上传到我的博客( http://blog.donews.com/21cnbao/)。所有文章中的例程源代码均经过亲手调试,验证无误;

(3)就嵌入式系统开发,笔者将继续进行此方面的创作,新近将推出《基于嵌入式实时OS VxWorks的多任务程序设计》及《领悟:从Windows多线程到VxWorks的多任务》。

非常感谢读者朋友对这些文章的喜爱,在下将竭尽所能地为您提供更多的好文章。

2.关于DLL的疑问

你好,看了你写的\"VC++ DLL编程深入浅出\",特别有收获。 只是有个地方我老搞不明白,就是用DLL导出全局变量时,指定了.lib的路径(#pragma

comment(lib,\"dllTest.lib\")),那么.dll的文件的路径呢,我尝试着把.dll文件移到别的地方程序就无法正常运行了,请问.dll在这里怎么指定。

希望您能在百忙中抽空给我解答一下,不胜感激!

一位编程爱好者

回答:

Windows按下列顺序搜索DLL:

(1)当前进程的可执行模块所在的目录;

(2)当前目录;

(3)Windows 系统目录,通过GetSystemDirectory 函数可获得此目录的路径;

(4)Windows 目录,通过GetWindowsDirectory 函数可获得此目录的路径;

(5)PATH 环境变量中列出的目录。

因此,隐式链接时,DLL文件的路径不需要指定也不能指定,系统指定按照1~5的步

673 674 675 676 677 678 679 680 681 682 683 684 685 骤寻找DLL,但是对应的.lib文件却需要指定路径;如果使用Windows API函数LoadLibrary动态加载DLL,则可以指定DLL的路径。

你好,我是一位C++初学者,我在PCONLINE看了教学之后,受益不浅。我想问一下能否在DLL里使用多线程?MSDN上用#using 这个指令之后实现了多线程,不过好象不支持DLL..

请问有什么办法支持制作多线程DLL??能否给一个源码来?

回答:

在DLL中可以处理多线程,WIN32对于多线程的支持是操作系统本身提供的一种能力,并不在于用户编写的是哪一类程序。即便是一个控制台程序,我们都可以使用多线程:

#include #include void ThreadFun(void) {

while(1) {

printf( \"this is new thread\\n\" ); Sleep( 1000 ); } }

int main() {

DWORD threadID;

CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun, NULL, 0, &threadID ); while(1) {

printf( \"this is main thread\\n\" ); Sleep( 1000 ); } }

686 687 688 689 690 691 692 693 694

观察程序运行的结果为在控制台窗口上交替输出this is main thread、this is new thread。

我们来看下面的一个多线程DLL的例子。

DLL程序提供一个接口函数SendInit,在此接口中启动发送线程SendThreadFunc,在这个线程的对应工作函数中我们使用原始套接字socket发送报文。参考微软出版的经典书籍《Windows核心编程》,我们发现,不宜在DLL被加载的时候(即进程绑定时)启动一个

695 696 697 698 699 700 新的线程。

这个线程等待一个CEvent事件(用于线程间通信),应用程序调用DLL中的接口函数SendMsg( InterDataPkt sendData )可以释放此事件。下面是相关的源代码:

(1)发送报文线程入口函数

/////////////////////////////////////////////////////////////////////////// //函数名:SendThreadFunc

//函数功能:发送报文工作线程入口函数,使用UDP协议

//////////////////////////////////////////////////////////////////////////// DWORD WINAPI SendThreadFunc( LPVOID lpvThreadParm )

//提示:对于线程函数应使用WINAPI声明,WINAPI被宏定义为__stdcall {

/* 创建socket */

sendSock = socket ( AF_INET, SOCK_DGRAM, 0 ); if ( sendSock == INVALID_SOCKET ) {

AfxMessageBox ( \"Socket创建失败\" ); closesocket ( recvSock ); }

/* 获得目标节点端口与地址 */ struct sockaddr_in desAddr; desAddr.sin_family=AF_INET;

desAddr.sin_port=htons( DES_RECV_PORT ); //目标节点接收端口 desAddr.sin_addr.s_addr = inet_addr( DES_IP );

/* 发送数据 */ while(1) {

WaitForSingleObject( hSendEvent, 0xffffffffL );//无限等待事件发生 ResetEvent( hSendEvent );

sendto( sendSock, (char *)sendSockData.data, sendSockData.len, 0, (struct sockaddr*)&desAddr, sizeof(desAddr) ); }

return -1; }

701 702

(2)MFC规则DLL的InitInstance函数

///////////////////////////////////////////////////////////////////////////// // CMultiThreadDllApp initialization

BOOL CMultiThreadDllApp::InitInstance() {

if ( !AfxSocketInit() ) //初始化socket {

AfxMessageBox( IDP_SOCKETS_INIT_FAILED ); return FALSE; }

return TRUE; }

703 704

(3)启动发送线程

////////////////////////////////////////////////////////////////////////////////

//函数名:SendInit

//函数功能:DLL提供给应用程序调用接口,用于启动发送线程

///////////////////////////////////////////////////////////////////////////// void SendInit(void) {

hSendThread = CreateThread( NULL, 1000, SendThreadFunc, this, 1, &uSendThreadID ); }

705 706

(4)SendMsg函数

////////////////////////////////////////////////////////////////////////////////

//函数名:SendMsg

//函数功能:DLL提供给应用程序调用接口,用于发送报文

///////////////////////////////////////////////////////////////////////////// extern \"C\" void WINAPI SendMsg( InterDataPkt sendData ) {

sendSockData = sendData;

SetEvent( hSendEvent ); //释放发送事件 }

707 708 709 710 711 712 713 714 715

以上程序仅仅是一个简单的例子,其实在许多工程应用中,我们经常看到这样的处理方式。这个DLL对用户而言仅仅使一个简单的接口函数SendMsg,对调用它的应用程序屏蔽了多线程的技术细节。与之类似,MFC提供的CSocket类在底层自己采用了多线程机制,所以使我们免去了对多线程的使用。

您好,看了您的DLL文章,发现导出函数可以直接用_declspec(dllexport)声明或在.def文件中定义,变量的导出也一样。我想知道类是否也可以在.def文件中导出?您的文章中只讲了在类前添加_declspec(dllexport)导出类的方法。请您指教!

716 717 718 719 720 721 722 723 724

回答:

一般我们不采用.def文件导出类,但是这并不意味着类不能用.def文件导出类。

使用Depends查看连载2的\"导出类\"例程生成的DLL,我们发现其导出了如图21的众多\"怪\"symbol,这些symbol都是经过编译器处理的。因此,为了以.def文件导出类,我们必须把这些\"怪\"symbol全部导出,实在是不划算啊!所以对于类,我们最好直接以_declspec(dllexport)导出。

图1 导出类时导出的symbol

725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747

您好,看了您的DLL文章,知道怎么创建DLL了,但是面对一个具体的工程,我还是不知道究竟应该把什么做成DLL?您能给一些这方面的经验吗?

回答:

DLL一般用于软件模块中较固定、较通用的可以被复用的模块,这里有一个非常好的例子,就是豪杰超级解霸。梁肇新大师把处理视频和音频的算法模块专门做成了两个DLL,供超级解霸的用户界面GUI程序调用,实在是DLL设计的模范教程。所谓\"万变不离其宗\",超级解霸的界面再cool,用到的还是那几个DLL!具体请参考《编程高手箴言》一书。

您好,您的DLL文章讲的都是Windows的,请问Linux操作系统上可以制作DLL吗?如果能,和Windows有什么不一样?谢谢!

回答:

在Linux操作系统中,也可以采用动态链接技术进行软件设计,但与Windows下DLL的创建和调用方式有些不同。

Linux操作系统中的共享对象技术(Shared Object)与Windows里的DLL相对应,但名称不一样,其共享对象文件以.so作为后缀。与Linux共享对象技术相关的一些函数如下:

(1)打开共享对象,函数原型:

//打开名为filename共享对象,并返回操作句柄; void *dlopen (const char *filename, int flag); 748 749

(2)取函数地址,函数原型:

//获得接口函数地址

void *dlsym(void *handle, char *symbol);

750 751

(3)关闭共享对象,函数原型:

//关闭指定句柄的共享对象 int dlclose (void *handle);

752 753

(4)动态库错误函数,函数原型:

//共享对象操作函数执行失败时,返回出错信息 const char *dlerror(void);

754 755 756 757 758 759 760

从这里我们分明看到Windows API――LoadLibrary、FreeLibrary和GetProcAddress的影子!又一个\"万变不离其宗\"!

本系列文章的连载暂时告一段落,您可以继续给笔者发送email(mailto:

21cnbao@21cn.com)讨论DLL的编程问题。对于文中的错误和纰漏,也热诚欢迎您指正。

因篇幅问题不能全部显示,请点此查看更多更全内容