How the C# Binding is Implemented   

<<< The Mathematical Form Of B-Geometry Chapters Persistent Mesh (PSM) Format >>>

Contents

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.

[back to top]


C.1 Function pointers and delegates

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:

[back to top]


C.2 Initialisation macro, _t and _v structs

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:

 

namespace PLMComponents.Parasolid.PK_.Unsafe
{
  public unsafe partial class BCURVE
  {
    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct fit_data_t
    {
      public PK.BCURVE.fit_eval_type_t  eval_type;
      public PK.BCURVE.fit_eval_f_t     eval_fn;
      public PK.BCURVE.fit_eval_data_t  eval_data;
      public PK.BCURVE.fit_err_method_t err_method;
      public PK.LOGICAL_t               rational;
 
      // Constructor that takes all arguments
      public fit_data_t
      (
      PK.BCURVE.fit_eval_type_t  eval_type,
      PK.BCURVE.fit_eval_f_t     eval_fn,
      PK.BCURVE.fit_eval_data_t  eval_data,
      PK.BCURVE.fit_err_method_t err_method,
      PK.LOGICAL_t               rational
      )
      {
        this.eval_type = eval_type;
        this.eval_fn = eval_fn;
        this.eval_data = eval_data;
        this.err_method = err_method;
        this.rational = rational;
      }
 
  // Constructor that takes a bool, using values from the macro
      public fit_data_t( bool dummy): 
      this
      ( 
      PK.BCURVE.fit_eval_type_t.chain_c, 
      null, 
      new PK.BCURVE.fit_eval_data_t(), // See below
      PK.BCURVE.fit_err_method_t.parm_c, 
      PK.LOGICAL_t.@false
      )
      {} // Empty constructor body
    };   // End of struct declaration
  };     // Close partial class
};       // Close namespace

Figure C-5 C# structure PK.BCURVE.fit_data_t with its constructors

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:

 

namespace PLMComponents.Parasolid.PK_.Unsafe
{ public unsafe partial class BCURVE
  {
    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct fit_data_v
    { public  PK.BCURVE.fit_eval_type_t eval_type;
      // This is the field that differs from the _t struct
      public  IntPtr eval_fn;
      public  PK.BCURVE.fit_eval_data_t eval_data;
      public  PK.BCURVE.fit_err_method_t err_method;
      public  PK.LOGICAL_t rational;
      // Constructor that takes a _t struct
      public fit_data_v( fit_data_t arg)
      { this.eval_type = arg.eval_type;
        // Avoid asking M.GFPFD to process NULL
        this.eval_fn = ((arg.eval_fn == null) ? 
          (IntPtr)0 : 
          Marshal.GetFunctionPointerForDelegate( arg.eval_fn));
        this.eval_data = arg.eval_data;
        this.err_method = arg.err_method;
        this.rational = arg.rational;
      }
      // Dummy-arg constructor for _v struct
      public fit_data_v( bool dummy): 
        this( new fit_data_t( 
        PK.BCURVE.fit_eval_type_t.chain_c, 
        null, 
        new PK.BCURVE.fit_eval_data_t(true), 
        PK.BCURVE.fit_err_method_t.parm_c, 
        PK.LOGICAL_t.@false))
      {} // empty constructor body
      // Operator to make an _t struct from an _v struct 
      public static implicit operator fit_data_t( fit_data_v arg)
      { fit_data_t __ret;
        __ret.eval_type = arg.eval_type;
        // Be careful with zeroes and nulls
        if( arg.eval_fn == (IntPtr)0)
          {
          __ret.eval_fn = null;
          }
        else
          {
          __ret.eval_fn = null;
          __ret.eval_fn = (PK.BCURVE.fit_eval_f_t)
            Marshal.GetDelegateForFunctionPointer( 
            arg.eval_fn, __ret.eval_fn.GetType());
          }
        __ret.eval_data = arg.eval_data;
        __ret.err_method = arg.err_method;
        __ret.rational = arg.rational;
        return __ret;
      }  // Close constructor body
    };   // Close struct declaration
  };     // Close partial class
};       // Close namespace

Figure C-6 C# structure PK.BCURVE.fit_data_v with its constructors

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:

 

_t version (C)

