DLL动态库的创建,隐式加载和显式加载


动态库的创建


打开VS,创建如下控制台工程,工程命名为DllTest:



在弹出的对话框中选择“DLL”后单击“完成”按钮:




在工程中新建DllTest.h和DllTest.cpp文件,在DllTest.h头文件中加入以下代码:


#ifndef _DLLTEST_H_
#define _DLLTEST_H_

extern "C" _declspec(dllexport) int Add ( int a, int b);
extern "C" _declspec(dllexport) int Sub ( int a, int b);

#endif


其中#ifndef和#define的配合使用可以防止头文件被重复引用。


declspec(dllexport)将一个函数声明为导出函数,就是说这个函数是要其他程序调用的,作为DLL的一个对外函数接口。

extern “C” 的主要作用是为了让C++代码能够正确调用其他用C语言编写的代码,加上这个限定之后,编译器会把其后的代码段按照C语言的编译方式编译,而不是按照C++语言的编译方式编译。

C++编译方式跟C编译方式的不同之处在于:C++支持函数的重载,编译器在编译函数时会附带将函数的参数个数、参数类型信息一起添加到编译之后的代码中,而不仅仅只是包含函数名称,C++正是通过这种编译机制实现函数重载的。而编译器对C语言代码中的函数在编译时只包含函数名称,不附带参数类型或参数个数信息。


所以extern “C”声明的主要作用是为了实现C语言代码在C++代码中的混合编程。


在DllTest.cpp文件中加入以下代码:


// DllTest.cpp : 定义 DLL 应用程序的导出函数。
//

#include "stdafx.h"
#include "DllTest.h"

int Add(int a, int b)
{
	return a+b;
}

int Sub(int a, int b)
{
	return a-b;
}


以上操作相当于在DllTest.h文件中对所有DLL导出的函数进行导出声明,并且方便引用DLL的工程查看导出函数列表。DllTest.cpp文件中是对导出函数的具体实现。


编译DllTest工程,会在工程目录Debug/Release目录下生成DllTest.dll和DllTest.lib文件,加上DllTest.h文件,这三个文件就是导入DLL文件所需要的全部文件了。


在另一个工程中对DLL文件的加载有两种实现方式,分别是隐式加载(加载时动态链接)和显式加载(运行时动态加载)


隐式加载


隐式加载是在系统启动时一次性把所有的DLL的导出函数加载到可执行文件中,需要用到.h和.lib文件。隐式加载的步骤为:


1. 新建一个测试工程,把DllTest工程中的头文件DllTest.h拷入工程目录下,并在工程的头文件中添加该头文件。


2. 在工程中的cpp文件中加入以下代码:


#include <iostream>
#include "DllTest.h"

using namespace std;

#pragma comment (lib,"DllTest.lib")
extern "C" _declspec(dllimport) int Add( int a, int b);
extern "C" _declspec(dllimport) int Sub( int a, int b);

int main(int argc,char *argv[])
{
	int num1=5,num2=3;
	cout<<num1<<"+"<<num2<<" = "<<Add(num1,num2)<<endl;
	cout<<num1<<"-"<<num2<<" = "<<Sub(num1,num2)<<endl;
	system("pause");
	return 0;
}


_declspec(dllimport)指明从DLL文件中导出的函数。注意通过隐式链接加载的dll,也可以不用列出_declspec(dllimport),实际效果是一样的(可以尝试把这两段代码注释掉,也可以正常运行)。

所不同的是,加入_declspec(dllimport)后相当于明确告知编译器其后的函数是从外部dll加载的,调用的时候直接取dll文件的对应函数入口处调用,而不加_declspec(dllimport)则编译器不能分辨出当前函数是普通函数还是从别的dll文件中加载的函数,调用的时候需要在二进制代码中通过一个JMP指令跳转到dll文件中函数的入口地址。


3. 隐式加载需要使用到动态库的导入库——.lib文件,在工程中加入.lib文件主要有以下3中方式:

  • 拷贝.lib文件到工程目录下,然后在程序中通过 #pragma comment(lib, "DllTest.lib")链接即可;
  • 不直接拷贝.lib文件,而是通过.lib文件所在的绝对路径访问,在程序中通过 #pragma comment(lib, "D:\\XX\\DllTest.lib")链接绝对路径;
  • 在工程上右键->属性->链接器->输入->附加依赖项,在附加依赖项栏中输入.lib文件所在的绝对路径,如“D:\\XX\\DllTest.lib”,在程序中就不再需要指令#pragma comment(lib, "DllTest.lib");

