如何从 C++ function/Qt 方法按需加载动态库
How to load a dynamic library on demand from a C++ function/Qt method
我创建的动态库如下
猫myfile.cc
struct Tcl_Interp;
extern "C" int My_Init(Tcl_Interp *) { return 0; }
1) 编译cc文件
g++ -fPIC -c myfile.cc
2) 创建共享库
g++ -static-libstdc++ -static-libgcc -shared -o libmy.so myfile.o -L/tools/linux64/qt-4.6.0/lib -lQtCore -lQtGui
3) 从 TCL proc 加载库
然后我给出命令
tclsh
并给出命令
% 负载 libmy.so
是否有任何 C++ 函数/Qt 等效于加载,可以从另一个 C++ 函数按需加载共享库。
我的要求是在函数内部运行时间加载动态库,然后直接使用qt函数
1) 加载 qt 共享库(对于 lib1.so)
2) 直接调用函数而不调用 resolve
例如,我们有 dopen,但对于每个函数调用,我们都必须调用 dsym。我的要求是只调用共享库然后直接调用那些函数。
您想要boilerplate-less延迟加载。在 Windows 上,MSVC 通过发出一个通过函数指针解析函数的存根来实现 delay loading。你也可以做到的。首先,让我们观察一下函数指针和函数是可以互换的,如果您所做的只是调用它们的话。调用函数或函数指针的语法相同:
void foo_impl() {}
void (*foo)() = foo_impl;
int main() {
foo_impl();
foo();
}
想法是将函数指针最初设置为一个 thunk,它将在运行时解析实际函数:
extern void (*foo)();
void foo_thunk() {
foo = QLibrary::resolve("libmylib", "foo");
if (!foo) abort();
return foo();
}
void (*foo)() = foo_thunk;
int main() {
foo(); // calls foo_thunk to resolve foo and calls foo from libmylib
foo(); // calls foo from libmylib
}
第一次调用foo
时,会真正调用foo_thunk
,解析函数地址,调用真正的foo
实现。
为此,您可以将库拆分为两个库:
- 库实现。它不知道 demand-loading.
- 一个demand-load存根。
可执行文件会link到demand-load存根库;那是静态的还是动态的。 demand-load 存根将在运行时自动解析符号并调用实现。
如果你够聪明,你可以为实现设计 header,这样 header 本身就可以用来生成所有的存根,而无需输入它们的详细信息两次。
完整示例
万事俱备,也可从https://github.com/KubaO/Whosebugn/tree/master/questions/demand-load-39291032
top-level 项目包括:
lib1
- 动态库
lib1_demand
- lib1
的静态 demand-load thunk
main
- 使用 lib1_demand
的应用程序
demand-load-39291032.pro
TEMPLATE = subdirs
SUBDIRS = lib1 lib1_demand main
main.depends = lib1_demand
lib1_demand.depends = lib1
我们可以把聪明分解成一个单独的 header。 header 允许我们定义库接口,以便自动生成 thunk。
由于 C 的限制,需要大量使用预处理器和有点冗余的语法。如果您只想为 C++ 实现此功能,则无需重复参数列表。
demand_load.h
// Configuration macros:
// DEMAND_NAME - must be set to a unique identifier of the library
// DEMAND_LOAD - if defined, the functions are declared as function pointers, **or**
// DEMAND_BUILD - if defined, the thunks and function pointers are defined
#if defined(DEMAND_FUN)
#error Multiple inclusion of demand_load.h without undefining DEMAND_FUN first.
#endif
#if !defined(DEMAND_NAME)
#error DEMAND_NAME must be defined
#endif
#if defined(DEMAND_LOAD)
// Interface via a function pointer
#define DEMAND_FUN(ret,name,args,arg_call) \
extern ret (*name)args;
#elif defined(DEMAND_BUILD)
// Implementation of the demand loader stub
#ifndef DEMAND_CAT
#define DEMAND_CAT_(x,y) x##y
#define DEMAND_CAT(x,y) DEMAND_CAT_(x,y)
#endif
void (* DEMAND_CAT(resolve_,DEMAND_NAME)(const char *))();
#if defined(__cplusplus)
#define DEMAND_FUN(ret,name,args,arg_call) \
extern ret (*name)args; \
ret name##_thunk args { \
name = reinterpret_cast<decltype(name)>(DEMAND_CAT(resolve_,DEMAND_NAME)(#name)); \
return name arg_call; \
}\
ret (*name)args = name##_thunk;
#else
#define DEMAND_FUN(ret,name,args,arg_call) \
extern ret (*name)args; \
ret name##_impl args { \
name = (void*)DEMAND_CAT(resolve_,DEMAND_NAME)(#name); \
name arg_call; \
}\
ret (*name)args = name##_impl;
#endif // __cplusplus
#else
// Interface via a function
#define DEMAND_FUN(ret,name,args,arg_call) \
ret name args;
#endif
然后,动态库本身:
lib1/lib1.pro
TEMPLATE = lib
SOURCES = lib1.c
HEADERS = lib1.h
INCLUDEPATH += ..
DEPENDPATH += ..
我们将使用 demand_load.h
中的 DEMAND_FUN
,而不是直接声明函数。如果在包含 header 时定义了 DEMAND_LOAD_LIB1
,它将为库提供一个 demand-load 接口。如果定义了 DEMAND_BUILD
,它将 定义 demand-load thunk。如果两者都没有定义,它将提供一个普通的界面。
我们注意取消定义 implementation-specific 宏,以免污染全局命名空间。然后我们可以在项目中包含多个库,每个库都可以在按需加载和 non-demand 加载之间单独选择。
lib1/lib1.h
#ifndef LIB_H
#define LIB_H
#ifdef __cplusplus
extern "C" {
#endif
#define DEMAND_NAME LIB1
#ifdef DEMAND_LOAD_LIB1
#define DEMAND_LOAD
#endif
#include "demand_load.h"
#undef DEMAND_LOAD
DEMAND_FUN(int, My_Add, (int i, int j), (i,j))
DEMAND_FUN(int, My_Subtract, (int i, int j), (i,j))
#undef DEMAND_FUN
#undef DEMAND_NAME
#ifdef __cplusplus
}
#endif
#endif
实施无争议:
lib1/lib1.c
#include "lib1.h"
int My_Add(int i, int j) {
return i+j;
}
int My_Subtract(int i, int j) {
return i-j;
}
对于这样一个库的用户,需求加载减少到定义一个宏并使用 thunk 库 lib1_demand
而不是动态库 lib1
.
main/main.pro
if (true) {
# Use demand-loaded lib1
DEFINES += DEMAND_LOAD_LIB1
LIBS += -L../lib1_demand -llib1_demand
} else {
# Use direct-loaded lib1
LIBS += -L../lib1 -llib1
}
QT = core
CONFIG += console c++11
CONFIG -= app_bundle
TARGET = demand-load-39291032
TEMPLATE = app
INCLUDEPATH += ..
DEPENDPATH += ..
SOURCES = main.cpp
main/main.cpp
#include "lib1/lib1.h"
#include <QtCore>
int main() {
auto a = My_Add(1, 2);
Q_ASSERT(a == 3);
auto b = My_Add(3, 4);
Q_ASSERT(b == 7);
auto c = My_Subtract(5, 7);
Q_ASSERT(c == -2);
}
最后,thunk的实现。这里我们可以选择使用 dlopen
+dlsym
或 QLibrary
。为了简单起见,我选择了后者:
lib1_demand/lib1_demand.pro
QT = core
TEMPLATE = lib
CONFIG += staticlib
INCLUDEPATH += ..
DEPENDPATH += ..
SOURCES = lib1_demand.cpp
HEADERS = ../demand_load.h
lib1_demand/lib1_demand.cpp
#define DEMAND_BUILD
#include "lib1/lib1.h"
#include <QLibrary>
void (* resolve_LIB1(const char * name))() {
auto f = QLibrary::resolve("../lib1/liblib1", name);
return f;
}
除了将库加载到您的 C++ 代码中(Kuber Ober 的回答涵盖得很好)之外,您加载的代码是错误的;即使您设法加载它,您的代码 也会 崩溃!这是因为您在文件范围内有一个 Tcl_Interp
类型的变量;那是对 Tcl 库的错误使用。相反,该库只提供了一种获取解释器上下文句柄的方法,Tcl_CreateInterp()
(以及一些其他包装它的函数),并且 returns 一个 Tcl_Interp*
具有已经正确初始化。 (严格来说,它实际上 returns 是 Tcl_Interp
的有效内部子类的句柄,所以你 真的 不能有效地自己分配一个。)
库的正确用法是这样的:
Tcl_FindExecutable(NULL); // Or argv[0] if you have it
Tcl_Interp *interp = Tcl_CreateInterp();
// And now, you can use the rest of the API as you see fit
这是为了在您的代码中 放置一个 Tcl 解释器。反过来,你创建了一个 int My_Init(Tcl_Interp*)
函数,就像你描述的那样,它用来告诉你解释器在哪里,但是你不会问如何加载代码,因为 Tcl 有合理的已经支持了。
我创建的动态库如下
猫myfile.cc
struct Tcl_Interp;
extern "C" int My_Init(Tcl_Interp *) { return 0; }
1) 编译cc文件
g++ -fPIC -c myfile.cc
2) 创建共享库
g++ -static-libstdc++ -static-libgcc -shared -o libmy.so myfile.o -L/tools/linux64/qt-4.6.0/lib -lQtCore -lQtGui
3) 从 TCL proc 加载库 然后我给出命令
tclsh 并给出命令 % 负载 libmy.so
是否有任何 C++ 函数/Qt 等效于加载,可以从另一个 C++ 函数按需加载共享库。
我的要求是在函数内部运行时间加载动态库,然后直接使用qt函数
1) 加载 qt 共享库(对于 lib1.so) 2) 直接调用函数而不调用 resolve
例如,我们有 dopen,但对于每个函数调用,我们都必须调用 dsym。我的要求是只调用共享库然后直接调用那些函数。
您想要boilerplate-less延迟加载。在 Windows 上,MSVC 通过发出一个通过函数指针解析函数的存根来实现 delay loading。你也可以做到的。首先,让我们观察一下函数指针和函数是可以互换的,如果您所做的只是调用它们的话。调用函数或函数指针的语法相同:
void foo_impl() {}
void (*foo)() = foo_impl;
int main() {
foo_impl();
foo();
}
想法是将函数指针最初设置为一个 thunk,它将在运行时解析实际函数:
extern void (*foo)();
void foo_thunk() {
foo = QLibrary::resolve("libmylib", "foo");
if (!foo) abort();
return foo();
}
void (*foo)() = foo_thunk;
int main() {
foo(); // calls foo_thunk to resolve foo and calls foo from libmylib
foo(); // calls foo from libmylib
}
第一次调用foo
时,会真正调用foo_thunk
,解析函数地址,调用真正的foo
实现。
为此,您可以将库拆分为两个库:
- 库实现。它不知道 demand-loading.
- 一个demand-load存根。
可执行文件会link到demand-load存根库;那是静态的还是动态的。 demand-load 存根将在运行时自动解析符号并调用实现。
如果你够聪明,你可以为实现设计 header,这样 header 本身就可以用来生成所有的存根,而无需输入它们的详细信息两次。
完整示例
万事俱备,也可从https://github.com/KubaO/Whosebugn/tree/master/questions/demand-load-39291032
top-level 项目包括:
lib1
- 动态库lib1_demand
-lib1
的静态 demand-load thunk
main
- 使用lib1_demand
的应用程序
demand-load-39291032.pro
TEMPLATE = subdirs
SUBDIRS = lib1 lib1_demand main
main.depends = lib1_demand
lib1_demand.depends = lib1
我们可以把聪明分解成一个单独的 header。 header 允许我们定义库接口,以便自动生成 thunk。
由于 C 的限制,需要大量使用预处理器和有点冗余的语法。如果您只想为 C++ 实现此功能,则无需重复参数列表。
demand_load.h
// Configuration macros:
// DEMAND_NAME - must be set to a unique identifier of the library
// DEMAND_LOAD - if defined, the functions are declared as function pointers, **or**
// DEMAND_BUILD - if defined, the thunks and function pointers are defined
#if defined(DEMAND_FUN)
#error Multiple inclusion of demand_load.h without undefining DEMAND_FUN first.
#endif
#if !defined(DEMAND_NAME)
#error DEMAND_NAME must be defined
#endif
#if defined(DEMAND_LOAD)
// Interface via a function pointer
#define DEMAND_FUN(ret,name,args,arg_call) \
extern ret (*name)args;
#elif defined(DEMAND_BUILD)
// Implementation of the demand loader stub
#ifndef DEMAND_CAT
#define DEMAND_CAT_(x,y) x##y
#define DEMAND_CAT(x,y) DEMAND_CAT_(x,y)
#endif
void (* DEMAND_CAT(resolve_,DEMAND_NAME)(const char *))();
#if defined(__cplusplus)
#define DEMAND_FUN(ret,name,args,arg_call) \
extern ret (*name)args; \
ret name##_thunk args { \
name = reinterpret_cast<decltype(name)>(DEMAND_CAT(resolve_,DEMAND_NAME)(#name)); \
return name arg_call; \
}\
ret (*name)args = name##_thunk;
#else
#define DEMAND_FUN(ret,name,args,arg_call) \
extern ret (*name)args; \
ret name##_impl args { \
name = (void*)DEMAND_CAT(resolve_,DEMAND_NAME)(#name); \
name arg_call; \
}\
ret (*name)args = name##_impl;
#endif // __cplusplus
#else
// Interface via a function
#define DEMAND_FUN(ret,name,args,arg_call) \
ret name args;
#endif
然后,动态库本身:
lib1/lib1.pro
TEMPLATE = lib
SOURCES = lib1.c
HEADERS = lib1.h
INCLUDEPATH += ..
DEPENDPATH += ..
我们将使用 demand_load.h
中的 DEMAND_FUN
,而不是直接声明函数。如果在包含 header 时定义了 DEMAND_LOAD_LIB1
,它将为库提供一个 demand-load 接口。如果定义了 DEMAND_BUILD
,它将 定义 demand-load thunk。如果两者都没有定义,它将提供一个普通的界面。
我们注意取消定义 implementation-specific 宏,以免污染全局命名空间。然后我们可以在项目中包含多个库,每个库都可以在按需加载和 non-demand 加载之间单独选择。
lib1/lib1.h
#ifndef LIB_H
#define LIB_H
#ifdef __cplusplus
extern "C" {
#endif
#define DEMAND_NAME LIB1
#ifdef DEMAND_LOAD_LIB1
#define DEMAND_LOAD
#endif
#include "demand_load.h"
#undef DEMAND_LOAD
DEMAND_FUN(int, My_Add, (int i, int j), (i,j))
DEMAND_FUN(int, My_Subtract, (int i, int j), (i,j))
#undef DEMAND_FUN
#undef DEMAND_NAME
#ifdef __cplusplus
}
#endif
#endif
实施无争议:
lib1/lib1.c
#include "lib1.h"
int My_Add(int i, int j) {
return i+j;
}
int My_Subtract(int i, int j) {
return i-j;
}
对于这样一个库的用户,需求加载减少到定义一个宏并使用 thunk 库 lib1_demand
而不是动态库 lib1
.
main/main.pro
if (true) {
# Use demand-loaded lib1
DEFINES += DEMAND_LOAD_LIB1
LIBS += -L../lib1_demand -llib1_demand
} else {
# Use direct-loaded lib1
LIBS += -L../lib1 -llib1
}
QT = core
CONFIG += console c++11
CONFIG -= app_bundle
TARGET = demand-load-39291032
TEMPLATE = app
INCLUDEPATH += ..
DEPENDPATH += ..
SOURCES = main.cpp
main/main.cpp
#include "lib1/lib1.h"
#include <QtCore>
int main() {
auto a = My_Add(1, 2);
Q_ASSERT(a == 3);
auto b = My_Add(3, 4);
Q_ASSERT(b == 7);
auto c = My_Subtract(5, 7);
Q_ASSERT(c == -2);
}
最后,thunk的实现。这里我们可以选择使用 dlopen
+dlsym
或 QLibrary
。为了简单起见,我选择了后者:
lib1_demand/lib1_demand.pro
QT = core
TEMPLATE = lib
CONFIG += staticlib
INCLUDEPATH += ..
DEPENDPATH += ..
SOURCES = lib1_demand.cpp
HEADERS = ../demand_load.h
lib1_demand/lib1_demand.cpp
#define DEMAND_BUILD
#include "lib1/lib1.h"
#include <QLibrary>
void (* resolve_LIB1(const char * name))() {
auto f = QLibrary::resolve("../lib1/liblib1", name);
return f;
}
除了将库加载到您的 C++ 代码中(Kuber Ober 的回答涵盖得很好)之外,您加载的代码是错误的;即使您设法加载它,您的代码 也会 崩溃!这是因为您在文件范围内有一个 Tcl_Interp
类型的变量;那是对 Tcl 库的错误使用。相反,该库只提供了一种获取解释器上下文句柄的方法,Tcl_CreateInterp()
(以及一些其他包装它的函数),并且 returns 一个 Tcl_Interp*
具有已经正确初始化。 (严格来说,它实际上 returns 是 Tcl_Interp
的有效内部子类的句柄,所以你 真的 不能有效地自己分配一个。)
库的正确用法是这样的:
Tcl_FindExecutable(NULL); // Or argv[0] if you have it
Tcl_Interp *interp = Tcl_CreateInterp();
// And now, you can use the rest of the API as you see fit
这是为了在您的代码中 放置一个 Tcl 解释器。反过来,你创建了一个 int My_Init(Tcl_Interp*)
函数,就像你描述的那样,它用来告诉你解释器在哪里,但是你不会问如何加载代码,因为 Tcl 有合理的已经支持了。