【问题标题】:How to load a dynamic library on demand from a C++ function/Qt method如何从 C++ 函数/Qt 方法按需加载动态库
【发布时间】:2016-09-02 11:15:11
【问题描述】:

我创建了如下动态库

cat 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 等效于 load 可以从另一个 C++ 函数按需加载共享库。

我的要求是在函数内部运行时加载动态库,然后直接使用qt函数

1) 加载 qt 共享库(用于 lib1.so) 2) 直接调用函数,不调用resolve

例如我们有dopen,但是对于每个函数调用我们必须调用dsym。我的要求是只调用共享库,然后直接调用这些函数。

【问题讨论】:

  • 你看QLibrary了吗?
  • 据我了解,我需要解决对 QT 方法的所有调用,这些调用似乎并不干净。你能建议一些更简洁的方法吗,我只有几行代码可以在运行时加载共享库
  • 好的,我在阅读您的问题时没有明白这一点。如果您在运行时使用 dlopen 加载库,AFAIK 没有 dllsym 就没有办法。另一种方法是在编译时链接到库。
  • 这个问题是不完整的,没有提到您正在尝试使用裸 C++ API 即 qtcore 按需加载 C++ 库。

标签: qt linker tcl shared-libraries system-calls


【解决方案1】:

您想要无样板延迟加载。在 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实现。

为此,您可以将库拆分为两个库:

  1. 库实现。它不知道需求负载。
  2. 按需加载存根。

可执行文件将链接到按需加载存根库;那是静态的或动态的。按需加载存根将在运行时自动解析符号并调用实现。

如果你够聪明,你可以为实现设计头文件,这样头文件本身就可以用来生成所有存根,而不必输入两次它们的详细信息。

完整示例

一切如下,也可以从https://github.com/KubaO/stackoverflown/tree/master/questions/demand-load-39291032获得

顶层项目包括:

  • lib1 - 动态库
  • lib1_demand - lib1 的静态需求负载重击
  • main - 使用 lib1_demand 的应用程序

demand-load-39291032.pro

TEMPLATE = subdirs
SUBDIRS = lib1 lib1_demand main
main.depends = lib1_demand
lib1_demand.depends = lib1

我们可以将聪明之处分解到一个单独的标题中。这个头文件允许我们定义库接口,以便自动生成 thunk。

由于 C 的限制,需要大量使用预处理器和一些冗余的语法。如果您只想为 C++ 实现这一点,则无需重复参数列表。

需求加载.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.hDEMAND_FUN,而不是直接声明函数。如果在包含标头时定义了DEMAND_LOAD_LIB1,它将为库提供按需加载接口。如果定义了DEMAND_BUILD,它将定义按需加载thunk。如果两者都没有定义,它将提供一个正常的接口。

我们注意取消定义特定于实现的宏,以免污染全局命名空间。然后,我们可以在项目中包含多个库,每个库都可以在按需加载和非按需加载之间单独选择。

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+dlsymQLibrary。为简单起见,我选择了后者:

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;
}

【讨论】:

  • 共享库是我的情况 m 我必须在运行时按需加载是 libQtCore.so 和 libQtGui.so 在我的情况下,对 Qt 库代码的函数调用将在 80000 左右,所以我了解我需要为 QT 函数的每次调用都有 DEMAND_FUNC ,这对我来说实际上不可行。如果有任何更清洁的方法,对于我的问题
  • 通过dlsym/QLibrary 将 C++ 库与裸 C++ API 一起使用是不可能的,因为您不能拥有指向构造函数/析构函数的方法指针。为什么你认为你需要按需加载 Qt?你需要修改你的设计。如果你真的认为你确实需要它,那么你将不得不做一些艰苦的工作。您必须根据您要链接的目标文件,在程序集中自动生成 thunk。您还必须处理不可调用的符号 - 我确定 Qt 有一个或两个全局符号,而这些只能通过利用 PIC 来实现。你有一些真正的工作摆在你面前。
【解决方案2】:

除了将库加载到 C++ 代码中的过程(Kuber Ober 的回答很好)之外,您加载的代码是错误的;即使您设法加载它,您的代码崩溃!这是因为您在文件范围内有一个 Tcl_Interp 类型的变量;这是对 Tcl 库的错误使用。相反,该库仅提供一种方法来获取解释器上下文的句柄Tcl_CreateInterp()(以及一些其他封装它的函数),并返回一个已经正确初始化的Tcl_Interp*。 (严格地说,它实际上返回了一个句柄,该句柄实际上是 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 有合理的支持已经这样了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-15
    • 2023-04-01
    • 2011-02-18
    • 2020-03-19
    • 1970-01-01
    • 2012-01-31
    • 2011-09-06
    • 2013-01-19
    相关资源
    最近更新 更多