程序热加载原理

本文说明了程序热加载的原理。

热加载原理很简单,就是让程序在运行时打开动态库,并且获得里面的新函数。

这个技术也可以用在软件更新的时候,可以只将更新的动态库发布,用户下载之后在客户端重新打开库即可,这就不需要重新下载整个软件了。

这里我们来实战一下:

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,随便输入一些东西后程序会输出

1
script say 1

这个时候不要关闭程序,再次打开script.cpp修改其输出为script say 2,编译,然后我们在程序中输入reload,他就会输出script say 2了。

updatedupdated2023-06-082023-06-08