Loading...
Searching...
No Matches
alc native

COMPILE A native-library into a managed-object.

Introduction

Basically, the alc-compiler is a tool for the programmer who is faced with the question of extracting information from a definition file, e.g. a c-header-file, in order to start various actions from it.

The example below shows a workflow using the native library libconfig which is used as a reference implementation.

‍Libconfig is a simple library for processing structured configuration files. This file format is more compact and more readable than XML. And unlike XML, it is type-aware, so it is not necessary to do string parsing in application code.

The libconfig can be found at: http://hyperrealm.github.io/libconfig/

libconfig is a native-library which means that the library itself, as downloaded from the internet, does not contain any managed-object information and is therefore completely independent of the alc-compiler project.


'cls_MqC' - Create a "class" from the class definition database

First it needs a framework for the managed-object library.

The basic framework consists of the class-definitions and the library-definition. The class-definitions are generated from a database containing the definitions of all classes and ultimately sort to ensure that the class-hierarchy is enforced.

A class is a managed-object and is defined by one or more struct in C. The class definition is replaced by cls_MqC added to the header file.

The class definition database ClassDB is very simple :

    # ====================================================================================
    # Define "LcConfig" classes

    define class MkObjectS LcConfigS {
      desc      "LcConfigC class handle"
      Short     Cfg
      header    LcConfigC
    }

    define class MkObjectS LcSettingS {
      desc      "LcSettingC class handle"
      Short     Cfs
      header    LcSettingC
    }
Note
This tool does not add class-attributes or class-methods, this tool only add the code to proper use a class as managed-object.

There will be one file per class ( LcConfigC_lc.h and LcSettingC_lc.h ) and one file as library-definition ( LibLcConfig_lc.h ).

And create the following code (example: LcConfigS) :

Example from LcConfigC_def_lc.h class hierarchy

// BEGIN-LcConfigS-super - created by 'c_Class.tcl -i NHI1_HOME/theConfig/c/gen/c_lcconfig.meta' - DO NOT change
union {
struct MkObjectS obj; // instance-base MkObjectS
} super;
// END-LcConfigS-super - created by 'c_Class.tcl -i NHI1_HOME/theConfig/c/gen/c_lcconfig.meta' - DO NOT change

Example from LcConfigC_def_lc.h class definition

// BEGIN-LcConfigS-Definition - created by 'c_Class.tcl -i NHI1_HOME/theConfig/c/gen/c_lcconfig.meta' - DO NOT change
__parser__push__(prefix=Class, doc-group=Define, doc-index=Class);
// Signature --------------------------------------------------------------
#define LcConfigC_SIGNATURE (MkObjectC_SIGNATURE ^ (15u<<10))
#define LcConfigC_MASK (((1u<<22)-1)<<10)
// CompileTimeCast --------------------------------------------------------------
#define LcConfigC_X2cfg(x) (x)
#define LcConfigC_X2obj(x) MkOBJ(x)
// TypeDef --------------------------------------------------------------
__parser__(ignore) typedef struct LcConfigS LcConfigCR;
__parser__(ignore) typedef const struct LcConfigS LcConfigCNR;
#define LcConfigC_T ( (struct MkSuperTypeS *) (LcConfigC_TT) )
#define LcConfigST LcConfigC_T
#define LcConfigSTT (MkTYP(LcConfigST))
#define LcConfigC_type LC_CFG
#define LcConfigCT_X(instance) ( (struct MkSuperTypeS *) (MkOBJ_R(instance).type) )
#define LcConfigCTT_X(instance) (MkOBJ_R(instance).type)
#define LcConfigCT_TT(typ) ( (struct MkSuperTypeS *) (typ) )
#define LcConfigC_NS LC
#define LcConfigCTT LcConfigCTT
#define LcConfigCT ( (struct MkSuperTypeS *) LcConfigCTT )
// TypeCheck --------------------------------------------------------------
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"
__parser__(class=LcConfigC,static,hide)
return MkSanitizeCheck(LcConfigC,mng);
}
__parser__(class=LcConfigC,static,hide)
return MkSanitizeCheckO(LcConfigC,obj);
}
#pragma GCC diagnostic pop
#define LcConfigC_Check(mng) LcCfgCheck(mng)
// RunTimeCast --------------------------------------------------------------
__parser__(class=LcConfigC,hide,static)
META_ATTRIBUTE_SANITIZE
return (LcCfgCheck(mng) ? (LC_CFG)mng : NULL);
}
__parser__(class=LcConfigC,hide,static)
META_ATTRIBUTE_SANITIZE
return (LcCfgCheck(mng) ? (LC_CFGN)mng : NULL);
}
#define LcCfgRaise(_cfg) if (!_MkCheckX(LcConfigC,_cfg)) { \
MkErrorSetC_1E("'LcConfigC' hdl is NULL"); \
goto error ; \
}
#define LcCFG_R(x) (*(x)).super.cfg
#define LcCFG(x) (&LcCFG_R(x))
// LcConfigC_Class_Define_C_API
// END-LcConfigS-Definition - created by 'c_Class.tcl -i NHI1_HOME/theConfig/c/gen/c_lcconfig.meta' - DO NOT change
bool LcCfgCheckO(MK_OBJN obj)
LC_CFG LcCfg(MK_MNG mng)
MkThreadLocal MK_TYP LcConfigC_TT
LC_CFGN LcCfgN(MK_MNGN mng)
bool LcCfgCheck(MK_MNGN mng)
#define LC_EXTERN_DATA
#define mk_inline
#define __parser__pop__
#define __parser__push__(...)
#define __parser__(...)
const MK_PTRB * MK_MNGN
MK_PTRB * MK_MNG
#define MkSanitizeCheck(_root, _m)
#define MkSanitizeCheckO(_root, _o)
#define MkThreadLocal
struct MkObjectS obj

