COM新手使用中一个易混淆的问题

来源:互联网 发布:终端医院数据库 编辑:程序博客网 时间:2024/06/05 09:22

其实也没什么,[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++又不便宜,但是扩展性更好)。

原创粉丝点击