If you want to get the best out of your products, our Technical Support Team is available to assist you in any question, request, or technical issue that you may have.
Open Inventor 9.5
The .NET framework implies differences between the C++ and the .NET API. This section may be skipped by .NET-only developers. However, it may be helpful if you are already familiar with the Open Inventor C++ API.
The following features cannot be implemented in the same way as they are implemented in C++.
The callback mechanism is used very often by Open inventor C++. Callback functions are defined as follows:
typedef <return_type> functionCB(void * userData, type1 arg1,..., typen argn);
static <return_type> myFunctionCB(void * userData, type1 arg1,..., typen argn)
{
...
}
The callback is registered by calling an addCallback method:
addCallback((functionCB*) myFunctionCB, this);
The .NET implementation is based on delegates and each type of callback is implemented by a specific delegate.
The following example shows how to set a callback on a selection point event.
bool SelectionPointCB(SoAction action, SoPrimitiveVertex v)
{
SoDetail detail = v.GetDetail();
int coordIndex = ((SoPointDetail) detail).GetCoordinateIndex();
...
return false; // con tinue examining next primitives
}
The delegate is registered as follows:
SoExtSelection selection = new SoExtSelection();
selection.PointFilterCallback = new SoExtSelection.PointCB(SelectionPointCB);
Most objects are handled through references -- not pointers or values, but only references. However, struct types (e.g. SbVec3f)are passed by value.
C++
SoCone *cone = new SoC one;
cone->height.setValue( 3 );
C#
SoCone cone = new SoCone();
cone.height.SetValue( 3 ); // note also difference in capitalization of method name
Another minor difference between C++ and C# involves the new operator. The example below shows setting coordinate values into the vertex field of an SoVertexProperty node.
C++
Node->vertex.se t1Value (0, SbVec3f(x,y,z));
C#
Node.vertex.Set 1Value (0, new SbVec3f(x,y,z)); // "new" is required
The Open Inventor .NET framework offers two possibilities to write custom nodes:
The first step is to write your custom node in the usual way, as if you were extending the C++ Open Inventor API. Extended C++ nodes must be built in a separate DLL that exports derived classes and overridden methods.
The following files, Pyramid.h and Pyramid.cpp, illustrate a custom C++ geometry node that draws a pyramid.
Most of time, the managed class has the same name as the native class. To avoid class name conflicts, all managed classes must be fully qualified except the class you're writing or classes in the same namespace. Do not use the "using namespace" directive in your source file if it already includes a native header containing a class contained in the managed namespace.
... #include <Inventor/nodes/SoSeparator.h> using namespace OIV.Inventor.Nodes; ... namespace CustomNodes { //--------------------------------------------------------- void MyCustomNode::MyMethod() { ... SoSeparator *newSeparator = new SoSeparator(); // Error: could be native SoSeparator or managed SoSeparator ... } ... } // CustomNodes namespace end ... #include <Inventor/nodes/SoSeparator.h> ... namespace CustomNodes { //--------------------------------------------------------- void MyCustomNode::MyMethod() { ... OIV::Inventor::Nodes::SoSeparator^ managedSeparator = gcnew OIV::Inventor::Nodes::SoSeparator(); // Ok no conflict ::SoSeparator *nativeSeparator = new ::SoSeparator(); // Ok no conflict ... } ... } // CustomNodes namespace end
Native class must not be defined within a namespace.
The managed class must be written using the managed C++ syntax (not C++/CLI syntax). If your application is entirely written in C#, you must create a separate assembly for your custom nodes.
Two constructors required by the Open Inventor .NET kernel module must be defined in your custom node with "public protected" visibility. The signatures are:
The default minimal implementation must be the following:
//--------------------------------------------------------- MyCustomNode::MyCustomNode(System::IntPtr obj, OIV::Inventor::SoNetBase::ConstructorCommand c) : OIV::Inventor::Nodes::TheMotherClass(OIV::Inventor::SoNetBase::ConstructorCommand::DontCreateInstance) { ::MyCustomNode* p0_handle = (::MyCustomNode *) obj.ToPointer(); SetUnmanagedHandle(p0_handle); InitializeManagedFields(); } //--------------------------------------------------------- MyCustomNode::MyCustomNode(OIV::Inventor::SoNetBase::ConstructorCommand c) : OIV::Inventor::Nodes::TheMotherClass(c) { }
::MyCustomNode represents the native class. TheMotherClass is the mother class name of the managed class.
The SetUnmanagedHandle method sets the native pointer.
The InitializeManagedFields() method instantiates the additional fields of the class. See the next section for further details.
In this context, the class cannot be instantiated outside the assembly (remember the "public protected" visibility of the two first constructors). These constructors are only used by Open Inventor .NET kernel module to instantiate a managed object from a native object.
You can define public constructors in order for your class to be accessible outside the assembly.
//--------------------------------------------------------- MyCustomNode::MyCustomNode() : OIV::Inventor::Nodes::TheMotherClass(OIV::Inventor::SoNetBase::ConstructorCommand::DontCreateInstance) { System::Threading::Monitor::Enter(OIV::Inventor::SoNetBase::ConstructorMutex); ::MyCustomNode* p0_handle = new ::MyCustomNode(); System::Threading::Monitor::Exit(OIV::Inventor::SoNetBase::ConstructorMutex); // The last flag set to 'true' indicates that managed object is responsible // for deleting native object. SetUnmanagedHandle(p0_handle, true); InitializeManagedFields(); }
The constructor is responsible for instantiating the native object, setting the native pointer, and initializing additional fields.
![]() |
You should lock the global constructor mutex when creating the native object (required for database consistency since garbage collector runs in a separate thread). |
Additional fields must be defined in the "public" visibility section of the new class. All available fields are in namespace OIV.Inventor.Fields. For the Pyramid, we need to define additional fields; the native counterpart fields are already defined in the native Pyramid node.
public: // parts OIV::Inventor::Fields::SoSFBitMask^ parts; // baseWidth OIV::Inventor::Fields::SoSFFloat^ baseWidth; // baseDepth OIV::Inventor::Fields::SoSFFloat^ baseDepth; // height OIV::Inventor::Fields::SoSFFloat^ height;
The managed fields must be created from the native fields. The InitializeManagedFields creates links.
//--------------------------------------------------------- void Pyramid::InitializeManagedFields() { parts = static_cast<OIV::Inventor::Fields::SoSFBitMask^> (OIV::Inventor::PanObjectManager::CreateManagedObjectFor( &(((::Pyramid*) UnmanagedHandle.ToPointer())->parts), OIV::Inventor::Fields::SoSFBitMask::typeid, false)); baseWidth = static_cast<OIV::Inventor::Fields::SoSFFloat^> (OIV::Inventor::PanObjectManager::CreateManagedObjectFor( &(((::Pyramid*) UnmanagedHandle.ToPointer())->baseWidth), OIV::Inventor::Fields::SoSFFloat::typeid, false)); baseDepth = static_cast<OIV::Inventor::Fields::SoSFFloat^> (OIV::Inventor::PanObjectManager::CreateManagedObjectFor( &(((::Pyramid*) UnmanagedHandle.ToPointer())->baseDepth), OIV::Inventor::Fields::SoSFFloat::typeid, false)); height = static_cast<OIV::Inventor::Fields::SoSFFloat^> (OIV::Inventor::PanObjectManager::CreateManagedObjectFor( &(((::Pyramid*) UnmanagedHandle.ToPointer())->height), OIV::Inventor::Fields::SoSFFloat::typeid, false)); // Do not forget to call mother class method! OIV::Inventor::Nodes::SoShape::InitializeManagedFields(); }
The CreateManagedObjectFor instantiates a managed object from an unmanaged (native) object.
What about the native class init? Before instantiating any native node, the InitClass() method must be called.
This method will be responsible for calling the native InitClass()
::Pyramid::InitClass();
This method will also create a link between the native type and the managed type. Most of the native classes define the getClassTypeId() method. We use the string representation of the native class to create the link. The PanObjectManager::RegisterClassLink method must be called with the following parameters:
OIV::Inventor::PanObjectManager::RegisterClassLink( ::Pyramid::getClassTypeId().getName().getString(), Pyramid::typeid );
You can perform additional initialization as required by your node.
The .NET framework allows you to define static constructors that are called once when the first instance of the class is created. The static constructor will call the InitClass() method previously defined.
public : static Pyramid() { InitClass(); }
Once you have built the assembly containing the new node, you can use it just like existing Open Inventor nodes in your application. References to InventorNet.dll must be added or updated for the PyramidNet and PyramidNetTest projects.
If you want to get the best out of your products, our Technical Support Team is available to assist you in any question, request, or technical issue that you may have.
FEI Visualization Sciences Group operates at a worldwide level through offices in the United States, United Kingdom, France and distributors. Feel free to contact us.
Request an evaluation version of Open Inventor. Evaluation versions include full access to our technical support and product experts.
FEI Visualization Sciences Group is the leading provider of advanced 3D visualization and analysis software tools for developers, engineers and scientists in natural resources, medical and life sciences, and engineering.
©2015 FEI. FEI, the FEI logo, and Open Inventor are trademarks of FEI Company or its affiliates. All other trademarks belong to their respective owners.