我正在使用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))()
但是这似乎不起作用。我是这个话题的新手,即使我以正确的方式开始,我也不知道。
所以,如何调用这种方式加载的类的析构函数?如何调用其他成员?还是我最好以完全其他的方式来做?
P.S。我知道,有一个用于KWallet的DBUS API,甚至还有一些包装库,例如qtkeychain,但我想了解使用此示例进行这种依赖的方式。
在进入解决方案之前,我应该指出这是一个非常糟糕的主意,除非您使用的是编译共享库中的类,否则您将无法编辑其源代码,并且类没有替代的虚拟接口。
在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,并且据说此方法更适合缓存,因此大多数编译器都使用它
我已经找到解决方法。
这个想法是用这样的包装函数编写一个小的共享库
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);