背景
项目上遇到一份老代码的框架,其中有queryInterface的接口看着让人无所适从,查阅了com组件的一些资料后,做一个简单的总结。
场景
你有一份代码main.cpp编译出可执行文件A,依赖于libs.cpp编译出来的动态库B。编译完成后,动态库做了一些改动,重新编译后推到系统lib64文件夹内。试下想A是否能正常运行?
开始时状态是这样的:
// libs.cpp
Class MyBaseClass {
public:
virtual void doSth();
private:
int valA;
};
Class MyClass : public MyBaseClass {
public:
virtual void doSth() override
{
...
}
};
// main.cpp
int main()
{
MyClass* tmp = new MyClass();
MyBaseClass* p = static_cast<MyBaseClass*>(tmp);
p->doSth();
return 0;
}
可执行文件A编译完后,不作改动。libs.cpp改动后重新编译,改动如下:
// libs.cpp
Class MyBaseClass {
...
private:
int valA;
int valB; // 新增变量,重新编译libs
};
Class MyClass : public MyBaseClass {
...
private:
int val; // 新增变量,重新编译libs
};
A是否能正常运行?
理论上会报错,原因是static_cast把子类转换成父类是基于内存里面的两个类的offset的,增加变量后内存分布不同了。
解决办法
能不能把构造函数、类型转换封装成一个函数,重新编译B后,A依然还是调用这几个接口,这样A就不用重新编译也能正常使用了。这就是queryInterface的一个设计原因了,同样原因的还有构造函数用的CreateObj(大概这个名字)。
修改后的代码如下:
// libs.cpp
Class Intf {
public:
virtual static Intf* CreateObj(int IID) = 0;
virtual int queryInterface(int IID, void** ppv) = 0;
private:
static int CUR_IID = 123;
};
Class MyBaseClass : public Intf {
public:
virtual static Intf* CreateObj(int IID) override
{
if (IID == CUR_IID) {
MyBaseClass* tmp = new MyBaseClass();
}
...
return tmp;
}
virtual int queryInterface(int IID, void** ppv) override
{
if (IID == Intf::CUR_IID) {
*ppv = static_cast<Intf*>(this);
return 0;
}
...
}
...
private:
...
static int CUR_IID = 456;
};
Class MyClass : public MyBaseClass {
public:
virtual static Intf* CreateObj(int IID) override
{
if (IID == CUR_IID) {
MyClass* tmp = new MyClass();
}
...
return tmp;
}
virtual int queryInterface(int IID, void** ppv) override
{
if (IID == Intf::IID) {
*ppv = static_cast<Intf*>(this);
return 0;
}
if (IID == MyBaseClass::IID) {
*ppv = static_cast<MyBaseClass*>(this);
return 0;
}
...
}
...
private:
...
static int CUR_IID = 789;
};
// main.cpp
int main()
{
Intf* tmp = MyClass::CreateObj(MyClass::CUR_IID);
if (tmp == nullptr) {
// error msg
...
}
MyClass* p = nullptr;
if (queryInterface(MyClass::CUR_IID, p) != 0 || p == nullptr) {
// error msg
...
}
p->doSth();
return 0;
}
Intf的虚函数表已经被目标子类重写了,CreateObj是一定实现的函数,对象创建后内存空间分配是跟新版本的动态库一致的,同时queryInterface也必然在虚函数表中被重写,static_cast被封装在里面就可以根据最新版本两个类间的offset做转换,这样就不会报错了。
后续还有AddRef这些操作,就不继续分析了。才疏学浅,代码凭印象瞎写的,大概说明个原理跟思路,欢迎指正。
本文探讨了在COM组件中queryInterface接口的作用和重要性。当动态库发生改动时,通过封装构造函数和类型转换,利用queryInterface实现旧代码与新库的兼容,避免重新编译可执行文件。文章通过具体场景分析了queryInterface如何解决内存分布改变导致的错误,阐述了其实现原理。
314

被折叠的 条评论
为什么被折叠?



