关于COM组件queryInterface的需求思考

本文探讨了在COM组件中queryInterface接口的作用和重要性。当动态库发生改动时,通过封装构造函数和类型转换,利用queryInterface实现旧代码与新库的兼容,避免重新编译可执行文件。文章通过具体场景分析了queryInterface如何解决内存分布改变导致的错误,阐述了其实现原理。

背景

项目上遇到一份老代码的框架,其中有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这些操作,就不继续分析了。才疏学浅,代码凭印象瞎写的,大概说明个原理跟思路,欢迎指正。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值