vtk用户指南 第十四章 贡献代码

        前几章通过实例介绍了VTK。到目前为止,应该很明显,VTK提供了创建强大的图形,成像和可视化应用程序的功能。此外,由于您可以访问源代码,您可以通过添加自己的类来扩展VTK。在用户指南的第三部分,我们展示了如何扩展VTK以适应您的应用程序的需要。在本章的开头,我们将介绍一些您可以考虑采用的编码惯例——特别是如果您希望为VTK社区贡献代码的话。我们还描述了您的对象必须实现的标准约定和方法才能并入VTK。稍后在第三部分中,我们将描述算法和数据对象的实现细节,以及控制可视化管道执行的方法;我们还讨论了如何将VTK接口到各种窗口系统。

14.1编码注意事项

        如果您开发了自己的过滤器或visualtoolkit的其他附加功能,我们鼓励您贡献源代码。您必须考虑从法律的角度贡献代码意味着什么,使用什么编码风格和约定,以及如何贡献代码。

向VTK贡献代码的条件

        向VTK贡献代码的条件当您向VTK贡献代码时,必然会发生两件事。首先,很多人会看到代码被剖析、改进和修改;其次,由于不可避免地会对代码进行修改,您将在某种意义上“失去对代码的控制”。您不会想要发布专有代码或无法放弃控制的代码(另外,专利代码也是不允许的),您会想要仔细编写代码,以便其他人能够理解和改进它。VTK的版权是一个开源的版权(请参阅VTK/ copyright .txt查看完整的版权)。版权声明如下:

VTK是一个基于BSD许可的开源工具包。

        Ken Martin, Will Schroeder, Bill Lorensen

版权所有

在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用,无论是否修改:

•源代码的重新分发必须保留上述版权声明、本条件列表和以下免责声明。

•以二进制形式重新分发必须在文档和/或分发提供的其他材料中复制上述版权声明、本条件列表和以下免责声明。

•在没有事先书面许可的情况下,Ken Martin, Will Schroeder或Bill Lorensen的名字或任何贡献者的名字都不能被用于认可或推广源自本软件的产品。

