SourceForge.net Logo

Detailed Explanation of .NET Wrappers for VTK

Drew Dolgert

The Interface

Why Use Managed C++ 2.0?

There are two techniques to wrap C++ code for .NET, PInvoke and managed C++. Managed C++ lets you intermingle wrapper code with native C++ that would be needed to support either style of wrapping, so I chose managed C++. I first wrapped the code using v1.0 of managed C++, but it was fragile to compile and mixed native types into the namespace, so it wasn't very usable. This wrapper is written in Managed C++ v2.0, so it requires Microsoft Visual Studio 2005, also known as Microsoft Visual Studio 8.

Changes from VTK .NET by Skala and Frank

I made three changes to the interfaces used by Skala and Frank in order to conform more closely to other VTK wrappers.

Instead of instantiating classes with a static New(), the wrappers use a typical constructor, as do Python and Tcl. The use of New() and Delete() has, as I understand it, two origins in the C++ code. The most important is that static constructors allow factories to supply system-dependent derived types. They also ensure that allocation and deallocation is done within the VTK library so that the same C runtime does the allocation. This is important on Windows. Neither of these reasons holds in wrappers, so we use a typical constructor.

The wrapped classes all live in a single "vtk" namespace. The Python wrapper once supported a namespace for each kit, like vtkCommon, vtkFiltering and such, but that has gone away. Similarly, using both the vtk namespace and classnames beginning with vtk, while redundant, seems to be tradition, so we have vtk.vtkObject for class names.

The wrapper libraries are compiled into a separate file for each kit. Skala and Frank put all of the wrappers into a single library. This avoids some hairy complications in managed C++ but doesn't work well with CMake.

Structure of the Wrappers

These wrappers are very typical. In design patterns language, they would be called object adapters. Each instance of a wrapper contains a pointer to the unmanaged class. When managed C++ compiles mixed, native and managed, code, it makes all native types in an assembly private. As a result, even protected or public class members which are native types are inaccessible from one assembly to another. Because of this, the native type is stored as an IntPtr in the base class, vtk.vtkObjectBase.

Using Reflection to Create Wrapper Classes

It would be possible to use .NET reflection services to ensure that every wrapper class were of the most derived type. That is, when you instantiate a vtkRenderWindow, you get a vtkWin32OpenGLRenderWindow. Instantiation using reflection is, of the order of, ten times slower than creating a type directly. As a result, the wrappers just create a vtk.vtkRenderWindow when you ask for one. In order to downcast this type, you must use SafeDownCast. In C#, this looks like the following.

vtk.vtkRenderWindow renWin = new vtk.vtkRenderWindow(); vtk.vtkWin32OpenGLRenderWindow win32win = vtk.vtkWin32OpenGLRenderWindow.SafeDownCast(renWin); if ( null != win32win ) win32win.Clean();

Copying Semantics

Copying one managed wrapper to another is not a problem in managed code because "each managed type is an implicit duple," according to Stanley Lippman. There is only one instance of a managed type, and copying just gives you two references to it. There is no need for wrappers to support any copying semantics in order to manage references properly.

Mixing Wrappers with Native C++ Code

If one were to write managed C++ and perform some operations with native VTK types, it is simple to create a managed wrapper on the native type. In managed C++, this is

::vtkPlaneSource* nativePlaneSource = ::vtkPlaneSource::New(); bool isConst = false; vtk.vtkPlaneSource managedPlaneSource = gcnew vtk.vtkPlaneSource(nativePlaneSource, isConst);
This way, we can use managed C++ with native instances for efficiency when calling many VTK methods and then return to our managed code the constructed object.

Constness

You may have noticed that one argument to the constructor is whether the native class is const. Managed code does not have a concept of constness, so we represent it explicitly. There is only one class, and only three methods, in all of VTK that returns a const type. Internal to the wrapper class, we would like to check whether the native method is non-const and then forbid calling it for a const object, but the lexer and parser supplied with VTK do not report method constness, so this feature is currently unsupported.

Exigencies of Wrapping Within CMake

The VTK CMake project constructs a separate wrapper library for each kit. For Python, Tcl and Java wrappers, it seems to be enough for the wrapper to know what classes are in its own kit. Managed C++, however, is more demanding.

The kits have a set dependency. vtkCommon depends on vtkSys. vtkGraphics depends on vtkCommon, and so forth. If managed C++ encounters an argument in one kit whose type is defined in a dependent kit, then compilation will fail. For instance, vtkProperty2D::Render(vtkViewport*) in Common will cause compilation to fail because vtkViewport is defined in Rendering. In order to compile properly, the wrapper code has to check whether a vtk class argument is in a known kit and exclude the ones that are not.

To make this information available to the wrapper code, the CMake build process for .NET wrappers writes to the Wrapping\DotNet directory a list of all classes and their kits. It also writes a list of the dependencies among kits.

The code model for managed code requires that methods which override virtual methods in base classes state whether they override or replace the base class methods. In order to support this behavior in a VTK wrapper, the wrapping code would have to search through all base classes of a vtk class to see whether there are any methods overridden. That isn't possible with how CMake works for all of the other wrappers. It is also not really sensible because we would rather tell the compiler that, if it ever sees a derived class override a base class, that's what we meant to do.

Fortunately, the managed C++ compiler allows us to get away without specifying the "override" keyword. Unfortunately, it gives us a warning. I know of no way around this. There is no setting to declare that all overrides are intentional. Macros provided with the patch file disable this warning in order to speed the build.

VTK .NET home