(孙鑫 十九)动态连接库

动态链接库程序的编写。静态库与动态库的区别,以及调用程序在链接静态库和动态库时的区别。如何利用工具查看动态链接库输出的函数,Depends工具的使用,C++编译器名字改编技术对动态链接库输出函数的影响,extern "C"的用法,利用模块定义文件来解决C++名字改编的问题。用typedef定义指向函数的指针类型,如何获得动态连接库里的函数的指针。

1.基本概念

		动态链接库

自从微软推出第一个版本的Windows操作系统以来,动态链接库(DLL)一直是Windows操作系统的基础。
动态链接库通常都不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其它DLL调用来完成某项工作的函数。只有在其它模块调用动态链接库中的函数时,它才发挥作用。
Windows API中的所有函数都包含在DLL中。其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。

		静态库和动态库

静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.EXE文件)。&&

在使用动态库的时候,往往提供两个文件:一个引入库和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载DLL,访问DLL中导出的函数。

		使用动态链接库的好处
可以采用多种编程语言来编写。
增强产品的功能。
提供二次开发的平台。
简化项目管理。
可以节省磁盘空间和内存。
有助于资源的共享。
有助于实现应用程序的本地化。

  动态链接库可被多个进程访问。
	动态链接库加载的两种方式:
隐式链接
显示加载

2.新建一个win32 DLL工程 Dll1。新建一个C++源文件:写两个个完成加减法的函数:
int add(int a,int b)
{
	return a+b;
}

int subtract(int a,int b)
{
	return a-b;
}	//这时编译就可以生产dll了。

  但要让dll中的函数能被外部进程调用,该函数需要是导出的函数。 

  在cmd中切换到debug路径,输入dumpbin……若找不到的话,就到VC的bin目录中,找到它。这时可用此目录的VCVARS32.BAT文件重新建立环境变量。将它拖放到cmd上执行。
  然后输入:dumpbin -exports Dll1.dll 这时没有列出函数
  在缩写的函数前加上_declspec(dllexport)再编译,再执行上面的指令,这时就列出了两个函数了。
(_declspec=declare specific声明指定的……用于指定所给定类型的实例的与Microsoft相关的存储方式)

再新建一个DllTest MFC 基于对话框的工程:再对话框上添加两个按钮:Add和Subtract。双击添加响应代码,写入:
extern int add(int a,int b);
extern int subtract(int a,int b);
void CDllTestDlg::OnBtnAdd() 
{
	// TODO: Add your control notification handler code here
	CString str;
	str.Format("5+3=%d",add(5,3));
	MessageBox(str);
}

void CDllTestDlg::OnBtnSubtract() 
{
	// TODO: Add your control notification handler code here
	CString str;
	str.Format("5+3=%d",subtract(5,3));
	MessageBox(str);	
}
  但这时编译系统找不这两个函数,会出现链接错误。这时我们把dll1生产的lib库文件复制到DllTest工程目录,然后设置链接选项:在链接标签页的对象/库模块下添加:Dll1.lib
  lib文件提供链接需要的信息,比如重定位表(这个我还不太清楚)。可以用dumpbin -imports 查看dlltest的输入函数。
  但这时运行会提示找不到dll文件,我们要把dll1.dll文件复制到当前目录。

  VC TOOLS中有个Depends工具,可以打开dll、exe文件,查看其导入库和导出库。
  在声明外部函数的时候,可不用extern,而用_declspec(dllimport).(告诉是从dll中导入的函数,这样效率更高)。

  但有时编exe的不知道要用dll的哪些函数,所以要在dll工程中编写一个头文件,声明exe要导入的函数。比如在Dll1中添加一个Dll1.h,然后复制外部声明代码:
_declspec(dllimport) int add(int a,int b);
_declspec(dllimport) int subtract(int a,int b);
  然后在DllTest中,使用这些函数的cpp前包含头文件:#include "..\Dll1\Dll1.h"

  我们还可以改造一下头文件:
#ifdef DLL1_API		//定义了就是dll导出;没定义就是exe导入
#else
#define DLL1_API _declspec(dllimport)
#endif

DLL1_API int add(int a,int b);
DLL1_API int subtract(int a,int b);
  源文件:
#define	DLL1_API	_declspec(dllexport)
#include "Dll1.h"

 int add(int a,int b)
{
	return a+b;
}
 int subtract(int a,int b)
{
	return a-b;
}
  这样Dll1.H既可被exe也可被dll用了。

3.导出C++的类class

  在头文件添加:
 class DLL1_API Point	//注意在class后加_declspec(dll……)
 {
	public:
		void output(int x,int y);
 };

  源文件中添加:
 void Point::output(int x,int y)
 {
	
 }
  用GetForegroundWindow
The GetForegroundWindow function returns a handle to the foreground window (the window with which the user is currently working). The system assigns a slightly higher priority to the thread that creates the foreground window than it does to other threads. 

HWND GetForegroundWindow(VOID)   //获取前景窗口