本软件由版权持有人和贡献者“按原样”提供,不承担任何明示或暗示的保证,包括但不限于对适销性和适合特定目的的暗示保证。在任何情况下,作者或贡献者均不对任何直接、间接、偶然、特殊、惩戒性或后果性损害(包括但不限于替代商品或服务的采购;损失:失去使用、数据或利润;(或业务中断),无论如何造成的,以及根据任何责任理论,无论是合同责任,严格责任,还是侵权行为(包括疏忽或其他),即使被告知该等损害的可能性,也会因使用本软件而以任何方式产生。

        版权条款来自BSD许可证(参见www.opensource.org),除了上面的三个项目符号条款、保证和赔偿条款之外,对修改、复制和重新分发源代码或二进制代码没有任何限制。除了遵守这三个条款和遵守通常的赔偿条款外,您可以以任何方式使用VTK,包括在商业应用中。

        如果这些限制是可以接受的,您可以考虑贡献代码。但是,在您的代码被接受之前,您需要满足其他标准。这些标准不是形式化的,但必须与有用性、简单性和与系统其余部分的兼容性有关。要问的重要问题是:

•代码是否符合VTK编码标准(也请参阅299页的“编码风格”)?

•代码是否有文档化和注释?

•代码是否通用?还是特定于一个狭窄的应用程序?

•是否需要对系统进行大量修改?(例如,修改广泛使用的对象api)

•代码是否重复现有功能?

•代码是否健壮?

•代码是否属于可视化工具包?如果你能很好地回答这些问题,那么你的代码很有可能是一个很好的候选代码,可以包含在VTK中。

编码风格

        有许多有用的编码风格,但我们坚持您只遵循一种。我们知道这是一个有争议的问题,但我们发现保持一致的风格是非常重要的。一致的风格意味着代码更容易阅读、调试、维护、测试和扩展。这也意味着自动化的文档设施可以正常运行,其他自动化功能可供VTK的所有用户使用。

下面是对编码风格的总结。您可能希望检查VTK源代码,看看它看起来像什么。

•变量、方法和类名使用变化的大写来表示单独的单词。实例变量和方法总是以大写字母开头。不鼓励使用静态变量,但静态变量也应该以大写字母开头。局部变量以小写字母开头。SetNumberOfPoints()或PickList是方法名和实例变量的例子。

•类名以vtk为前缀,然后以大写字母开头的类名。例如,vtkActor或vtkPolyData是类名。vtk前缀允许vtk类库与其他库混合使用。

•类名和文件名相同。例如“vtkObject.h”、“vtkObject.h”。cxx是vtkObject的源文件。

•明确这个->指针在方法中使用。示例包括->Visibility、->Property和->Update()。我们发现,使用显式this->指针提高了代码的可理解性和可读性。

•变量名、方法名和类名应该拼出来。可以使用常见的缩写,但缩写必须完全用大写字母。例如,vtkPolyDataConnectivityFilter和vtkLODActor是可以接受的类名。

•预处理器变量用大写字母书写。这些变量是唯一使用下划线“_”分隔单词的变量。预处理器变量也应该像VTK_LARGE_FLOAT一样以VTK_开头。

•实例变量通常是受保护的或私有的类成员。通过Set/Get方法访问实例变量。注意,VTK提供了Set/Get宏,应该尽可能使用。(查看VTK/Common/vtkSetGet.h的实现,以及。h头文件的用法)

•缩进风格可以被描述为“缩进大括号”风格。缩进是两个空格,大括号(作用域分隔符)放在下一行,并随着代码缩进(即,大括号与代码对齐)。

•使用//注释代码。方法的注释方式是添加// Description:,后跟以//开头的行。

如何贡献代码

        一旦按照上面描述的编码约定创建了一个或多个类,贡献代码就相当容易了。首先,在.cxx和.h源文件中都包含版权声明。您可能希望将您的姓名,组织和/或其他识别信息放在版权声明的“谢谢:”字段中。(示例见VTK/Graphics/ vtkcurvature .h)。接下来,向kitware@kitware.com发送电子邮件,解释代码的功能、示例数据(如果需要)、测试代码(c++、Tcl或Python)和源代码(每个类一个.h和.cxx文件)。另一种方法是将相同的信息发送到vtkusers邮件列表。(有关加入该列表的信息,请参阅第6页的“附加参考资料”。)将贡献代码发送到邮件列表的好处是,用户可以立即利用代码。

        不能保证贡献的代码将被合并到官方的VTK版本中。这取决于代码的质量、有用性和通用性,如297页“向VTK贡献代码的条件”中所述。

14.2标准方法:创建和删除对象

        几乎VTK中的每个对象都响应一组标准方法。这些方法中的许多都是在vtkObject或vtkObjectBase中实现的,大多数VTK类都是从它们派生的。但是,子类通常要求您扩展或实现继承的方法以获得适当的行为。例如,New()方法应该由每个具体的(即,非抽象的,可实例化的)类实现,而Delete()方法通常继承自其超类的vtkObject。在开发任何代码之前,您应该熟悉这些标准方法,并确保您自己的类支持它们。这个静态类方法用于实例化对象。我们将此方法称为“对象工厂”,因为它用于创建类的实例。

在VTK中,每个New()方法都应该与Delete()方法配对。(参见第307页的“对象工厂”)

instance = NewInstance()这个方法是一个虚拟构造函数。也就是说,调用此方法会导致对象创建与自身类型相同的实例,然后返回指向新对象的指针。(在VTK/Common/vtkSetGet.h中找到的宏vtktypemmacro定义了这个方法。)

Delete()使用这个方法删除用New()或NewInstance()方法创建的VTK对象。根据被删除对象的性质,这可能会或可能不会实际删除对象。例如,引用计数的对象只有在其引用计数变为零时才会被删除。

DebugOn()/DebugOff()打开或关闭调试信息。这些方法继承自vtkObject。

Print()打印包含超类信息的对象。在vtkObjectBase中定义的Print()方法需要为每个类实现PrintSelf()方法。

PrintSelf()方法在一个链中调用,每个子类调用其超类的PrintSelf(),然后打印自己的实例变量。每个类都应该实现这个方法。该方法调用其父方法的PrintSelf(),后面跟着打印自身所需的代码。

name = GetClassName()以字符串形式返回类名。此方法用于调试

        信息。(在VTK/Common/tkSetGet.h中找到的宏vtkTypeMacro定义了这个方法)

flag = IsA(className)如果命名类是该类的超类或与该类相同的类型,则返回非零。(在VTK/Common/vtkSetGet.h中找到的宏vtktypemmacro定义了这个方法。)*ptr = <class>::SafeDownCast(vtkObject *o)这个静态类方法在c++中可用于执行安全的向下强制转换(即,将一般类强制转换为更专门的类)。如果ptr返回NULL,则向下强制转换失败,否则ptr指向类

<class>的实例。例如,ptr=vtkActor::SafeDownCast(prop)将返回非null,如果prop是vtkActor或vtkActor的子类。(在VTK/Common/vtkSetGet.h中找到的宏vtkTypeMacro定义了这个方法)

void Modified()这更新了对象的内部修改时间戳。该值保证唯一且单调递增。

mtime = GetMTime()返回对象的最后修改时间。通常这个方法是从vtkObject继承的,但是在某些情况下你会想重载它。有关更多信息,请参阅第391页的“计算修改时间”。

您可以从本节获得的最重要的信息是:实例应该使用New()方法创建,并使用Delete()方法销毁,并且对于每个New()都应该有一个Delete()方法。例如,要创建参与者的实例,请执行以下操作

vtkActor *anActor = vtkActor::New();
... (more stuff)
anActor->Delete();

除了::New和::Delete, VTK提供了一个智能指针类,可以用来管理
VTK对象。智能指针将自动增加或减少所指向对象的引用计数。这可以使代码更不可能泄漏VTK对象。智能指针类是一个模板类,使用方法如下:

#include <vtkSmartPointer.h>
…
vtkSmartPointer<vtkActor> anActor = vtkSmartPointer<vtkActor>::New();
... (more stuff)
// when anActor goes out of scope the reference count for that
// vtkActor will be decremented.

New()方法被称为对象工厂:它是一个用于创建类实例的类方法。通常,New()方法首先要求vtkObjectFactory创建对象的实例,如果失败,它就调用c++的New操作符。这是通过将vtkStandardNewMacro(在VTK/Common/vtkSetGet.h中找到)添加到.cxx文件来完成的,如下所示为vtkSphereSource。

vtkStandardNewMacro(vtkSphereSource);

但是,New()方法可以更复杂,例如,实例化与设备无关的类。例如,在vtkGraphicsFactory。cxx中,用于实例化vtkActor的代码如下所示。

if(strcmp(vtkclassname, "vtkActor") == 0)
{
return vtkOpenGLActor::New();
}

这里New()方法用于创建一个设备相关的vtkActor子类(即OpenGL),然后作为指向设备无关的vtkActor的指针返回。例如,根据编译时标志(例如,VTK_USE_OGLR)和可能的其他信息,如环境变量,不同的角色类型对应于渲染库(例如,OpenGL, Mesa等)被透明地创建给用户。使用这种机制,我们可以创建与设备无关的应用程序,或者在运行时选择应用程序中使用的不同类。(参见第307页的“对象工厂”了解更多信息。)

14.3复制对象和保护方法

构造函数、析构函数、operator=和复制构造函数方法是大多数VTK类的受保护成员或私有成员。这意味着对于VTK类vtkX,

方法

•vtkX() -构造函数

•~vtkX() -析构函数

是受保护的,方法

•operator=(const vtkX &) -等价操作符和

•vtkX(const vtkX &) -复制构造函数是私有的。

        此外,赋值操作符和复制构造函数只能声明,不能实现。这可以防止编译器自动创建一个,并且不会生成测试过程中无法覆盖的代码。这意味着您不能在应用程序中使用这些方法。这样做的原因是为了防止潜在的危险滥用这些方法。例如,引用计数可以通过使用构造函数和析构函数而不是前一节中描述的标准New()和Delete()方法来打破。

        由于复制构造函数和operator=方法是私有的,因此必须使用其他方法来复制对象的实例。使用的方法是DeepCopy()和/或ShallowCopy(),如下所示

vtkActor *a1 = vtkActor::New();
vtkActor *a2 = vtkActor::New();
a2->ShallowCopy(a1);

浅拷贝是一个对象的拷贝,它复制对象的引用(通过引用计数),而不是对象本身。例如,类vtkActor的实例引用vtkMapper对象(映射器表示角色的几何形状)。在浅拷贝中,避免了数据的复制,只复制了对vtkMapper的引用。然而,对共享映射的任何更改- /间接影响所有引用它的vtkActor实例。或者,DeepCopy()可以用来复制一个实例,包括它所代表的任何数据,而不需要引用任何其他对象。这里有一个例子:

vtkIntArray *ia1 = vtkIntArray::New();
vtkIntArray *ia2 = vtkIntArray::New();
ia1->DeepCopy(ia2);

在本例中,ia2中的数据被复制到ia1中。从现在开始,ia1和ia2可以在不影响彼此的情况下进行修改。
目前,VTK不支持所有类的DeepCopy()和ShallowCopy()。
这将在未来添加。

14.4使用STL

标准模板库(STL)是一个c++库,它提供了一组易于组合的c++容器类和泛型算法(模板化函数)。

•容器类包括vector、list、deque、set、multiset、map、multimaps、stack、queues和priority queues。

•泛型算法包括广泛的基本算法,用于最常见的数据操作,如搜索、排序、合并、复制和转换。

虽然STL确实为c++程序员提供了很多,但它存在一些实现问题(特别是在跨平台使用时),要求VTK程序员在使用STL时要小心。其中一些问题包括名称空间管理、DLL(动态链接库)边界问题和线程问题。为了解决这些问题,VTK制定了一项政策,描述了如何将STL与VTK结合使用。以下是该政策的原则。

1. STL用于实现,而不是接口。所有STL引用都应该包含在。cxx类实现文件中,而不是。h头文件中。

2. 使用PIMPL习惯用法转发引用/包含STL类。stl派生类应该是私有的,而不是受保护的或公共的,以避免dll边界问题。如果使用PIMPL习语的类的子类位于与父类不同的VTK工具包中,则会出现DLL边界问题。在一些没有子类的VTK类中使用PIMPL习语,stl派生类位于受保护部分而不是私有部分。PIMPL习语是类成员的私有实现技术。在网上搜索“粉刺习语c++”以获得更多信息。)

