DLL编写教程 转载精品文档12页.docx
- 文档编号:15771596
- 上传时间:2023-07-07
- 格式:DOCX
- 页数:14
- 大小:23.21KB
DLL编写教程 转载精品文档12页.docx
《DLL编写教程 转载精品文档12页.docx》由会员分享,可在线阅读,更多相关《DLL编写教程 转载精品文档12页.docx(14页珍藏版)》请在冰点文库上搜索。
DLL编写教程转载精品文档12页
DLL编写教程转载
要练说,得练看。
看与说是统一的,看不准就难以说得好。
练看,就是训练幼儿的观察能力,扩大幼儿的认知范围,让幼儿在观察事物、观察生活、观察自然的活动中,积累词汇、理解词义、发展语言。
在运用观察法组织活动时,我着眼观察于观察对象的选择,着力于观察过程的指导,着重于幼儿观察能力和语言表达能力的提高。
DLL编写教程(转载)2019年12月30日星期四13:
08DLL编写教程
唐宋或更早之前,针对“经学”“律学”“算学”和“书学”各科目,其相应传授者称为“博士”,这与当今“博士”含义已经相去甚远。
而对那些特别讲授“武事”或讲解“经籍”者,又称“讲师”。
“教授”和“助教”均原为学官称谓。
前者始于宋,乃“宗学”“律学”“医学”“武学”等科目的讲授者;而后者则于西晋武帝时代即已设立了,主要协助国子、博士培养生徒。
“助教”在古代不仅要作入流的学问,其教书育人的职责也十分明晰。
唐代国子学、太学等所设之“助教”一席,也是当朝打眼的学官。
至明清两代,只设国子监(国子学)一科的“助教”,其身价不谓显赫,也称得上朝廷要员。
至此,无论是“博士”“讲师”,还是“教授”“助教”,其今日教师应具有的基本概念都具有了。
半年不能上网,最近网络终于通了,终于可以更新博客了,写点什么呢?
决定最近写一个编程技术系列,其内容是一些通用的编程技术。
例如DLL,COM,Socket,多线程等等。
这些技术的特点就是使用广泛,但是误解很多;网上教程很多,但是几乎没有什么优质良品。
我以近几个月来的编程经验发现,很有必要好好的总结一下这些编程技术了。
一来对自己是总结提高,二来可以方便光顾我博客的朋友。
要练说,得练看。
看与说是统一的,看不准就难以说得好。
练看,就是训练幼儿的观察能力,扩大幼儿的认知范围,让幼儿在观察事物、观察生活、观察自然的活动中,积累词汇、理解词义、发展语言。
在运用观察法组织活动时,我着眼观察于观察对象的选择,着力于观察过程的指导,着重于幼儿观察能力和语言表达能力的提高。
好了,废话少说,言归正传。
第一篇就是《DLL编写教程》,为什么起这么土的名字呢?
为什么不叫《轻轻松松写DLL》或者《DLL一日通》呢?
或者更nb的《深入简出DLL》呢?
呵呵,常常上网搜索资料的弟兄自然知道。
本文对通用的DLL技术做了一个总结,并提供了源代码打包下载,下载地址为:
DLL的优点
简单的说,dll有以下几个优点:
1)节省内存。
同一个软件模块,若是以源代码的形式重用,则会被编译到不同的可执行程序中,同时运行这些exe时这些模块的二进制码会被重复加载到内存中。
如果使用dll,则只在内存中加载一次,所有使用该dll的进程会共享此块内存(当然,像dll中的全局变量这种东西是会被每个进程复制一份的)。
2)不需编译的软件系统升级,若一个软件系统使用了dll,则该dll被改变(函数名不变)时,系统升级只需要更换此dll即可,不需要重新编译整个系统。
事实上,很多软件都是以这种方式升级的。
例如我们经常玩的星际、魔兽等游戏也是这样进行版本升级的。
3)Dll库可以供多种编程语言使用,例如用c编写的dll可以在vb中调用。
这一点上DLL还做得很不够,因此在dll的基础上发明了COM技术,更好的解决了一系列问题。
最简单的dll
开始写dll之前,你需要一个c/c++编译器和链接器,并关闭你的IDE。
是的,把你的VC和C++BUILDER之类的东东都关掉,并打开你以往只用来记电话的记事本程序。
不这样做的话,你可能一辈子也不明白dll的真谛。
我使用了VC自带的cl编译器和link链接器,它们一般都在vc的bin目录下。
(若你没有在安装vc的时候选择注册环境变量,那么就立刻将它们的路径加入path吧)如果你还是因为离开了IDE而害怕到哭泣的话,你可以关闭这个页面并继续去看《VC++技术内幕》之类无聊的书了。
最简单的dll并不比c的helloworld难,只要一个DllMain函数即可,包含objbase.h头文件(支持COM技术的一个头文件)。
若你觉得这个头文件名字难记,那么用windows.H也可以。
源代码如下:
dll_nolib.cpp
#includeobjbase.h
#includeiostream.hBOOLAPIENTRYDllMain(HANDLEhModule,DWORDdwReason,void*lpReserved)
HANDLEg_hModule;
switch(dwReason)
caseDLL_PROCESS_ATTACH:
cout"Dllisattached!
"endl;
g_hModule=(HINSTANCE)hModule;
break;
caseDLL_PROCESS_DETACH:
cout"Dllisdetached!
"endl;
g_hModule=NULL;
break;
returntrue;
其中DllMain是每个dll的入口函数,如同c的main函数一样。
DllMain带有三个参数,hModule表示本dll的实例句柄(听不懂就不理它,写过windows程序的自然懂),dwReason表示dll当前所处的状态,例如DLL_PROCESS_ATTACH表示dll刚刚被加载到一个进程中,DLL_PROCESS_DETACH表示dll刚刚从一个进程中卸载。
当然还有表示加载到线程中和从线程中卸载的状态,这里省略。
最后一个参数是一个保留参数(目前和dll的一些状态相关,但是很少使用)。
从上面的程序可以看出,当dll被加载到一个进程中时,dll"Dllisattached!
"语句;当dll从进程中卸载时,打印"Dllisdetached!
"语句。
编译dll需要以下两条命令:
cl/cdll_nolib.cpp
这条命令会将cpp编译为obj文件,若不使用/c参数则cl还会试图继续将objexe,但是这里是一个dll,没有main函数,因此会报错。
不要紧,继续使用链接命令。
Link/dlldll_nolib.obj
这条命令会生成dll_nolib.dll。
注意,因为编译命令比较简单,所以本文不讨论nmake,有兴趣的可以使用nmake,或者写个bat批处理来编译链接dll。
加载DLL(显式调用)
使用dll大体上有两种方式,显式调用和隐式调用。
这里首先介绍显式调用。
编写一个客户端程序:
dll_nolib_client.cpp
#includewindows.h
#includeiostream.hintmain(void)
//加载我们的dllHINSTANCEhinst=:
LoadLibrary("dll_nolib.dll");
if(NULL!
=hinst)
cout"dllloaded!
"endl;
return0;
注意,调用dll使用LoadLibrary函数,它的参数就是dll的路径和名称,返回值是dll的句柄。
使用如下命令编译链接客户端:
Cldll_nolib_client.cpp
并执行dll_nolib_client.exe,得到如下结果:
Dllisattached!
dllloaded!
Dllisdetached!
以上结果表明dll已经被客户端加载过。
但是这样仅仅能够将dll加载到内存,不能找到dll中的函数。
使用dumpbin命令查看DLL中的函数
Dumpbin命令可以查看一个dll中的输出函数符号名,键入如下命令:
Dumpbin–exportsdll_nolib.dll
通过查看,发现dll_nolib.dll并没有输出任何函数。
如何在dll中定义输出函数
总体来说有两种方法,一种是添加一个def定义文件,在此文件中定义dll中要输出的函数;第二种是在源代码中待输出的函数前加上__declspec(dllexport)关键字。
Def文件
首先写一个带有输出函数的dll,源代码如下:
dll_def.cpp
#includeobjbase.h
#includeiostream.hvoidFuncInDll(void)
cout"FuncInDlliscalled!
"endl;
BOOLAPIENTRYDllMain(HANDLEhModule,DWORDdwReason,void*lpReserved)
HANDLEg_hModule;
switch(dwReason)
caseDLL_PROCESS_ATTACH:
g_hModule=(HINSTANCE)hModule;
break;
caseDLL_PROCESS_DETACH:
g_hModule=NULL;
break;
returnTRUE;
这个dll的def文件如下:
dll_def.def
;dll_defmodule-definitionfileLIBRARYdll_def.dllDESCRIPTION'(c)2019-2009WangXuebin'
EXPORTSFuncInDll@1PRIVATE
你会发现def的语法很简单,首先是LIBRARY关键字,指定dll的名字;然后一个可选的关键字DESCRIPTION,后面写上版权等信息(不写也可以);最后是EXPORTS关键字,后面写上dll中所有要输出的函数名或变量名,然后接上@以及依次编号的数字(从1到N),最后接上修饰符。
用如下命令编译链接带有def文件的dllCl/cdll_def.cppLink/dlldll_def.obj/def:
dll_def.def
再调用dumpbin查看生成的dll_def.dllDumpbin–exportsdll_def.dll
得到如下结果:
Dumpoffiledll_def.dllFileType:
DLLSectioncontainsthefollowingexportsfordll_def.dll0characteristics46E4EE98timedatestampMonSep1015:
13:
2820190.00version1ordinalbase1numberoffunctions1numberofnamesordinalhintRVAname1000001000FuncInDllSummary2000.data1000.rdata1000.reloc6000.text
观察这一行
1000001000FuncInDll
会发现该dll输出了函数FuncInDll。
显式调用DLL中的函数
写一个dll_def.dll的客户端程序:
dll_def_client.cpp
#includewindows.h
#includeiostream.hintmain(void)
//定义一个函数指针
typedefvoid(*DLLWITHLIB)(void);
//定义一个函数指针变量
DLLWITHLIBpfFuncInDll=NULL;
//加载我们的dllHINSTANCEhinst=:
LoadLibrary("dll_def.dll");
if(NULL!
=hinst)
cout"dllloaded!
"endl;
//找到dll的FuncInDll函数
pfFuncInDll=(DLLWITHLIB)GetProcAddress(hinst,"FuncInDll");
//调用dll里的函数
if(NULL!
=pfFuncInDll)
(*pfFuncInDll)();
return0;
有两个地方值得注意,第一是函数指针的定义和使用,不懂的随便找本c++书看看;第二是GetProcAddress的使用,这个API是用来查找dll中的函数地址的,第一个参数是DLL的句柄,即LoadLibrary返回的句柄,第二个参数是dll中的函数名称,即dumpbin中输出的函数名(注意,这里的函数名称指的是编译后的函数名,不一定等于dll源代码中的函数名)。
编译链接这个客户端程序,并执行会得到:
dllloaded!
FuncInDlliscalled!
这表明客户端成功调用了dll中的函数FuncInDll。
__declspec(dllexport)
为每个dll写def显得很繁杂,目前def使用已经比较少了,更多的是使用__declspec(dllexport)在源代码中定义dll的输出函数。
Dll写法同上,去掉def文件,并在每个要输出的函数前面加上声明__declspec(dllexport),例如:
__declspec(dllexport)voidFuncInDll(void)
这里提供一个dll源程序dll_withlib.cpp,然后编译链接。
链接时不需要指定/DEF:
参数,直接加/DLL参数即可,
Cl/cdll_withlib.cppLink/dlldll_withlib.obj
然后使用dumpbin命令查看,得到:
1000001000?
FuncInDll@YAXXZ
可知编译后的函数名为?
FuncInDll@YAXXZ,而并不是FuncInDll,这是因为c++编译器基于函数重载的考虑,会更改函数名,这样使用显式调用的时候,也必须使用这个更改后的函数名,这显然给客户带来麻烦。
为了避免这种现象,可以使用extern"C"指令来命令c++编译器以c编译器的方式来命名该函数。
修改后的函数声明为:
extern"C"__declspec(dllexport)voidFuncInDll(void)
dumpbin命令结果:
1000001000FuncInDll
这样,显式调用时只需查找函数名为FuncInDll的函数即可成功。
extern"C"
使用extern"C"关键字实际上相当于一个编译器的开关,它可以将c++语言的函数编译为c语言的函数名称。
即保持编译后的函数符号名等于源代码中的函数名称。
隐式调用DLL
显式调用显得非常复杂,每次都要LoadLibrary,并且每个函数都必须使用GetProcAddress来得到函数指针,这对于大量使用dll函数的客户是一种困扰。
而隐式调用能够像使用c函数库一样使用dll中的函数,非常方便快捷。
下面是一个隐式调用的例子:
dll包含两个文件dll_withlibAndH.cpp和dll_withlibAndH.h。
代码如下:
dll_withlibAndH.hextern"C"__declspec(dllexport)voidFuncInDll(void);
dll_withlibAndH.cpp
#includeobjbase.h
#includeiostream.h
#include"dll_withLibAndH.h"//看到没有,这就是我们增加的头文件
extern"C"__declspec(dllexport)voidFuncInDll(void)
cout"FuncInDlliscalled!
"endl;
BOOLAPIENTRYDllMain(HANDLEhModule,DWORDdwReason,void*lpReserved)
HANDLEg_hModule;
switch(dwReason)
caseDLL_PROCESS_ATTACH:
g_hModule=(HINSTANCE)hModule;
break;
caseDLL_PROCESS_DETACH:
g_hModule=NULL;
break;
returnTRUE;
编译链接命令:
Cl/cdll_withlibAndH.cppLink/dlldll_withlibAndH.obj
在进行隐式调用的时候需要在客户端引入头文件,并在链接时指明dll对应的lib文件(dll只要有函数输出,则链接的时候会产生一个与dll同名的lib文件)位置和名称。
然后如同调用api函数库中的函数一样调用dll中的函数,不需要显式的LoadLibrary和GetProcAddress。
使用最为方便。
客户端代码如下:
dll_withlibAndH_client.cpp
#include"dll_withLibAndH.h"
//注意路径,加载dll的另一种方法是Project|setting|link设置里
#pragmacomment(lib,"dll_withLibAndH.lib")
intmain(void)
FuncInDll();//只要这样我们就可以调用dll里的函数了
return0;
__declspec(dllexport)和__declspec(dllimport)配对使用
上面一种隐式调用的方法很不错,但是在调用DLL中的对象和重载函数时会出现问题。
因为使用extern"C"修饰了输出函数,因此重载函数肯定是会出问题的,因为它们都将被编译为同一个输出符号串(c语言是不支持重载的)。
事实上不使用extern"C"是可行的,这时函数会被编译为c++符号串,例如(?
FuncInDll@YAXH@Z、?
FuncInDll@YAXXZ),当客户端也是c++时,也能正确的隐式调用。
这时要考虑一个情况:
若DLL1.CPP是源,DLL2.CPP使用了DLL1中的函数,但同时DLL2也是一个DLL,也要输出一些函数供Client.CPP使用。
那么在DLL2中如何声明所有的函数,其中包含了从DLL1中引入的函数,还包括自己要输出的函数。
这个时候就需要同时使用__declspec(dllexport)和__declspec(dllimport)了。
前者用来修饰本dll中的输出函数,后者用来修饰从其它dll中引入的函数。
所有的源代码包括DLL1.H,DLL1.CPP,DLL2.H,DLL2.CPP,Client.cpp。
源代码可以在下载的包中找到。
你可以编译链接并运行试试。
值得关注的是DLL1和DLL2中都使用的一个编码方法,见DLL2.H
#ifdefDLL_DLL2_EXPORTS
#defineDLL_DLL2_API__declspec(dllexport)
#else
#defineDLL_DLL2_API__declspec(dllimport)
#endifDLL_DLL2_APIvoidFuncInDll2(void);
DLL_DLL2_APIvoidFuncInDll2(int);
在头文件中以这种方式定义宏DLL_DLL2_EXPORTS和DLL_DLL2_API,可以确保DLL端的函数用__declspec(dllexport)修饰,而客户端的函数用__declspec(dllimport)修饰。
当然,记得在编译dll时加上参数/D"DLL_DLL2_EXPORTS",或者干脆就在dll的cpp文件第一行加上#defineDLL_DLL2_EXPORTS。
VC生成的代码也是这样的!
事实证明,我是抄袭它的,hoho!
DLL中的全局变量和对象
解决了重载函数的问题,那么dll中的全局变量和对象都不是问题了,只是有一点语法需要注意。
如源代码所示:
dll_object.h
#ifdefDLL_OBJECT_EXPORTS
#defineDLL_OBJECT_API__declspec(dllexport)
#else
#defineDLL_OBJECT_API__declspec(dllimport)
#endifDLL_OBJECT_APIvoidFuncInDll(void);
externDLL_OBJECT_APIintg_nDll;
classDLL_OBJECT_APICDll_Object{
public:
CDll_Object(void);
show(void);
//TODO:
addyourmethodshere.
Cpp文件dll_object.cpp如下:
#defineDLL_OBJECT_EXPORTS
#includeobjbase.h
#includeiostream.h
#include"dll_object.h"
DLL_OBJECT_APIvoidFuncInDll(void)
cout"FuncInDlliscalled!
"endl;
DLL_OBJECT_APIintg_nDll=9;
CDll_Object:
CDll_Object()
cout"ctorofCDll_Object"endl;
CDll_Object:
show()
cout"functionshowinclassCDll_Object"endl;
BOOLAPIENTRYDllMain(HANDLEhModule,DWORDdwReason,void*lpReserved)
HANDLEg_hModule;
switch(dwReason)
caseDLL_PROCESS_ATTACH:
g_hModule=(HINSTANCE)hModule;
break;
caseDLL_PROCESS_DETACH:
g_hModule=NULL;
break;
returnTRUE;
编译链接完后Dumpbin一下,可以看到输出了5个符号:
1000001040?
0CDll_Object@QAE@XZ2100001000?
4CDll_Object@QAEAAV0@ABV0@Z3200001020?
FuncInDll@YAXXZ4300008040?
g_nDll@3HA5400001069?
show@CDll_Object@QAEHXZ
它们分别代表类CDll_Object,类的构造函数,FuncInDll函数,全局变量g_nDll和类的成员函数show。
下面是客户端代码:
dll_object_client.cpp
#include"dll_object.h"
#includeiostream.h
//注意路径,加载dll的另一种方法是Project|setting|link设置里
#pragmacomment(lib,"dll_object.lib")
intmain(void)
cout"calldll"endl;
cout"callfunctionindll"endl;
FuncInDll();//只要这样我们就可以调用dll里的函数了
cout"globalvarindllg_nDll="g_nDllendl;
cout"callmemberfunctionofclassCDll_Objectindll"endl;
CDll_Objectobj;
obj.show();
return0;
运行这个客户端可以看到:
calldllcallfunctionindllFuncInDlliscalled!
globalvarindllg_nDll=9callmemberfunctionofclassCDll_ObjectindllctorofCDll_ObjectfunctionshowinclassCDll_Object
可知,在客户端成功的访问了dll中的全局变量,并创建了dll中定
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- DLL编写教程 转载精品文档12页 DLL 编写 教程 转载 精品 文档 12