基本COM,Don-Box
第1章,作为二进制接口的抽象基,第18页
如果你没有这本书的副本,并且对组件对象模型(COM)感兴趣,请向下滚动到底部,在那里我会提供一些进一步的背景信息。
IFastString
接口类定义为
// ifaststring.h
class IFastString
{
public:
virtual void Delete() = 0;
virtual int Length() const = 0;
virtual int Find(const char*) const = 0;
};
extern "C"
IFastString* CreateIFastString(const char* psz);
FastString
类定义为
// faststring.h
#include "ifaststring.h"
class FastString : public IFastString
{
private:
const int m_ch;
char *m_psz;
public:
FastString(const char *psz);
~FastString();
void Delete();
int Length() const;
int Find(const char *psz) const;
};
最后,这里是FastString
的实现
// faststring.cpp
#include <string.h>
#include <faststring.h>
IFastString* CreateFastString(const char *psz)
{
return new FastString(psz);
}
FastString::FastString(const char *psz)
: m_ch(strlen(psz))
, m_psz(new char[m_ch + 1])
{
strcpy(m_psz, psz);
}
FastString::~FastString()
{
delete [] m_psz;
}
void FastString::Delete()
{
delete this;
}
int FastString::Length() const
{
return m_ch;
}
int FastString::Find(const char *psz) const
{
// find algorithm implementation goes here
}
当我第一次看到这个代码时;奇数";我很困惑为什么创建函数的定义方式与删除函数不同。
在我看来,这个代码有两种可能的修改。在我看来,以下两种选择都是可行的。
1:创建函数可以成为成员函数。(?)
我最初认为这是可能的,但现在我认为这是不可能的,因为C++类成员函数不能用extern "C"
链接定义。
因此,选择将创建函数定义为非成员并不是完全任意的。
2:删除功能可以成为非成员功能。然后,它将与创建功能更加匹配。
// h (interface)
extern "C" // is this needed?
void DeleteIFastString(IFastString *s);
// cpp
void DeleteIFastString(IFastString *s)
{
delete s;
}
// remove the virtual void Delete() function from both
// IFastString and FastString classes
我认为没有做出这个决定的唯一原因是,它用另一个非成员函数填充了全局命名空间。然而另一方面,具有语法上相似的";删除器";到您的";创建者";功能可以说是可取的。
deleter被实现为成员函数还有其他原因吗?
进一步的背景信息(COM)
上面例子中的表意符号的目的是解决两个问题:
- 动态库的设计应具有一个定义的接口,该接口不会更改
这样就可以在不需要修改客户端代码的情况下更改实现。有人可以更新";FastString";动态库保持接口不变,以便客户端代码可以链接到动态库的新版本,而无需更改客户端源代码。
- 动态库的设计应具有不改变的公开对象大小
这样,更改后的动态库可以发送到客户端,编译后的客户端代码可以加载动态库代码,函数将以正确的偏移量访问数据。
考虑以下几点:如果接口类的成员变量发生了更改,那么这些数据在内存中存储的偏移量或位置就会发生变化。如果一个接口类暴露于客户端代码,那么该接口类的不同编译版本将是二进制不兼容的。
- 如果使用与编译动态库代码的编译器不同的编译器编译客户端代码,则客户端应在运行时加载动态库并按预期工作
这可能不会发生的原因是编译器是否以不兼容的方式使用编译代码。例如,虚拟函数有多种可能的实现,因为C++标准没有定义多态性和虚拟函数应该如何实现。不同的编译器可能会产生不同的二进制代码,这些代码是不兼容的。
Don Box对这一点的描述比我在这里几页的描述更为精确。希望我提供了足够的信息,使之可以理解。
COM通过定义一个没有成员变量的接口类,以及一个客户端代码完全无法访问其数据成员和函数的实现类,解决了上述三个问题。除此之外,所有涉及实现类的成员数据的函数都是用同一编译器编译的。(用于编译动态库的编译器。)这意味着函数和数据由同一编译器编译,因此二进制兼容。最后,用户可访问的函数是用外部C链接定义的,这样它们是兼容的,并且可以与用不同编译器编译的客户端代码链接。
值得注意的是,以上假设的古建筑是相同的。例如,COM并不能解决x86代码与ARM系统不兼容的问题。(可能很明显,但为了避免混淆,值得一提。)
COM不公开用于实例创建的C++类构造函数或静态类成员函数。COM函数是CoCreateInstance
,但这不是唯一的可能。
一些COM接口是通过IClassFactory
上的方法构建的,但您仍然需要一种机制来获取IClassFactory
(CoGetClassObject
)
COM没有使用Delete
方法,相关的成员函数是Release
。所有COM接口都继承自IUnknown
。
如果对接口的所有引用都被释放,那么公开接口的对象有责任知道如何处理它自己。
您可以在具有不同大小的不同对象上运行访问同一接口的不同实现的代码,并且在不同的DLL中实现。你会调用哪个静态函数来删除它?这不是你的问题。这就是通过接口的要点——你不在乎实现是什么
用C++以外的语言构造COM接口是可能的,而调用者不会知道。
上述第2点无关紧要;对象的大小对调用者来说并不重要。COM公开接口,而不是对象。重要的是接口的二进制布局,它(在实践中)被定义为"二进制";MS编译器用这个C++定义做什么";。