3. 使用vtkstd::命名空间来引用STL类和函数。
这里有一个例子(从类vtkInterpolatedVelocityField)。在类.h文件中,通过前向声明一个类并使用它定义一个数据成员来创建PIMPL,如下所示。

class vtkInterpolatedVelocityFieldDataSetsType;
class VTK_FILTERING_EXPORT vtkInterpolatedVelocityField : public
vtkFunctionSet
{ 
protected:
vtkInterpolatedVelocityFieldDataSetsType* DataSets;
};

在.cxx文件中定义类(这里是从STL vector容器派生的)

typedef vtkstd::vector< vtkDataSet* > DataSetsTypeBase;
class vtkInterpolatedVelocityFieldDataSetsType:
public DataSetsTypeBase {};

(第一个typedef用于方便地缩短类的定义。)接下来,在.cxx文件中按如下方式构造和销毁类:

vtkInterpolatedVelocityField::vtkInterpolatedVelocityField()
{
this->DataSets = new vtkInterpolatedVelocityFieldDataSetsType;
}
vtkInterpolatedVelocityField::~vtkInterpolatedVelocityField()
{
delete this->DataSets;
}

因为类是从STL容器派生的,所以它可以用作这种类型的容器。

for ( DataSetsTypeBase::iterator i = this->DataSets->begin();
i != this->DataSets->end(); ++i)
{
ds = *i;
....
}

