【C++】动态链接库(dll)和静态链接库(lib)

概述

动态链接库(dll)是指在程序运行时动态加载的库文件。特点是可是将主文件变得很小,例如QQ.exe,里面只有程序的运行框架,其余大部分都是dll调用,需要某个功能,将dll加载进来再使用就好了。这样的优点是实现了模块化,dll可以被多个文件所加载;缺点是dll调用会有系统开销

静态链接库(lib)是指在程序运行前加载进程序的库文件,有像我们include<stdio.h>,将stdio.h文件中的所有东西都粘贴到相应位置,静态链接库也一样,在静态链接库里写了什么,那些东西就会粘到我们引用的位置,然后一起编译

dll的一个典型使用就是操作系统,操作系统有多个动态链接库,有控制界面的、控制进程的等等,这些库一般很大。为什么采用动态链接库的形式而不是静态链接库的形式呢?可以想象,如果采用静态链接的方式,所有的程序的指令中都会包含很多相同的内容,而且有很大部分根本用不到,对内存的占用就很大;而采用动态链接库的方式,整个系统中只保留一份,谁要用,就通过指针指向相应函数入口就可以了,有点像C++的引用对象

编写动态链接库

创建dll工程

在VS2019中,文件-创建-项目:

选择动态链接库类型,然后一直下一步即可

创建完成后的项目框架默认如下:

其中,pch是预编译头文件,详细的可以自己去百度一下,总结来说就是将一些不怎么变动的头文件预先编译,加快工程编译速度。

dllmain.cpp是dll程序的主文件

内容如下:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

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;
}

DllMain函数是dll的入口点,每次这个dll被加载都会执行DllMain,然后根据运行时状态执行不同的命令,即switch段

编写自己的dll函数

编写函数的方法与在其他C++文件中完全一致,就在dllmain.cpp中编写即可

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

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;
}

//编写函数 pch.h里面记得要添加include<iostream>
void test1()
{
    std::cout << "test1 is worked\n";
}

void test2()
{
    std::cout << "test2 is worked\n";
}

将函数或对象暴露给外界的方法

编写完函数或对象,外界还是无法执行的,这就像js里的模块化编程,你需要将想要给外界使用的功能暴露(export)出来。

暴露的方法有两种

(1) 直接函数名(对象)前加入暴露给外界的关键字

__declspec(dllexport) void test1()
{
    std::cout << "test1 is worked\n";
}

这个时候,test1就可以被外界访问到了。

但这里卖个关子,C语言这样写是完全没有问题的,C++在却会因为重载机制无法被外界访问,后面详细解释再给出解决方法

C++中,其实我们更常用的是暴露对象给外界(之后找个时间再写吧)

class __declspec(dllexport) test
{
    test() {};
};

(2)使用模块定义文件(.def)

右键项目-添加项-新建项

选择模块定义文件创建

自定义一个名字后在里面写入:

LIBRARY dll测试   ;LIBRAY后面跟dll的项目名称
EXPORTS    ;EXPORTS代表后面的都是要export出去的函数
test2  ;一行一个函数名

.def文件中以";"作为注释符

这样写好后保存,test2函数就被暴露给外界了。

生成dll

在vs的顶部工具栏,依次点击生成-生成dll测试,等待即可

找到项目文件夹的debug目录,会发现生成了相应的dll文件

在其他程序中使用DLL

新建一个空项目,编写一个简单的main函数,来练习如何调用加载DLL

//main.cpp : 测试动态链接库
#include <iostream>

typedef void(*func)();
int main(void)
{
	//引入要加载的动态链接库
	//HMODULE点进去看的话其实是HINSTANCE的一个别名,就是一个句柄
	//如果获取到了,会返回这个动态库的句柄,否则返回NULL
	HMODULE dlltest = LoadLibraryW(L"dll测试.dll");
	if (dlltest)
	{
		//获取函数名所在的地址,即函数指针
		//获取到的地址默认是void类型,因此要自己定义一个函数指针,进行强制类型转换
		func test1 = (func)GetProcAddress(dlltest, "test1");
		if (test1) {
			test1();
		}
		else {
			MessageBoxW(NULL, L"找不到test1方法", L"ERROR", NULL);
		}
		func test2 = (func)GetProcAddress(dlltest, "test2");
		if (test2) {
			test2();
		}
		else {
			MessageBoxW(NULL, L"找不到test2方法", L"ERROR", NULL);
		}
	}
	else {
		MessageBoxW(NULL,L"找不到dll",L"ERROR",NULL);
	}
}

执行后的结果,是先弹出一个提示框,提示”找不到test1方法“,然后出现控制台,显示"test2 is worked"

为什么会这样呢,我们明明export出了test1方法

这要从C++的函数名重载的机制说起

C++支持函数名重载,方法是将原有函数名粉碎,向函数名中添加关于参数的信息,就是说原来的函数名就是test1,但C++会粉碎成?test1@@YAXXZ这样的名字,这也就导致了我们暴露出去的函数名其实根本不是test1

我们可以用VS提供的调试工具,使用dumpbin命令来查看下这个DLL的暴露信息(有时间再更)

看到没有,test2的函数名暴露出来了,但test1的函数名被粉碎成了一串很复杂的名字

我们之前再程序中使用GetProcAddress(HMODULE dll, const char* name)  来获取函数名,name填写的是"test1",函数通过test1这个名字根本找不到,因此name必须填”?test1@@YAXXZ“才行

那么如何解决这个问题呢,只要在编写dll的时候在函数前这样做:


extern "C" __declspec(dllexport) void test1()
{
    std::cout << "test1 is worked\n";
}

这样就告诉编译器,用C风格来暴露test函数,就不会被粉碎函数名了

当然,在C++中,我们其实更常用的是对象方式的加载

 

【有时间再更】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值