_v version (C#)

PK_ATTDEF_callback_fns_t

PK.ATTDEF.callback_fns_v

PK_BCURVE_create_fitted_o_t

PK.BCURVE.create_fitted_o_v

PK_BCURVE_fit_data_t

PK.BCURVE.fit_data_v

PK_blend_fix_propagate_t

PK.blend_fix_propagate_v

PK_BODY_fix_blends_o_t

PK.BODY.fix_blends_o_v

PK_BODY_hollow_o_t

PK.BODY.hollow_o_v

PK_BODY_make_patterned_o_t

PK.BODY.make_patterened_o_v

PK_BSURF_create_fitted_o_t

PK.BSURF.create_fitted_o_v

PK_CURVE_general_user_t

PK.CURVE.general_user_v

PK_CURVE_general_t

PK.CURVE.general_v

PK_ERROR_frustrum_t

PK.ERROR.frustrum_v

PK_FACE_change_data_deform_t

PK.FACE.change_data_deform_v

PK_FACE_change_o_t

PK.FACE.change_o_v

PK_FACE_details_hole_o_t

PK.FACE.details_hole_o_v

PK_LATTICE_create_by_graph_o_t

PK.LATTICE.create_by_graph_o_v

PK_LATTICE_make_patterned_o_t

PK.LATTICE.make_patterned_o_v

PK_MARK_frustrum_t

PK.MARK.frustrum_v

PK_MEMORY_frustrum_t

PK.MEMORY.frustrum_v

PK_MESH_create_from_facets_o_t

PK.MESH.create_from_facets_o_v

PK_MESH_imprint_vectors_o_t

PK.MESH.imprint_vectors_o_v

PK_MFACET_find_perimeters_o_t

PK.MFACET.find_perimeters_o_v

PK_MTOPOL_make_meshes_o_t

PK.MTOPOL.make_meshes_o_v

PK_PARTITION_make_pmark_o_t

PK.PARTITION.make_pmark_o_v

PK_PARTITION_receive_deltas_o_t

PK.PARTITION.receive_deltas_o_v

PK_PARTITION_receive_o_t

PK.PARTITION.receive_o_v

PK_PMARK_goto_o_t

PK.PMARK.goto_o_v

PK_pattern_callback_t

PK.pattern_callback_v

PK_SESSION_applio_t

PK.SESSION.applio_v

PK_SESSION_ask_fru_o_t

PK.SESSION.ask_fru_o_v

PK_SESSION_frustrum_t

PK.SESSION.frustrum_v

PK_SESSION_indexio_t

PK.SESSION.indexio_v

PK_SESSION_register_fru_o_t

PK.SESSION.register_fru_o_v

PK_SURF_general_user_t

PK.SURF.general_user_v

PK_TOPOL_facet_choice_2_o_t

PK.TOPOL.facet_choice_2_o_v

[back to top]


C.3 Arrays

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.

C.3.1 Arrays using the keyword “fixed”

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.

C.3.2 Arrays as multiple values

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:

 

namespace PLMComponents.Parasolid.PK_.Unsafe
{
  [StructLayout(LayoutKind.Sequential)]
  public unsafe struct TRANSF_sf_t
  {
    public double matrixI0J0;
    public double matrixI0J1;
    public double matrixI0J2;
    public double matrixI0J3;
    public double matrixI1J0;
    public double matrixI1J1;
    public double matrixI1J2;
    public double matrixI1J3;
    public double matrixI2J0;
    public double matrixI2J1;
    public double matrixI2J2;
    public double matrixI2J3;
    public double matrixI3J0;
    public double matrixI3J1;
    public double matrixI3J2;
    public double matrixI3J3;
    // Constructor (abbreviated)
    public TRANSF_sf_t
    (
    double matrixI0J0,
    double matrixI0J1,
    double matrixI0J2,
    double matrixI0J3,
    double matrixI1J0,
    ...
    double matrixI3J3
    )
    {
      this.matrixI0J0 = matrixI0J0;
      this.matrixI0J1 = matrixI0J1;
      ...
      this.matrixI3J2 = matrixI3J2;
      this.matrixI3J3 = matrixI3J3;
    }
  };
}; // Close namespace

Figure C-9 PK.TRANSF_sf_t structure with abbreviated versions of its constructors in C#

This struct simply has sixteen doubles in place of a 4x4 array.

[back to top]


C.4 Unions

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;

Figure C-10 Example of a union within a struct in C

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:

 

namespace PLMComponents.Parasolid.PK_.Unsafe
{
  public unsafe partial class CURVE
  {
    [StructLayout(LayoutKind.Explicit)]
    public unsafe struct general_subunion001_t
    {
      [FieldOffset(0)] public PK.CURVE_t parasolid_curve;
      [FieldOffset(0)] public PK.CURVE.general_user_v
                                                   user_curve;
      // Union constructors ...
    };
 
    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct general_t
    {
      public PK.CURVE.general_type_t type;
      public PK.CURVE.general_subunion001_t curve;
      // Struct constructors ...
    };
  };
};

Figure C-11 Equivalent of a union within a struct in C#

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.

[back to top]


C.5 Applio

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:

 

namespace PLMComponents.Parasolid.PK_.Unsafe
{
  public unsafe partial class SESSION
  {
    [UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)]
    public unsafe delegate int applio_open_rd_t
    (
    int   arg001,
    byte* arg002,
    int*  arg003
    );
    public static readonly applio_open_rd_t applio_open_rd_t_null = null;
 
    [UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)]
    public unsafe delegate int applio_open_wr_t
    (
    int   arg001,
    byte* arg002,
    int*  arg003
    );
    public static readonly applio_open_wr_t applio_open_wr_t_null = null;
 
    // ... Etcetera
 
    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct applio_t
    {
      public PK.SESSION.applio_open_rd_t open_rd;
      public PK.SESSION.applio_open_wr_t open_wr;
      public PK.SESSION.applio_rd_chars_t rd_chars;
      public PK.SESSION.applio_rd_bytes_t rd_bytes;
      public PK.SESSION.applio_rd_shorts_t rd_shorts;
      public PK.SESSION.applio_rd_ints_t rd_ints;
      public PK.SESSION.applio_rd_doubles_t rd_doubles;
      public PK.SESSION.applio_wr_chars_t wr_chars;
      public PK.SESSION.applio_wr_bytes_t wr_bytes;
      public PK.SESSION.applio_wr_shorts_t wr_shorts;
      public PK.SESSION.applio_wr_ints_t wr_ints;
      public PK.SESSION.applio_wr_doubles_t wr_doubles;
      public PK.SESSION.applio_close_t close;
      public PK.SESSION.applio_open_uc_rd_t open_uc_rd;
      public PK.SESSION.applio_open_uc_wr_t open_uc_wr;
      public PK.SESSION.applio_open_rd_2_t open_rd_2;
      public PK.SESSION.applio_open_wr_2_t open_wr_2;
      public PK.SESSION.applio_open_uc_rd_2_t open_uc_rd_2;
      public PK.SESSION.applio_open_uc_wr_2_t open_uc_wr_2;
 

 

      // 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#

[back to top]


C.6 Functions

This section presents examples of how Parasolid functions are accessed by the .NET binding.

C.6.1 Simple functions

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.

C.6.2 Functions with array arguments

.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.

C.6.3 Functions with _v arguments

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.

C.6.4 Persistent callbacks

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.

 

    Performance issue: Each time you register a function pointer (or a structure containing several of them) with Parasolid, a small memory leak occurs. This is unavoidable, and vastly preferable to unpredictable (and irreproducible) crashes.

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.

 

namespace PLMComponents.Parasolid.PK_.Unsafe
{
  public unsafe partial class SESSION
  {
    // Function for user calling, manages persistence
    public static PK.ERROR.code_t watch_tags
    (
    int n_tags,
    int []tags,
    PK.SESSION.watch_create_cb_t  create_fn,
    PK.SESSION.watch_destroy_cb_t destroy_fn
    )
    {
      // Lock create_fn against GC - deliberate memory leak
      GCHandle.Alloc( create_fn );
      // Lock destroy_fn against GC - deliberate memory leak
      GCHandle.Alloc( destroy_fn );
      return _watch_tags( n_tags, tags, create_fn, destroy_fn);
    }  // End of function body
  };   // End of partial class
};     // End of namespace

Figure C-21 Example showing management of persistent callbacks in C#

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

Figure C-22 The P/Invoke function in C#

The functions (and corresponding arguments) that supply persistent callbacks to Parasolid are:

 

Function

Argument

PK_ATTDEF_register_callbacks

callbacks

PK_ATTDEF_register_cb

callbacks

PK_DEBUG_SESSION_watch_classes

create_fn

PK_DEBUG_SESSION_watch_classes

destroy_fn

PK_DEBUG_SESSION_watch_items

create_fn

PK_DEBUG_SESSION_watch_items

destroy_fn

PK_DELTA_register_callbacks

frustrum

PK_ERROR_register_callbacks

frustrum

PK_MARK_start

frustrum

PK_MEMORY_register_callbacks

frustrum

PK_SESSION_register_applio

new

PK_SESSION_register_applio_2

new

PK_SESSION_register_fru_2

options

PK_SESSION_register_frustrum

new

PK_SESSION_register_indexio

new

PK_SESSION_watch_tags

create_fn

PK_SESSION_watch_tags

destroy_fn

C.6.5 Modified function signatures

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:

 

namespace PLMComponents.Parasolid.PK_.Unsafe
{
  public unsafe partial class SESSION
  {
    // Function for user calling, manages persistence
    public static PK.ERROR.code_t register_applio_2(
    PK.SESSION.applio_t @new
    )
    {
      // Create/init new_v as a _v. 
      PK.SESSION.applio_v new_v = new PK.SESSION.applio_v( @new );
      // Lock new against GC - deliberate memory leak
      GCHandle.Alloc( @new );
      return _register_applio_2( &new_v );
    } // End of function body
  };  // End of partial class
};    // End of namespace

Figure C-24 Example of a modified function signature in C#

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

Figure C-25 The P/Invoke function in C#

The functions and arguments that have a modified interface are:

 

Function

Argument

PK_ATTDEF_register_callbacks

callbacks

PK_ATTDEF_register_cb

callbacks

PK_SESSION_ask_fru_2

options

PK_SESSION_register_applio

new

PK_SESSION_register_applio_2

new

PK_SESSION_register_fru_2

options

PK_SESSION_register_frustrum

new

PK_SESSION_register_indexio

new

C.6.6 Functions with string arguments

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.

C.6.6.1 Passing Ansi and Unicode strings to Parasolid

PK_ATTRIB_set_named_ustring provides an example of handing both kinds of string. Its C prototype is:

 

PK_ERROR_code_t PK_ATTRIB_set_named_ustring
(
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#

C.6.6.2 Receiving a .NET string from Parasolid

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:

 

namespace PLMComponents.Parasolid.PK_.Unsafe
{
  public unsafe partial class ATTRIB
  {
    // Outer function for out strings
    public static PK.ERROR.code_t ask_named_ustring
    (
    PK.ATTRIB_t attrib,
    string      field_name,
    out String  @string
    {
      char *arg03;
      @string = ““; 
      PK.ERROR.code_t __retval = 
                          _ask_named_ustring( attrib, field_name, &arg03 );
      if (PK.ERROR.code_t.ok == __retval)
      {
        @string = String( arg03);
        PK.ERROR.code_t _ret_free_string = PK.MEMORY.free( arg03 );
        if (PK.ERROR.code_t.ok != _ret_free_string)
        {
           return _ret_free_string;
        }
      }
      return __retval;
    }; // End of function definition
  };   // End of partial class
};     // End of namespace

Figure C-2 P/Invoke function called by the application in C#

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.

C.6.6.3 List of overloaded functions using .NET strings

The Parasolid functions that have versions using String are:

C name

C# name

PK_ATTDEF_find

PK.ATTDEF.find

PK_ATTRIB_ask_named_axes

PK.ATTRIB.ask_named_axes

PK_ATTRIB_ask_named_doubles

PK.ATTRIB.ask_named_doubles

PK_ATTRIB_ask_named_ints

PK.ATTRIB.ask_named_ints

PK_ATTRIB_ask_named_pointers

PK.ATTRIB.ask_named_pointers

PK_ATTRIB_ask_named_string

PK.ATTRIB.ask_named_string

PK_ATTRIB_ask_named_ustring

PK.ATTRIB.ask_named_ustring

PK_ATTRIB_ask_named_vectors

PK.ATTRIB.ask_named_vectors

PK_ATTRIB_ask_string

PK.ATTRIB.ask_string

PK_ATTRIB_ask_ustring

PK.ATTRIB.ask_ustring

PK_ATTRIB_set_named_axes

PK.ATTRIB.set_named_axes

PK_ATTRIB_set_named_doubles

PK.ATTRIB.set_named_doubles

PK_ATTRIB_set_named_ints

PK.ATTRIB.set_named_ints

PK_ATTRIB_set_named_pointers

PK.ATTRIB.set_named_pointers

PK_ATTRIB_set_named_string

PK.ATTRIB.set_named_string

PK_ATTRIB_set_named_ustring

PK.ATTRIB.set_named_ustring

PK_ATTRIB_set_named_vectors

PK.ATTRIB.set_named_vectors

PK_ATTRIB_set_string

PK.ATTRIB.set_string

PK_ATTRIB_set_ustring

PK.ATTRIB.set_ustring

PK_DEBUG_receive

PKD.UNCLASSED.receive

PK_DEBUG_report_comment

PKD.UNCLASSED.report_comment

PK_DEBUG_report_start

PKD.UNCLASSED.report_start

PK_DEBUG_transmit

PKD.UNCLASSED.transmit

PK_ENTITY_ask_description

PK.ENTITY.ask_description

PK_PARTITION_receive

PK.PARTITION.receive

PK_PARTITION_receive_u

PK.PARTITION.receive_u

PK_PARTITION_receive_version

PK.PARTITION.receive_version

PK_PARTITION_receive_version_u

PK.PARTITION.receive_version_u

PK_PARTITION_transmit

PK.PARTITION.transmit

PK_PARTITION_transmit_u

PK.PARTITION.transmit_u

PK_PART_receive

PK.PART.receive

PK_PART_receive_u

PK.PART.receive_u

PK_PART_receive_version

PK.PART.receive_version

PK_PART_receive_version_u

PK.PART.receive_version_u

PK_PART_transmit

PK.PART.transmit

PK_PART_transmit_u

PK.PART.transmit_u

PK_REPORT_create

PK.REPORT.create

PK_REPORT_find

PK.REPORT.find

PK_REPORT_set_function

PK.REPORT.set_function

PK_SESSION_ask_function

PK.SESSION.ask_function

PK_SESSION_comment

PK.SESSION.comment

PK_SESSION_receive

PK.SESSION.receive

PK_SESSION_receive_u

PK.SESSION.receive_u

PK_SESSION_receive_version

PK.SESSION.receive_version

PK_SESSION_receive_version_u

PK.SESSION.receive_version_u

PK_SESSION_transmit

PK.SESSION.transmit

PK_SESSION_transmit_u

PK.SESSION.transmit_u

PK_THREAD_ask_function

PK.THREAD.ask_function

The following Parasolid functions that process strings do not have overloaded versions using .NET strings:

 

C name

C# name

PK_DEBUG_SESSION_watch_fns

PKD.SESSION.watch_fns

PK_FUNCTION_find

PK.FUNCTION.find

[back to top]


C.7 Functions that use tristate delegates

“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.

C.7.1 Pointer organisation

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.

C.7.2 Using tristate delegates

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:

 

namespace PLMComponents.Parasolid.PK_.Unsafe
{
  public unsafe partial class SESSION
  {
    public unsafe struct register_fru_o_t
    {
    // Struct uses tri-state (tristate) pointers.
    public  int           o_t_version;
    public  PK.FSTART.f_t fstart;
    public  PK.FABORT.f_t fabort;
    public  PK.FSTOP.f_t  fstop;
    ...

This uses delegate types directly.

 

fstart = PK.FSTART.f_t_ignore;

 

The following Parasolid functions use tri-state delegates:

 

C name

C# name

PK_SESSION_ask_fru_2

PK.SESSION.ask_fru_2

PK_SESSION_register_fru_2

PK.SESSION.register_fru_2

C.7.3 Retrieving functions via tristate 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:

 

Function

Argument

PK_SESSION_ask_fru_2

options

 

[back to top]

<<< The Mathematical Form Of B-Geometry Chapters Persistent Mesh (PSM) Format >>>