在output中写入:
 void Point::output(int x,int y)
 {
	HWND hwnd=GetForegroundWindow();
	HDC hdc=GetDC(hwnd);
	char buf[20];
	memset(buf,0,20);
	sprintf(buf,"x=%d,y=%d",x,y);
	TextOut(hdc,0,0,buf,strlen(buf));
	ReleaseDC(hwnd,hdc);
 }		//每次修改编译了dll文件后,都要重新复制到测试用的exe目录去
  再在测试程序中加一个button output的,添加单击代码:
	Point pt;
	pt.output(5,3);

  但这时是导入的一个类,若要导出类中的一个函数,就只需要像导出一般函数一样,在函数前面加个_declspec(dllexport):这里可改为DLL1_API void output(int x,int y);   //要把class Point间的去掉
  这时也可在exe中定义相应的类,只是没有在前面加宏定义的函数不能使用。
注意C++编译系统:在编译后,dll中导出的函数名发生了改变,如: ?subtract@@YAHHH@Z,这样C语言编写的程序去访问就会有问题了。
  为了让编译系统在编译之后名字不发生改变,我们在定义宏的时候,添加一个extern "C",如:
#define DLL1_API extern "C" _declspec(dllimport)

  我们将类的定义注释起来,然后在两个地方添加extern "C"  //注意是大写的C
  这时编译后的名称就没有改变了,可用dumpbin查看。这样就解决了C++和C相互调用的问题。但这样只能导出全局函数,不能导出类的成员函数。
  可以给函数加一个调用约定,如_stdcall(即pascal调用约定)。如:
int _stdcall add(int a,int b)  //delphi可以用,但名字还是变了

4.新建一个dll工程Dll2,增加一个c++源文件:再写add和subtract函数。
int add(int x,int y)
{
	return x+y;
}

int subtract(int x,int y)
{
	return x-y;
}

  然后在Dll2目录新建一个文本文档,名字改为Dll2.def(模块定义文件)。将这个文件加入到工程中。
  然后编辑Dll2.def:
LIBRARY DLL2    //必须的,DLL2必须和生成的dll文件名一样

EXPORTS		//导入的函数名
add
subtract	

  这时编译后,用dumpbin查看导出的文件名就没有改变了。

5.动态加载:先把dlltest的链接选项的dll1.Lib去掉,将先前的代码注释起来。
The LoadLibrary function maps the specified executable module into the address space of the calling process. 

HINSTANCE LoadLibrary(
  LPCTSTR lpLibFileName   // address of filename of executable module
);
//不仅可加载dll,还可加载exe

The GetProcAddress function returns the address of the specified exported dynamic-link library (DLL) function.  //获取导出的动态链接库函数的地址

FARPROC GetProcAddress(
  HMODULE hModule,    // handle to DLL module
  LPCSTR lpProcName   // name of function
);

  在发送按钮的OnBtnAdd函数中添加:
	HINSTANCE hInst;
	hInst=LoadLibrary("Dll2.dll");
	typedef int (*ADDPROC)(int x,int y);   //typedef定义了一个类似函数的指针
	ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"add");
	if(!Add)
	{
		MessageBox("获取函数地址失败!");
	}
	CString str;
	str.Format("5+3=%d",Add(5,3));
	MessageBox(str);  
//动态链接则只需要dll文件,不需要lib了。

  这时若在函数前面添加_stdcall,再用dumpbin查看,可发现导出的名字没有改变。
  但这时构造的标准指针类型需要改变:
typedef int (_stdcall *ADDPROC)(int x,int y);   //这样就可以了

6.新建一个Dll3的工程:添加一个add的导出函数:
_declspec(dllexport) int add(int x,int y)
{
	return x+y;
}
  然后在Dlltest中加载dll3,把_stdcall注释起来。
  这时运行test找不到函数地址,因为C++编译器又更改了函数名,这时复制dumpbin显示的函数名到GetProcAddress中即可得到。
  也可用函数的序号来获取,比如MAKEINTRESOURCE(1);  //1是从dumpbin看到的

7.动态链接库的入口:DllMain(可选)
The DllMain function is an optional method of entry into a dynamic-link library (DLL). If the function is used, it is called by the system when processes and threads are initialized and terminated, or upon calls to the LoadLibrary and FreeLibrary functions. &&&

DllMain is a placeholder for the library-defined function name. Earlier versions of the SDK documentation used DllEntryPoint as the entry-point function name. You must specify the actual name you use when you build your DLL. For more information, see the documentation included with your development tools. 

BOOL WINAPI DllMain(
  HINSTANCE hinstDLL,  // handle to DLL module
  DWORD fdwReason,     // reason for calling function:DLL_PROCESS/THREAD_ATTACH/DETACH
  LPVOID lpvReserved   // reserved
);
// DllMain是动态链接库的入口函数,他负责dll的初始化,卸载等功能。//网上说

  我们新建一个工程MFC DLL的:Dll4。
  这时有三种类型的dll:普通的dll(with MFC dll),只要有这个dll程序就可运行;普通dll用共享MFC dll的,还需要mfc 的dll才能运行;mfc扩展的dll(共享mfc dll),这样可以导出mfc的类。

The FreeLibrary function decrements消耗 the reference count of the loaded dynamic-link library (DLL) module. When the reference count reaches zero, the module is unmapped from the address space of the calling process and the handle is no longer valid. 

BOOL FreeLibrary(
  HMODULE hLibModule   // handle to loaded library module
);
  在调用完成之后就调用它释放dll。这时就可在OnBtnAdd后添加它来释放dll:
	FreeLibrary(hInst);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值