14.5管理包含文件

        软件系统具有固有的模块间依赖关系。在c++中,这是因为子类依赖于它们的超类,或者访问结构或类需要了解API和数据偏移量。这些依赖关系在实现中显示为#include语句,其中包含定义类、结构和接口的头文件。在构建过程中,每当任何一个头文件发生更改时,依赖于该文件的所有代码都必须重新编译。通过将头文件包含在头文件中,而头文件又包含在其他文件中,这可能导致过多的代码间依赖,从而在代码更改时延长编译时间。在某些编译器中,过多的包含文件甚至会导致编译器失败。为了解决这些问题,VTK开发者社区制定了一个关于包含文件的政策。基本规则很简单:一个类定义(.h)文件最多应该包含另一个#include文件——它的超类定义文件。所有其他的#include应该放在类实现(.cxx)文件中。然而,该策略需要遵循特定的编码约定。

        首先,类必须包含指向其他类或结构的指针,而不是它们的实例。其次,必须避免调用类方法(除了超类)的内联代码。所有这些操作都需要访问类定义,因此需要访问相关的#include文件。在特别,旧的VTK宏vtkSetObjectMacro()不再使用。这个宏调用用作数据成员的任何类上的几个方法,因此需要包括其类定义文件。当前批准的过程是声明set方法,并使用vtkCxxSetObjectMacro()在.cxx文件中实现该方法。例如,类vtkCutter有一个数据成员,它是指向vtkImplicitFunction的指针。在定义文件VTK/Graphics/vtkCutter.h中声明了SetCutFunction()

virtual void SetCutFunction(vtkImplicitFunction*);

在.cxx文件中实现了该方法

vtkCxxSetObjectMacro(vtkCutter,CutFunction,vtkImplicitFunction);

当然,所有的规则都是用来被打破的。在某些情况下,性能问题或其他问题可能需要额外的#include文件。然而,这是要尽可能避免的。当无法避免时,在行中加上#include注释,解释为什么需要它。参见下面的例子从VTK/Graphics/vtkSynchronizedTemplates3D.h

#include “vtkPolyDataAlgorithm.h”
#include “vtkContourValues.h” // Passes calls through

14.6编写VTK类:概述

        在本节中,我们将概述如何编写VTK类。如果你正在编写一个新的过滤器,你还需要参考第17章“如何为VTK编写算法”的第385页,当然,你应该阅读本章的前一部分。

        编写VTK类最困难的部分可能是确定是否需要它,如果需要,它适合系统的哪个位置。随着经验的积累,这些决定会变得更容易。与此同时,您可能想要开始与其他VTK开发人员合作,或发布到vtkusers邮件列表。(请参阅第6页的“附加参考资料”。)如果确定需要一个类,则需要查看以下问题。

找一个相似的class

最好的起点是找一个和你想做的事情相似的课程。这通常会指导对象API的创建,和/或超类的选择。

识别超类

大多数类应该派生自vtkObject或vtkObject的一个后代。这条规则的例外很少,因为vtkObject(或它的超类vtkObjectBase)实现了重要的功能,如引用计数、命令/观察者用户方法、打印方法和调试标志。所有VTK类都使用单继承。虽然这是一个有争议的问题,但有很好的理由支持这个策略,包括Java支持(Java只允许单继承)和简化包装过程以及代码。您可能希望参考第437页“对象图”中的对象图。这些提供了在VTK中发现的许多继承关系的简要概述。

单个类/ .h文件

VTK中的类在每个。h头文件中实现一个类,以及任何相关的。cxx实现文件。这条规则有一些例外,例如,当您必须定义内部助手类时。然而,在这些异常中,helper类在principal类之外是不可见的。如果它是,它应该放在它自己的。h/中。cxx文件。

  • 请求方法

每个VTK类必须定义如下几个方法和宏。有关这些方法的描述,请参阅第300页的“标准方法:创建和删除对象”。

