[C++]如何动态加载DLL

实际流程

1.动态加载动态库

LoadLibrary("xxxx.dll")	// 宏函数 依据项目定义字符格式决定函数版本
LoadLibraryA("xxxx.dll")// 窄字符版本
LoadLibraryW("xxxx.dll")// 宽字符版本

该函数返回模块句柄.

类型:HINSTANCE
本质是个结构体,里面包含一个int类型变量.
可以使用HMODULE来替换

或使用auto

例子1:

HINSTANCE MyModule{ LoadLibraryA("MyCode.dll") };	//倘若该对象内模块地址不会改变可以加const修饰

例子2:

HMODULE MyModule{ LoadLibraryA("MyCode.dll") };		//倘若该对象内模块地址不会改变可以加const修饰

例子3:

auto MyModule{ LoadLibraryA("MyCode.dll") };		//倘若该对象内模块地址不会改变可以加const修饰

2.获取导出函数地址

GetProcAddress(模块句柄, "导出函数名")

该函数返回导出函数地址.
类型:FARPROC
该类型命名是历史遗留问题,早期计算机硬件技术不行,资源有限,对于内存地址分远近地址(near&far)
存储远端地址就要用可以表达更大范围的类型
可以按照早期版本一样,使用该类型.
该类型本质是 整数类型返回值的无参数stdcall调用标准函数指针.


3.通过导出函数地址进行调用

当你知道了导出函数地址还不够,还需要知道这个函数的原型.
假设导出函数原型如下

int Add(int,int)

调用原理:

(int(*)(int,int))(导出函数地址)(参数1,参数2);

以合法的方式调用改地址.
编译器会依托于此来生成汇编代码.

4.伪代码

两个函数原型
int Add(int,int);
double Add(double,double);
导出重载函数,应确保每一个重载有独一无二的导出函数名.
函数名是确保你获得导出函数地址的关键.
具体实现方法请搜索: .def 模块定义文件

例子1:

if (const auto MyModule{ LoadLibraryA("MyCode.dll") })
    {
        void* IntegerAdd{ GetProcAddress(MyModule, "IntegerAdd") };   
        void* DoubleAdd{ GetProcAddress(MyModule,"DoubleAdd") };                
        std::cout << static_cast<int(*)(int, int)>(IntegerAdd)(500, 123);          
        std::cout << std::endl;
        std::cout << static_cast<double(*)(double, double)>(DoubleAdd)(123.321, 123.321);
        FreeLibrary(MyModule);
    }

例子2:

if (const auto MyModule{ LoadLibraryA("MyCode.dll") })
    {
        std::cout << reinterpret_cast<int(*)(int, int)>(GetProcAddress(MyModule, "IntegerAdd"))(1, 2);
        std::cout << std::endl;
        std::cout << reinterpret_cast<double(*)(double, double)>(GetProcAddress(MyModule, "DoubleAdd"))(1., 2.);
        FreeLibrary(MyModule);
    }

例子3:

if (const auto MyModule{ LoadLibraryA("MyCode.dll") })
    {
        int(*IntegerAdd)(int, int) { reinterpret_cast<int(*)(int, int)>(GetProcAddress(MyModule, "IntegerAdd")) };
        double(*DoubleAdd)(double, double) { reinterpret_cast<double(*)(double, double)>(GetProcAddress(MyModule, "DoubleAdd")) };
        std::cout << IntegerAdd(1, 2);
        std::cout << std::endl;
        std::cout << DoubleAdd(1., 2.);
        FreeLibrary(MyModule);
    }

如果存储导出函数地址的变量不会被重新赋值,那么可以使用const修饰
如果不想写很长的一串函数指针声明,可以使用auto
例子4:

if (const auto MyModule{ LoadLibraryA("MyCode.dll") })
    {
        const auto IntegerAdd {reinterpret_cast<int(*)(int, int)>(GetProcAddress(MyModule, "IntegerAdd")) };
        const auto DoubleAdd { reinterpret_cast<double(*)(double, double)>(GetProcAddress(MyModule, "DoubleAdd")) };
        std::cout << IntegerAdd(1, 2);
        std::cout << std::endl;
        std::cout << IntegerAdd(55, 2);
        std::cout << std::endl;
        std::cout << DoubleAdd(1., 2.);
        FreeLibrary(MyModule);
    }

额外-关于函数调用

只要掌握原理,调用动态库导出函数可以有很多很多种方法.
可以将函数名理解为函数在内存中的首地址.(整个函数流程的的第一次操作地址)
就像数组的下标0一样,是其在内存空间中的首地址.
你每次写下xxx();这样的函数调用语句.
都可以理解为 函数地址();
而地址,就是无符号整数类型.
合法的调用函数,就是传函数需要你传的数据,接函数需要你接的数据.
C/Cpp不会限制你的语法,只要你符合计算机的视角下的规则就可以.
你甚至可以这么调用函数,如果你的老师或者同事喜欢的话

#include <iostream>

void 我是一个函数()
{
    std::cout << "很抱歉以这种方式认识你" << std::endl;
}

int main()
{
    void* 我是函数地址{ static_cast<void*>(&我是一个函数) };
    static_cast<void(*)()>(我是函数地址)();	// 我现在等价于函数,并且被调用
    return 0;
}

这是完全合法0error(s)0warning(s),在编译器(计算机)眼中很合理的代码


警告!
本文中出现使用static_cast和reinterpret_cast在对象指针和函数指针间进行转换是微软扩展语法
该语法本身未定义.
请不要在多系统兼容项目中使用,不保证任何兼容性,也不保证在非MSVC编译器中合法
为保障通用性,请使用C风格转换


动态加载DLL原理

1.将DLL加载到目标进程虚拟内存空间

加载
动态库
进程内存空间

在这里插入图片描述

2.获取DLL导出函数的地址

在这里插入图片描述
当你调用一个函数的时候,会去跳转到被调用函数的首地址进行执行语句.
而使用DLL导出的函数,也是一个原理,所以你需要知道DLL导出的函数首地址.
比如DLL导出的函数A的首地址是0x10086,那么就应该获取到这个地址.
有了这个地址之后,就可以使用该函数.

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八宝咸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值