Example from LibLcConfig_lc.h class type definition

// BEGIN-ShortDef - created by 'c_Class.tcl -i NHI1_HOME/theConfig/c/gen/c_lcconfig.meta' - DO NOT change
__parser__(type=ME_CCC_LcConfigC:"LcConfigC class handle":primary)
typedef struct LcConfigS * LC_CFG;
__parser__(type=ME_CCN_LcConfigC:"const - LcConfigC class handle":primary)
typedef const struct LcConfigS * LC_CFGN;
__parser__(ignore)
typedef struct LcConfigS LC_CFGR;
// LcConfigC_Class_C_API
__parser__(type=ME_CCC_LcSettingC:"LcSettingC class handle":primary)
typedef struct LcSettingS * LC_CFS;
__parser__(type=ME_CCN_LcSettingC:"const - LcSettingC class handle":primary)
typedef const struct LcSettingS * LC_CFSN;
__parser__(ignore)
typedef struct LcSettingS LC_CFSR;
// LcSettingC_Class_C_API
// END-ShortDef - created by 'c_Class.tcl -i NHI1_HOME/theConfig/c/gen/c_lcconfig.meta' - DO NOT change
const struct LcConfigS * LC_CFGN
struct LcConfigS * LC_CFG
const struct LcSettingS * LC_CFSN
struct LcSettingS * LC_CFS

Example from LcConfigC_def_lc.h class introspection

// BEGIN-Class-Introspection - created by 'c_Class.tcl -i NHI1_HOME/theConfig/c/gen/c_lcconfig.meta' - DO NOT change
__parser__push__(doc-name=Introspection,doc-index=Class,class=LcConfigC,no-rpc,null-return-allow,flags=new);
}
MK_ATTR_HDL_CHECK(cfg);
return (LC_CFG)LcConfigC_X2obj(cfg)->obj_protect.next;
}
MK_ATTR_HDL_CHECK(cfg);
return (LC_CFG)LcConfigC_X2obj(cfg)->obj_protect.prev;
}
// LcConfigC_Class_C_API
// END-Class-Introspection - created by 'c_Class.tcl -i NHI1_HOME/theConfig/c/gen/c_lcconfig.meta' - DO NOT change
LC_CFG LcConfigInstances_RT(MK_RT_PARSER_ONLY)
MK_ATTR_HDL LC_CFG LcConfigPrev(LC_CFG const cfg)
MK_ATTR_HDL LC_CFG LcConfigNext(LC_CFG const cfg)
#define LcConfigC_X2obj(x)
#define LcRtSetup_NULL_RT
MK_ATTR_HDL
#define MK_RT_PARSER_ONLY
MK_OBJ instances

'c_Meta' - Create the "meta-code" from the header file(s)

The meta-code is the most important file from the alc-compiler as near all tools use this file to extract the api-definition.

The config_lc.h file is the wrapper needed only for compiling with c_Meta to add __parser__ directives that ultimately adapt the native definitions in libconfig.h to the managed-object structure. In addition to the native API, the classes generated using c_Class are also compiled.