•New() -每个非抽象类(即不实现纯虚函数的类)必须定义New()方法。

•vtkTypeMacro(className,superclassName) -这个宏是一种方便的方式来定义在运行时使用的方法来确定实例的类型,或执行安全的向下转换。vtktypemmacro在文件vtkSetGet.h中定义。宏定义了isttypeof()、IsA()、GetClassName()、SafeDownCast()方法和虚拟构造函数NewInstance()。

•PrintSelf() -以智能的方式打印出实例变量。

•构造函数(必须被保护)•析构函数(必须被保护)

•复制构造函数(必须是私有的,不被实现)

•operator=(必须是私有的,不被实现)构造函数、复制构造函数、析构函数和operator=不能是公共的。New()方法应该使用第307页“对象工厂”中概述的过程。当然,根据类的父类,可能需要实现其他方法来满足类API(即,填充纯虚函数)。

  • 文献标识码

        VTK类的文档依赖于文档指令的正确使用。每个.h类文件都应该有一个// . name描述,其中包括类名和对类功能的单行描述。此外,头文件应该有一个// . section Description部分,这是一个多段描述(用c++注释指示符//分隔),给出关于类及其工作方式的详细信息。其他可能的信息包含在// . section warnings部分,其中描述了类的特点和限制,以及// . section See Also部分,其中引用了其他相关的类。

        方法也应该被记录。如果方法满足超类API,或者重载超类方法,则可能不需要记录该方法。你记录的那些方法使用以下结构:

// Description:

// This is a description...

当然,您可能希望在代码中嵌入其他文档、注释等,以帮助其他开发人员和用户理解您所做的工作。

使用SetGet宏

只要有可能,使用VTK/Common/vtkSetGet.h中的SetGet宏来定义实例变量的访问方法。此外,您还需要使用调试宏和其他
#根据需要在代码中定义(例如,VTK_LARGE_FLOAT)。

添加类到VTK

一旦你创建了你的类,你会想要决定你是否希望它被合并到VTK构建或分离在您自己的应用程序。如果您将类添加到VTK中,请在适当的子目录中修改CMakeLists.txt文件。然后你必须重新运行CMake(如第10页“CMake”所述),然后重新编译。您可以通过创建一个名为LocalUser的文件来添加一个完整的新库。在您的VTK源代码树的顶层。该文件仅在退出时使用。你可以在文件中添加一个CMake add_subdirectory命令,告诉CMake进入新库的目录。在目录中,您需要创建一个新的CMakeLists.txt文件,其中包含用于构建库的CMake命令。如果您使用的是一种包装语言,比如Tcl,那么您可以使用另一个LocalUser将库添加到VTK可执行文件中。否则,您将不得不构建共享库,并在运行时将新库加载到Tcl环境中。要添加简单使用VTK的类,请参阅第30页的“c++”,例如CMakeLists.txt文件。

14.7对象工厂

VTK 3.0及以后的版本有一个强大的功能,允许您在运行时扩展VTK。使用对象工厂,您可以用自己创建的对象替换VTK对象。例如,如果你有特殊的硬件,你可以在运行时使用你自己的特殊高性能过滤器,用你自己的对象替换VTK中的对象。因此,如果您想用在硬件中执行FFT的过滤器替换vtkImageFFT过滤器,或者用高性能的汇编代码实现替换单元格vtkTetra,您可以这样做。下面是使用对象工厂的好处。

•允许在现有代码中使用子类来代替父类。

•应用程序可以动态加载新的对象实现。

•您可以在运行时扩展VTK。

•专有扩展可以与公共VTK构建隔离。

•在c++代码中删除了许多#ifdefs;例如,OpenGL工厂可以替换vtkRenderer和vtkRenderWindow中的所有#ifdefs。对象工厂可以用作调试辅助工具。例如,可以创建一个除了跟踪每个类的New()调用之外什么都不做的工厂。

•在不同硬件上实现加速或替代VTK对象(类似于Netscape和Photoshop的插件模型)更容易。

综述

        实现对象工厂时的关键类是vtkObjectFactory。vtkObjectFactory维护一个“已注册”工厂的列表。它还定义了一个静态类方法,用于按字符串名称创建VTK对象——CreateInstance()方法——该方法接受const char*作为参数。create方法遍历已注册的工厂,依次要求每个工厂创建对象。如果工厂返回一个对象,该对象将作为创建的实例返回。因此,返回对象的第一个工厂就是用于创建对象的工厂。

        一个示例将有助于说明如何使用对象工厂。来自类vtkVertex的New()方法如下所示(在VTK/Common/vtkSetGet.h中定义的vtkStandardNewMacro扩展之后)。

vtkVertex* vtkVertex::New()
{
// First try to create the object from the vtkObjectFactory
vtkObject* ret = vtkObjectFactory::CreateInstance("vtkVertex");
if(ret)
{
return static_cast<vtkVertex*>(ret);
}
// If the factory was unable to create the object, then create it
return new vtkVertex;
}

