孙鑫19课动态链接库.docx
- 文档编号:10922945
- 上传时间:2023-02-23
- 格式:DOCX
- 页数:17
- 大小:190.79KB
孙鑫19课动态链接库.docx
《孙鑫19课动态链接库.docx》由会员分享,可在线阅读,更多相关《孙鑫19课动态链接库.docx(17页珍藏版)》请在冰豆网上搜索。
孙鑫19课动态链接库
动态链接库
⏹自从微软推出第一个版本的Windows操作系统以来,动态链接库(DLL)一直是Windows操作系统的基础。
⏹动态链接库通常都不能直接运行,也不能接收消息。
它们是一些独立的文件,其中包含能被可执行程序或其它DLL调用来完成某项工作的函数。
只有在其它模块调用动态链接库中的函数时,它才发挥作用。
⏹WindowsAPI中的所有函数都包含在DLL中。
其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。
静态库和动态库
⏹静态库:
函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。
在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.EXE文件)。
⏹在使用动态库的时候,往往提供两个文件:
一个引入库和一个DLL。
引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。
在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载DLL,访问DLL中导出的函数。
使用动态链接库的好处
⏹可以采用多种编程语言来编写。
⏹增强产品的功能。
⏹提供二次开发的平台。
⏹简化项目管理。
⏹可以节省磁盘空间和内存。
⏹有助于资源的共享。
⏹有助于实现应用程序的本地化。
动态链接库加载的两种方式
⏹隐式链接
⏹显示加载
下面是DLL的隐式连接。
选择WIN32动态链接库(工程名字:
DLL1,空的工程,新建一个C++源文件,名字DLL1)
查看导出的函数。
(只有导出的函数,才可以被其他程序调用)
_declspec(dllexport)intadd(inta,intb)
{
returna+b;
}
_declspec(dllexport)intsubtract(inta,intb)
{
returna-b;
}
_declspec(dllexport)表示函数可以导出。
如图所示:
DLL1.lib表示内部的函数名和变量名。
DLL1.exp表示导出的信息。
新建一个MFC文档(基于对话框,DLLTest)加两个按钮,(ADD,SUBTRACT),为它们增加命名响应函数。
externintadd(inta,intb);
externintsubtract(inta,intb);
voidCDLLTestDlg:
:
OnBtnAdd()
{
//TODO:
Addyourcontrolnotificationhandlercodehere
CStringstr;
str.Format("5+3=%d",add(5,3));
MessageBox(str);
}
voidCDLLTestDlg:
:
OnBtnSubtract()
{
//TODO:
Addyourcontrolnotificationhandlercodehere
CStringstr;
str.Format("5-3=%d",subtract(5,3));
MessageBox(str);
}
这时候编译,会出现:
Linking...
DLLTestDlg.obj:
errorLNK2001:
unresolvedexternalsymbol"int__cdecladd(int,int)"(?
add@@YAHHH@Z)
DLLTestDlg.obj:
errorLNK2001:
unresolvedexternalsymbol"int__cdeclsubtract(int,int)"(?
subtract@@YAHHH@Z)
Debug/DLLTest.exe:
fatalerrorLNK1120:
2unresolvedexternals
解决办法:
将DLL1.lib和DLL1.dll放到DLLTest目录下面。
在LINK上DLL1.lib。
下面的方法表明所用的加法和减法是从动态链接库导入的:
//externintadd(inta,intb);
//externintsubtract(inta,intb);
_declspec(dllimport)intadd(inta,intb);
_declspec(dllimport)intsubtract(inta,intb);
在实际中我们并不知道一个动态链接库导出来了哪些函数。
因此一般包含一个头文件,在这个头文件中有函数的说明。
为DLL1增加一个头文件,名字是DLL1.h.
DLL1.h.中的代码如下:
_declspec(dllimport)intadd(inta,intb);
_declspec(dllimport)intsubtract(inta,intb);
//dllimport表明是从动态链接库中导入的。
给DLLTestDlg.cpp增加#include"..\DLL1\DLL1.H"
//_declspec(dllimport)intadd(inta,intb);
//_declspec(dllimport)intsubtract(inta,intb);注释这俩句。
或者在DLL1.H中:
#ifdefDLL1_API
#else
#defineDLL1_API_declspec(dllimport)
#endif
DLL1_APIintadd(inta,intb);
DLL1_APIintsubtract(inta,intb);
DLL1.CPP中:
#defineDLL1_API_declspec(dllexport)
#include"DLL1.H"
intadd(inta,intb)
{
returna+b;
}
intsubtract(inta,intb)
{
returna-b;
}
这时将DLL1.LIB放到测试程序下,也可以得到结果。
//dllimport表明是从动态链接库中导入的。
在动态链接库中,只能导出的函数才能用。
下面导出一个类:
在DLL1.H中加入下面代码:
classDLL1_APIPoint
{
public:
//只有公有的在外部才可以访问。
voidoutput(intx,inty);
protected:
private:
};
DLL1.CPP中的代码如下:
#defineDLL1_API_declspec(dllexport)
#include"DLL1.H"
#include
#include
intadd(inta,intb)
{
returna+b;
}
intsubtract(inta,intb)
{
returna-b;
}
voidPoint:
:
output(intx,inty)//将x和y输出在调用动态链接库的窗口上。
{
//首先要获得调用动态链接库的窗口的句柄。
HWNDhwnd=GetForegroundWindow();
HDChdc=GetDC(hwnd);
charbuf[20];
memset(buf,0,20);
sprintf(buf,"x=%d,y=%d",x,y);
TextOut(hdc,0,0,buf,strlen(buf));
ReleaseDC(hwnd,hdc);
}
编译后,将生成的动态链接库和.lib拷贝到测试程序目录下。
(动态链接库和.LIB文件一旦有变,要重新拷贝)
在测试程序中再加一个按钮,为OUTPUT按钮。
(对类进行操作)
voidCDLLTestDlg:
:
OnBtnOutput()
{
//TODO:
Addyourcontrolnotificationhandlercodehere
Pointpt;
pt.output(5,3);
}
结果是在测试窗口输出x=5,y=3.
这就是导出整个类的操作。
这时导出的情况如下:
(.exe也可以看它的导入和导出情况)。
如果只想导出Point:
:
output,则DLL1.H中的代码如下:
classPoint
{
public:
//只有公有的在外部才可以访问。
DLL1_APIvoidoutput(intx,inty);
protected:
private:
};
此时的导出情况如下:
在访问上没有区别。
C++导出函数是会改变名字,而在C语言中是不认识的。
因此为了让其它的客户端可以访问导出函数,导出函数名字不变。
做如下修改。
(将类的操作注释起来)。
DLL1.H中的代码:
#ifdefDLL1_API
#else
#defineDLL1_APIextern"C"_declspec(dllimport)
#endif
DLL1_APIintadd(inta,intb);
DLL1_APIintsubtract(inta,intb);
DLL1.CPP中的代码:
#defineDLL1_APIextern"C"_declspec(dllexport)//extern"C"一定是要大写的C
#include"DLL1.H"
#include
#include
intadd(inta,intb)
{
returna+b;
}
intsubtract(inta,intb)
{
returna-b;
}
此时再看它的导出情况,如下表所示:
注意到,此时的函数的名称没有改变。
用extern"C"不能导出一个类的成员函数。
只能导出全局函数。
如果函数的调用约定改变,即使你用了extern"C",也会发生名字的改变。
如果采用标准调用约定,即在DLL1.H中的程序如下:
#ifdefDLL1_API
#else
#defineDLL1_APIextern"C"_declspec(dllimport)
#endif
DLL1_APIint_stdcalladd(inta,intb);
DLL1_APIint_stdcallsubtract(inta,intb);
_stdcall标准调用约定是WINAPI也是PASCAL调用约定,和C调用约定是不一样的,如果没有加_stdcall,就是C调用约定。
在DLL1.CPP中代码如下:
#defineDLL1_APIextern"C"_declspec(dllexport)//extern"C"一定是要大写的C
#include"DLL1.H"
#include
#include
int_stdcalladd(inta,intb)
{
returna+b;
}
int_stdcallsubtract(inta,intb)
{
returna-b;
}
编译一下,此时的导出情况:
函数后面有个8,这个8是指导出函数的参数所占的字节数,两个整形是8个字节。
C语言和delphi是不一样的。
Delphi用的是标准调用约定。
若给delphi用,则要用标准调用约定。
要解决函数名字改变问题,用模块定义文件方式。
新建一个DLL2的动态链接库文件,新建一个空的文件。
DLL2.CPP中的程序:
intadd(inta,intb)
{
returna+b;
}
intsubtract(inta,intb)
{
returna-b;
}
下面写一个模块定义文件:
在DLL2的目录下添加DLL2.def(def是一个模块定义后缀),然后将DLL2.def增加到DLL2工程中。
在DLL2.def中增加代码:
LIBRARYDLL2
//LIBRARY用来指定动态链接库的内部名称。
这个名称和生成的动态链接库的名字要一样。
EXPORTS
//要导出的函数的名字
add
subtract
生成DLL2.dll,查看的结果如下:
然后测试DLL2中的函数。
动态加载DLL方法(将DLL2.dll放到测试目录下)
将DLL1的相关信息删除,不用LINKDLL2.lib.
要动态加载DLL,要用到LoadLibrary函数。
voidCDLLTestDlg:
:
OnBtnAdd()
{
//TODO:
Addyourcontrolnotificationhandlercodehere
HINSTANCEhinst;//HINSTANCE和HMODULE通用
hinst=LoadLibrary("DLL2.dll");
//得到导出的函数的地址
typedefint(*ADDPROC)(inta,intb);
//定义一个函数指针类型(*ADDPROC),它的函数有两个参数(inta,intb),函数返回值是int
//
ADDPROCaddproc=(ADDPROC)GetProcAddress(hinst,"add");
if(NULL==addproc)
{
MessageBox("获取函数地址失败!
");
return;
}
CStringstr;
str.Format("5+3=%d",addproc(5,3));
MessageBox(str);
}
动态加载的好处在需要的时候加载。
隐式连接启动的时候比较慢,加大进程启动时间。
如果此时DLL2中的代码如下:
int_stdcalladd(inta,intb)
{
returna+b;
}
int_stdcallsubtract(inta,intb)
{
returna-b;
}
此时
可以看到名字没有发生改变。
这时的voidCDLLTestDlg:
:
OnBtnAdd()中的代码改变如下:
(若不改,会发生运行错误。
)
typedefint(_stdcall*ADDPROC)(inta,intb);
调用约定要一样。
这时的测试程序中:
没有DLL2信息。
新建一个DLL3,(提供的动态链接库发生了名字改变,当动态加载的时候发生的情况)
DLL3.CPP:
_declspec(dllexport)intadd(inta,intb)
{
returna+b;
}
将DLL3生成的DLL3.dll拷贝到测试程序下。
将测试程序改成:
voidCDLLTestDlg:
:
OnBtnAdd()
{
//TODO:
Addyourcontrolnotificationhandlercodehere
HINSTANCEhinst;//HINSTANCE和HMODULE通用
hinst=LoadLibrary("DLL3.dll");
//得到导出的函数的地址
typedefint(/*_stdcall*/*ADDPROC)(inta,intb);
//定义一个函数指针类型(*ADDPROC),它的函数有两个参数(inta,intb),函数返回值是int
//
ADDPROCaddproc=(ADDPROC)GetProcAddress(hinst,"add");
if(NULL==addproc)
{
MessageBox("获取函数地址失败!
");
return;
}
CStringstr;
str.Format("5+3=%d",addproc(5,3));
MessageBox(str);
}
运行,此时获取函数地址失败
因为DLL3中的addC++编译器要进行名字改变。
将此时的测试程序改成:
ADDPROCaddproc=(ADDPROC)GetProcAddress(hinst,"?
add@@YAHHH@Z");即可运行程序。
或者:
ADDPROCaddproc=(ADDPROC)GetProcAddress(hinst,MAKEINTRESOURCE
(1));//1为add的序列号
建议用函数名。
BOOLWINAPIDllMain(
HINSTANCEhinstDLL,//handletotheDLLmodule
DWORDfdwReason,//reasonforcallingfunction在哪种情况下被调用
LPVOIDlpvReserved//reserved
);
动态链接库的入口函数。
可选,在写DLL时写与不写都可以。
不要做太复杂调用。
因为此时有些DLL还没有加载进来。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 孙鑫 19 动态 链接