main 9.0
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_lc.h class hierarchy

// BEGIN-LcConfigS-super - created by 'cls_MqS.tcl -i NHI1_HOME/theConfig/liblcconfig/.liblcconfig.meta' - DO NOT change
union {
struct MkObjectS obj; // base CLASS \MkObjectS
} super;
// END-LcConfigS-super - created by 'cls_MqS.tcl -i NHI1_HOME/theConfig/liblcconfig/.liblcconfig.meta' - DO NOT change

Example from LcConfigC_lc.h class definition

// BEGIN-LcConfigS-ClassDef - created by 'cls_MqS.tcl -i NHI1_HOME/theConfig/liblcconfig/.liblcconfig.meta' - DO NOT change
// Signature --------------------------------------------------------------
#define LcConfigC_SIGNATURE (MkObjectC_SIGNATURE ^ (11u<<10))
#define LcConfigC_MASK (((1u<<22)-1)<<10)
// Signature --------------------------------------------------------------
#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 MkDefTypeS *) (LcConfigC_TT) )
#define LcConfigST LcConfigC_T
#define LcConfigSTT (MkTYP(LcConfigST))
#define LcConfigC_type LC_CFG
#define LcConfigCT_X(instance) ( (struct MkDefTypeS *) (MkOBJ_R(instance).type) )
#define LcConfigCTT_X(instance) (MkOBJ_R(instance).type)
#define LcConfigCT_TT(typ) ( (struct MkDefTypeS *) (typ) )
#define LcConfigC_NS LC
#define LcConfigCTT LcConfigCTT
#define LcConfigCT ( (struct MkDefTypeS *) LcConfigCTT )
// TypeCheck --------------------------------------------------------------
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"
__parser__(class=LcConfigC,static,hide)
return MkSanitizeCheck(LcConfigC,mng);
}
#pragma GCC diagnostic pop
#define LcConfigC_Check(mng) LcCfgCheck(mng)
// ObjectCast --------------------------------------------------------------
__parser__push__(prefix=Cast);
__parser__(class=LcConfigC,hide,static)
META_ATTRIBUTE_SANITIZE
mk_inline LC_CFG LcCfg(MK_MNG mng) {
return (LcCfgCheck(mng) ? (LC_CFG)mng : NULL);
}
__parser__(ignore)
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))
// END-LcConfigS-ClassDef - created by 'cls_MqS.tcl -i NHI1_HOME/theConfig/liblcconfig/.liblcconfig.meta' - DO NOT change
MkThreadLocal MK_TYP LcConfigC_TT
bool LcCfgCheck(MK_MNGN mng)
LC_CFGN LcCfgN(MK_MNGN mng)
#define LC_EXTERN_DATA
#define mk_inline
const MK_PTRB * MK_MNGN
MK_PTRB * MK_MNG
#define MkSanitizeCheck(_root, _m)
#define MkThreadLocal
#define __parser__pop__
#define __parser__push__(...)
#define __parser__(...)

Example from LibLcConfig_lc.h class type definition

// BEGIN-ShortDef - created by 'cls_MqS.tcl -i NHI1_HOME/theConfig/liblcconfig/.liblcconfig.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;
__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;
// END-ShortDef - created by 'cls_MqS.tcl -i NHI1_HOME/theConfig/liblcconfig/.liblcconfig.meta' - DO NOT change

Example from LcConfigC_lc.h class introspection

// BEGIN-ClassInline - created by 'cls_MqS.tcl -i NHI1_HOME/theConfig/liblcconfig/.liblcconfig.meta' - DO NOT change
// ClassInline --------------------------------------------------------------
__parser__push__(doc=Introspection,class=LcConfigC,no-rpc,null-return-allow,flags=new);
}
return (LC_CFG)LcConfigC_X2obj(cfg)->obj_protect.next;
}
return (LC_CFG)LcConfigC_X2obj(cfg)->obj_protect.prev;
}
// END-ClassInline - created by 'cls_MqS.tcl -i NHI1_HOME/theConfig/liblcconfig/.liblcconfig.meta' - DO NOT change
#define LcConfigC_X2obj(x)
LC_CFG LcConfigPrev(LC_CFG const cfg)
LC_CFG LcConfigNext(LC_CFG const cfg)
LC_CFG LcConfigInstances(void)
#define LcRtSetup_NULL
MK_OBJ instances

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

The meta-code ist 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 cls_MqS are also compiled.

The c_Meta write the meta-code into the source directory because this file will be added into the release-management (git). By default the filename .liblcconfig.meta follow the project name LcConfig.

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

call: grep '\<LcConfigLookupInt64\>' NHI1_HOME/theConfig/liblcconfig/.liblcconfig.meta
...
attributeDEF LcConfigLookupInt64,2,arg-name value_out
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,native2alias config_lookup_int64
attributeDEF LcConfigLookupInt64,type-attr {value_out P}
attributeDEF LcConfigLookupInt64,value_out,cast ME_NIX_MK_LLG
attributeDEF LcConfigLookupInt64,value_out,out yes
funcDEF LcConfigLookupInt64 ME_ENE_MkErrorE {{ME_CCN_LcConfigC config} {ME_PSN_MK_STRN path} {ME_NI8_MK_WID value_out}}
...
enum MkErrorE LcConfigLookupInt64(LC_CFGN config, MK_STRN path, MK_WID *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 MkBufferAppendC function.

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

back

theCompiler