HOOPS for C# Developers

Introduction

For developers who want to integrate HOOPS with C# GUI toolkits like Winforms and WPF, documentation is provided in the HOOPS/Winforms and HOOPS/WPF documentation, respectively.

Please see the release notes for information regarding the required version of the .NET framework.

Compilation and Execution

The following steps are required to compile and run a C# based application:

Compiling: Your application must reference the HOOPS/3DF C# wrapper classes.

Executing: Ensure that the following native DLLs are in your application’s directory or in your PATH.

The above files are located in your <hoops>/bin/nt_<platform>_vc<Visual Studio version> directory.

Strong-Named Assemblies

As described in this link, we create our .NET assemblies as “Strong-Named Assemblies”. Advantages:

  • Strong-named assemblies contain public key and digital signature (created with the TS3D private signing key). The public key combined with the assembly name/version allow an application to uniquily reference a strong-named assembly; this is similar to a GUID, and hence the name “strong-named”. Additionally, the public key combined with the digital signature allows the .NET runtime to verify that the DLL bits were not tampered with (ie, modified).

  • Strong-named assemblies can be placed in the global assembly cache (GAC)

  • Customer built strong-named assemblies can reference HOOPS/3DF strong-named assemblies

Tech Soft 3D keeps a private copy of the signing key generated by the Microsoft sn.exe tool. However, the affected strong-named assembly projects all reference this signing key. Customers can provide their own strong name key .snk file if they need to re-build any Tech Soft 3D strong-named assembly. If they do not provide one, a custom pre-build event is in place which will auto-generate a new random .snk file.

The projects expect the .snk file to be at the top-level HOOPS/3DF directory with the name signing_key.snk: $(HOOPS_INSTALL_DIR)/signing_key.snk.

Interface Notes: General Usage

The routines and classes of the HOOPS/3DGS, HOOPS/MVO and HOOPS/Stream toolkits are all generally accessible via the C# wrappers, with a few exceptions and notes covered below. Developers should refer to the API reference manual for each component, and can additionally leverage the Intellisense capability of Visual Studio to dynamically access listings of class members and function/method argument while programming.

Defines

You should also add this “define” to your document somewhere:

#if _M_X64
  using HLONG = System.Int64;
#else
  using HLONG = System.Int32;
#endif

Then, for 64 bit projects, you should define the _M_X64 (you can name this anything you want). You should use HLONG in all places where you would normally use a C “long”. This is because C#’s “long” is always 64 bit, whereas C’s “long” is often 32 bit, and are incompatible. Using HLONG will help ensure compatability. (You can also simply use ints for 32 bit projects and longs for 64 bit projects, but things may get confusing).

HOOPS/3DGS-Specific

Routine Prefixes

Each routine name, such as Insert_Polyline in your HOOPS program must have a prefix before the name will actually be usable on your computer system. The prefix varies depending on which language you’re calling from, and sometimes depending on the brand of the language you’re calling from. When calling from C#, all calls to the HOOPS/3DGS API are made through the HCS class, such as:

HCS.Open_Segment("newsegment");
HCS.Insert_Line(0, 0, 0, 1, 1, 1);
HCS.Close_Segment();

HOOPS Routines Available in C#

Most functions available under C are available in C# except for:

  • ::Define_Callback_Name

  • ::Define_Exit_Handler

  • ::UnDefine_Exit_Handler

  • ::Define_Callback_Name

  • ::UnDefine_Callback_Name

  • ::Show_Callback_Name

Two functions are different under C# compared to C:

  • ::UnDefine_Error_Handler

  • ::Define_Error_Handler

  • ::Find_Error_Handler

Both take an HCSU.ErrorFunc object (which is a delegate) as their argument. If null is passed to UnDefine_Error_Handler, it will remove the default HOOPS error handler. HCSU.ErrorFunc denotes error functions that take proper C# string arrays as arguments as opposed to unsafe C# pointers. If an already registered HCSU.ErrorFunc is passed, that handler will be removed.