这个New()方法的实现类似于VTK中发现的大多数其他New()方法。
如果对象工厂没有返回类vtkVertex的实例,则使用vtkVertex的构造函数。注意,工厂必须返回一个类的实例,该实例是调用类的子类
(例如,vtkVertex)。

如何编写一个工厂

您需要做的第一件事是创建vtkObjectFactory的子类。必须在工厂中实现两个虚函数:GetVTKSourceVersion()和GetDescription()。

virtual const char* GetVTKSourceVersion();

virtual const char* GetDescription();

GetDescription()返回一个定义对象工厂功能的字符串。GetVTKSourceVersion()方法应该返回VTK_SOURCE_VERSION,而不应该调用vtkVersion::GetVTKSourceVersion()。你不能调用vtkVersion函数,因为版本必须被编译到你的工厂,如果你调用vtkVersion函数,它只会使用工厂加载到的VTK版本,而不是它被构建的版本。此方法用于根据已安装的VTK检查工厂的版本号及其创建的对象。

如果软件版本不同,则会产生警告,很有可能会出现严重的程序错误。工厂有两种方式来创建对象。最方便的方法是使用vtkObjectFactory的受保护方法RegisterOverride()。

void RegisterOverride(const char* classOverride,
const char* overrideClassName,
const char* description,
int enableFlag,
CreateFunction createFunction);

对于工厂提供的每个对象,应该在构造函数中调用此方法一次。以下是对这些参数的描述。

•clasoverride -这是您要替换的类的名称。

•overrideClassName -这是将取代clasoverride的类的名称

•description -这是一个基于文本的描述,描述你的替换类的功能。如果您希望在运行时选择要使用的对象,这在GUI中非常有用。

•enableFlag -这是一个布尔标志,应该是0或1。如果为0,则不会使用此覆盖。注意,可以在运行时通过vtkObjectFactory类接口更改这些标志。

•createFunction——这是一个指向创建你的类的函数的指针。函数必须像这样:

vtkObject* createFunction();您可以编写自己的函数或使用vtkObjectFactory.h中提供的VTK_CREATE_CREATE_FUNCTION宏。(见VTK /并行/ vtkParallelFactory。如果使用此宏,请参见CXX示例。)对象工厂创建对象的第二种方式是虚拟函数

CreateObject(): virtual vtkObject* CreateObject(const char* vtkclassname);

如果您的工厂不想处理它被要求创建的类名,该函数应该返回NULL。它应该返回命名的VTK类的子类,如果它想要覆盖该类。由于CreateObject()方法返回vtkObject*,因此除了对象必须是vtkObject之外,没有太多类型安全,因此请注意只返回对象的子类以避免运行时错误。工厂可以处理任意数量的对象。如果要创建许多对象,最好使用散列表将字符串名称映射到对象创建。该方法应该尽可能快,因为它可能被频繁调用。还要注意,这个方法不允许GUI像RegisterOverride()方法那样选择性地启用和禁用单个对象。

如何安装工厂

工厂的安装方式取决于它们是否被编译到您的VTK库或应用程序中,或者它们是否是动态加载的dll或共享库。编译内的工厂只需要调用

vtkObjectFactory::RegisterFactory ( MyFactory::New() );

对于动态加载的工厂,必须创建包含对象工厂子类的共享库或DLL。库应该包含宏VTK_FACTORY_INTER-
FACE_IMPLEMENT (factoryName)。这个宏定义了三个名为vtkGetFactoryCompilerUsed()、vtkGetFactoryVersion()和vtkLoad()的外部“C”链接函数,它们返回库提供的工厂实例。

#define VTK_FACTORY_INTERFACE_IMPLEMENT(factoryName) \
extern "C" \
VTK_FACTORY_INTERFACE_EXPORT \
const char* vtkGetFactoryCompilerUsed() \
{ \
 return VTK_CXX_COMPILER; \
} \
extern "C" \
VTK_FACTORY_INTERFACE_EXPORT \
const char* vtkGetFactoryVersion() \
{ \
 return VTK_SOURCE_VERSION; \
} \
extern "C" \
VTK_FACTORY_INTERFACE_EXPORT \
vtkObjectFactory* vtkLoad() \
{ \
 return factoryName ::New(); \
}

然后必须将库放入VTK_AUTOLOAD_PATH中。该变量遵循机器上的PATH约定,在Windows上使用分隔符“;”,在Unix上使用“:”。第一次要求vtkObjectFactory创建对象时,它会加载VTK_AUTOLOAD_PATH中的所有共享库或dll。对于路径中的每个库,调用vtkLoad()来创建vtkObjectFactory的实例。为了避免性能问题,只在第一次执行此操作。但是,可以在运行时通过调用来重新检查新工厂的路径

vtkObjectFactory::ReHash();

(注意,VTK类vtkDynamicLoader处理与操作系统无关的共享库或dll加载。)

工厂模式例子

下面是一个简单的工厂,它使用Windows操作系统上的OLE自动化来重定向所有


