COM新手使用中一个易混淆的问题
CSDN贴地址:
http://blog.csdn.net/noslopforever/article/details/7278355
其实也没什么,[don box]里面也提过这个问题,但是没有继续展开。
比如依照图形系统而言,一般封装时,接口可能会这么来设计:
interface IRenderObject{};
interface IRenderResource: public IRenderObject {};
interface IRenderTexture : public IRenderResource {};
实现时,所有的Render Object您都希望将其绑定到Device上,所以您一定会希望有一个Render Object的公共基类来管理链表之类的。于是就可能这样:
class MyObjectBase : public IRenderObject
{
MyObjectBase* m_pNext;
};
然后,为了记录资源的大小等信息,同理:
class MyResource
: public MyObjectBase
, public IRenderResource
{
uint m_unSize;
};
最后,实现了一个Texture:
class MyTexture
: public MyResource
, public IRenderTexture
{
GLuint m_hTexture;
};
ok,准备工作完成。
接下来,使用者拿到这个库后,可能会通过这个系统的一个Facade创建一个Texture资源:
IRenderTexture* texture = XXXXSystem()->CreateTexture();
然后,系统同时提供了一个资源管理的很好用,他想把这个用到资源管理系统中,于是他:
XXXXSystem()->ManagerResource( texture );
这里,因为ManagerResource也是在XXXXSystem里提供的,为了实现功能,系统的提供者可能会这么写:
void ManagerResource(IRenderResource* InResource)
{
MyResource* resource = static_cast<MyResource*>(InResource);
resource->InnerMethod();
resource->InlineMethod();
}
好了。
到这里,您可以先考虑一下,现在会发生虾米事情?
最好的情况——Crash。最差的情况——没有Crash,但是内部完全乱套了。
为什么?
我们看一下,这中间我们一直使用的是MyTexture的实例,它的内存布局如何呢?
4字节vtbl
4字节 void* m_pNext
4字节IRenderResource vtbl
4字节 uint m_unSize
4字节IRenderTexture vtbl
4字节 GLuint m_hTexture;
也就是说,
IRenderTexture* texture = XXXXSystem()->CreateTexture();
这句话返回的是这个实例从头往下的第16个字节(0起始)。
而且,最糟糕的是,在下面这一句中:
XXXXSystem()->ManagerResource( texture );
因为IRenderTexture同时“是一个”IRenderResource,所以,这个+16会被直接当做IRenderResource传入给ManagerResource。
但事实上,按照ManagerResource的实现,它所希望的并非+16的IRenderTexture所包含的那个IRenderResource,而是+8的IRenderResource本身:
MyResource* resource = static_cast<MyResource*>(InResource);
这句话所做的,是把InResource的指针地址-8,如果传入的是+8的IRenderResource,它正好索取到这个对象的起始位置,一切就都正常了。但是,我们传入的事实上却是+16,于是——
程序发生了未可预知的错误,请与提供者或者微软联系……
这个问题怎么解决呢?
虽然[Don Box]里没有讨论这个情况,但却讨论了一个跟这个相关的主题,最后有一个原则性的结论,请千万要记住:
接口不是C++指针!!
因此:IRenderTexture接口就是IRenderTexture接口,它不能被当做IRenderResource接口使用,它里面所包含的IRenderResource的部分,只是说明
“我Render Texture也具有这些部分的功能”。
但并不代表C++意义上的:“我Render Texture同时也是一个Render Resource”。
所以,如果遇到这种情况,应该这么做:
IRenderTexture* texture = XXXXSYstem()->CreateTexture();
...
IRenderResource* resource = (IRenderResource*)texture->QueryInterface(IID_IRenderResource);
if (resource){
XXXXSystem()->ManagerResource(resource);
resource->Release();
}
这样就完全没有问题了。
题外话:
用Direct3D,总得接触一些COM,当时初学的时候,啥都喜欢追根究底,还真搬弄着Don Box的《Com本质论》猛读了一阵,后来发现工作中根本没啥用途,Direct3D那能叫COM吗?只是一些连皮毛都不算的东西,每本书还都煞有介事地用这个概念来唬人。Direct3D那些所谓接口云云,跟其它C++API库没什么不同,QueryInterface您用么?不用吧。Marshal什么的您用么?也不用吧。什么“接口并非指针”的问题,咱们也不会关注吧?若非必要,dxguid.lib估计很多人都不会去装载。其实COM的概念比起Direct3D用的程度要复杂得多,要不微软也就不至于去推.NET了——COM写起来太累了啊!!!!!
一开始总觉得COM只是一个“更好地C++”,其实也提不上更好,因为很多C++好用的东西在COM中是无从体现的,而单纯以扩展性而言,比起具备强制二进制标准的纯C又好不了到哪去。不过后面慢慢习惯了COM那套概念以后,发现确实还是有好处的,不需要再回去写纯C,也不需要因此把很多本来很容易明白的概念封装成大量的函数和Handle,调用起来也很清晰,不会出现我把Texture Handle给扔到设置Vertex Buffer Object的地方。难了实现者,便宜了使用者(当然比起纯C++又不便宜,但是扩展性更好)。