1、深入浅出 Visual C+动态链接库(Dll)编程动态链接库(DLL)是 Windows 系统的核心,也是 COM 技术的基础,因此突破动态链接库一直是技术人员的攻坚目标,本期专题将由浅入深的介绍动态链接库的基础慨念、分类、实现和应用。VC+动态链接库编程之基础慨念1.概论 先来阐述一下 DLL(Dynamic Linkable Library)的概念,你可以简单的把 DLL 看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上经历了“无库静态链接库动态链接库”的时代。静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直
2、接包含在最终生成的 EXE 文件中了。但是若使用 DLL,该 DLL 不必被包含在最终 EXE文件中,EXE 文件执行时可以“ 动态” 地引用和卸载这个与 EXE 独立的 DLL 文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。对动态链接库,我们还需建立如下概念:(1)DLL 的编制与具体的编程语言及编译器无关只要遵循约定的 DLL 接口规范和调用方式,用各种语言编写的 DLL 都可以相互调用。譬如 Windows提供的系统 DLL(其中包括了 Windows 的 API) ,在任何开发环境中都
3、能被调用,不在乎其是 Visual Basic、Visual C+还是 Delphi。(2)动态链接库随处可见我们在 Windows 目录下的 system32 文件夹中会看到 kernel32.dll、user32.dll 和 gdi32.dll,windows的大多数 API 都包含在这些 DLL 中。kernel32.dll 中的函数主要处理内存管理和进程调度;user32.dll 中的函数主要控制用户界面;gdi32.dll 中的函数则负责图形方面的操作。一般的程序员都用过类似 MessageBox 的函数,其实它就包含在 user32.dll 这个动态链接库中。由此可见 DLL 对我
4、们来说其实并不陌生。(3)VC 动态链接库的分类Visual C+支持三种 DLL,它们分别是 Non-MFC DLL(非 MFC 动态库) 、MFC Regular DLL(MFC规则 DLL) 、MFC Extension DLL(MFC 扩展 DLL) 。非 MFC 动态库不采用 MFC 类库结构,其导出函数为标准的 C 接口,能被非 MFC 或 MFC 编写的应用程序所调用;MFC 规则 DLL 包含一个继承自 CWinApp 的类,但其无消息循环; MFC 扩展 DLL 采用MFC 的动态链接版本创建,它只能被用 MFC 类库所编写的应用程序所调用。由于本文篇幅较长,内容较多,势必需
5、要先对阅读本文的有关事项进行说明,下面以问答形式给出。问:本文主要讲解什么内容?答:本文详细介绍了 DLL 编程的方方面面,努力学完本文应可以对 DLL 有较全面的掌握,并能编写大多数 DLL 程序。问:如何看本文?答:本文每一个主题的讲解都附带了源代码例程,可以随文下载(每个工程都经 WINRAR 压缩) 。所有这些例程都由笔者编写并在 VC+6.0 中调试通过。当然看懂本文不是读者的最终目的,读者应亲自动手实践才能真正掌握 DLL 的奥妙。问:学习本文需要什么样的基础知识?答:如果你掌握了 C,并大致掌握了 C+,了解一点 MFC 的知识,就可以轻松地看懂本文。2.静态链接库对静态链接库的
6、讲解不是本文的重点,但是在具体讲解 DLL 之前,通过一个静态链接库的例子可以快速地帮助我们建立“库”的概念。图 1 建立一个静态链接库如图 1,在 VC+6.0 中 new 一个名称为 libTest 的 static library 工程(单击此处下载本工程) ,并新建 lib.h 和 lib.cpp 两个文件,lib.h 和 lib.cpp 的源代码如下:/文件:lib.h#ifndef LIB_H#define LIB_Hextern “C“ int add(int x,int y); /声明为 C 编译、连接方式的外部函数#endif/文件:lib.cpp#include “lib.
7、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 #in
8、clude “lib.h“#pragma comment( lib, “debuglibTest.lib“ ) /指定与静态库一起连接int main(int argc, char* argv)printf( “2 + 3 = %d“, add( 2, 3 ) );静态链接库的调用就是这么简单,或许我们每天都在用,可是我们没有明白这个概念。代码中#pragma comment( lib , “debuglibTest.lib“ )的意思是指本文件生成的.obj 文件应与 libTest.lib 一起连接。如果不用#pragma comment 指定,则可以直接在 VC+中设置,如图 2,依次选
9、择tools、options 、directories、library files 菜单或选项,填入库文件路径。图 2 中加红圈的部分为我们添加的 libTest.lib 文件的路径。图 2 在 VC 中设置库文件路径这个静态链接库的例子至少让我们明白了库函数是怎么回事,它们是哪来的。我们现在有下列模糊认识了:(1)库不是个怪物,编写库的程序和编写一般的程序区别不大,只是库不能单独执行;(2)库提供一些可以给别的程序调用的东东,别的程序要调用它必须以某种方式指明它要调用之。以上从静态链接库分析而得到的对库的懵懂概念可以直接引申到动态链接库中,动态链接库与静态链接库在编写和调用上的不同体现在库的
10、外部接口定义及调用方式略有差异。4.1 一个简单的 DLL 第 2 节给出了以静态链接库方式提供 add 函数接口的方法,接下来我们来看看怎样用动态链接库实现一个同样功能的 add 函数。如图 6,在 VC+中 new 一个 Win32 Dynamic-Link Library 工程 dllTest(单击此处下载本工程) 。注意不要选择 MFC AppWizard(dll),因为用 MFC AppWizard(dll)建立的将是第 5、6 节要讲述的 MFC 动态链接库。图 6 建立一个非 MFC DLL在建立的工程中添加 lib.h 及 lib.cpp 文件,源代码如下:/* 文件名:lib
11、.h */#ifndef LIB_H#define LIB_Hextern “C“ int _declspec(dllexport)add(int x, int y);#endif/* 文件名:lib.cpp */#include “lib.h“int add(int x, int y)return x + y;与第 2 节对静态链接库的调用相似,我们也建立一个与 DLL 工程处于同一工作区的应用工程dllCall,它调用 DLL 中的函数 add,其源代码如下:#include #include typedef int(*lpAddFun)(int, int); /宏定义函数指针类型int
12、main(int argc, char *argv)HINSTANCE hDll; /DLL 句柄 lpAddFun addFun; /函数指针hDll = LoadLibrary(“DebugdllTest.dll“);if (hDll != NULL)addFun = (lpAddFun)GetProcAddress(hDll, “add“);if (addFun != NULL)int result = addFun(2, 3);printf(“%d“, result);FreeLibrary(hDll);return 0;分析上述代码,dllTest 工程中的 lib.cpp 文件与第
13、 2 节静态链接库版本完全相同,不同在于 lib.h 对函数 add 的声明前面添加了_declspec(dllexport)语句。这个语句的含义是声明函数 add 为 DLL 的导出函数。DLL 内的函数分为两种:(1)DLL 导出函数,可供应用程序调用;(2) DLL 内部函数,只能在 DLL 程序使用,应用程序无法调用它们。而应用程序对本 DLL 的调用和对第 2 节静态链接库的调用却有较大差异,下面我们来逐一分析。首先,语句 typedef int ( * lpAddFun)(int,int)定义了一个与 add 函数接受参数类型和返回值均相同的函数指针类型。随后,在 main 函数中
14、定义了 lpAddFun 的实例 addFun;其次,在函数 main 中定义了一个 DLL HINSTANCE 句柄实例 hDll,通过 Win32 Api 函数LoadLibrary 动态加载了 DLL 模块并将 DLL 模块句柄赋给了 hDll;再次,在函数 main 中通过 Win32 Api 函数 GetProcAddress 得到了所加载 DLL 模块中函数 add 的地址并赋给了 addFun。经由函数指针 addFun 进行了对 DLL 中 add 函数的调用;最后,应用工程使用完 DLL 后,在函数 main 中通过 Win32 Api 函数 FreeLibrary 释放了已
15、经加载的DLL 模块。通过这个简单的例子,我们获知 DLL 定义和调用的一般概念:(1)DLL 中需以某种特定的方式声明导出函数(或变量、类) ;(2)应用工程需以某种特定的方式调用 DLL 的导出函数(或变量、类) 。下面我们来对“特定的方式进行 ”阐述。4.2 声明导出函数DLL 中导出函数的声明有两种方式:一种为 4.1 节例子中给出的在函数声明中加上_declspec(dllexport),这里不再举例说明;另外一种方式是采用模块定义(.def) 文件声明,.def 文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。下面的代码演示了怎样同.def 文件将函数 add 声明为
16、 DLL 导出函数(需在 dllTest 工程中添加 lib.def文件):; lib.def : 导出 DLL 函数LIBRARY dllTestEXPORTSadd 1.def 文件的规则为:(1)LIBRARY 语句说明.def 文件相应的 DLL;(2)EXPORTS 语句后列出要导出函数的名称。可以在.def 文件中的导出函数名后加n,表示要导出函数的序号为 n(在进行函数调用时,这个序号将发挥其作用) ;(3).def 文件中的注释由每个注释行开始处的分号 (;) 指定,且注释不能与语句共享一行。由此可以看出,例子中 lib.def 文件的含义为生成名为“dllTest”的动态链接
17、库,导出其中的 add 函数,并指定 add 函数的序号为 1。4.3 DLL 的调用方式在 4.1 节的例子中我们看到了由“LoadLibrary-GetProcAddress-FreeLibrary”系统 Api 提供的三位一体“DLL 加载-DLL 函数地址获取-DLL 释放” 方式,这种调用方式称为 DLL 的动态调用。动态调用方式的特点是完全由编程者用 API 函数加载和卸载 DLL,程序员可以决定 DLL 文件何时加载或不加载,显式链接在运行时决定加载哪个 DLL 文件。与动态调用方式相对应的就是静态调用方式, “有动必有静” ,这来源于物质世界的对立统一。 “动与静”,其对立与统
18、一竟无数次在技术领域里得到验证,譬如静态 IP 与 DHCP、静态路由与动态路由等。从前文我们已经知道,库也分为静态库与动态库 DLL,而想不到,深入到 DLL 内部,其调用方式也分为静态与动态。 “动与静” ,无处不在。 周易已认识到有动必有静的动静平衡观, 易系辞曰:“动静有常,刚柔断矣”。哲学意味着一种普遍的真理,因此,我们经常可以在枯燥的技术领域看到哲学的影子。静态调用方式的特点是由编译系统完成对 DLL 的加载和应用程序结束时 DLL 的卸载。当调用某DLL 的应用程序结束时,若系统中还有其它程序使用该 DLL,则 Windows 对 DLL 的应用记录减 1,直到所有使用该 DLL
19、 的程序都结束时才释放它。静态调用方式简单实用,但不如动态调用方式灵活。下面我们来看看静态调用的例子(单击此处下载本工程) ,将编译 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);
20、printf(“%d“,result);return 0;由上述代码可以看出,静态调用方式的顺利进行需要完成两个动作:(1)告诉编译器与 DLL 相对应的 .lib 文件所在的路径及文件名,#pragma comment(lib,“dllTest.lib“)就是起这个作用。程序员在建立一个 DLL 文件时,连接器会自动为其生成一个对应的.lib 文件,该文件包含了 DLL 导出函数的符号名及序号(并不含有实际的代码) 。在应用程序里,.lib 文件将作为 DLL 的替代文件参与编译。(2)声明导入函数,extern “C“ _declspec(dllimport) add(int x,int
21、y)语句中的_declspec(dllimport) 发挥这个作用。静态调用方式不再需要使用系统 API 来加载、卸载 DLL 以及获取 DLL 中导出函数的地址。这是因为,当程序员通过静态链接方式编译生成应用程序时,应用程序中调用的与.lib 文件中导出符号相匹配的函数符号将进入到生成的 EXE 文件中,.lib 文件中所包含的与之对应的 DLL 文件的文件名也被编译器存储在 EXE 文件内部。当应用程序运行过程中需要加载 DLL 文件时,Windows 将根据这些信息发现并加载DLL,然后通过符号名实现对 DLL 函数的动态链接。这样,EXE 将能直接通过函数名调用 DLL 的输出函数,就
22、象调用程序内部的其他函数一样。第 4 节我们对非 MFC DLL 进行了介绍,这一节将详细地讲述 MFC 规则 DLL 的创建与使用技巧。 另外,自从本文开始连载后,收到了一些读者的 e-mail。有的读者提出了一些问题,笔者将在本文的最后一次连载中选取其中的典型问题进行解答。由于时间的关系,对于读者朋友的来信,笔者暂时不能一一回复,还望海涵!由于笔者的水平有限,文中难免有错误和纰漏,也热诚欢迎读者朋友不吝指正!5. MFC 规则 DLL5.1 概述MFC 规则 DLL 的概念体现在两方面:(1) 它是 MFC 的“是 MFC 的”意味着可以在这种 DLL 的内部使用 MFC;(2) 它是规则
23、的“是规则的” 意味着它不同于 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 与
24、MFC 库(包括 MFC 扩展 DLL)静态链接,将 MFC 库的代码直接生成在.dll 文件中。在调用这种 DLL 的接口时,MFC 使用 DLL 的资源。因此,在静态链接到 MFC 的规则 DLL 中不需要进行模块状态的切换。使用这种方法生成的规则 DLL 其程序较大,也可能包含重复的代码。(2)动态链接到 MFC 的规则 DLL动态链接到 MFC 的规则 DLL 可以和使用它的可执行文件同时动态链接到 MFC DLL 和任何 MFC扩展 DLL。在使用了 MFC 共享库的时候,默认情况下,MFC 使用主应用程序的资源句柄来加载资源模板。这样,当 DLL 和应用程序中存在相同 ID 的资源
25、时(即所谓的资源重复问题) ,系统可能不能获得正确的资源。因此,对于共享 MFC DLL 的规则 DLL,我们必须进行模块切换以使得 MFC 能够找到正确的资源模板。我们可以在 Visual C+中设置 MFC 规则 DLL 是静态链接到 MFC DLL 还是动态链接到 MFC DLL。如图 8,依次选择 Visual C+的 project - Settings - General 菜单或选项,在 Microsoft Foundation Classes 中进行设置。图 8 设置动态/静态链接 MFC DLL5.2 MFC 规则 DLL 的创建我们来一步步讲述使用 MFC 向导创建 MFC
26、规则 DLL 的过程,首先新建一个 project,如图 9,选择project 的类型为 MFC AppWizard(dll)。点击 OK 进入如图 10 所示的对话框。图 9 MFC DLL 工程的创建图 10 所示对话框中的 1 区选择 MFC DLL 的类别。2 区选择是否支持 automation(自动化)技术, automation 允许用户在一个应用程序中操纵另外一个应用程序或组件。例如,我们可以在应用程序中利用 Microsoft Word 或 Microsoft Excel 的工具,而这种使用对用户而言是透明的。自动化技术可以大大简化和加快应用程序的开发。3 区选择是否支持
27、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;4 区选择是否由 MFC 向导自动
28、在源代码中添加注释,一般我们选择“Yes,please” 。图 10 MFC DLL 的创建选项动态链接库 DLL 实现了库的共享,体现了代码重用的思想。我们可以把广泛的、具有共性的、能够多次被利用的函数和类定义在库中。这样,在再次使用这些函数和类的时候,就不再需要重新添加与这些函数和类相关的代码。具有共性的问题大致有哪些呢?笔者归纳如下: (1)通用的算法图像处理、视频音频解码、压缩与解压缩、加密与解密通常采用某些特定的算法,这些算法较固定且在这类程序中往往经常被使用。(2)纯资源 DLL我们可以从 DLL 中获取资源,对于一个支持多种语言的应用程序而言,我们可以判断操作系统的语言,并自动为
29、应用程序加载与 OS 对应的语言。这是多语言支持应用程序的一般做法。(3)通信控制 DLL串口、网口的通信控制函数如果由 DLL 提供则可以使应用程序轻松不少。在工业控制、modem 程序甚至 socket 通信中,经常使用通信控制 DLL。本节将给出 DLL 的三个典型应用实例。7.1 算法 DLL我们直接用读者的一个提问作为例子。宋宝华先生,您好!我在 上看到你连载的 VC+动态链接库编程 ,觉得非常好。我以前主要是用Delphi 的,C/C+学过,对 Win32 和 VCL 比较熟悉,但是没有接触过 VC+,对 MFC 很陌生。这段时间和一个同学合作做光学成像的计算机模拟,用到傅立叶变
30、换,手里面有例程是 VC+写的。我们的界面是用 Delphi 开发,需要将其傅立叶变换功能提出做一个 DLL 供 Delphi 调用。苦于不懂 MFC,试了很多方法,都不成功,最后只得采用折衷方案,简单修改一下程序,传一个参数进去,当作 exe 来调用,才没有耽搁后续进程。谢谢!致礼!某某学习过较高级别数学(概率统计与随机过程) 、信号与线性系统及数字信号处理的读者应该知道,傅立叶变换是一种在信号分析中常用的算法,用于时域和频域的相互转换。FFT 变换算法通用而有共性,我们适宜把它集成在一个 DLL 中。随后,这位读者提供了这样的一个函数:/* 函数名称:FFT()* 参数 :* comple
31、x * 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 count / 2;X1 = new complexcount;X2 = new complexcount;/ 计
32、算加权系数for(i = 0; i (cos(angle), sin(angle);/ 将时域点写入 X1memcpy(X1, TD, sizeof(complex) * count);/ 采用蝶形算法进行快速傅立叶变换for(k = 0; k using namespace std;extern “C“ void _declspec(dllexport) _stdcall FFT(complex * TD, complex * FD, int r);#define PI 3.1415926#endiffft.cpp 的源代码为:/* 文件名:fft.cpp */#include “fft.h
33、“void _stdcall FFT(complex * TD, complex * FD, int r)/读者提供的函数代码在任何编程语言中使用 Win32 API LoadLibrary 都可以加载这个 DLL,而使用 GetProcAddress(hDll, “FFT“)则可以获得函数 FFT 的地址,读者所提到的 Delphi 当然也不例外。这个 DLL 中有两点需要注意:(1)使用 extern “C“修饰函数声明,否则,生成的 DLL 只能供 C+调用;(2)使用_stdcall 修饰函数声明及定义,_stdcall 是 Windows API 的函数调用方式。从前文可知,DLL
34、在程序编制中可作出巨大贡献,它提供了具共性代码的复用能力。但是,正如一门高深的武学,若被掌握在正义之侠的手上,便可助其仗义江湖;但若被掌握在邪恶之徒的手上,则必然在江湖上掀起腥风血雨。DLL 正是一种这样的武学。 DLL 一旦染上了魔性,就不再是正常的 DLL 程序,而是DLL 木马,一种恶贯满盈的病毒,令特洛伊一夜之间国破家亡。 DLL 木马的原理DLL 木马的实现原理是编程者在 DLL 中包含木马程序代码,随后在目标主机中选择特定目标进程,以某种方式强行指定该进程调用包含木马程序的 DLL,最终达到侵袭目标系统的目的。正是 DLL 程序自身的特点决定了以这种形式加载木马不仅可行,而且具有良
35、好的隐藏性:(1)DLL 程序被映射到宿主进程的地址空间中,它能够共享宿主进程的资源,并根据宿主进程在目标主机的级别非法访问相应的系统资源;(2)DLL 程序没有独立的进程地址空间,从而可以避免在目标主机中留下“ 蛛丝马迹“,达到隐蔽自身的目的。DLL 木马实现了 “真隐藏“,我们在任务管理器中看不到木马“进程“,它完全溶进了系统的内核。与“ 真隐藏“对应的是“假隐藏“,“假隐藏“木马把自己注册成为一个服务。虽然在任务管理器中也看不到这个进程,但是“假隐藏“木马本质上还具备独立的进程空间。“假隐藏“ 只适用于 Windows9x 的系统,对于基于WINNT 的操作系统,通过服务管理器,我们可以
36、发现系统中注册过的服务。DLL 木马注入其它进程的方法为远程线程插入。远程线程插入技术指的是通过在另一个进程中创建远程线程的方法进入那个进程的内存地址空间。将木马程序以 DLL 的形式实现后,需要使用插入到目标进程中的远程线程将该木马 DLL 插入到目标进程的地址空间,即利用该线程通过调用 Windows API LoadLibrary 函数来加载木马 DLL,从而实现木马对系统的侵害。DLL 木马注入程序这里涉及到一个非常重要的 Windows APICreateRemoteThread 。与之相比,我们所习惯使用的CreateThread API 函数只能在进程自身内部产生一个新的线程,而
37、且被创建的新线程与主线程共享地址空间和其他资源。而 CreateRemoteThread 则不同,它可以在另外的进程中产生线程!CreateRemoteThread 有如下特点:(1)CreateRemoteThread 较 CreateThread 多一个参数 hProcess,该参数用于指定要创建线程的远程进程,其函数原型为:HANDLE CreateRemoteThread(HANDLE hProcess, /远程进程句柄LPSECURITY_ATTRIBUTES lpThreadAttributes,SIZE_T dwStackSize,LPTHREAD_START_ROUTINE l
38、pStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);(2)线程函数的代码不能位于我们用来注入 DLL 木马的进程所在的地址空间中。也就是说,我们不能想当然地自己写一个函数,并把这个函数作为远程线程的入口函数;(3)不能把本进程的指针作为 CreateRemoteThread 的参数,因为本进程的内存空间与远程进程的不一样。1.关于文章的获取 许多读者发来 e-mail 询问本系列文章的相关事宜,如:(1) 是否已出版?(2) 哪里可以下载打包版?(3) 哪里可以下载笔者的其它文章?还有一些读者对日
39、前笔者在天极网发表的 C 语言嵌入式系统编程修炼之道非常喜爱,给予了热情洋溢的赞扬,询问笔者能否继续创作嵌入式编程方面的文章。对于这些问题,统一作答如下:(1)本系列文章暂时尚未出版;(2)您可以在天极网软件频道下载笔者的多数拙作。另外,我也将不定期将这些文章上传到我的博客( http:/ 。所有文章中的例程源代码均经过亲手调试,验证无误; (3)就嵌入式系统开发,笔者将继续进行此方面的创作,新近将推出基于嵌入式实时 OS VxWorks 的多任务程序设计及领悟:从 Windows 多线程到 VxWorks 的多任务 。非常感谢读者朋友对这些文章的喜爱,在下将竭尽所能地为您提供更多的好文章。2
40、.关于 DLL 的疑问你好,看了你写的“VC+ DLL 编程深入浅出“ ,特别有收获。 只是有个地方我老搞不明白,就是用DLL 导出全局变量时,指定了.lib 的路径(#pragma comment(lib,“dllTest.lib“)) ,那么.dll 的文件的路径呢,我尝试着把.dll 文件移到别的地方程序就无法正常运行了,请问.dll 在这里怎么指定。希望您能在百忙中抽空给我解答一下,不胜感激!一位编程爱好者回答:Windows 按下列顺序搜索 DLL:(1)当前进程的可执行模块所在的目录;(2)当前目录;(3)Windows 系统目录,通过 GetSystemDirectory 函数可
41、获得此目录的路径;(4)Windows 目录,通过 GetWindowsDirectory 函数可获得此目录的路径;(5)PATH 环境变量中列出的目录。因此,隐式链接时,DLL 文件的路径不需要指定也不能指定,系统指定按照 15 的步骤寻找 DLL,但是对应的.lib 文件却需要指定路径;如果使用 Windows API 函数 LoadLibrary 动态加载 DLL,则可以指定 DLL 的路径。你好,我是一位 C+初学者,我在 PCONLINE 看了教学之后,受益不浅。我想问一下能否在 DLL 里使用多线程?MSDN 上用#using 这个指令之后实现了多线程 ,不过好象不支持 DLL请问
42、有什么办法支持制作多线程 DLL?能否给一个源码来?回答:在 DLL 中可以处理多线程,WIN32 对于多线程的支持是操作系统本身提供的一种能力,并不在于用户编写的是哪一类程序。即便是一个控制台程序,我们都可以使用多线程:#include #include void ThreadFun(void)while(1)printf( “this is new threadn“ );Sleep( 1000 ); int main()DWORD threadID;CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun, NULL, 0, whil
43、e(1)printf( “this is main threadn“ );Sleep( 1000 );观察程序运行的结果为在控制台窗口上交替输出 this is main thread、this is new thread。我们来看下面的一个多线程 DLL 的例子。DLL 程序提供一个接口函数 SendInit,在此接口中启动发送线程 SendThreadFunc,在这个线程的对应工作函数中我们使用原始套接字 socket 发送报文。参考微软出版的经典书籍Windows 核心编程 ,我们发现,不宜在 DLL 被加载的时候(即进程绑定时)启动一个新的线程。这个线程等待一个 CEvent 事件(用
44、于线程间通信) ,应用程序调用 DLL 中的接口函数 SendMsg( InterDataPkt sendData )可以释放此事件。下面是相关的源代码:(1)发送报文线程入口函数/函数名:SendThreadFunc/函数功能:发送报文工作线程入口函数,使用 UDP 协议/DWORD WINAPI SendThreadFunc( LPVOID lpvThreadParm ) /提示:对于线程函数应使用 WINAPI 声明,WINAPI 被宏定义为_stdcall/* 创建 socket */sendSock = socket ( AF_INET, SOCK_DGRAM, 0 );if ( s
45、endSock = 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( hSendEven
46、t, 0xffffffffL );/无限等待事件发生ResetEvent( hSendEvent ); sendto( sendSock, (char *)sendSockData.data, sendSockData.len, 0, (struct sockaddr*) return -1;(2)MFC 规则 DLL 的 InitInstance 函数/ CMultiThreadDllApp initializationBOOL CMultiThreadDllApp:InitInstance()if ( !AfxSocketInit() ) /初始化 socketAfxMessageBox(
47、 IDP_SOCKETS_INIT_FAILED );return FALSE;return TRUE;(3)启动发送线程/函数名:SendInit/函数功能:DLL 提供给应用程序调用接口,用于启动发送线程/void SendInit(void)hSendThread = CreateThread( NULL, 1000, SendThreadFunc, this, 1, (4)SendMsg 函数/函数名:SendMsg/函数功能:DLL 提供给应用程序调用接口,用于发送报文/extern “C“ void WINAPI SendMsg( InterDataPkt sendData ) sendSockData = sendData;SetEvent( hSendEvent ); /释放发送事件以上程序仅仅是一个简单的例子,其实在许多工程应用中,我们经常看到这样的处理方式。这个 DLL对用户而言仅仅使一个简单的接口函数 SendMsg,对调用它的应用程序屏蔽了多线程的技术细节。与之类似,MFC 提供