Here is an example of declaring an error handler:

public void error_handler(int category, int specific, int severity, int msgc, sbyte ** msgv, int stackc, sbyte ** stackv)
{
}

public void init()
{
        HCSU.ErrorFunc error_hndlr_ptr = new HCSU.ErrorFunc(error_handler);
        HCS.Define_Error_Handler(error_hndlr_ptr);

        HCS.Open_Segment("?picture");
          //  do something
        HCS.Close_Segment();
}

NOTE: You may have to set your project to “allow unsafe code” in order to use the error handler, as it uses pointers.

Multi-Threading

You must call Define_System_Options("multi-threading=full") as your first HOOPS call. This is because C# apps must be thought of as multi-threaded, since the garbage collector will run on a separate thread and may trigger HOOPS calls on a separate thread if it invokes certain destructors.

Pointers and Write-Back Values

The largest difference between using HOOPS with C# and C is found when you have a write-back value, i.e. you pass a pointer to a function and it writes a value or values to that location. In HOOPS C#, arrays can typically be written to without any extra help, while single variables need a “ref” keyword before the object name (similar to the "&" operator in C/C++).

For example, the C HOOPS code that uses a char*:

char ex[1024];

HC_Show_Alias("?picture", ex);
printf("?picture=%s\n", ex);

is replaced in C# by StringBuilder:

StringBuilder ex = new StringBuilder(1024);

HCS.Show_Alias("?picture", ex);

Console.WriteLine("?picture="+ex);

NOTE: The “StringBuilder” type is used for write-back values. You should use the string type for constant string values. These are enforced by the function signatures, so you should not be able to pass in the wrong type.

The C HOOPS code that uses &[float_value]:

float x,y,z;

HC_Open_Segment("?picture");
HC_Show_Camera_Position(&x, &y, &z);
HC_Close_Segment();

printf(" x=%f y=%f z=%f\n", x, y, z);

…is used in a similar manner in C#, with the out keyword:

float x = new float();
float y = new float();
float z = new float();

HCS.Open_Segment("?picture");
HCS.Show_Camera_Position(out x, out y, out z);
HCS.Close_Segment();

Console.WriteLine(" x="+ x + " y="+ y + " z="+ z);

Arrays can be used in C# in a similar manner as they are used in C. The following C code:

float v1[3] = {1.0f,0.0f,0.0f};
float v2[3] = {0.0f,1.0f,0.0f};
float v3[3];

HC_Compute_Cross_Product(v1,v2,v3);
printf("%f %f %f\n", v3[0], v3[1], v3[2]);

…is written in C# by:

float[] v1 = {1.0f,0.0f,0.0f};
float[] v2 = {0.0f,1.0f,0.0f};
float[] v3 = new float[3];
HCS.Compute_Cross_Product(v1,v2,v3);
Console.WriteLine(v3[0] + " " + v3[1] + " " + v3[2]);

HOOPS Keys

Another difference between using HOOPS with C# and C is found when dealing with HOOPS keys. In C#, you should use HLONG (assuming you defined it as mentioned in the previous section). Thus, the C code:

HC_KEY key = 0;

key = HC_Open_Segment("?picture");
// ...
HC_Open_Segment_By_Key(key);

…is replaced in C# by:

HLONG key = 0;

key = HCS.Open_Segment("?picture");
// ...
HCS.Open_Segment_By_Key(key);

Datatype Names and Language Declarations in C#

The C# datatypes translate as follows:

HOOPS HOOPS (HC) type C# type
'string' const char * string
'writes string' char * StringBuilder
'string' unsigned short const * string
'writes string' unsigned short * StringBuilder
'HC_KEY' HC_KEY HLONG (System.Int32 / System.Int64)
'int' int int
'writes int' &int out int
'float' float float
'writes float' &float out float
'point' float[3] float[3]
'writes point' &float float[]
'writes bytes' unsigned char * sbyte[]
'writes ints' &int int[]

