【问题标题】:Invoking non static class member function of dynamically loaded library调用动态加载库的非静态类成员函数
【发布时间】:2020-07-22 00:22:31
【问题描述】:

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

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

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 相同 - 函数也是我班级的静态成员,但我不确定这是否是个好主意。 这是我班上的定义

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))()

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

那么,如何调用这种方式加载类的析构函数呢?我如何调用它的其余成员?还是我应该以完全不同的方式更好地做到这一点?

附:我知道,有一个用于 KWallet 的 DBUS API,甚至是一些像 qtkeychain 这样的包装库,但我想了解使用这个示例制作这种依赖项的方式。

【问题讨论】:

  • 除此之外,您为什么要尝试显式调用析构函数?你不应该简单地使用delete wallet吗?
  • 老实说 - 我不知道。理论上我应该在同一个模块中分配和释放内存。更重要的是 - 我的主模块不知道用 KF5Wallet.so 的虚拟析构函数编写的代码,所以,我只是按照我的感觉和猜测。你认为这种情况在某个地方得到了深层次的处理吗?我可以像delete wallet 甚至wallet.deleteLater() 这样称呼它吗?
  • 我猜你写了一个小的 .cpp 只是试图加载库并使用它。整个问题受到不完整的代码和模糊的描述的影响(尽管代码的运行时加载很困难,Qt 是一团糟,所以这是很自然的)。除了问题中的内容外,还要以我们不必了解 KWallet 的方式编写它。见stackoverflow.com/help/minimal-reproducible-example

标签: c++ qt dynamic shared-libraries


【解决方案1】:

我找到了解决办法。

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

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 并在构建时将链接指向真正的依赖项,在我的情况下为 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);

【讨论】:

    【解决方案2】:

    在进入解决方案之前,我应该声明这是一个非常糟糕的主意,我看不出这样做的合理理由,除非您使用的是来自已编译共享库的类,而您无法编辑其源代码并且该类没有替代的虚拟接口。

    在 C++ 中实现这一点的更好方法是使用虚拟方法,方法是创建一个包含您需要使用的功能的基类,并且共享库中的任何子类都可以覆盖这些虚拟方法以自定义行为。

    现在这是您的情况的解决方案:

    类的非静态方法具有thiscall 的调用约定,这意味着它们与普通函数一样,只是它们将指向类实例的指针作为第一个参数,这是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;
    };
    

    因此,在您的情况下:为每个方法声明添加一个指向作为第一个参数的类实例的指针,当您想在实例上调用此方法时,只需将其作为第一个指针传递。

    虚拟功能有两种实现方式:

    1 - 类本身内部的 vtable,如 c vtables

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

    【讨论】:

    • 我明白了,但这正是我想要做的!我正在尝试访问只有 .h 并且可能是 .so 的第三方编译代码,这将在运行时被发现。我们的想法是像这里 (archlinux.org/packages/extra/x86_64/ark) 一样创建一个可选依赖项,你看到了吗?您只需安装一些其他软件包并获得 rar 支持或 7z 支持,但没有它们它仍然可以工作
    • 我在回答中为这个案例提供了解决方案
    猜你喜欢
    • 1970-01-01
    • 2014-04-14
    • 1970-01-01
    • 2017-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    相关资源
    最近更新 更多