??xml version="1.0" encoding="utf-8" standalone="yes"?>
自从微Y推出16位的Windows操作pȝP此后每种版本的Windows操作pȝ都非怾赖于动态链接库(DLL)中的函数和数据,实际上Windows操作pȝ中几乎所有的内容都由DLL以一U或另外一UŞ式代表着Q例如显C的字体和图标存储在GDI DLL中、显CWindows桌面和处理用L输入所需要的代码被存储在一个User DLL中、Windows~程所需要的大量的API函数也被包含在Kernel DLL中?br>
在Windows操作pȝ中用DLL有很多优点,最主要的一Ҏ多个应用E序、甚x不同语言~写的应用程序可以共享一个DLL文gQ真正实C资源"׃n"Q大大羃了应用E序的执行代码,更加有效的利用了内存Q用DLL的另一个优ҎDLL文g作ؓ一个单独的E序模块Q封装性、独立性好Q在软g需要升U的时候,开发h员只需要修改相应的DLL文g可以了Q而且Q当DLL中的函数改变后,只要不是参数的改?E序代码q不需要重新编译。这在编E时十分有用Q大大提高了软g开发和l护的效率?br>
既然DLL那么重要Q所以搞清楚什么是DLL、如何在Windows操作pȝ中开发用DLL是程序开发h员不得不解决的一个问题。本文针对这些问题,通过一个简单的例子Q即在一个DLL中实现比较最大、最整数这两个单函敎ͼ全面地解析了在Visual C++~译环境下编E实现DLL的过E,文章中所用到的程序代码在Windows98pȝ、Visual C++6.0~译环境下通过?br>
二、DLL的概?/strong>
DLL是徏立在客户/服务器通信的概念上Q包含若q函数、类或资源的库文Ӟ函数和数据被存储在一个DLLQ服务器Q上q由一个或多个客户导出而用,q些客户可以是应用程序或者是其它的DLL。DLL库不同于静态库Q在静态库情况下,函数和数据被~译q一个二q制文gQ通常扩展名ؓ*.LIBQ,Visual C++的编译器在处理程序代码时从静态库中恢复这些函数和数据q把他们和应用程序中的其他模块组合在一L成可执行文g。这个过E称?静态链?Q此时因为应用程序所需的全部内定w是从库中复制了出来,所以静态库本nq不需要与可执行文件一起发行?br>
?span style="COLOR: #ff0000">动态库的情况下Q有两个文gQ一个是引入库(.LIBQ文Ӟ一个是DLL文gQ引入库文g包含被DLL导出的函数的名称和位|,DLL包含实际的函数和数据Q?/span>应用E序使用LIB文g链接到所需要用的DLL文gQ库中的函数和数据ƈ不复制到可执行文件中Q因?span style="COLOR: #ff0000">在应用程序的可执行文件中Q存攄不是被调用的函数代码Q而是DLL中所要调用的函数的内存地址Q?/u>q样当一个或多个应用E序q行是再把程序代码和被调用的函数代码链接hQ从而节省了内存资源。从上面的说明可以看出,DLL?LIB文g必须随应用程序一起发行,否则应用E序会产生错误?br>
微Y的Visual C++支持三种DLLQ它们分别是Non-MFC DllQ?span style="COLOR: #ff0000">非MFC动态库Q、Regular DllQ?span style="COLOR: #ff0000">常规DLLQ、Extension DllQ?span style="COLOR: #ff0000">扩展DLLQ?span style="COLOR: #ff0000">Non-MFC DLL指的是不用MFC的类库结构,直接用C语言写的DLLQ其导出的函数是标准的C接口Q能被非MFC或MFC~写的应用程序所调用?/span>Regular DLL:和下q的Extension Dlls一P是用MFCcd~写的,它的一个明昄特点是在源文仉有一个承CWinApp的类Q注意:此类DLL虽然从CWinAppzQ但没有消息循环Q?被导出的函数是C函数、C++cL者C++成员函数Q注意不要把术语C++cMMFC的微软基C++cȝhQ,调用常规DLL的应用程序不必是MFC应用E序Q只要是能调用类C函数的应用程序就可以Q它们可以是在Visual C++、Dephi、Visual Basic、Borland C{编译环境下利用DLL开发应用程序?br>
常规DLL又可l分成静态链接到MFC和动态链接到MFC上的Q这两种常规DLL的区别将在下面介l。与常规DLL相比Q用扩展DLL用于导出增强MFC基础cȝ函数或子c,用这U类型的动态链接库Q可以用来输Z个从MFC所l承下来的类?br>
扩展DLL是用MFC的动态链接版本所创徏的,q且它只被用MFCcd所~写的应用程序所调用。例如你已经创徏了一个从MFC的CtoolBarcȝzcȝ于创Z个新的工hQؓ了导个类Q你必须把它攑ֈ一个MFC扩展的DLL中?span style="COLOR: #ff0000">扩展DLL 和常规DLL不一P它没有一个从CWinAppl承而来的类的对象,所以,开发h员必dDLL中的DllMain函数d初始化代码和l束代码?/span>
三、动态链接库的创?br>
在Visual C++6.0开发环境下Q打开File\New\Project选项Q可以选择Win32 Dynamic-Link Library或MFC AppWizard[dll]来以不同的方式来创徏Non-MFC Dll、Regular Dll、Extension Dll{不同种cȝ动态链接库?br>
1Q?Win32 Dynamic-Link Library方式创徏Non-MFC DLL动态链接库
每一个DLL必须有一个入口点Q这p我们用C~写的应用程序一P必须有一个WINMAIN函数一栗在Non-MFC DLL中DllMain是一个缺省的入口函数Q你不需要编写自qDLL入口函数Q用q个~省的入口函数就能动态链接库被调用时得到正确的初始化。如果应用程序的DLL需?span style="COLOR: #ff0000">分配额外的内存或资源Ӟ或者说需?span style="COLOR: #ff0000">Ҏ个进E或U程初始化和清除操作Ӟ需要在相应的DLL工程?CPP文g中对DllMain()函数按照下面的格式书写?br>
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; } |
参数中,hMoudle是动态库被调用时所传递来的一个指向自q句柄(实际上,它是指向_DGROUPD늚一个选择W?Qul_reason_for_call是一个说明动态库被调原因的标志,当进E或U程装入或卸载动态链接库的时候,操作pȝ调用入口函数Qƈ说明动态链接库被调用的原因Q它所有的可能gؓQDLL_PROCESS_ATTACH: q程被调用、DLL_THREAD_ATTACH: U程被调用、DLL_PROCESS_DETACH: q程被停止、DLL_THREAD_DETACH: U程被停止;lpReservedZ留参数。到此ؓ止,DLL的入口函数已l写了,剩下部分的实C不难Q你可以在DLL工程中加入你所惌输出的函数或变量了?br>
我们已经知道DLL是包含若q个函数的库文gQ应用程序用DLL中的函数之前Q应该先导出q些函数Q以便供l应用程序用。要导出q些函数有两U方?/span>Q?span style="COLOR: #ff0000">一是在定义函数时用导出关键字_declspec(dllexport)Q?/span>另外一U方?span style="COLOR: #ff0000">是在创徏DLL文g时用模块定义文?Def。需要读者注意的是在使用W一U方法的时候,不能使用DEF文g。下面通过两个例子来说明如何用这两种Ҏ创徏DLL文g?br>
1Q用导出函数关键字_declspec(dllexport)创徏MyDll.dllQ该动态链接库中有两个函数Q分别用来实现得C个数的最大和最数。在MyDll.h和MyDLL.cpp文g中分别输入如下原代码Q?br>
//MyDLL.h extern "C" _declspec(dllexport) int Max(int a, int b); extern "C" _declspec(dllexport) int Min(int a, int b); //MyDll.cpp #include<stdio.h></stdio.h> #include"MyDll.h" int Max(int a, int b) { if(a>=b)return a; else return b; } int Min(int a, int b) { if(a>=b)return b; else return a; } |
该动态链接库~译成功后,打开MyDll工程中的debug目录Q可以看到MyDll.dll、MyDll.lib两个文g?span style="COLOR: #ff0000">LIB文g中包含DLL文g名和DLL文g中的函数名等Q该LIB文g只是对应该DLL文g?映像文g"Q与DLL文g中,LIB文g的长度要的多,在进?u>隐式链接DLL时要用到它?/span>读者可能已l注意到在MyDll.h中有关键?/span>"extern C"Q它可以使其他编E语a讉K你编写的DLL中的函数?br>
2Q用.def文g创徏工程MyDll
Z?def文g创徏DLLQ请先删除上个例子创建的工程中的MyDll.h文gQ保留MyDll.cppq在该文件头删除#include MyDll.h语句Q同时往该工E中加入一个文本文Ӟ命名为MyDll.defQ再在该文g中加入如下代码:
LIBRARY MyDll
EXPORTS
Max
Min
其中LIBRARY语句说明该def文g是属于相应DLL的,EXPORTS语句下列导出的函数名U。我们可以在.def文g中的导出函数后加@nQ如Max@1QMin@2Q表C导出的函数顺序号Q在q行昑ּq时可以用到它。该DLL~译成功后,打开工程中的Debug目录Q同样也会看到MyDll.dll和MyDll.lib文g?br>
2QMFC AppWizard[dll]方式生成常规/扩展DLL
在MFC AppWizard[dll]下生成DLL文g又有三种方式Q在创徏DLL是,要根据实际情况选择创徏DLL的方式。一U是常规DLL静态链接到MFCQ另一U是常规DLL动态链接到MFC。两者的区别是:前者用的是MFC的静态链接库Q生成的DLL文g长度大,一般不使用q种方式Q后者用MFC的动态链接库Q生成的DLL文g长度;动态链接到MFC的规则DLL所有输出的函数应该以如下语句开?/span>Q?
AFX_MANAGE_STATE(AfxGetStaticModuleState( )) //此语句用来正地切换MFC模块状?/td> |
最后一U是MFC扩展DLLQ这UDLL特点是用?u>建立MFC的派生类QDll只被用MFCcd所~写的应用程序所调用。前面我们已l介l过QExtension DLLs 和Regular DLLs不一P?span style="COLOR: #ff0000">没有一个从CWinAppl承而来的类的对?/span>Q编译器默认了一个DLL入口函数DLLMain()作ؓ对DLL的初始化Q你可以在此函数中实现初始化,代码如下Q?br>
BOOL WINAPI APIENTRY DLLMain(HINSTANCE hinstDllQDWORD reason QLPVOID flmpload) { switch(reason) { ……………//初始化代码; } return true; } |
参数hinstDll存放DLL的句柄,参数reason指明调用函数的原因,lpReserved是一个被pȝ所保留的参数。对于隐式链接是一个非零|对于昑ּ链接值是零?br>
在MFC下徏立DLL文gQ会自动生成def文g框架Q其它与建立传统的Non-MFC DLL没有什么区别,只要在相应的头文件写入关键字_declspec(dllexport)函数cd和函数名{,或在生成的def文g中EXPORTS下输入函数名可以了。需要注意的是在向其它开发h员分发MFC扩展DLL Ӟ不要忘记提供描述DLL中类的头文g以及相应?LIB文g和DLL本nQ此后开发h员就能充分利用你开发的扩展DLL了?
四、动态链接库DLL的链?br>
应用E序使用DLL可以采用两种方式Q一U是隐式链接Q另一U是昑ּ链接。在使用DLL之前首先要知道DLL中函数的l构信息。Visual C++6.0在VC\bin目录下提供了一个名?span style="COLOR: #ff0000">Dumpbin.exe的小E序Q用它可以查看DLL文g中的函数l构。另外,Windowspȝ遵循下面的搜烦序来定位DLLQ?1Q?span style="COLOR: #ff0000">包含EXE文g的目录,2Q进E的当前工作目录Q?3QWindowspȝ目录Q?4QWindows目录Q?Q列在Path环境变量中的一pd目录?br>
1Q隐式链?br>
隐式链接是在程序开始执行时将DLL文g加蝲到应用程序当中。实现隐式链接很ҎQ只要将导入函数关键字_declspec(dllimport)函数名等写到应用E序相应的头文g中就可以了。下面的例子通过隐式链接调用MyDll.dll库中的Min函数。首先生成一个项目ؓTestDllQ在DllTest.h、DllTest.cpp文g中分别输入如下代码:
//Dlltest.h #pragma comment(libQ?MyDll.lib") extern "C"_declspec(dllimport) int Max(int a,int b); extern "C"_declspec(dllimport) int Min(int a,int b); //TestDll.cpp #include<stdio.h></stdio.h> #include"Dlltest.h" void main() {int a; a=min(8,10) printf("比较的结果ؓ%d\n"Qa); } |
在创建DllTest.exe文g之前Q要先将MyDll.dll和MyDll.lib拯到当前工E所在的目录下面Q也可以拯到windows的System目录下。如果DLL使用的是def文gQ要删除TestDll.h文g中关键字extern "C"。TestDll.h文g中的关键字Progam commit是要Visual C+的编译器在linkӞ链接到MyDll.lib文gQ当Ӟ开发h员也可以不?pragma comment(libQ?MyDll.lib")语句Q而直接在工程的Setting->Link늚Object/Moduls栏填入MyDll.lib既可?br>
2Q显式链?br>
昑ּ链接是应用程序在执行q程中随时可以加载DLL文gQ也可以随时卸蝲DLL文gQ这是隐式链接所无法作到的,所以显式链接具有更好的灉|性,对于解释性语a更ؓ合适。不q实现显式链接要ȝ一些。在应用E序中用LoadLibrary或MFC提供?span style="COLOR: #ff0000">AfxLoadLibrary昑ּ的将自己所做的动态链接库调进?/span>Q动态链接库的文件名x上述两个函数的参敎ͼ此后再用GetProcAddress()获取惌引入的函数。自此,你就可以象用如同在应用E序自定义的函数一h调用此引入函C。在应用E序退Z前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态链接库。下面是通过昑ּ链接调用DLL中的Max函数的例子?br>
#include <studio.h></studio.h> #include<widows.h></widows.h> void main(void) { typedef int(*pMax)(int a,int b); typedef int(*pMin)(int a,int b); HINSTANCE hDLL; PMax Max HDLL=LoadLibrary("MyDll.dll");//加蝲动态链接库MyDll.dll文gQ?br>Max=(pMax)GetProcAddress(hDLL,"Max"); A=Max(5,8); Printf("比较的结果ؓ%d\n"Qa); FreeLibrary(hDLL);//卸蝲MyDll.dll文gQ?br>} |
在上例中使用cd定义关键字typedefQ定义指向和DLL中相同的函数原型指针Q然后通过LoadLibray()DLL加蝲到当前的应用E序中ƈq回当前DLL文g的句柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针,函数调用完毕后,使用FreeLibrary()卸蝲DLL文g。在~译E序之前Q首先要DLL文g拯到工E所在的目录或Windowspȝ目录下?br>
使用昑ּ链接应用E序~译时不需要用相应的Lib文g。另外,使用GetProcAddress()函数Ӟ可以利用MAKEINTRESOURCE()函数直接使用DLL中函数出现的序P如将GetProcAddress(hDLL,"Min")改ؓGetProcAddress(hDLL, MAKEINTRESOURCE(2))Q函数Min()在DLL中的序h2Q,q样调用DLL中的函数速度很快Q但是要C函数的用序P否则会发生错误?br>
本文通过通俗易懂的方式,全面介绍了动态链接库的概c动态链接库的创建和动态链接库的链接,q给Z单明了的例子Q相信读者看了本文后Q能够创q动态链接库q应用到后箋的Y件开发当中去了,当然Q读者要熟练操作DLLQ还需要在大量的实践中不断摸烦Q希望本文能起到抛砖引玉的作用?/p>
q程是装入内存ƈ准备执行的程序,每个q程都有U有的虚拟地址I间Q由代码、数据以及它可利用的pȝ资源(如文件、管道等)l成。多q程/多线E是Windows操作pȝ的一个基本特征。Microsoft Win32应用~程接口(Application Programming Interface, API)提供了大量支持应用程序间数据׃n和交换的机制Q这些机制行使的zdUCؓq程间通信(InterProcess Communication, IPC)Q进E通信是指不同进E间q行数据׃n和数据交换?br> 正因Z用Win32 APIq行q程通信方式有多U,如何选择恰当的通信方式成为应用开发中的一个重要问题,下面本文对Win32中进E通信的几U方法加以分析和比较?
2 q程通信Ҏ
2.1 文g映射
文g映射(Memory-Mapped Files)能ɘq程把文件内容当作进E地址区间一块内存那h对待。因此,q程不必使用文gI/O操作Q只需单的指针操作可d和修Ҏ件的内容?br> Win32 API允许多个q程讉K同一文g映射对象Q各个进E在它自q地址I间里接收内存的指针。通过使用q些指针Q不同进E就可以L修改文g的内容,实现了对文g中数据的׃n?br> 应用E序有三U方法来使多个进E共享一个文件映对象?br> (1)l承Q第一个进E徏立文件映对象,它的子进E承该对象的句柄?br> (2)命名文g映射Q第一个进E在建立文g映射对象时可以给该对象指定一个名?可与文g名不?。第二个q程可通过q个名字打开此文件映对象。另外,W一个进E也可以通过一些其它IPC机制(有名道、邮件槽{?把名字传l第二个q程?br> (3)句柄复制Q第一个进E徏立文件映对象,然后通过其它IPC机制(有名道、邮件槽{?把对象句柄传递给W二个进E。第二个q程复制该句柄就取得对该文g映射对象的访问权限?br> 文g映射是在多个q程间共享数据的非常有效ҎQ有较好的安全性。但文g映射只能用于本地机器的进E之_不能用于|络中,而开发者还必须控制q程间的同步?br>2.2 ׃n内存
Win32 API中共享内?Shared Memory)实际是文g映射的一U特D情c进E在创徏文g映射对象时用0xFFFFFFFF来代替文件句?HANDLE)Q就表示了对应的文g映射对象是从操作pȝ面文g讉K内存Q其它进E打开该文件映对象就可以讉K该内存块。由于共享内存是用文件映实现的Q所以它也有较好的安全性,也只能运行于同一计算Z的进E之间?br>2.3 匿名道
道(Pipe)是一U具有两个端点的通信通道Q有一端句柄的q程可以和有另一端句柄的q程通信。管道可以是单向Q一端是只读的,另一端点是只写的Q也可以是双向的一道的两端点既可M可写?br> 匿名道(Anonymous Pipe)是在父进E和子进E之_或同一父进E的两个子进E之间传输数据的无名字的单向道。通常qq程创徏道Q然后由要通信的子q程l承通道的读端点句柄或写端点句柄Q然后实现通信。父q程q可以徏立两个或更多个承匿名管道读和写句柄的子q程。这些子q程可以使用道直接通信Q不需要通过父进E?br> 匿名道是单Z实现子进E标准I/O重定向的有效ҎQ它不能在网上用,也不能用于两个不相关的进E之间?br>2.4 命名道
命名道(Named Pipe)是服务器q程和一个或多个客户q程之间通信的单向或双向道。不同于匿名道的是命名道可以在不相关的进E之间和不同计算Z间用,服务器徏立命名管道时l它指定一个名字,Mq程都可以通过该名字打开道的另一端,Ҏl定的权限和服务器进E通信?br> 命名道提供了相对简单的~程接口Q通过|络传输数据q不比同一计算Z两进E之间通信更困难,不过如果要同时和多个q程通信它就力不从心了?br>2.5 邮g?/strong>
邮g?Mailslots)提供q程间单向通信能力QQ何进E都能徏立邮件槽成ؓ邮g槽服务器。其它进E,UCؓ邮g槽客P可以通过邮g槽的名字l邮件槽服务器进E发送消息。进来的消息一直放在邮件槽中,直到服务器进E读取它为止。一个进E既可以是邮件槽服务器也可以是邮件槽客户Q因此可建立多个邮g槽实现进E间的双向通信?br> 通过邮g槽可以给本地计算Z的邮件槽、其它计机上的邮g槽或指定|络区域中所有计机上有同样名字的邮件槽发送消息。广播通信的消息长度不能超q?00字节Q非q播消息的长度则受邮件槽服务器指定的最大消息长度的限制?br> 邮g槽与命名道怼Q不q它传输数据是通过不可靠的数据?如TCP/IP协议中的UDP?完成的,一旦网l发生错误则无法保证消息正确地接Ӟ而命名管道传输数据则是徏立在可靠q接基础上的。不q邮件槽有简化的~程接口和给指定|络区域内的所有计机q播消息的能力,所以邮件槽不失为应用程序发送和接收消息的另一U选择?br>2.6 剪脓?/strong>
剪脓?Clipped Board)实质是Win32 API中一l用来传输数据的函数和消息,为Windows应用E序之间q行数据׃n提供了一个中介,Windows已徏立的剪切(复制)Q粘贴的机制Z同应用程序之间共享不同格式数据提供了一条捷径。当用户在应用程序中执行剪切或复制操作时Q应用程序把选取的数据用一U或多种格式攑֜剪脓板上。然后Q何其它应用程序都可以从剪贴板上拾取数据,从给定格式中选择适合自己的格式?br> 剪脓板是一个非常松散的交换媒介Q可以支持Q何数据格式,每一格式׃无符h数标识,Ҏ?预定?剪脓板格式,该值是Win32 API定义的常量;寚w标准格式可以使用Register Clipboard Format函数注册为新的剪贴板格式。利用剪贴板q行交换的数据只需在数据格式上一致或都可以{化ؓ某种格式p。但剪脓板只能在ZWindows的程序中使用Q不能在|络上用?br>2.7 动态数据交?/strong>
动态数据交?DDE)是用共享内存在应用E序之间q行数据交换的一U进E间通信形式。应用程序可以用DDEq行一ơ性数据传输,也可以当出现新数据时Q通过发送更新值在应用E序间动态交换数据?br> DDE和剪贴板一h支持标准数据格式(如文本、位囄)Q又可以支持自己定义的数据格式。但它们的数据传输机制却不同Q一个明昑别是剪脓板操作几乎L用作对用h定操作的一ơ性应{-如从菜单中选择Paste命o。尽DDE也可以由用户启动Q但它l发挥作用一般不必用戯一步干预。DDE有三U数据交换方式:
(1) 冷链Q数据交换是一ơ性数据传输,与剪贴板相同?br> (2) 温链Q当数据交换时服务器通知客户Q然后客户必请求新的数据?br> (3) 热链Q当数据交换时服务器自动l客户发送数据?br> DDE交换可以发生在单机或|络中不同计机的应用程序之间。开发者还可以定义定制的DDE数据格式q行应用E序之间特别目的IPCQ它们有更紧密耦合的通信要求。大多数ZWindows的应用程序都支持DDE?br>2.8 对象q接与嵌?/strong>
应用E序利用对象q接与嵌?OLE)技术管理复合文?由多U数据格式组成的文档)QOLE提供使某应用E序更容易调用其它应用程序进行数据编辑的服务。例如,OLE支持的字处理器可以嵌套电子表|当用戯~辑电子表格时OLE库可自动启动电子表格~辑器。当用户退出电子表格编辑器Ӟ该表格已在原始字处理器文档中得到更新。在q里电子表格~辑器变成了字处理器的扩展,而如果用DDEQ用戯昑ּ地启动电子表格编辑器?br> 同DDE技术相同,大多数基于Windows的应用程序都支持OLE技术?br>2.9 动态连接库
Win32动态连接库(DLL)中的全局数据可以被调用DLL的所有进E共享,q就又给q程间通信开辟了一条新的途径Q当然访问时要注意同步问题?br> 虽然可以通过DLLq行q程间数据共享,但从数据安全的角度考虑Q我们ƈ不提倡这U方法,使用带有讉K权限控制的共享内存的Ҏ更好一些?br>2.10 q程q程调用
Win32 API提供的远E过E调?RPC)使应用程序可以用远E调用函敎ͼq在网l上用RPCq行q程通信像函数调用那样单。RPC既可以在单机不同q程间用也可以在网l中使用?br> ׃Win32 API提供的RPC服从OSF-DCE(Open Software Foundation Distributed Computing Environment)标准。所以通过Win32 API~写的RPC应用E序能与其它操作pȝ上支持DEC的RPC应用E序通信。用RPC开发者可以徏立高性能、紧密耦合的分布式应用E序?br>2.11 NetBios函数
Win32 API提供NetBios函数用于处理低|络控制Q这主要是ؓIBM NetBiospȝ~写与Windows的接口。除非那些有Ҏ低|络功能要求的应用程序,其它应用E序最好不要用NetBios函数来进行进E间通信?br>2.12 Sockets
Windows Sockets规范是以U.C.Berkeley大学BSD UNIX中流行的Socket接口例定义的一套Windows下的|络~程接口。除了Berkeley Socket原有的库函数以外Q还扩展了一l针对Windows的函敎ͼ使程序员可以充分利用Windows的消息机制进行编E?br> 现在通过Sockets实现q程通信的网l应用越来越多,q主要的原因是Sockets的跨q_性要比其它IPC机制好得多,另外WinSock 2.0不仅支持TCP/IP协议Q而且q支持其它协?如IPX)。Sockets的唯一~点是它支持的是底层通信操作Q这使得在单机的q程间进行简单数据传递不太方便,q时使用下面介l的WM_COPYDATA消息更合适些?br>2.13 WM_COPYDATA消息
WM_COPYDATA是一U非常强大却鲜ؓ人知的消息。当一个应用向另一个应用传送数据时Q发送方只需使用调用SendMessage函数Q参数是目的H口的句柄、传递数据的起始地址、WM_COPYDATA消息。接收方只需像处理其它消息那样处理WM_COPY DATA消息Q这h发双方就实现了数据共享?br> WM_COPYDATA是一U非常简单的ҎQ它在底层实际上是通过文g映射来实现的。它的缺Ҏ灉|性不高,q且它只能用于Windowsq_的单机环境下?
3 l束?/strong>
Win32 API为应用程序实现进E间通信提供了如此多U选择ҎQ那么开发者如何进行选择呢?通常在决定用哪UIPCҎ之前应考虑下一些问题,如应用程序是在网l环境下q是在单机环境下工作{?br>
#include "stdafx.h"
#include <iostream>
#include <windows.h>
struct FILEINFO
{
int curnumber;//写入的当前数?br> char filename[2];//文g?br> CRITICAL_SECTION cs;//临界
FILEINFO()
{
curnumber = 0;
InitializeCriticalSection(&cs);
memset(filename,0,2);
}
};
FILEINFO *pFileInfo =0;
const int MAXFILESIZE=4;
char szfilename[]="ABCD";
/////U程函数////////////////
DWORD WINAPI twritefile(LPVOID parm)
{
int number =(int) parm;
int nIndex = number-1;//保证U程i从文件i-1开始写
FILE *fp;
char szbuf[2];
sprintf(szbuf,"%d",number);
bool isfirst = true;
char sztmp[16]={0};//用来保存文g?/p>
while(true)
{
if(nIndex ==MAXFILESIZE)
nIndex = 0;
EnterCriticalSection(&pFileInfo[nIndex].cs);
if((number-pFileInfo[nIndex].curnumber)!=1 && !isfirst)
{
LeaveCriticalSection(&pFileInfo[nIndex].cs);
nIndex++;
continue;
}
sprintf(sztmp,"%s",pFileInfo[nIndex].filename);
fp = fopen(sztmp,"a+b");
if(fp!=0)
{
fwrite(szbuf,1,1,fp);
fclose(fp);
if(number==MAXFILESIZE) //当ؓW?个线E时Q文件结构的curnumber讄?;否则,讄为线EID
pFileInfo[nIndex].curnumber=0;
else
pFileInfo[nIndex].curnumber = number;
isfirst=false;
}
LeaveCriticalSection(&pFileInfo[nIndex].cs);
nIndex++;
}
}
int main(int argc, char* argv[])
{
pFileInfo = new FILEINFO[MAXFILESIZE];
DWORD TID;
for(int nIndex =0;nIndex<MAXFILESIZE;nIndex++)
{
pFileInfo[nIndex].filename[0] = szfilename[nIndex];
}
for(nIndex =1;nIndex<=MAXFILESIZE;nIndex++)
{
CreateThread(NULL,0,twritefile,(void*)nIndex,0,&TID);
}
while(1)
Sleep(100000);
}
else //否则的话回溯Q重新匹?br> {
i= ftell(fp);
fseek(fp,j,1);//向前回溯一个字W?br> j= 0;
}
}
}
printf("string %s occurs %d times in file2\n",line,sum);
return sum;
}
/*边界试
设计试用例时要考虑边界输入和非法输入,q里l称为特D输入,E序
员在~写代码时也要考虑Ҏ输入Q要为特D输入编写处理代码。在实际工作中,E序员没有考虑到某?/p>
Ҏ输入是很常见的,q也是程序错误的一个重要来源?br>*/
2.1 文g映射
文g映射(Memory-Mapped Files)能ɘq程把文件内容当作进E地址区间一块内存那h对待。因此,q程不必使用文gI/O操作Q只需单的指针操作可d和修Ҏ件的内容?br>Win32 API允许多个q程讉K同一文g映射对象Q各个进E在它自q地址I间里接收内存的指针。通过使用q些指针Q不同进E就可以L修改文g的内容,实现了对文g中数据的׃n?br>应用E序有三U方法来使多个进E共享一个文件映对象?br>(1)l承Q第一个进E徏立文件映对象,它的子进E承该对象的句柄?br>(2)命名文g映射Q第一个进E在建立文g映射对象时可以给该对象指定一个名?可与文g名不?。第二个q程可通过q个名字打开此文件映对象。另外,W一个进E也可以通过一些其它IPC机制(有名道、邮件槽{?把名字传l第二个q程?br>(3)句柄复制Q第一个进E徏立文件映对象,然后通过其它IPC机制(有名道、邮件槽{?把对象句柄传递给W二个进E。第二个q程复制该句柄就取得对该文g映射对象的访问权限?br>文g映射是在多个q程间共享数据的非常有效ҎQ有较好的安全性。但文g映射只能用于本地机器的进E之_不能用于|络中,而开发者还必须控制q程间的同步?/p>
2.2 ׃n内存
Win32 API中共享内?Shared Memory)实际是文g映射的一U特D情c进E在创徏文g映射对象时用0xFFFFFFFF来代?文g句柄(HANDLE)Q就表示了对应的文g映射对象是从操作pȝ面文g讉K内存Q其它进E打开该文件映对象就可以讉K该内存块。由于共享内存是?文g映射实现的,所以它也有较好的安全性,也只能运行于同一计算Z的进E之间?/p>
a.讑֮一块共享内存区?br>HANDLE CreateFileMapping(HANDLE,LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCSTR)// 产生一个file-mapping核心对象
LPVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAcess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
DWORD dwNumberOfBytesToMap
);得到׃n内存的指?/p>
b.扑և׃n内存
军_q块内存要以点对点(peer to peer)的Ş式呈现每个进E都必须有相同的能力Q生共享内存ƈ它初始化。每个进E都应该调用CreateFileMapping(),然后调用GetLastError().如果传回的错误代码是 ERROR_ALREADY_EXISTS,那么q程可以假设这一׃n内存?域已l被别的q程打开q初始化了,否则该进E就可以合理的认?排在W?一位,q接下来共享内存初始化。还是要使用client/server架构中只有serverq程才应该生ƈ初始化共享内存。所有的q程都应该?br>HANDLE OpenFileMapping(DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName);
再调用MapViewOfFile(),取得׃n内存的指?/p>
c.同步处理(Mutex)
d.清理(Cleaning up) BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);
CloseHandle()
2.3 匿名道
道(Pipe)是一U具有两个端点的通信通道Q有一端句柄的q程可以和有另一端句柄的q程通信。管道可以是单向Q一端是只读的,另一端点是只写的Q也可以是双向的一道的两端点既可M可写?/p>
匿名道(Anonymous Pipe)?在父q程和子q程之间Q或同一父进E的两个子进E之间传输数据的无名字的单向道。通常qq程创徏?道,然后p通信的子q程l承通道的读端点句柄或写 端点句柄Q然后实现通信。父q程q可以徏立两个或更多个承匿名管道读和写句柄的子q程。这些子q程 可以使用道直接通信Q不需要通过父进E?br>匿名道是单Z实现子进E标准I/O重定向的有效ҎQ它不能在网上用,也不能用于两个不相关的进E之间?/p>
2.4 命名道
命名道(Named Pipe)是服务器q程和一个或多个客户q程之间通信的单向或双向道。不同于匿名道的是命名道可以在不相关的进E之间和?同计机之间使用Q服务器建立命名道时给它指定一个名字,Mq程都可以通过该名字打开道的另一端,Ҏl定的权限和服务器进E通信?br>命名道提供了相对简单的~程接口Q通过|络传输数据q不比同一计算Z两进E之间通信更困难,不过如果要同时和多个q程通信它就力不从心了?/p>
2.5 邮g?br>邮g?Mailslots)?供进E间单向通信能力QQ何进E都能徏立邮件槽成ؓ邮g槽服务器。其它进E,UCؓ邮g槽客P可以通过邮g槽的名字l?邮g槽服务器q程发送消息。进来的?息一直放在邮件槽中,直到服务器进E读取它为止。一个进E既可以是邮件槽服务器也可以是邮件槽客户Q因此可建立多个 邮g槽实现进E间的双向通信?br>通过邮g槽可以给本地计算Z的邮件槽、其它计机上的邮g槽或指定|络区域中所有计机上有同样名字的邮件槽发送消息。广播通信的消息长度不能超q?00字节Q非q播消息的长度则受邮件槽服务器指定的最大消息长度的限制?br>邮g槽与命名道怼Q不q它传输数据是通过不可靠的数据?如TCP/IP协议中的UDP?完成的,一旦网l发生错误则无法保证消息正确地接Ӟ?命名道传输数据则是建立在可靠连接基上的。不q邮件槽有简化的~程接口和给指定|络区域内的所有计机q播消息的能力,所以邮件槽不失为应用程序发?和接收消息的另一U选择?/p>
2.6 剪脓?br> 剪脓?Clipped Board)实质是Win32 API中一l用来传输数据的函数和消息,为Windows应用E序之间q行数据׃n提供了一?中介QWindows已徏立的剪切(复制)Q粘贴的机制Z同应用程序之间共享不同格式数据提供了一条捷径。当用户在应用程序中执行剪切或复制操作时Q应 用程序把选取的数据用一U或多种格式攑֜剪脓板上。然后Q何其它应用程序都可以从剪贴板上拾取数据,从给定格式中选择适合自己的格式?br>剪脓?是一个非常松散的交换媒介Q可以支持Q何数据格式,每一格式׃无符h数标识,Ҏ?预定?剪脓板格式,该值是Win32 API定义的常量;寚w 标准格式可以使用Register Clipboard Format函数注册为新的剪贴板格式。利用剪贴板q行交换的数据只需在数据格式上一致或都可?转化为某U格式就行。但剪脓板只能在ZWindows的程序中使用Q不能在|络上用?/p>
2.7 动态数据交?br>动态数据交?DDE)是用共享内存在应用E序之间q行数据交换的一U进E间通信形式。应用程序可以用DDEq行一ơ性数据传输,也可以当出现新数据时Q通过发送更新值在应用E序间动态交换数据?br>DDE和剪贴板一h支持标准数据格式(如文本、位囄)Q又可以支持自己定义的数据格式。但它们的数据传输机制却不同Q一个明昑别是剪脓板操作几?L用作对用h定操作的一ơ性应{-如从菜单中选择Paste命o。尽DDE也可以由用户启动Q但它l发挥作用一般不必用戯一步干预。DDE有三 U数据交换方式:
(1) 冷链Q数据交换是一ơ性数据传输,与剪贴板相同?br>(2) 温链Q当数据交换时服务器通知客户Q然后客户必请求新的数据?br>(3) 热链Q当数据交换时服务器自动l客户发送数据?br>DDE交换可以发生在单机或|络中不同计机的应用程序之间。开发者还可以定义定制的DDE数据格式q行应用E序之间特别目的IPCQ它们有更紧密耦合的通信要求。大多数ZWindows的应用程序都支持DDE?/p>
2.8 对象q接与嵌?br>应用E序利用对象q接与嵌?OLE)技术管理复合文?由多U数据格式组成的文档)QOLE提供使某应用E序更容易调用其它应用程序进行数据编辑的?务。例如,OLE支持的字处理器可以嵌套电子表|当用戯~辑电子表格时OLE库可自动启动电子表格~辑器。当用户退出电子表格编辑器Ӟ该表格已在原 始字处理器文档中得到更新。在q里电子表格~辑器变成了字处理器的扩展,而如果用DDEQ用戯昑ּ地启动电子表格编辑器?br>同DDE技术相同,大多数基于Windows的应用程序都支持OLE技术?/p>
2.9 动态连接库
Win32动态连接库(DLL)中的全局数据可以被调用DLL的所有进E共享,q就又给q程间通信开辟了一条新的途径Q当然访问时要注意同步问题?br>虽然可以通过DLLq行q程间数据共享,但从数据安全的角度考虑Q我们ƈ不提倡这U方法,使用带有讉K权限控制的共享内存的Ҏ更好一些?/p>
2.10 q程q程调用
Win32 API提供的远E过E调?RPC)使应用程序可以用远E调用函敎ͼq在网l上用RPCq行q程通信像函数调用那样单。RPC既可以在单机不同q程间用也可以在网l中使用?br>׃Win32 API提供的RPC服从OSF-DCE (Open Software Foundation Distributed Computing Environment)标准。所以通过 Win32 API~写的RPC应用E序能与其它操作pȝ上支持DEC的RPC应用E序通信。用RPC开发者可以徏立高性能、紧密耦合的分布式应用E?序?/p>
2.11 NetBios函数
Win32 API提供NetBios函数用于处理低|络控制Q这主要是ؓIBM NetBiospȝ~写与Windows的接口。除非那些有Ҏ低|络功能要求的应用程序,其它应用E序最好不要用NetBios函数来进行进E间通信?/p>
2.12 Sockets
Windows Sockets规范是以U.C.Berkeley大学BSD UNIX中流行的Socket接口例定义的一套Windows下的|?l编E接口。除了Berkeley Socket原有的库函数以外Q还扩展了一l针对Windows的函敎ͼ使程序员可以充分利用Windows的消息机 制进行编E?br>现在通过Sockets实现q程通信的网l应用越来越多,q主要的原因是Sockets的跨q_性要比其它IPC机制好得多,?外WinSock 2.0不仅支持TCP/IP协议Q而且q支持其它协?如IPX)。Sockets的唯一~点是它支持的是底层通信操作Q这使得在单?的进E间q行单数据传递不太方便,q时使用下面介l的WM_COPYDATA消息更合适些?/p>
2.13 WM_COPYDATA消息
WM_COPYDATA是一U非常强大却鲜ؓ人知的消息。当一个应用向另一个应用传送数据时Q发送方只需使用调用SendMessage函数Q参数是?的窗口的句柄、传递数据的起始地址、WM_COPYDATA消息。接收方只需像处理其它消息那样处理WM_COPY DATA消息Q这h发双方就实现?数据׃n?br>WM_COPYDATA是一U非常简单的ҎQ它在底层实际上是通过文g映射来实现的。它的缺Ҏ灉|性不高,q且它只能用于Windowsq_的单机环境下?/span>
引自Q?a >http://blog