VTK调试输出到Microsoft Word文档。要使用这个工厂,只需将代码编译成
DLL,并把它放在您的VTK_AUTOLOAD_PATH。

#include "vtkOutputWindow.h"
#include "vtkObjectFactory.h"
#pragma warning (disable:4146)
#import "mso9.dll"
#pragma warning (default:4146)
#import "vbe6ext.olb"
#import "msword9.olb" rename("ExitWindows", "WordExitWindows")
// This class is exported from the vtkWordOutputWindow.dll
class vtkWordOutputWindow : public vtkOutputWindow {
public:
 vtkWordOutputWindow();
 virtual void DisplayText(const char*);
 virtual void PrintSelf(vtkOstream& os, vtkIndent indent);
 static vtkWordOutputWindow* New() { return new vtkWordOutputWindow;}
protected:
 Word::_ApplicationPtr m_pWord;
 Word::_DocumentPtr m_pDoc;
};
class vtkWordOutputWindowFactory : public vtkObjectFactory
{
public:
 vtkWordOutputWindowFactory();
 virtual const char* GetVTKSourceVersion();
 virtual const char* GetDescription();
};
// vtkWordOutputWindow.cpp : the entry point for the DLL application.
//
#include "vtkWordOutputWindow.h"
#include "vtkVersion.h"
BOOL APIENTRY DllMain( HANDLE hModule,
 DWORD ul_reason_for_call,
 LPVOID lpReserved )
{
if(ul_reason_for_call == DLL_PROCESS_ATTACH)
 {
 CoInitialize(NULL);
 }
 return TRUE;
}
void vtkWordOutputWindow::PrintSelf(vtkOstream& os, vtkIndent indent)
{
 vtkOutputWindow::PrintSelf(os, indent);
 os << indent << "vtkWordOutputWindow " << endl;
}
// This is the constructor of a class that has been exported.
// see vtkWordOutputWindow.h for the class definition
vtkWordOutputWindow::vtkWordOutputWindow()
{
 try
 {
 HRESULT hr = m_pWord.CreateInstance(__uuidof(Word::Application));
 if(hr != 0) throw _com_error(hr);

 m_pWord->Visible = VARIANT_TRUE;
 m_pDoc = m_pWord->Documents->Add();
 }
 catch (_com_error& ComError)
 {
 cerr << ComError.ErrorMessage() << endl;
 }
}
void vtkWordOutputWindow::DisplayText(const char* text)
{
 m_pDoc->Content->InsertAfter(text);
}
// Use the macro to create a function to return a vtkWordOutputWindow
VTK_CREATE_CREATE_FUNCTION(vtkWordOutputWindow);
// Register the one override in the constructor of the factory
vtkWordOutputWindowFactory::vtkWordOutputWindowFactory()
{
this->RegisterOverride("vtkOutputWindow",
 "vtkWordOutputWindow",
 "OLE Word Window",
 1,
 vtkObjectFactoryCreatevtkWordOutputWindow);
}
// Methods to load and insure factory compatibility.
VTK_FACTORY_INTERFACE_IMPLEMENT(vtkWordOutputWindowFactory);
// return the version of VTK that the factory was built with
const char* vtkWordOutputWindowFactory::GetVTKSourceVersion()
{
 return VTK_SOURCE_VERSION;
}
// return a text description of the factory
const char* vtkWordOutputWindowFactory::GetDescription()
{
 return "vtk debug output to Word via OLE factory";
}

14.8 Kitware的质量软件过程

VTK的一个突出特点是用于开发、维护和测试工具包的软件过程。
由于世界各地的开发人员和用户的努力,可视化工具包软件继续快速发展,因此软件过程对于保持其质量至关重要。如果您计划向VTK贡献代码或使用CVS源代码存储库,那么您需要了解这个过程。(参见第5页的“获取软件”。)这些信息将帮助您了解何时以及如何在软件发生变化时进行更新和使用。以下部分

CVS源代码存储库

        VTK的源代码、数据和示例保存在一个称为CVS (Concurrent Versions system)的源代码版本控制系统中。CVS的主要目的是跟踪软件的更改。CVS为每个添加到存储库的内容标记日期和版本—还提供特殊的用户指定标记—以便可以随时返回到特定的状态或时间点。任何两点之间的差异由一个“diff”文件表示,它是一个紧凑的、增量的变化表示。CVS支持并发开发,因此两个开发人员可以同时编辑同一个文件;然后(通常)将这些编辑合并在一起而不发生意外(如果存在冲突,则标记)。此外,主开发主干的分支提供软件的并行开发。

        像CVS这样的系统的主要优点是,它使开发人员可以自由地尝试新的想法和更改,而不必担心丢失以前的软件工作版本。它还提供了一种简单的方法,可以在向存储库添加新特性时增量地更新代码。

回归测试系统

