![]() |
How the C# Binding is Implemented |
<<< The Mathematical Form Of B-Geometry | Chapters | Persistent Mesh (PSM) Format >>> |
This Appendix shows how the .NET binding is constructed, using examples drawn from the binding source code. If you would like to licence the binding source in order to extend it, please contact Parasolid Support. This will require your company to sign a short licensing agreement with Siemens PLM Software specifically for the source code.
This section presents examples of how Parasolid function pointers are used via C# delegates. As a first example, the declarations of PK_ATTDEF_name_cb_f_t in C and C# are:
typedef const char *( *PK_ATTDEF_name_cb_f_t ) ( const PK_ATTDEF_sf_2_t*, /* attribute definition standard form */ PK_POINTER_t /* context */ ); |
namespace PLMComponents.Parasolid.PK_.Unsafe { public unsafe partial class ATTDEF { [UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)] public unsafe delegate byte* name_cb_f_t ( PK.ATTDEF.sf_2_t* arg001, PK.POINTER_t arg002 ); }; }; |
Figure C-1 Declaration of PK_ATTDEF_name_cb_f_t in C (above) and C# (below) |
Similarly, for PK_detail_hole_cb_f_t, the declarations are:
typedef int ( *PK_detail_hole_cb_f_t ) ( int, /* number of faces in hole detail */ const PK_FACE_t[], /* faces in hole detail */ PK_detail_def_hole_t, /* hole detail definition */ PK_POINTER_t /* context */ ); |
namespace PLMComponents.Parasolid.PK_.Unsafe { [UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)] public unsafe delegate int detail_hole_cb_f_t ( int arg001, PK.FACE_t* arg002, PK.detail_def_hole_t arg003, PK.POINTER_t arg004 ); public unsafe partial class UNCLASSED { public static int detail_hole_cb_f_t_fn_ignore( int arg001, PK.FACE_t* arg002, PK.detail_def_hole_t arg003, PK.POINTER_t arg004 ) { System.Console.WriteLine( "error: called detail_hole_cb_f_t_fn_ignore"); return -1; } public static readonly PK.detail_hole_cb_f_t detail_hole_cb_f_t_null = null; public static readonly PK.detail_hole_cb_f_t detail_hole_cb_f_t_ignore = detail_hole_cb_f_t_fn_ignore; }; }; |
Figure C-2 Declaration of PK_detail_hole_cb_f_t in C (above) and C# (below) |
Some points to note regarding the code excerpts above:
const
qualifiers are dropped, and C arrays are converted to pointers, since [] arrays mean something quite different in C#.
typedef
; the .NET binding has individual
delegate
types for these. For an example, see Appendix C.5, “Applio”.
Since C# does not have a general-purpose macro pre-processor, the C initialisation macros for structures used in the PK interface have to be implemented as
struct
constructors.
Some PK structs contain function pointers, and the corresponding C# structs therefore contain delegates. However, it is not possible in C# to take the address of a structure that contains a delegate. Naturally, this causes problems with structures that are passed to Parasolid via pointers.
To overcome this, the .NET binding declares variant versions of such structures, which have the trailing “_t“ in their names replaced with “_v“. For an example, see PK.BCURVE.fit_data_t and PK.BCURVE.fit_data_v (Figure C-5 and Figure C-6, respectively). The
_v
structures have their delegate fields replaced with IntPtr fields.
The C structure PK_BCURVE_fit_data_t is:
struct PK_BCURVE_fit_data_s { PK_BCURVE_fit_eval_type_t eval_type; /* what evaluator to use */ PK_BCURVE_fit_eval_f_t eval_fn; /* user evaluator (NULL) */ PK_BCURVE_fit_eval_data_t eval_data; /* evaluator data */ PK_BCURVE_fit_err_method_t err_method; /* error method for B-curve */ PK_LOGICAL_t rational; /* if B-curve is to be rational */ }; typedef struct PK_BCURVE_fit_data_s PK_BCURVE_fit_data_t; |
Figure C-3 The PK_BCURVE_fit_data_t structure in C |
It has the following initialisation macro:
#define PK_BCURVE_fit_data_m ( fit_data ) \ ( \ (fit_data).eval_type = PK_BCURVE_fit_eval_chain_c, \ (fit_data).eval_fn = NULL, \ PK_BCURVE_fit_eval_data_m( (fit_data).eval_data ), \ (fit_data).err_method = PK_BCURVE_fit_err_parm_c, \ (fit_data).rational = PK_LOGICAL_false \ ) |
Figure C-4 Initialisation macro for PK_BCURVE_fit_data_t in C |
The corresponding C# structure in the .NET binding, PK.BCURVE.fit_data_t, with its constructors, is:
The first constructor simply takes one argument per field of the structure; the second constructor uses the
this(...)
syntax to call the first constructor with arguments taken from the C initialisation macro. The .NET binding, by convention, uses a constructor with a single
bool
argument to supply values taken from a C macro; the value of the bool is ignored.
The third argument to the
this
call above uses two C# features that can be confusing for C++ programmers. The
new
operator, used this way, does not allocate memory, but simply calls a constructor and returns the initialised object into memory provided by the caller. C# also supplies a default constructor with no arguments, which initialises all fields to the default values for their type.
The PK.BCURVE.fit_data_v struct in the .NET binding, with its constructors, is:
This _v struct is the same as PK.BCURVE.fit_data_t except that the
eval_fn
field is an IntPtr rather than a delegate type. The first constructor takes a _t struct and makes an _v struct out of it; note that one has to avoid passing null into the Marshal function that extracts a function pointer from a delegate.
The second constructor makes a _t struct, using values from the C macro, and then makes a _v struct from it, using the first constructor. You may have noticed that the second constructor could have been written using the PK.BCURVE.fit_data_t(bool) constructor; this is the kind of optimisation that is much harder to do in automatically generated code than in code written by hand.
The third constructor makes a fit_data_t struct out of a fit_data_v struct.
The following structures have _v versions:
C# arrays are rather different from C arrays, which means that special measures are required to handle the arrays included in many of Parasolid’s C structs. One-dimensional, fixed-size arrays of basic C# value types (
byte
,
short
,
int
,
long
,
char
,
sbyte
,
ushort
,
uint
,
ulong
,
float
and
double
) use the
fixed
keyword, and references to them need special handling. All two-dimensional arrays, and arrays of non-basic types have to be expanded into several separate scalar values.
PK_INTERVAL_t provides a simple example. The C version of this struct is:
struct PK_INTERVAL_s { double value[2]; }; typedef struct PK_INTERVAL_s PK_INTERVAL_t; |
Figure C-7 PK_INTERVAL_t structure in C |
The C# version, PK.INTERVAL_t, is declared in the .NET binding with its constructors as:
namespace PLMComponents.Parasolid.PK_.Unsafe { [StructLayout(LayoutKind.Sequential)] public unsafe struct INTERVAL_t { public fixed double value[2]; // Basic constructor, taking an array public INTERVAL_t ( double []value ) { fixed( double *a = this.value) a[0] = value[0]; fixed( double *a = this.value) a[1] = value[1]; } // Extra constructor, taking arrays as multiple values public INTERVAL_t ( double valueI0, double valueI1 ) { fixed( double *a = this.value) a[0] = valueI0; fixed( double *a = this.value) a[1] = valueI1; } } // Close structure }; // Close namespace |
Figure C-8 PK_INTERVAL_t structure with its constructors in C# |
This has the same array of two doubles, called “value“, but it has the
fixed
prefix on its declaration, and
fixed()
statements have to be used to access its elements.
The only example of a two-dimensional array in the PK interface is PK_TRANS_sf_t. The C# version provided in the binding, PK.TRANSF_sf_t, is, with abbreviated versions of its constructor:
This struct simply has sixteen doubles in place of a 4x4 array.
The Parasolid C interface uses unions within C structs, as follows:
struct PK_CURVE_general_s { PK_CURVE_general_type_t type; union { PK_CURVE_t parasolid_curve; PK_CURVE_general_user_t user_curve; } curve; }; typedef struct PK_CURVE_general_s PK_CURVE_general_t; |
The .NET binding does not support using unions within structs. Instead, it provides a separate struct, with field offset attributes, to support the same semantics:
The “subunion001“ suffix in the name simply indicates that this is an automatically created sub-union, and that the binding generator allows for many such sub-unions in a single C struct.
The PK_SESSION_applio_t struct is quite complex. The C# version requires the creation of delegate types and the use of a _v struct. The C struct is:
typedef struct PK_SESSION_applio_s { int (*open_rd) (int keylen, const char* key, int *strid); int (*open_wr) (int keylen, const char* key, int *strid); int (*rd_chars) (int strid, int n, char *data); int (*rd_bytes) (int strid, int n, unsigned char *data); int (*rd_shorts) (int strid, int n, short *data); int (*rd_ints) (int strid, int n, int *data); int (*rd_doubles) (int strid, int n, double *data); int (*wr_chars) (int strid, int n, const char *data); int (*wr_bytes) (int strid, int n, const unsigned char *data); int (*wr_shorts) (int strid, int n, const short *data); int (*wr_ints) (int strid, int n, const int *data); int (*wr_doubles) (int strid, int n, const double *data); int (*close) (int strid, int abort); int (*open_uc_rd) (const PK_UCHAR_t *key, int *strid); int (*open_uc_wr) (const PK_UCHAR_t *key, int *strid); int (*open_rd_2) (int guise, int keylen, const char* key, int *strid); int (*open_wr_2) (int guise, int keylen, const char* key, int *strid); int (*open_uc_rd_2) (int guise, const PK_UCHAR_t *key, int *strid); int (*open_uc_wr_2) (int guise, const PK_UCHAR_t *key, int *strid); } PK_SESSION_applio_t; |
Figure C-12 PK_SESSION_applio_t structure in C |
The C# declaration of this struct, provided for your use in the binding, creates delegate types first, and then declares the struct and its _v variant:
// Constructor (abbreviated) public applio_t ( PK.SESSION.applio_open_rd_t open_rd, PK.SESSION.applio_open_wr_t open_wr, PK.SESSION.applio_rd_chars_t rd_chars, PK.SESSION.applio_rd_bytes_t rd_bytes, PK.SESSION.applio_rd_shorts_t rd_shorts, // ... PK.SESSION.applio_open_uc_wr_t open_uc_wr ) { this.open_rd = open_rd; this.open_wr = open_wr; this.rd_chars = rd_chars; this.rd_bytes = rd_bytes; this.rd_shorts = rd_shorts; // ... etc. } // End of constructor }; // End of applio_t [StructLayout(LayoutKind.Sequential)] public unsafe struct applio_v { public IntPtr open_rd; public IntPtr open_wr; public IntPtr rd_chars; public IntPtr rd_bytes; public IntPtr rd_shorts; // ... etc. public IntPtr open_uc_wr; // Constructor to make applio_v from applio_t (abbr’d) public applio_v( applio_t arg ) { // Avoid asking M.GFPFD to process NULL this.open_rd = ((arg.open_rd == null) ? (IntPtr)0 : Marshal.GetFunctionPointerForDelegate( arg.open_rd)); // Avoid asking M.GFPFD to process NULL this.open_wr = ((arg.open_wr == null) ? (IntPtr)0 : Marshal.GetFunctionPointerForDelegate( arg.open_wr)); // Avoid asking M.GFPFD to process NULL this.rd_chars = ((arg.rd_chars == null) ? (IntPtr)0 : Marshal.GetFunctionPointerForDelegate(arg.rd_chars)); // Etcetera } // End of constructor } // End of applio_v }; // Close class }; // Close namespace |
Figure C-13 PK_SESSION_applio_t structure with _v variant in C# |
This section presents examples of how Parasolid functions are accessed by the .NET binding.
A standard PK function maps very simply onto a P/Invoke call. For example, PK_APPITEM_is is declared in C as:
PK_ERROR_code_t PK_APPITEM_is ( int may_be_appitem, /*potential appitem */ PK_LOGICAL_t *const is_appitem ); |
Figure C-14 Declaration of PK_APPITEM_is in C |
The corresponding C# declaration for PK.APPITEM.@is, as provided in the binding, is:
namespace PLMComponents.Parasolid.PK_.Unsafe { public unsafe partial class APPITEM { [DllImport(“pskernel.dll“, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = “PK_APPITEM_is“)] public static extern PK.ERROR.code_t @is( int may_be_appitem, PK.LOGICAL_t *is_appitem ); // End of function declaration }; // End of class }; // End of namespace |
Figure C-15 Declaration of PK_APPITEM_is in C# |
While this is rather longer than the C declaration, much of the extra text simply establishes the calling convention (“Cdecl“) and character set mapping (Windows ANSI) to be used. The rest connects the DLL name and entry point to the function being declared, and declares the arguments. The marshalling of C# arguments' data to C arguments is handled automatically by P/Invoke.
.NET arrays are rather different from C arrays. In order to support both, functions with array arguments are overloaded: two versions are provided, one taking C-style arrays as pointers, and one taking .NET arrays. For example, PK_ATTRIB_set_doubles is declared in C as:
PK_ERROR_code_t PK_ATTRIB_set_doubles ( PK_ATTRIB_t attrib, /* attribute */ int field_no, /* field number (>= 0) */ int n_doubles, /* number of doubles (>= 0) */ const double doubles[] /* doubles to set */ ); |
Figure C-16 Declaration of PK_ATTRIB_set_doubles in C |
The two C# declarations provided in the .NET binding are:
namespace PLMComponents.Parasolid.PK_.Unsafe { public unsafe partial class ATTRIB { [DllImport(“pskernel.dll“, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = “PK_ATTRIB_set_doubles“)] public static extern PK.ERROR.code_t set_doubles( PK.ATTRIB_t attrib, int field_no, int n_doubles, double []doubles // Different to second declaration ); // Overloaded function, uses C-style instead // of managed arrays [DllImport(“pskernel.dll“, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = “PK_ATTRIB_set_doubles“)] public static extern PK.ERROR.code_t set_doubles( PK.ATTRIB_t attrib int field_no, int n_doubles, double *doubles // Different to first declaration ); // End of function declaration }; // End of class }; // End of namespace |
Figure C-17 Declarations of PK_ATTRIB_set_doubles in C# |
The only difference between these two declarations is the last function argument,
doubles
, which is declared in one as
[]doubles
and in the other as
*doubles
. The C# compiler selects whichever of these is appropriate for each call in your code.
For functions with several array arguments, the binding still only provides two versions: one with all array arguments as C-style arrays, and one with all .NET arrays.
Some functions require that their arguments are “_v” versions of structs. For example, PK_BCURVE_create_fitted is declared in C as:
PK_ERROR_code_t PK_BCURVE_create_fitted ( const PK_BCURVE_create_fitted_o_t *options, /* options for fitting */ PK_BCURVE_t *const *bcurve, /* B-curve fit to input */ PK_BCURVE_fitted_fault_t *const *fault /* any faults found */ ); |
Figure C-18 Declaration of PK_BCURVE_create_fitted in C |
The options structure PK_BCURVE_create_fitted_o_t contains a function pointer, so the C# version of the structure must contain a delegate. That necessitates an _v version of the structure; so the C# declaration of the function, as provided in the binding, is:
namespace PLMComponents.Parasolid.PK_.Unsafe { public unsafe partial class BCURVE { [DllImport(“pskernel.dll“, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = “PK_BCURVE_create_fitted“)] public static extern PK.ERROR.code_t create_fitted( // Note _v version of struct PK.BCURVE.create_fitted_o_v *options, PK.BCURVE_t *bcurve, PK.BCURVE.fitted_fault_t *fault ); // End of function declaration }; // End of class }; // End of namespace |
Figure C-19 Declaration of PK_BCURVE_create_fitted in C# |
You must therefore create and initialise the _t structure first, create an _v structure from it, using the constructor supplied in the binding, and then take the address of that _v structure and pass it to the C# function. The C# versions of most structures can be passed by address without a problem: it is those that contain delegates that must be converted to _v versions.
Parasolid can use function pointers that have been provided to it by an application (via delegates, in the .NET binding) in the following ways:
The second type can cause a problem in a .NET environment. If a delegate that provided a function pointer to Parasolid is moved or deleted by the .NET garbage collector, the function pointer that Parasolid was holding becomes invalid. Calling the function that Parasolid still thinks is available will crash your application. There is no mechanism for the garbage collector to tell Parasolid about this, so it is necessary to ensure that the delegate is never moved or deleted.
To ensure this, the .NET binding takes a copy of the delegate - or rather, the structure containing that delegate - and locks it against garbage collection before extracting a function pointer to be passed to Parasolid. This means that every time you call a function that passes a function pointer of the second type to Parasolid, some memory is allocated to make sure that the function pointer remains valid. This memory cannot be recovered or moved by the .NET garbage collector. It is allocated or pinned, and there is no way to de-allocate or un-pin it within your program.
The only way in which this memory can be recovered is to terminate the process that contains Parasolid. Stopping and starting the Parasolid session does not work because the .NET environment has no concept of the Parasolid session.
This is not a problem in desktop applications used by one person at a time. However, in a server-based application where many users are served by a single server process, this kind of memory leak could be a problem. One strategy for avoiding it would be to use multiple server processes, each of which will only accept a limited number of users, and will terminate itself when all of them have finished using it.
This kind of memory leak does not occur for function pointers of the first type. Function pointers of the second type are best registered at start-up, and left unchanged for the life of the process.
For example, PK_DEBUG_SESSION_watch_items is declared in C as:
PK_ERROR_code_t PK_DEBUG_SESSION_watch_items ( PK_ITEM_array_t create, /* objects to watch*/ PK_DEBUG_SESSION_create_cb_t create_fn, /* create callback*/ PK_ITEM_array_t destroy, /* items to watch*/ PK_DEBUG_SESSION_destroy_cb_t destroy_fn /* destroy callback*/ ); |
Figure C-20 Declaration of PK_DEBUG_SESSION_watch_items in C |
Both
create_fn
and
destroy_fn
are
persistent callbacks: pointers to functions that could be called at any subsequent time. The .NET binding for this function must therefore ensure that they are preserved. To do this, it needs to run some code, so it provides a function to be called by applications. This manages the persistence of the arguments, and calls a second function to make the P/Invoke call to Parasolid.
The P/Invoke function is standard except for the “_“ prefix on its name:
namespace PLMComponents.Parasolid.PK_.Unsafe { public unsafe partial class SESSION { [DllImport(“pskernel.dll“, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = “PK_SESSION_watch_tags“)] public static extern PK.ERROR.code_t _watch_tags // Note the “_“ prefix ( int n_tags, int []tags, PK.SESSION.watch_create_cb_t create_fn, PK.SESSION.watch_destroy_cb_t destroy_fn ); // End of function declaration }; // End of partial class }; // End of namespace |
The functions (and corresponding arguments) that supply persistent callbacks to Parasolid are:
create_fn |
|
destroy_fn |
|
create_fn |
|
destroy_fn |
|
frustrum |
|
new |
|
create_fn |
|
destroy_fn |
For some functions that supply persistent callbacks to Parasolid, and are needed in almost every Parasolid-based application, the .NET binding modifies the function's arguments to simplify programming. The modified functions receive types by value, rather than address, make _v versions of them, and supply the address of the _v version to Parasolid. For example, PK_SESSION_register_applio_2 uses this method. The C declaration of the function is:
PK_ERROR_code_t PK_SESSION_register_applio_2 ( const PK_SESSION_applio_t *new /* functions to register (may be NULL) */ ); |
Figure C-23 Declaration of PK_SESSION_register_applio_2 in C |
Like the previous example, the function to be called by the application does some processing before calling the P/Invoke function:
Note that the
_t
structure,
@new
, is the one that is locked. That is needed throughout the life of the program, because it contains the delegates themselves. The
_v
structure,
new_v
only contains the
addresses of the delegates.
The P/Invoke function is just like the one in the previous example:
namespace PLMComponents.Parasolid.PK_.Unsafe { public unsafe partial class SESSION { [DllImport(“pskernel.dll“, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = “PK_SESSION_register_applio_2“)] public static extern PK.ERROR.code_t _register_applio_2( PK.SESSION.applio_v *@new ); // End of function declaration }; // End of partial class }; // End of namespace |
The functions and arguments that have a modified interface are:
new |
Almost all Parasolid functions that receive or return strings, including Unicode strings, have overloaded versions in the .NET binding that use .NET string types (for ease of programming). In place of the
char
* or PK_UCHAR_t* in the C interface, the overloaded version uses a
System.String
(C# string) type.
PK_ATTRIB_set_named_ustring provides an example of handing both kinds of string. Its C prototype is:
( PK_ATTRIB_t attrib, /* attribute */ const char* field_name, /* field name */ const PK_UCHAR_t* string /* null-terminated string to set */ ); |
Figure C-1 Declaration of PK_ATTRIB_set_named_ustring in C |
This takes a
char
* for the field name and a PK_UCHAR_t * for the Unicode string to be set in the attribute. The version that uses .NET strings, below, takes C# string arguments, but uses MarshalAs to tell the C# compiler that the string to be set is a “Wide“ or Unicode string.
namespace PLMComponents.Parasolid.PK_.Unsafe { public unsafe partial class ATTRIB { // Function to take C# strings, not character pointers [DllImport(“pskernel.dll“, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = “PK_ATTRIB_set_named_ustring“)] public static extern PK.ERROR.code_t set_named_ustring( PK.ATTRIB_t attrib, string field_name, [MarshalAs(UnmanagedType.LPWStr)] string @string ); // End of function declaration }; // End of partial class }; // End of namespace |
Figure C-2 Overloaded version of PK_ATTRIB_set_named_ustring in C# |
When Parasolid is asked to return a string, it allocates memory to hold the string using PK_MEMORY_alloc. The .NET binding calls Parasolid, then Parasolid returns to the binding passing back a pointer to the string. The binding copies the string into a
System.String
, passed to the function as an “out“ parameter so that it can be changed, and frees the memory allocated by Parasolid using PK_MEMORY_free.
As for functions with persistent arguments, the process is implemented with two functions. The first is to be called by the application; it calls a second P/Invoke function, and manages the construction of the String.
The P/Invoke function is much like those above. The only unusual part is its third argument,
char
**@string. A .NET
char
is a 16-bit Unicode character, unlike the conventional C 8-bit
char
.
namespace PLMComponents.Parasolid.PK_.Unsafe { public unsafe partial class ATTRIB { // Function to take C# strings, not character pointers [DllImport(“pskernel.dll“, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = “PK_ATTRIB_ask_named_ustring“)] public static extern PK.ERROR.code_t ask_named_ustring( PK.ATTRIB_t attrib, string field_name, char **@string ); // End of function declaration }; // End of partial class }; // End of namespace |
Figure C-1 Overloaded version of PK_ATTRIB_ask_named_ustring in C# |
The function provided for the application to call contains more code than most of the functions in the binding:
This function receives an “out String“ as its third argument. It declares a
char
* variable and passes its address to Parasolid, so as to get the address of the string that Parasolid will return. Note that this would be an
sbyte
* if the string the Parasolid will return was made up from 8-bit characters. Having done that, it constructs a
System.String
from the
char
*, Finally, it needs to free the memory that Parasolid allocated. This task is normally left to the application, but that would involve passing the address back to the application in some way; it is simpler for the binding to call PK.MEMORY.free to do the job. If PK.MEMORY.free returns an error, the error is returned; note that the code does not attempt to free memory unless the PK.ATTRIB.ask_named_ustring call was successful.
Since constructing the String uses standard .NET constructors for String, this code could throw a .NET exception. This is unlikely in practice, unless the application is about to run out of memory or you store extremely long strings within Parasolid data.
The Parasolid functions that have versions using String are:
The following Parasolid functions that process strings do not have overloaded versions using .NET strings:
“Tristate” delegates are delegates whose “ignore” value is significant. They were introduced for use by functions that register function pointers, or ask Parasolid for the values of registered function pointers, via their options structures. Examples include PK_SESSION_ask_fru_2 and PK_SESSION_register_fru_2.
PK_SESSION_register_fru_o_t is defined in C as:
struct PK_SESSION_register_fru_o_s { int o_t_version; /* version number */ PK_FSTART_f_t *fstart; /* (NULL) */ PK_FABORT_f_t *fabort; /* (NULL) */ PK_FSTOP_f_t *fstop; /* (NULL) */ ... }; typedef struct PK_SESSION_register_fru_o_s PK_SESSION_register_fru_o_t; |
The
_f_t
types used in the structure are function pointer types, and each element of the structure is a pointer to a function pointer. This allows the structure to convey three types of information:
Note: This structure does not contain memory for the actual function pointers, only for the pointers to function pointers. The actual function pointers have to be stored elsewhere. |
Tristate delegates have a third defined state in addition to the normal states of “null” and “pointing to a function” that all delegates have. The third state is the delegate’s “ignore” value.
For example, the C# version of PK_SESSION_register_fru_o_t is:
This uses delegate types directly.
fstart = PK.FSTART.f_t_ignore;
The following Parasolid functions use tri-state delegates:
Supporting the C function PK_SESSION_ask_fru_2 in C#, as PK.SESSION.ask_fru_2, requires its options structure to be a “ref” argument in the C# binding. This is used to make the request, and to pass the results back to your application.
The following Parasolid functions use ref arguments:
<<< The Mathematical Form Of B-Geometry | Chapters | Persistent Mesh (PSM) Format >>> |