一,动态链接库项目创建
dll工程内部架构
用VS2019创建动态链接库dll工程。
初始会有如下几个文件:
pch.h和pch.cpp与动态链接库功能无关。是用来保存多个文件时共同引用的头文件,函数,类等等,使得项目编译时这些代码只需要在pch.h中编译一次即可,节约时间。
dllmain.cpp里包含动态链接库的入口函数,作用是进程加载或卸载动态链接库时会调用此函数,可以编写switch中代码进行信息提示等。
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
如:你的进程加载动态库时,case DLL_PROCESS_ATTACH:
后的代码会被执行。
上述这些文件可以被删除,不影响动态库的使用。但若想用隐式调用,工程里必须至少保留一个头文件用于放要被引用的函数声明(因为隐式调用依赖.h文件)
除这些初始文件之外,程序员可以自己往dll工程中添加任意数量头文件或cpp文件,在其中写入想被引用的变量,函数,类等等。但所有的cpp文件开始都要引用pch.h(若其未被删除),且这些文件不能有同名的变量函数等。
上述所有文件在工程被生成时都会被整体编译出一个lib静态库文件和dll动态库文件。
引用部分
被引用函数格式:
extern “C” _declspec(dllexport) void fuc(){}
变量和类可以通过函数用返回值提供,例:
class A {
};
extern "C" _declspec(dllexport) A GetA() {
A a;
return a;
}
extern “C” :文件是C++的时候需要添加。若写在.h里,如果你的头文件不能被C文件引用,只能被C++引用。
功能是:表明函数使用c语言的风格编译函数,DLL跟其使用者都采用这种约定,那么就可以解决函数重命名规则不一致导致的错误,后面显式调用时,GetProcAddress第二个参数就是原来的函数名。否则如果使用C++编译的话支持函数重载,则第二个参数会发生变化而产生错误。
_declspec(dllexport) :声明导出函数、类、对象等供外面调用,表示这是要被外界引用的。省略给出.def文件。即将函数、类等声明为导出函数,供其它程序调用,作为动态库的对外接口函数、类等。同时调用函数也可以选择性加上_declspec(dllimport)表示这是从dll里调用的函数。
补充:.def文件(模块定义文件):是包含一个或多个描述各种DLL属性的Module语句的文本文件。.def文件或__declspec(dllexport)都是将公共符号导入到应用程序或从DLL导出函数。如果不提供__declspec(dllexport)导出DLL函数,则DLL需要提供.def文件。
二,动态链接库的调用
要生成动态链接库文件的代码
h
extern "C" _declspec(dllexport) void fuc();
cpp
#include<iostream>
using namespace std;
extern "C" _declspec(dllexport) void fuc() {
cout << 3 << endl;
}
隐式调用
隐式链接调用就是在程序开始执行时就将dll文件加载到程序当中且整个执行过程无法分离。调用十分简单,但需要h,lib和dll文件的支持。
程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件,该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。 LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。
调用方简单代码:(需要.h,.lib,.dll文件都放在要执行文件目录下,放在别处需直接寻址)
例:
#include"testdll.h"
#pragma comment(lib, "TESTDLL.lib")
int main()
{
fuc();
}
显式调用
显式链接调用是执行过程中随时可以加载或卸载dll文件。调用较麻烦但可以脱离h和lib,只需要dll文件。显式链接具有更好的灵活性,在工程较大时可以节省内存。对于解释性语言更为合适,也节省了编译时间。显示调用可以利用指针实现多态。
调用方简单代码:(需要.dll文件放在要执行文件目录下,放在别处需直接寻址)
例:
#include<Windows.h>
#include <iostream>
using namespace std;
void (*fuc)();
int main()
{
HMODULE hdll = LoadLibrary(L"test.dll");
if (hdll == NULL) {
printf("加载dll失败\n");
return 0;
}
fuc = (void(*)())GetProcAddress(hdll, "fuc");
if (fuc == NULL) {
cout << "获取函数失败"<<endl;
return 0;
}
fuc();
FreeLibrary(hdll);
}
注: 在实际工程项目中,若想在dll增加时,完全不需要改动代码就实现读取新的dll,就需要让代码直接读取dll所在的目录而非单个dll:
void GetDLL() {
char* filePath = "存放dll文件夹目录";
long hFile = 0;
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(filePath).append("\\*.dll").c_str(), &fileinfo)) != -1)
{
do
{
files.push_back(p.assign(filePath).append("\\").append(fileinfo.name));
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
kindnumber = files.size();
typedef ShapePackage* (*FUNGSP)();
HINSTANCE hShape;
FUNGSP GetPackage;
for (int i = 0; i < files.size(); i++)
{
hShape = LoadLibrary(files[i].c_str());
GetPackage = (FUNGSP)GetProcAddress(hShape, "函数名");
shapePackage[i] = GetPackage();
}
}
三,补充知识:
动态链接库和静态链接库
静态链接库:
程序在编译时,就会把静态库的内容和执行程序一起打包成一个exe文件,两者是一体的,exe文件可以直接执行。多个执行程序都有各自的静态库,一个静态库不能被多个程序共用。(整体)
静态链接库(.a,.lib)可以看作是obj文件,只不过多的代码隐蔽性。
动态链接库:
程序在被编译的时候,只有少量的有关dll的符号信息会被打包到执行程序中,只有执行时才会去寻找动态链接库并调用。dll文件是独立的,一个dll可以被多个exe程序调用,exe执行需要dll文件的存在。(分离)
静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件;动态链接就是把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码,因此需要相应DLL文件的支持。
.h头文件,.lib库文件,.dll动态链接库文件的关系
.h头文件是编译时必须的,lib是链接时需要的,dll是运行时需要的。
附加依赖项的是.lib不是.dll,若生成了DLL,则肯定也生成 LIB文件。如果要完成源代码的编译和链接,有头文件和lib就够了。如果也使动态连接的程序运行起来,有dll就够了。在开发和调试阶段,当然最好都有。
.h .lib .dll三者的关系是:
H文件作用是:声明函数接口
DLL文件作用是: 函数可执行代码
当我们在自己的程序中引用了一个H文件里的函数,编链器怎么知道该调用哪个DLL文件呢?这就是LIB文件的作用: 告诉链接器 调用的函数在哪个DLL中,函数执行代码在DLL中的什么位置,这也就是为什么需要附加依赖项, .LIB文件,它起到桥梁的作用。(因此动态链接库的隐式调用必须加载lib库来当向导,而显示调用没有向导则需要自己用GetProcAddress获取dll里函数地址(原本是lib库文件的任务),都是为了能找到dll里的对应函数)。
如果生成静态库文件,则没有DLL ,只有lib,这时函数可执行代码部分也在lib文件中。(静态链接库,不需要dll保存可执行代码,代码直接在lib里,编译时和主代码一起成为exe文件)
目前以lib后缀的库有两种,一种为包含函数代码本身,称为静态链接库(Static Libary,以下简称“静态库”),另一种是包含了函数所在DLL文件和文件中函数位置的信息,为动态连接库(DLL,以下简称“动态库”)的导入库(Import Libary,以下简称“导入库”)。般现有的DLL,用的是前一种库;以前在DOS下的TC/BC等,是后一种库。包含函数原型声明的,是头文件(.h)。
静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。比如你链接一个静态库,如果其中有错,它会准确的找到是哪个obj有错,即静态lib只是壳子。动态库一般会有对应的导入库,方便程序静态载入动态链接库,否则你可能就需要自己LoadLibary调入DLL文件,然后再手工GetProcAddress获得对应函数了。有了导入库,你只需要链接导入库后按照头文件函数接口的声明调用函数就可以了。导入库和静态库的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。
一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。在动态库的情况下,有两个文件,而一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)