VTK软件过程的独特功能之一是CDash回归测试系统(http://www.cdash.org)。简而言之,CDash所做的就是在开发人员签入新代码并进行更改时向他们提供可量化的反馈。反馈包括各种测试的结果,结果发布在一个可公开访问的网页上(我们称之为仪表板,如图14-1所示);VTK的仪表板可从http:// www.cdash.org/CDash/index.php?project=VTK访问。VTK的所有用户和开发人员都可以查看仪表板,这对签入有问题代码的开发人员产生了相当大的同行压力。Dart仪表板是开发人员交流的工具,每当您考虑通过CVS更新软件时,都应该查看它。

CDash支持多种测试类型。这些包括以下内容。

•编译。所有的源代码被编译和链接。报告任何产生的错误和警告。

•回归。大多数VTK测试产生图像作为输出。测试需要将每个测试的输出与有效映像进行比较。如果图像匹配,则测试通过。由于许多3D图形系统(如OpenGL)在不同平台上产生的结果略有不同,因此必须仔细进行比较。

•记忆。在任何计算机程序中发现的最棘手的问题之一是与内存有关的问题。内存泄漏、未初始化内存以及超出分配空间的读写都是此类问题的示例。VTK使用Purify(一个由Rational生产的商业软件包)或者Valgrind(一个用于x86-Linux程序的开源内存调试器)来检查内存。

•PrintSelf。VTK中的所有类都应该正确地打印出它们的所有实例变量。这个测试检查以确保是这种情况。

•SetGet。开发人员经常对实例变量的值做出假设;也就是说,它们假定它们是非null的,等等。SetGet测试使用Get__()方法对所有实例变量执行Get操作,然后使用Get__()方法返回的值对实例变量执行Set方法。这个测试发现问题的次数之多令人惊讶。

•TestEmptyInput。这个看似简单的测试捕获了许多问题,因为开发人员假设算法的输入是非null的,或者输入数据对象包含一些数据。TestEmptyInput只是对vtkAlgorithm的每个子类执行这两个条件,并在遇到问题时报告问题。

•报道。VTK的开发人员中有这样一种说法:“如果它没有被覆盖,那么它就坏了。”这意味着在测试期间没有执行的代码很可能是错误的。覆盖率测试识别在visualtoolkit测试套件中未执行的行,在测试结束时报告覆盖的总百分比。由于错误处理代码和在实践中很少遇到的类似结构,将覆盖率提高到100%几乎是不可能的,但是覆盖率应该达到75%或更高。没有被充分覆盖的代码需要额外的测试。

CDash的另一个不错的特性是,它维护源代码更改的历史记录(通过与CVS协调),并将更改总结为指示板的一部分。这对于跟踪问题和保持最新的新添加到VTK是有用的。

  • 工作过程模拟

VTK软件过程跨越三个周期——连续周期、每日周期和发布周期。

这个持续的循环围绕着开发人员在将代码签入CVS时的操作展开。当更改或新代码签入CVS时,CDash连续测试过程就会启动。执行少量测试(包括编译),如果出现问题,将向所有在连续循环中签入代码的开发人员发送电子邮件。开发人员应该立即解决这个问题。每天的周期以24小时为周期。白天对源库所做的更改将通过夜间的CDash回归测试序列进行广泛测试。这些测试在世界各地的计算机和操作系统的不同组合上进行,结果每天都会发布到CDash仪表板上。签入代码的开发人员应该访问指示板,并确保他们的更改是可接受的——也就是说,他们不会引入编译错误或警告,也不会破坏任何其他测试,包括回归、内存、打印自我和Set/Get。开发人员应该立即修复问题。发布周期每年只发生几次。这需要标记和分支CVS存储库、更新文档和生成新的发布包。尽管执行了额外的测试以确保包的一致性,但是保持每日发布的无错误最小化了减少发布所需的工作。VTK用户通常使用发行版,因为它们是最稳定的。开发人员使用CVS存储库,或者有时使用定期快照(特定的每日发布),以便利用新添加的特性。非常重要的是,开发人员要仔细观察仪表板,只有当仪表板处于良好状态时(即“绿色”)才更新他们的软件。如果某一天的软件发布不稳定,不这样做可能会导致严重的中断。

过程的有效性

这个过程的效果是深远的。通过电子邮件和网页(即仪表板)向开发人员提供即时反馈,VTK的质量非常高,特别是考虑到算法和系统的复杂性。与在发布时捕获错误相比,意外引入的错误会被快速捕获。等待到发布的时间太长,因为代码更改或添加与bug之间的因果关系丢失了。这个过程是如此强大,以至于它经常捕获供应商的图形驱动程序(例如,OpenGL驱动程序)或外部子系统(如Mesa OpenGL软件库)的更改中的错误。构成这个过程的所有这些工具(CMake、CVS和CDash都是开源的)。许多大大小小的系统,如ITK (Insight Segmentation and Registration Toolkit http:// www.itk.org)使用相同的过程,得到类似的结果。我们鼓励在您的环境中采用该过程。(注:此过程的商业支持、咨询和培训可从Kitware, Inc.获得,网址为kitware@kitware.com。)

本书为英文翻译而来,供学习vtk.js的人参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值