_By_Ref Routines

In general, users should not use these methods since HOOPS may end up holding a pointer to managed memory which may be de-allocated or relocated by the C# garbage collector.

If it is essential for you to use any _By_Ref variants, you must ensure that your memory is not affected by the garbage collector. This can be achieved by using the C# fixed keyword, or by using GCHandle.Alloc.

HOOPS Intermediate Mode

The HOOPS intermediate mode library provides a means for an application to trap the HOOPS update cycle at certain points in the rendering pipeline through a set of callback classes. When you trap the update cycle at a callback point, you can decide what and how something is drawn, or even abort the process itself. The HOOPS I.M. library also provides a set of functions you can call from your callback class to draw to the display in an “immediate mode” style and to query the graphics database and the device characteristics. In the HOOPS/3dGS Programming Guide, you can learn more about HOOPS I.M. and how to implement and use the callback classes.

HOOPS/MVO and HOOPS/Stream

Protected Class Members

When using the C# wrappers, protected members of a parent HOOPS/MVO or HOOPS/Stream class can be accessed just like any member from a child class.

Accessing overloaded Virtual Methods

Support for overriding virtual methods of C#/Java classes is enabled via our use of a C++ wrapper-concept called ‘directors’. Directors are currently only enabled for a subset of C#/Java classes. Developers can see which classes have directors enabled by examining the directors.i files at each of the following locations.

C#:

  • [HOOPS]\Dev_Tools\hoops_mvo\source\csharp_mvo\

  • [HOOPS]\Dev_Tools\hoops_stream\source\csharp_stream\

  • [HOOPS]\Dev_Tools\base_stream\source\csharp_bstream\

  • [HOOPS]\Dev_Tools\hoops_3dgs\source\csharp_im\

Java:

  • [HOOPS]\Dev_Tools\hoops_mvo\source\java_mvo\

  • [HOOPS]\Dev_Tools\hoops_stream\source\java_stream\

  • [HOOPS]\Dev_Tools\hoops_3dgs\source\java_im\

Garbage Collection and the Dispose Method

In C#/Java, the garbage collector takes care of all the memory management for purely C# or Java objects, thus you don’t need to explicitly clean them up. However, for native/unmanaged resources, such as the HOOPS C#/Java wrappers which wrap C++ objects (e.g. HOOPS/MVO and HOOPS/Stream objects, callback classes, etc…), the garbage collector may cleanup such objects, but will do so in an non-deterministic order.

For example, each time you create a C# HBaseView, a C++ HBaseView is created. The C++ object stays until HBaseView.Dispose() is called, or until the garbage collector calls it for you. But due to dependencies, certain objects need to be destroyed before others. Another example is that HBaseView needs to be deleted before HBaseModel, which should in turn be deleted before the HDB.

IMPORTANT: C#/Java developers need to be aware that they must explicitly call Dispose() or delete() as if it were a C++ program, in order to trigger the C++ destructor. Additionally, objects should be destroyed in the reverse order they were created. (C#/Java developers who work with unmanaged resources/JNI may already be accustomed to this).

On a related note, users cannot expect objects to stay around when passed as arguments to MVO/Stream functions, such as with a Set operation (for example, SetHandleOperator). If the user does not manually store a reference to the object that was passed to the function in their application, then code may unexpectedly fail at times due to garbage collection. For example:

// Incorrect way. Will cause unexpected garbage collection! It may appear that
// view.SetCurrentOperator() holds a reference to the newly created
// HOpCreateSphere. In fact, it does have a handle to the underlying C++ object,
// but not to the C# proxy object.
HBaseView view;
view.SetCurrentOperator(new HOpCreateSphere(view));

// Correct way.  We need to keep a handle to the C# proxy object for it to persist.
// For this object, C# will automatically call the destructor when the object goes out of scope.
m_pHPanel.SetCurrentOperator(new HOpCreateSphere(m_pHPanel.m_pHView));