无论是.lib文件采取哪种方式加入到工程中,在编译成功生成.exe可执行文件之后就不再需要.lib文件了,发布的时候也不需要带着.lib文件发布


4. 把.dll文件拷贝到程序根目录下的Debug/Release文件夹里,跟.exe文件同一目录,发布的时候也随.exe文件一起发布。


完成以上4个步骤之后就把Dll文件成功加载到程序中了,运行效果:




显式加载


显式加载不需要通过.lib和.h文件链接(当然前提是要知道.dll文件中包含的函数列表)。新建一个测试工程,在.cpp文件中加入以下代码:

#include <Windows.h>
#include <iostream>

using namespace std;

typedef int (*AddFunc) (int a, int b);
typedef int (*SubFunc) (int a, int b);

int main(int argc,char *argv[])
{
	int numb1=5, numb2=3;
	HMODULE hDll=LoadLibrary(L"DllTest.dll");
	if(hDll!=NULL)
	{
		AddFunc add=(AddFunc)GetProcAddress(hDll,"Add");
		SubFunc sub=(SubFunc)GetProcAddress(hDll,"Sub");
		if(add)
		{
			cout<<numb1<<"+"<<numb2<<" = "<<add(numb1,numb2)<<endl;
		}
		if(sub)
		{
			cout<<numb1<<"-"<<numb2<<" = "<<sub(numb1,numb2)<<endl;
		}
		system("pause");
	}

	FreeLibrary(hDll);
	
	return 0;
}


之后拷贝DllTest.dll文件到程序根目录下的Debug/Release文件夹中即可,在发布程序的时候也需要随着程序一起发布。

在显示加载中,程序会在需要的时候才去加载DLL文件,获取到DLL文件中相关的函数入口地址,然后执行,执行完之后可以立即释放掉资源。显示加载具有更好的灵活性,能更加有效的使用内存,在编写大型程序时往往使用显示加载方式。


程序加载了一个DLL文件,但生成的EXE在脱离了DLL文件后仍然可以 单独使用,这是动态加载DLL技术。即:调用资源DLL。 此技术的好处:EXE可以使用DLL的函数,但不会额外增加一 个DLL文件,在使用DLL文件的时候不需要先把DLL释放到硬盘。 在动态加载的这个DLL定义了一个函数MRun,该函数可以动态执行一 个EXE,即:调用资源的EXE文件或TMemoryStream被载入的EXE流。 此技术的好处:直接把资源的EXE加载到内存执行,使用程序自 身嵌入的EXE文件的时候不需要先把EXE释放到硬盘上就可以直接执行。 对保密EXE文件很有用。例如:我编写的程序是A.exe,它在运行后需要 使用B.exe,而B.exe是别人编写的我没有源码,但我必须又要在我的程 序用B.exe,这时我就把它包含到我的A.exe,这个非常容易做到, 但是,程序A.exe在使用程序B.exe的时候按照常理必须先把B.exe释放 到硬盘上才可以用WinExec或ShellExecute等函数调用它,但你在释放 到硬盘上的时候容易被别人直接复制走,而你只想让别人用你的A.exe不 想让别人直接用B.exe(因为B.exe是别人写的等原因),此时如何保密 B.exe呢?这时只要用到上面所说的MRun函数就可以了,程序A.exe在执 行B.exe的时候不需要释放到硬盘上就可以直接执行B.exe啦,是不是很爽? 说一下MRun的调用方式: MRun(流,参数,进程id); 调用成功返回True,失败返回False,三个参数解释如下: 第一个参数:一个载入了EXE的资源流或者内存流等流类型。 第二个参数:传递调用EXE的参数。如果EXE调用不需要参数,可设置为空串。 第三个参数:如果调用成功,则返回被调用的EXE对应的进程ID。 细节性问题,请直接双击Project1.dpr文件进入演示代码,了解更多。演示代 码动态加载了MemRun.dll文件,动态调用了5555044.exe文件,如果你想更换 动态调用的EXE文件,只需要用其它EXE覆盖5555044.exe文件并双击Clear.bat 文件后,在Delphi按F9重新编译运行即可。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值