我正在尝试从 Casey Muratori 的热门 Handmade Hero series 中复制一个很酷的技巧.在 win32 上,Casey 能够重新加载 DLL 并看到他的代码更改,而延迟只有几毫秒。
我正在尝试使用 dlopen、dlsym、dlclose 和 stat 在 Linux 上复制此行为,但我遇到了以下行为,而且我有一种预感,我要么误解了 ELF,例如,链接器,或者共享对象的概念。
我能够让他的代码在 win32 上毫不费力地工作,所以我觉得这是我所缺少的 Linux 特有的东西。
我正在使用 CMake 进行构建,但我并不特别认为 CMake 是罪魁祸首。
我复制了共享库 dynamic.so,并加载它。每当原始共享对象的 mtime 更新时,我关闭旧拷贝的句柄,创建一个新拷贝,然后尝试加载新拷贝。
我想指出,我打算在第一次更改后打破循环,因为我只是想弄清楚这一点。
#include <stdio.h>
#include <dlfcn.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
void
CopyFile(const char* src, const char* dest)
{
FILE* fsrc;
FILE* fdest;
unsigned char buffer[512];
size_t bytes;
fprintf(stderr, "copy from: %s to %s!\n", src, dest);
fsrc = fopen(src, "rb");
if ( fsrc == NULL )
┆ fprintf(stderr, "failed to open file: %s for reading\n", src);
fdest = fopen(dest, "wb");
if ( fdest == NULL )
┆ fprintf(stderr, "failed to open file: %s for reading\n", src);
while ( (bytes = fread(buffer, 1, sizeof(buffer), fsrc)) > 0 )
{
┆ fwrite(buffer, 1, bytes, fdest);
}
fclose(fsrc);
fclose(fdest);
fprintf(stderr, "copy complete!\n");
}
int main(int argc, char** argv)
{
const char* libpath = "/home/bacon/dynamic.so";
const char* copypath = "/home/bacon/dynamic-copy.so";
CopyFile(libpath, copypath);
void* handle = dlopen(copypath, RTLD_NOW | RTLD_GLOBAL);
if ( handle == NULL )
fprintf(stderr, "failed to load %s, error = %s\n", copypath, dlerror());
struct stat s;
stat(libpath, &s);
time_t oldtime = s.st_mtime;
while (true)
{
stat(libpath, &s);
if ( oldtime != s.st_mtime )
{
if ( handle != NULL )
{
if ( dlclose(handle) )
fprintf(stderr, "dlclose failed: %s\n", dlerror());
else
handle = NULL;
}
CopyFile(libpath, copypath);
handle = dlopen(copypath, RTLD_NOW | RTLD_GLOBAL);
if ( handle == NULL )
fprintf(stderr, "failed to load %s, error = %s\n", copypath, dlerror());
break;
}
}
}
至于动态库,什么都应该做(例如标题):
#ifndef DYNAMIC_HEADER
#define DYNAMIC_HEADER 1
#define DYNAMIC_API __attribute__ ((visibility("default")))
extern "C" DYNAMIC_API int
Add(int x, int y);
#endif /* DYNAMIC_HEADER */
和源文件:
#include "Dynamic.h"
int
Add(int x, int y)
{
return x + y;
}
共享库只是提供了一些例程来将几个数字加在一起,并且我已经验证了我能够在没有热重载技巧的情况下使用 dlopen 和 dlsym。
我还验证了我的复制例程实际上复制了共享对象。
我希望最初的 dlopen 能够成功,并且 dlsym 能够正确链接 Add(它会)。然后我会编辑 Dynamic.cpp 并可能返回 x + x + y 或其他东西,保存文件并重新编译,期望 while 循环在 st_mtime 中获取更改。
我注意到当我运行代码并更新时我收到了错误:
dlopen: file too short
果然,当我 ls -la 包含共享对象的目录时,拷贝大小为 0。
不知何故,stat报告的st_mtime更新了,但是共享对象的实际内容是空的?链接器是否锁定共享对象并阻止读取?
如果我的代码没有严重错误,我该如何避免这种行为?
我不愿意休眠并重试,因为这是一个相当即时的更新。
最佳答案
If my code isn't horribly wrong
这是非常错误的:您的代码正在与(静态)链接器(由 make
或 cmake
调用)竞争。
当 make
运行时,它(最终)调用:
gcc -shared -o /home/bacon/dynamic.so foo.o bar.o ...
然后链接器将执行open("/home/bacon/dynamic.so", O_WRONLY|O_CREAT, ...)
(或等效的),一段时间后将写入
,最后关闭
文件。
当 m_time
发生变化时,您的程序将被唤醒,这是 open
之后的任何时间,并且将尝试复制文件。如果您的复制发生在最终关闭
之前的任何时间,那么您可能会得到部分拷贝(包括包含 0 字节的部分拷贝)。
最明显的解决方案是 Zsigmond 建议的解决方案:您必须修改 Makefile
以链接一个与您正在观看的文件不同的文件,然后执行 mv
作为最后(原子)步骤到达最终目的地。
另一种解决方案是拥有一个依赖于dynamic.so
的make
目标,例如
dynamic.so.done: dynamic.so
touch dynamic.so.done
在您的程序中,您会观察 m_time
以获取 dynamic.so.done
,只有当 那个 文件更新时,才执行dynamic.so
的拷贝(保证到那时已经close
d)。
关于c++ - 如何在 Linux 上热重载共享库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56334288/