本文说明了程序热加载的原理。
热加载原理很简单,就是让程序在运行时打开动态库,并且获得里面的新函数。
这个技术也可以用在软件更新的时候,可以只将更新的动态库发布,用户下载之后在客户端重新打开库即可,这就不需要重新下载整个软件了。
这里我们来实战一下:
Window上的函数是:
1
2
3
|
void* LoadLibraryA(libname); // 加载动态库
void* GetProcAddress(handle, funcname); // 从动态库内获得函数
void FreeLibrary(handle); // 释放动态库
|
Linux上则是:
1
2
3
|
void* dlopen(const char *filename, int flags); // flags为RTLD_NOW即可
void* dlsym(void *handle, const char *symbol);
int dlclose(void *handle);
|
我这里直接用SDL给我封装好的函数了(编译平台是Linux):
1
2
3
|
void* SDL_LoadObject(const char *sofile);
void* SDL_LoadFunction(void *handle, const char *name);
void SDL_UnloadObject(void *handle);
|
首先我们写个库,这个库只有简简单单一个函数:
1
2
3
4
5
6
7
8
9
10
11
12
|
// script.cpp
#include <iostream>
extern "C" {
void ScriptSay() {
std::cout << "script say 1" << std::endl;
}
}
// 编译指令
// g++ script.cpp -shared -fPIC -o libscript.so
|
这里我用的C++编译,C++编译会将函数的符号导出成其他的,所以我们要加上extern "C"
来确保其导出正确。。
然后我们来写调用的此库的主程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
// main.cpp
#include <iostream>
#include <string>
#include "SDL.h"
using namespace std;
// 库的句柄
void* MyHandle = nullptr;
// 要获得的函数指针类型
using MyFunc = void(*)();
int main(int argc, char** argv) {
std::string str;
// 加载库
MyHandle = SDL_LoadObject("./libscript.so");
if (!MyHandle) {
std::cout << "load lib failed: " << SDL_GetError() << std::endl;
}
// 从库中导入需要的函数
MyFunc myFunc = (MyFunc)SDL_LoadFunction(MyHandle, "ScriptFunc");
while (cin>>str) {
if (str == "reload") { // 如果输入reload,我们就重新加载库和函数
SDL_UnloadObject(MyHandle);
MyHandle = SDL_LoadObject("./libscript.so");
MyFunc myFunc = (MyFunc)SDL_LoadFunction(MyHandle, "ScriptFunc");
}
if (!myFunc) {
std::cout << "load func failed: " << SDL_GetError() << std::endl;
} else {
myFunc(); // 调用函数
}
}
SDL_UnloadObject(MyHandle); // 释放库句柄
return 0;
}
// 编译指令
// g++ main.cpp -o main.out `sdl2-config --libs --cflags`
|
这样就写好了,注意不需要在编译指令中加上链接库,那种方法是编译时连接。
然后我们运行main.out
,随便输入一些东西后程序会输出
这个时候不要关闭程序,再次打开script.cpp
修改其输出为script say 2
,编译,然后我们在程序中输入reload
,他就会输出script say 2
了。