Hybird3D

乔捷的技术博客

博客园 首页 新随笔 联系 订阅 管理

近日拜读了陈硕大牛的文章C++ 工程实践(5):避免使用虚函数作为库的接口,文章的观点认为应该避免使用C++的class纯虚函数来定义API接口,并以COM作为反向教材进行批判,对此本人有些不同意见,记录在此与各位一同探讨。

陈硕大牛认为C++的虚函数是以虚函数在class中定义的位置来确定其虚表的绑定位置,在class扩充的过程中,原有的虚函数位置不可以变动,因此带来了接口扩展的脆弱与僵硬。虚函数位置不可以随意变动是事实,但是给接口扩展造成问题不敢苟同。陈硕举出的linux接口的例子,个人认为完全不妥,完全没有必要使用百层的继承,有很多种技巧可以解决这个问题,比方可以写成这样:

 

class LinuxKernel
{
  
//v0.1
  int restart_syscall() = 0;  //0
  int exit() = 0;  //1
  int fork() = 0;  //2
  .....
  
#if LINUX_VER>0.1
  
int sigaction() = 0;
#endif  

#if LINUX_VER>0.11
  
int sgetmask() = 0;  //68
  int ssetmask() = 0;  //69
  int setreuid() = 0;  //70
  int setregid() = 0;  //71
#endif  

#if LINUX_VER>0.12
  
int sigsuspend() = 0;  //72
  int sigpending() = 0;  //73
  int sethostname() = 0;  //74
  int setrlimit() = 0;  //75
#endif  

 
//.....
};

 

一般来说kernel级的API会有点特殊,Kernel是系统最底层的基础,在构建kernel时往往C++编译器都还没有,所以在实践中通常我们不会用C++去定义kernel API,但是这不代表从理论上行不通。搞过系统底层的同学,应该对中断入口表这类的数据结构不会陌生,所谓中断入口表其实就是一组函数指针的数组,跟C++的虚表在物理结构上是很类似的,要把中断入口表映射成虚表,从理论上说是完全可以做到的。

以上这种接口扩展方式是以保留全部遗留函数为前提的。但是在实践中,库的升级往往伴随架构的进化,很多老的API函数需要被废弃,或者函数的定义需要扩充和修改。此时,C-style的API定义风格就会遭遇一些困难,要把老的函数废弃不是那么轻松的。而用COM接口的话就很简单了,只要简单的定义一套新的接口,让新的库带上版本号保存在新的DLL里就可以了。老的客户代码依然可以使用老的COM接口和组件,不需要修改任何代码。新的客户代码直接使用新版本的COM接口,新的接口里也不再需要保留那些已经废弃的老版本的API函数。

 

这个例子我们可以看OpenGL vs D3D

早期的OpenGL 1.0的接口定义是C-Style的,这种API定义风格比较僵化,在扩展时只能添加函数而不能轻易的删除或修改原有的函数,而图形接口的架构变化是非常快的,2~3年就会伴随一次大的硬件架构的升级。所以到了1.1之后引入了GL Extension机制,不再增加新的API函数接口,而是让客户代码自己去通过GL扩展机制去查询当前硬件支持的特性,获取新特性的函数指针后再进行调用,同时因为使用扩展机制需要程序手工查询再取得函数指针,使用非常麻烦,所以还是增加了一些C-style的API来进行包装和简化。这样虽然使得OpenGL可以快速的跟进硬件系统的发展,但是也导致各个硬件厂商在OpenGL里随意的添加自己的私有扩展,导致平台的混乱和分裂。到了OpenGL2.0时代,大家终于坐下来着手解决这个问题,利用委员会制定的ARB扩展来统一各个厂商之间的私有扩展。但此时OpenGL的扩展已经比较发展的混乱和缓慢了,各种老的API及扩展特性原本应该被完全废弃的也因为需要考虑兼容性的缘故而不得不保留,OpenGL变得越来越庞大和难用。OpenGL这些年发展的失误,跟使用僵化的C-Style API以及采用不成熟的扩展机制是很有关系的。

 

早期的D3D只是OpenGL后面的一个小跟班,其功能和影响力都远不及OpenGL。但是D3D的进化速度非常的快,从95年到现在大约发布了10来个大的D3D版本。由于D3D采用了COM作为其API接口,在每次架构升级时微软都采取了大刀阔斧式的改进,所有在硬件架构升级中被淘汰的函数都不需要保留到下一代的D3D接口中,同时很多新的功能和接口都可以自由的添加到新的D3D版本中,因此新的D3D组件库不需要考虑为老版本的兼容性买单,老的客户代码依然可以用老的D3D组件库运行,新的客户代码使用新的D3D组件库来支持最新的硬件。

写成伪代码的话大致是这样:

interface D3D1
{
   DrawTriangle();
};

interface D3D2
{
   SetMatrix();
   DrawPrimitive();
};

//.....

interface D3D9
{
   SetShader();
   BindShader();
   DrawPrimitive();
};

 

可以看到,每个新的D3D版本完全不需要去考虑跟老版本的兼容问题,每次发布新版本的时候重新定义接口就是了。客户代码在使用是创建自己需要的D3D版本的对象就可以了。windows平台本身所存在的混乱和兼容性问题,大部分是因为其闭源特性造成的,COM的二进制兼容性从实践证明来看是没有问题的。从WIN95一直到现在的Windows7,新平台保持了对大部分旧软件的向下兼容性,COM组件不兼容的现象很少见,而不采用COM的普通DLL倒是经常出现版本冲突问题。

 

暂时先写这么多,欢迎更多讨论。 

posted on 2011-03-16 17:04  qiaojie  阅读(2367)  评论(2编辑  收藏