The c_Meta write the meta-code into the package-directory because this file will be added into the release-management (git). By default the filenames gen/c_lcconfig.meta and gen/nat_lcconfig.meta are created and follow the project name LibLcConfig.

The following image shows the meta-code of the native function config_lookup_int64 :

call: grep '\<LcConfigLookupInt64\>' NHI1_HOME/theConfig/c/gen/nat_lcconfig.meta
...
attributeDEF LcConfigLookupInt64,cast yes
attributeDEF LcConfigLookupInt64,config,native {nat config_t §S§M§P}
attributeDEF LcConfigLookupInt64,default-args value_out
attributeDEF LcConfigLookupInt64,error-check ME_ENE_LcErrorE
attributeDEF LcConfigLookupInt64,inline yes
attributeDEF LcConfigLookupInt64,native-alias config_lookup_int64
attributeDEF LcConfigLookupInt64,type-attr {value_out P#1}
attributeDEF LcConfigLookupInt64,value_out,out yes
funcDEF LcConfigLookupInt64 ME_ENE_MkErrorE {{ME_CCN_LcConfigC config} {ME_PSN_MK_STRN path} {ME_NI8_MK_I64 value_out}}
...
enum MkErrorE LcConfigLookupInt64(LC_CFGN config, MK_STRN path, MK_I64 *value_out)
LIBCONFIG_API int config_lookup_int64(const config_t *config, const char *path, long long *value)

'c_Native' - Add code to wrap "native-code" into "managed-object-code"

The problem is that a native-library lacks all the functionality required to function properly as a managed-object

The c_Native perform the following tasks:

  1. change the api-data-types into PRIMITIVE TYPE
  2. change the error-handling from native to managed-object-error
  3. create an inline-wrapper for every public native-api-function
  4. convert a native-struct (handle) into a managed-object-class

The following image shows the translation of the native function config_lookup_int64 into the manged-object function LcConfigLookupInt64.


'c_Overload' - Add missig functions to the given API

An "overload" is a function that adds an additional function with the same or a similar name to an existing function to represent a "function" that does not exist in the original API.

"C" requires an "overload" for :

  1. The default-argument
  2. The runtime-argument
  3. Error Handling

The default-argument is implemented in "C" using the overload-language-extension to "C" that appends the postfix _X (X=0,1,2,3,...) to the original function name to identify the number of arguments in the "overload".

Example, From the managed-object API function :
  • void MyFunc(type1 arg1, type2 arg2 __parser_(default=xyz))
the common API function :
  • void MyFunc(type1 arg1, type2 arg2)
and the 'Overload' API function :
  • #define MyFunc_1(...) MyFunc(__VA_ARGS__,xyz)
are created

The following image shows the "overload" using the verbose -v mode :

The following image shows the "overload" of the manged-object function LcConfigLog :


'c_MqS' - Add missing declarations to the Managed-Object-API

The c_MqS exists for every programming language, where the "c" is replaced by the respective language abbreviation (e.g. tcl_MqS.tcl) and is used to declare the api.

‍A declaration can be an object (e.g. enum) or a function.

The X_MqS really shows its importance with the NONE-"C" language connection, where, for example, the interpreter interface is defined using X_MqS or a translation layer for data types (C#).

Usually the X_MqS does not write any code but only code references or code declarations, the code itself is written in X_MqC.

The following image shows the "declaration" of the manged-object callback for enum LcConfigFormatE :


'c_MqC' - Add missing wrapper-source-code to the Managed-Object-API

The c_MqC exists for every programming language, whereby the "c" is replaced by the respective language abbreviation (e.g. tcl_MqC.tcl).

The c_MqC is used to write the wrapper source code.

The wrapper-source-code ultimately has the task of integrating the managed-object-api-function into the target-language so that the namespace of the managed-object is valid for input and the namespace for the target-language is valid for output.

‍The integration includes the data type or the type-cast as well as the error handling.

Usually the X_MqC write wrapper-source-code :

The following image shows the C-wrapper-source-code of the manged-object LcConfigLookupInt64 :


example - compile manged-object-api-function into C# api function

The example in the alc-compiler C# backend shows the debugging of the MkBufferAppendSTR function.

This function is from the library LibMkKernel_mk.h or mk for short and is a method of the class MkBufferC .

back

theCompiler