Not Only Algorithm,不仅仅是算法,关注数学、算法、数据结构、程序员笔试面试以及一切涉及计算机编程之美的内容 。。
你的位置:NoAlGo博客 » 技术 » ,

在Visual Stuido中创建DLL

DLL(Dynamic Link Library,动态链接库)是Windows中一个包含可由多个程序同时使用的代码和数据的共享库,是实现代码复用提高软件开发效率的重要途径。Linux下等价的概念称为so(share object)文件,本文主要关注Windows方面的内容。

一般的复用如应用程序框架等是源码级的复用,属于白盒复用,它暴露了源代码,造成代码严重耦合,模块更新比较困难。DLL属于二进制级别的代码复用,称为黑盒复用,它隐藏了源码,在运行时被动态加载,可以被多个应用程序同时调用,节省内存资源,而且可以独立地更新而不影响其调用者。

DLL有很多种,以下以非MFC动态链接库为例,讲解在Visual Studio中创建一个DLL的完整过程。
关于创建之后的DLL的使用方法详见:在Visual Stuido中使用DLL

一 新建项目

创建一个DLL项目和创建普通的控制台应用程序类似,只是在最后应用程序设置时选择DLL而不是控制台程序。

新建myDll项目之后,根据DLL的内容创建相关文件。这里添加了myLib.h和myLib.cpp文件。

二 定义DLL内容

为了演示,这里的DLL导出的内容包含三部分:一个共享的全局变量globalVar,一个函数addFun,一个类myClass,其中类成员只有一个public的showMessage函数。

在DLL导出内容有不同的方法,这里主要完整地讲解一种方法,其他方法会在后面稍微涉及。

这里讲的是在DLL 中添加关键字__declspec(dllexport)进行导出,比如导出一个二元函数addFun只需进行如下声明:

__declspec(dllexport) int addFun(int a, int b);

但是这里涉及到C++和C的编译方式的不同,C++在编译之后会在函数名称前后加上C++的类型标志,为了让DLL能够被C和C++同时使用,我们需要以extern "C"的方式进行导出,即

extern "C" __declspec(dllexport) int addFun(int a, int b);

通常为了方便地处理多个这种情况,一般在程序的开头使用#ifdef __cplusplus判断是否为C++编译器,然后统一以extern "C"的方式进行。导出变量和类的方法类似。

另外在使用DLL时可以使用关键字__declspec(dllimport)进行导入,比如使用刚刚导出的函数只需进行如下声明:

extern "C" __declspec(dllimport) int addFun(int a, int b);

于是为了方便使用,一般会使用一个宏代替上面的关键字。而且,头文件可能会同时用来导出DLL和被调用DLL的程序使用,于是需要包含export和import两种情况,我们使用一个预定义的宏对这两种情况进行区分。在生成DLL时有这个宏而在调用DLL时没有这个宏,同时使用条件编译,可以分别使用不同的关键字。

三 加入必要控制信息

在myLib.h头文件中,首先使用#ifndef MYLIB_H条件编译指令进行防止重复包含头文件的工作,只有没有定义MYLIB_H的时候才编译该头文件。

然后使用#ifdef MYDLL_EXPORTS根据预定义的宏判断是进行导出还是导入,使用一个宏DLL_API表示相应的关键字。注意MYDLL_EXPORTS宏是在myLib.cpp中定义的,在实际调用者程序是没有定义的。在使用完之后,使用#undef MYDLL_EXPORTS取消这个宏定义。

使用#ifdef __cplusplus指令判断是否是C++编译器,如果是的话需要使用extern "C"以C的方式进行导出。注意在末尾还需在判断一次,以补上表示结束的右大括号。

有了以上控制信息,之后可以填写具体DLL的内容了。

四 导出变量

在.h头文件中进行变量导出,在.cpp实现文件中进行变量的定义。因为在头文件中变量并没有进行定义,需要加上extern关键字表示它会咋别处定义,否则会有重定义的错误。

具体代码为:

//in myLib.h
DLL_API extern int globalVar; //导出变量(在实现中定义,需要extern)
//in myLib.cpp
int globalVar = 123; //变量定义

五 导出函数

在.h头文件中进行函数的生命,在.cpp中进行函数的定义实现,跟一般写函数的方法一致,具体代码为:

//in myLib.h
DLL_API int addFun(int a, int b); //导出函数
//in myLib.cpp
int addFun(int a, int b) //函数定义
{ 
	return a + b;
}

六 导出类

也是跟一般写类的方法一致,在.h头文件中进行类的声明,在.cpp文件中进行变量及函数的实现。

//in myLib.h
class DLL_API myClass //导出类
{
public:
	void showMessage();
};
//in myLib.cpp
void myClass::showMessage() //类的成员函数定义
{
	cout << "Hello World!" << endl;
}

七 定义DLL主函数(可选)

同每个应用程序一样,DLL也有一个主函数,不过是可选的,如果没有实现的话,编译器会用一个默认的主函数进行。在.cpp文件中进行如下签名的主函数的实现

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)

第二个参数指的是调用DLL的原因,实现中通过switch语句进行判断,可以添加相应的逻辑,参考具体代码实现。

八 编译生成DLL

到这里DLL内容已经全部编写完成,可以生成具体的DLL了。点击Build下面的Build Solution可以进行构建,构建完成之后再项目目录下的Debug文件夹中可以看到已经生成的myLib.dll文件和myLib.lib文件。其中dll文件是编译好的二进制的代码内容,在运行时用到;lib文件定义的是DLL的接口,是供其它程序调用DLL使用。

为了方便查看,这里贴出DLL项目的整体代码。

myLib.h文件中的内容如下:

#ifndef MYLIB_H
#define MYLIB_H

#ifdef MYDLL_EXPORTS //根据预定义(在cpp实现中定义)的宏判断角色,使用不同API
#define DLL_API __declspec(dllexport) //导出者使用export
#else
#define DLL_API __declspec(dllimport) //调用者使用import
#endif

#ifdef __cplusplus
extern "C" {
#endif

DLL_API extern int globalVar; //导出变量(在实现中定义,需要extern)

DLL_API int addFun(int a, int b); //导出函数

class DLL_API myClass //导出类
{
public:
	void showMessage();
};

#ifdef __cplusplus
}
#endif

#undef MYDLL_EXPORTS //取消预定义宏

#endif

myLib.cpp文件中的内容如下:

#define MYDLL_EXPORTS //预定义宏,控制头文件中选择export对于的API

#include "myLib.h"
#include <iostream>
#include <Windows.h>
using namespace std;

int globalVar = 123; //变量定义

int addFun(int a, int b) //函数定义
{ 
	return a + b;
}

void myClass::showMessage() //类的成员函数定义
{
	cout << "Hello World!" << endl;
}

//Dll主函数,可以省略
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		cout << "======Process attach of dll======" << endl; break;
	case DLL_PROCESS_DETACH:
		cout << "======Process detach of dll======" << endl; break;
	case DLL_THREAD_ATTACH:
		cout << "======Thread attach of dll======" << endl; break;
	case DLL_THREAD_DETACH:
		cout << "======Thread detach of dll======" << endl;	break;
	}
	return TRUE;
}

九 使用def文件导出DLL内容(可选)

除了以上所说的使用关键字__declspec(dllexport)导出DLL内容之外,还可以使用模块定义文件(.def)文件进行导出。

这时在头文件声明中不需要添加对应的关键字,但是需要穿件一个新的模块定义文件,如lib.def,方法跟普通添加新建项的方法一致。添加完成之后,需要在项目中声明,右键项目名称,选择属性,然后选择链接器->输入->模块定义文件,加入刚刚建立的文件。

在新建的def文件中添加导出内容的信息,如下所示:

;lib.def : 导出DLL函数

LIBRARY myDll

EXPORTS

globalVar @ 1 data
addFun @ 2

分号开始的是注释,LIBRARY表示导出的DLL名称,EXPORTS表示导出的内容,这里导出一个变量和一个函数,@后面表示该符号的序号,data表示这是一个变量。

添加完毕后可以build产生对应的dll。

上一篇: 下一篇:

我的博客

NoAlGo头像编程这件小事牵扯到太多的知识,很容易知其然而不知其所以然,但真正了不起的程序员对自己程序的每一个字节都了如指掌,要立足基础理论,努力提升自我的专业修养。

站内搜索

最新评论