Array Handling in C#

Notes on MVO/HStream/BStream/IM methods which return arrays

  • C++ methods which return arrays (as a return type) have corresponding methods in C# which return IntPtr

  • Each C# module (HCSMVO, HCSSTREAM, HCSBSTREAM, HICS) has three extract methods which must be used to obtain the C# array associated with the IntPtr:

    • public static float[] ExtractFloatArray(IntPtr source, int count)

    • public static int[] ExtractIntArray(IntPtr source, int count)

    • public static T[] ExtractArray<T>(IntPtr source, int count)

Example for HShellObject::GetFlist():

public IntPtr GetFlist();

Sample usage:

// let's assume that hso is a valid HshellObject
int[] flist = HCSMVO.ExtractIntArray(hso.GetFlist(), hso.GetFlistLen());

Notes on MVO/HStream/BStream/IM array member variables

Array member variables are of type IntPtr and read-only:

public IntPtr face_list{ get{…} }

Sample usage:

// shell is a valid HShell object
int[] flist = HCSMVO.ExtractIntArray(shell.face_list, shell.face_count);

HFileInputResult and HFileOutputResult

C# users should use the enum HFileIOResult wherever HFileInputResult or HFileOutputResult are mentioned in our documentation. For example:

HFileIOResult result = myView.GetModel().Read("c:/datasets/bnc.hsf", myView);

if (result != HFileIOResult.HIO_OK)
{
  // ... error
}

HOOPS/MVO Classes in C#/Java

A majority of HOOPS/MVO classes are available in C# and Java. However, a number of HOOPS/MVO classes are not provided due to inherent limitations of creating C#/Java wrappers for C++ classes. HOOPS/MVO provides the ability create and modify animations. The HOOPS/MVO animation classes that are accessible in C#/Java are HBhvAnimation and HBhvBehaviorManager. These classes give you the ability to build and manipulate a wide variety of keyframe based behaviors as described in sections on behaviors and animations and defining behaviors in the HOOPS/MVO Programming Guide. The classes associated with animations that are not accessible in C#/Java include:

  • HBhvInterpolator[type]

  • HBhvCondition[type]

  • HBhvAction

  • HBhvTimeline

  • HBhvUtility

  • TK_Bhv_XML

A number of input and output handlers are not available in C#/Java. They include:

  • HUtilityPMI - This class is wrapped in Java, but not in C#. Please see the Java Programming Guide for usage details.

  • HIOUtilityAVI

  • AVIExportInfo

  • HIOUtilityCGM

  • HIOUtilityHMF

  • HIOUtilityPostscript

  • HIOUtilityHPGL2

  • HIOUtilityPDF

  • HPDFOptions

  • HPDFPoster

  • HIOUtilityHsf

  • HIOUtilityHTML

  • HIOUtilityMagick

  • HIOUtilityObj

  • HIOUtilityPly

  • HIOUtilityStl

  • HIOUtilityVRML

Multiple Inheritance

The concept of multiple inheritance is not supported in C# or Java. Thus, access to HOOPS/MVO classes that utilize multiple inheritance is limited. Specifically, these classes were wrapped with only one class inherited. The following is a list of the C++ MVO classes that use multiple inheritance and what single class they now inherit from in C#/Java.

HOOPS/MVO Class

Parent Class in C#/Java

Parent Classes in C++

HBaseView

HUpdateListener

HUpdateListener, HMouseListener, HObjectManipulationListener

HTCObjectRotate

HBaseOperator

HBaseOperator, HTClient

Additionally, the inability to inherit from multiple classes will impede the use of the HEventListener, HEventListenerItem, HEventListenerManager and HEventManager classes. When deriving a class from one of these event classes, it is likely that you will want to derive from another HOOPS/MVO class like HBaseOperator at the same time. Thus, the event classes cannot be used in this manner.

Note that pointers to vlist and vhash are also not accessible via C# or Java.