调用动态加载库的非静态class成员函数

Invoking non static class member function of dynamically loaded library

我正在用 KWallet 编写一个具有可选运行时依赖性的应用程序。这意味着如果用户系统上安装了 KWallet,它就会被使用,如果没有,它仍然可以工作,但没有 KWallet 支持。

这是我加载库的方式,它是我的包装器 class 的静态 属性。然后在状态条件下的构造函数中,我从库中解析符号。

QLibrary Core::PSE::KWallet::lib("KF5Wallet");
...
lib.load();  
openWallet = (OpenWallet) lib.resolve("_ZN7KWallet6Wallet10openWalletERK7QStringyNS0_8OpenTypeE");
networkWallet = (NetworkWallet) lib.resolve("_ZN7KWallet6Wallet13NetworkWalletEv");
destructor = (Destructor) lib.resolve("_ZN7KWallet6WalletD2Ev");

与 QLibrary 一样 - 函数也是我的 class 的静态成员,但我不确定这是否是个好主意。 以下是我的 class

的定义
typedef ::KWallet::Wallet* (*OpenWallet)(const QString &, WId, ::KWallet::Wallet::OpenType);
typedef QString (*NetworkWallet)();
typedef void (*WalletOpened)(bool);
typedef void (*Destructor)();

static OpenWallet openWallet;
static NetworkWallet networkWallet;
static Destructor destructor;

这是我分配对象的方式

wallet = openWallet(networkWallet(), 0, ::KWallet::Wallet::Asynchronous);

一切顺利,直到我需要执行非静态成员,尤其是析构函数。据我所知,它应该是这样的

((*wallet).(destructor))()

但这似乎不起作用。我对这个话题完全陌生,即使我以正确的方式开始,我也不知道。

那么,如何调用这种方式加载的析构函数class?我如何调用它的其余成员?还是我最好用完全不同的方式来做?

P.S。我知道,KWallet 有一个 DBUS API,甚至是一些包装库,如 qtkeychain,但我想了解如何使用此示例建立这种依赖关系。

在进入解决方案之前,我应该声明这是一个非常糟糕的主意,我看不出这样做的合理理由,除非您使用编译共享库中的 class,您可以'编辑它的源代码,class 没有替代的虚拟接口。

在 C++ 中实现此目的的更好方法是通过创建包含您需要使用的功能的基础 class 来使用虚拟方法,并且共享库中的任何子 class 都可以覆盖那些虚拟方法自定义行为的方法。

现在这是您的案例的解决方案:

class 的非静态方法具有 thiscall 的调用约定,这意味着它们类似于普通函数,只是它们将指向 class 实例的指针作为第一个参数,这是 this 指针!实际上,c++(非虚拟)中的方法是对结构

进行操作的 c 函数的语法糖

这段代码说明了:

struct somestruct
{
    int j;
};

void add(somestruct* this, int i)
{
    this->j += i;
}


class someclass
{
public:
    void add(int i)
    {
        j += i;
    }
private:
    int j;
};

所以在你的情况下:为每个方法声明添加一个指向 class 实例的指针,这是第一个参数,当你想在实例上调用这个方法时,只需将它作为第一个指针传递。

虚函数有两种实现方式:

1 - class 内部的 vtable 本身就像 c vtables

2 - 一个指向 class 内的 vtable 的指针,因此每个 class 声明只有一个 vtable,据说这种方法更适合缓存,因此大多数编译器都使用它

我找到了解决方案。

想法是编写一个带有类似包装函数的小型共享库

extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) {
    return KWallet::Wallet::openWallet(name, w, ot);
}
extern "C" void deleteWallet(KWallet::Wallet* w) {
    w->deleteLater();
}
extern "C" const char* networkWallet() {
    return KWallet::Wallet::NetworkWallet().toStdString().c_str();
}
extern "C" int readPassword(KWallet::Wallet* w, const QString &key, QString &value) {
    return w->readPassword(key, value);
}

我们称这个小包装器为 foo.so。所以,然后你构建这个 foo.so 并在构建时将 link 定位到真正的依赖项,在我的例子中是 KWallet

然后在主代码中,您将尝试动态加载此 foo.so,而不是 KWallet 本身。如果 KWallet 在启动机器上不存在,这个 foo.so 根本不会加载,这就是我必须知道的技巧!

那么你当然可以像这样解析符号

QLibrary Core::PSE::KWallet::lib("foo");
...
lib.load();  
openWallet = (OpenWallet) lib.resolve("openWallet");
networkWallet = (NetworkWallet) lib.resolve("networkWallet");
deleteWallet = (DeleteWallet) lib.resolve("deleteWallet");
readPassword = (ReadPassword) lib.resolve("readPassword");

然后这样称呼它

wallet = openWallet(networkWallet(), 0, ::KWallet::Wallet::Asynchronous);
...
QString password;
int result = readPassword(wallet, *i, password);
...
deleteWallet(wallet);