The kl2edk Utility

As of Fabric Engine version 1.11.0 there is a utility called kl2edk that greatly helps extension development by automatically generating C++ versions of KL types as well as function prototypes for the C++ functions that need to be provided by the extension.

If you use the build script included in the sample extension as a base for building your custom extension then kl2edk will automatically be run for you as part of the build process. If you need to use a different build system then you will need to invoke kl2edk as part of the build process. The usage of kl2edk is very straightforward; simply run kl2edk --help from a command prompt for detailed usage information.

Required Build Flags

If you are using the sample extension’s SConscript file as a base for your custom extension then the required compile flags will already be set in there. If you are setting up a custom build environment and are on Linux or OSX then you will need to ensure that the -fno-omit-frame-pointer build flag is passed to the compiler. Frame pointer omission is an optimization that must be disabled for our KL stack trace generation to work (a feature that outputs a KL stack trace on runtime errors). Failing to add this flag will result in crashes on KL errors within the C++ portion of your extension. The requirement of using this flag may be removed in an upcoming version.

The Generated Header Files

The header files generated by kl2edk from the KL source files by default includes three sections.

The first section includes the base FabricEDK.h file that provides basic types and macros that are needed for the rest of the header as well as for general extension development. We discuss FabricEDK.h more below.

The second section provides C++ versions of all KL structures, objects and interfaces that can potentially be referenced by the KL source code. We provide more information about this in the section Types and Extensions.

The third section provides C++ prototypes for the functions expected to be implemented by the extension in native code. This are precisely the functions that include = "FunctionSymbolNane"; in the KL function declarations. These prototypes will cause a compiler error in the C++ extension code if the function does not have an exact signature match; this helps to ensure that the C++ function is able to interface correctly with the KL code that calls it.

Cpp Code Generation

kl2edk can also be used to generate the function implementations as well as the extension main cpp files. To autogenerate the conversions between the C++ types and the KL types, one additional json file can be provided to assist kl2edk when generating code. The file has to be suffixed with .codegen.json and prefixed with the same name as the extension .fpm.json file.

Note

The codegen json file is optional, kl2edk will still generate cpp files with empty function bodies if it doesn’t exist.

kl2edk will attempt to generate bodies for your C++ methods based on the provided codegen json file. Please see the json example below for more information. Any incomplete cpp function will contain a warning pragma, which will allow you to see the missing implementations at compile time. To enable cpp code generation you need to specify the output folder for the cpp files with the -c command line flag like so:

kl2edk test.fpm.json -o /tmp/test/h -c /tmp/test/cpp

This will put all of the generated headers into the folder /temp/test/h and the the generated implementation files in the corresponding folder.

The code generation relies on conversion functions to be provided. The conversion functions have to use the following notation:

inline bool StringFromKL(const KL::String & from, std::string & to) {
  to = from.data();
  return true;
}

inline bool StringToKL(const std::string & from, KL::String & to) {
  to = from.c_str();
  return true;
}

The sample below will illustrate a simple case. Note that the example is trivial and not a production relevant scenario. printf of course provides redundant functionality which is already covered by KL’s report function.

KL file content:

function String String.substr(UInt32 start, UInt32 length) = "String_substr";
function String.printf() = "String_printf";

codegen.json file content:

{
  "header": "
// my extension includes
#include \"custom_conversion.h\"
#include <thirdparty_api.h>
  ",
  "functionentry": "FUNCTION_ENTRY",
  "functionexit": "FUNCTION_EXIT",
  "functionexitreturn": "FUNCTION_EXIT_RETURN",
  "parameterprefix": "local",
  "typemapping": {
    "UInt32": {
      "ctype": "unsigned int",
      "from": "uintFromKL",
      "to": "uintToKL",
      "methodop": ".",
      "defaultvalue": "0"
    },
    "String": {
      "ctype": "std::string",
      "from": "StringFromKL",
      "to": "StringToKL",
      "methodop": ".",
      "defaultvalue": "std::string()"
    }
  },
  "functionbodies": {
    "String_printf": "
  printf(\"My custom printf '%s'!\\n\", localThis_.c_str());
    "
  }
}

Resulting generated c++ code:

FABRIC_EXT_EXPORT void String_substr(
  Fabric::EDK::KL::Traits< Fabric::EDK::KL::String >::Result _result,
  Fabric::EDK::KL::Traits< Fabric::EDK::KL::String >::INParam this_,
  Fabric::EDK::KL::Traits< Fabric::EDK::KL::UInt32 >::INParam start,
  Fabric::EDK::KL::Traits< Fabric::EDK::KL::UInt32 >::INParam length
)
{
  FUNCTION_ENTRY("String_substr")

  std::string localThis_ = std::string();
  if(!StringFromKL(this_, localThis_)){
    setError("Error in String_substr. unable to convert: this_");
    return;
  }
  unsigned int localStart = 0;
  if(!uintFromKL(start, localStart)){
    setError("Error in String_substr. unable to convert: start");
    return;
  }
  unsigned int localLength = 0;
  if(!uintFromKL(length, localLength)){
    setError("Error in String_substr. unable to convert: length");
    return;
  }
  std::string local_result = localThis_.substr(localStart, localLength);
  StringToKL(local_result, _result);

  FUNCTION_EXIT("String_substr")
}

FABRIC_EXT_EXPORT void String_printf(
  Fabric::EDK::KL::Traits< Fabric::EDK::KL::String >::IOParam this_
)
{
  FUNCTION_ENTRY("String_printf")

  std::string localThis_ = std::string();
  if(!StringFromKL(this_, localThis_)){
    setError("Error in String_printf. unable to convert: this_");
    return;
  }

  printf("My custom printf '%s'!\n", localThis_.c_str());

  FUNCTION_EXIT("String_printf")
}

Note

kl2edk will never overwrite existing cpp files, but instead write files suffixed with .stubs.

The content of the codegen file in a little more detail:

  • header: a string (can be multi-line) which gets inserted into every generated cpp right after the standard includes. this can be useful to include thirdparty api headers etc. This should also provide additional includes such as the conversion functions.
  • functionentry: a macro which is called on every function entry. This parameter is optional, so leave this field empty if you do not need to bracket your functions with entry/exit statements. The notation of the macro should be (the name ENTRYMACRO of course is up to you). ENTRYMACRO(symbolname).
  • functionexit: a macro which is called on every function exit for void functions. This parameter is optional, so leave this field empty if you do not need to bracket your functions with entry/exit statements. The notation of the macro should be: EXITMACRO(symbolname)
  • functionexitreturn: a macro which is called on every function exit for functions with return values. this is optional. The notation of the macro should be: EXITRETURNMACRO(symbolname, defaultValue)
  • parameterprefix: A prefix used for the native data type parameter names. This is parameter mandatory and must be specified.
  • typemapping: A dictionary providing information for all type conversions. The key of the dictionary contains the KL type. The flags in the dictionary are:
  • ctype: the native type to be converted to
  • from: the function to be called to perform the from conversion, from KL type to native type. the notation of both the to and from conversion functions has to be bool conversion(const fromType & from, toType & to); The conversion functions need to return true or false to flag the conversion as successful or not. the conversion functions can also call on Fabric::EDK::setError or raise an exception, for example, to provide more information about an unsuccessful conversion.
  • to: the function to be called to perform the to conversion, from native to KL type.
  • defaultvalue: the default value to be used for the type for non void functions.
  • methodop: an operator to use when accessing methods on the native type. this can be ”.” or “->”.
  • functionbodies: Optionally you can specify a dictionary here of function body implementations. This doesn’t not have to include the conversions, these will be added automatically by kl2edk. You only need to worry about the third-party api specific implementation code. As seen in the json example above, the callback String_printf will be generated with the additional lines of C++ code provided.
  • parameterconversionstoskip: Optionally you can specify an array here of strings containing a combination of a c++ symbol name and a parameter. Each symbol and parameter will be skipped for the code generation when creating the parameter conversion code. For example:
"parameterconversionstoskip": [
  "MyClass_init.this_"
]
  • methodmapping: Additionally you can specify a dictionary containing a string map from c++ symbolname to class method name. This can be important when you have multiple implementations of the same method name in KL, since the c++ symbolnames have to be different. Example:
"methodmapping": {
  "MyClass_set_variant1": "set",
  "MyClass_set_variant2": "set"
}

Namespacing

All macros provided by FabricEDK.h, with the exception of IMPLEMENT_FABRIC_EDK_ENTRIES (below), begin with FABRIC_EDK_.... All C++ types and functions provided by FabricEDK.h as well as the header file generated by kl2edk are defined in the Fabric::EDK namespace. Both of this are to avoid conflicts with third-party libraries.

For convenience, you can use the use namespace functionality of C++ to avoid prefixing the types and functions:

using namespace Fabric::EDK;

However, we recommend manually prefixing types and functions so that it is easier to find them in the case of a future API change.

The FabricEDK.h Header File

The C++ source code for an extension must always begin with the lines:

#include "MyExtensionName.h"

IMPLEMENT_FABRIC_EDK_ENTRIES( MyExtensionName )

The first line includes the header file generated by kl2edk, which in turn includes FabricEDK.h. The second line implements some internal functions that allow the Fabric Engine core to initialize the extension.

In addition to providing a definition for the IMPLEMENT_FABRIC_EDK_ENTRIES macro, the FabricEDK.h header file provides definitions for built-in KL types (see Types and Extensions), several macros required for definitions of functions and custom types, and several debugging and error-handling functions.

Build Macros

FabricEDK.h provides several flags that can be used to detect various aspects of the system on which the extension is being built. Each of these should be tested using the #ifdef C++ preprocessor directive.

FABRIC_EDK_PLATFORM_POSIX
The current system is a *NIX-type system; defined on Linux and Mac OS X.
FABRIC_EDK_PLATFORM_WINDOWS
The current system is a Windows system; defined on Windows
FABRIC_EDK_OS_LINUX
The current system is running some version of Linux
FABRIC_EDK_OS_DARWIN
The current system is running some version of Mac OS X
FABRIC_EDK_OS_WINDOWS
The current system is running some version of Windows
FABRIC_EDK_ARCH_64BIT
The current system is running a 64-bit operating system
FABRIC_EDK_ARCH_32BIT
The current system is running a 32-bit operating system
FABRIC_EDK_BUILD_RELEASE
The current build is a release build with optimizations enabled and debug symbols disabled
FABRIC_EDK_BUILD_DEBUG
The current build is a debug build with optimizations disabled and with debug symbols enabled

The FABRIC_EXT_EXPORT Function Definition Prefix

FabricEDK.h provides the single function definition macro FABRIC_EXT_EXPORT that should prefix any function definition that should be made available in KL. For example:

FABRIC_EXT_EXPORT void MyFunctionToExport(
  // ...
  )
{
  // ...
}

If you use the kl2edk utility to generate the header file for your extension and then copy the function prototypes for your function implementations then they will already use FABRIC_EXT_EXPORT.

FABRIC_EXT_EXPORT does two things: it guarantees that the symbol is visible (“exported”) in the native code shared library, and it disables “mangling” of the symbol name so that the name of the exported symbol is exactly the function name.

Note

Only symbols that will be called from KL need the FABRIC_EXT_EXPORT prefix; other functions and methods in the native code source file can be left without the prefix.

Debugging and Error-Handling Functions

FabricEDK.h provides several functions and macros that are used for debugging and error handling.

void Fabric::EDK::report(char const *format, ...)
void Fabric::EDK::report(uint32_t length, char const *data)

The equivalent of the KL report function. The first variation takes a printf-style format string and a list of arguments, while the second takes a string length and a pointer to raw character data.

void Fabric::EDK::log(char const *format, ...)
void Fabric::EDK::log(uint32_t length, char const *data)

Aliases for Fabric::EDK::report.

char const *Fabric::EDK::getExtensionName()

Returns a C-style null-terminated string that is the name of the extension.

void Fabric::EDK::setError(char const *cStrFormat, ...)
void Fabric::EDK::setError(uint32_t length, char const *data)
void Fabric::EDK::setError(std::string const &string)

The equivalent of the KL setError function. The first variation takes a printf-style format string and a list of arguments, the second takes a string length and a pointer to raw character data, and the third takes an STL std::string.

void Fabric::EDK::throwException(char const *cStrFormat, ...)
void Fabric::EDK::throwException(uint32_t length, char const *data)
void Fabric::EDK::throwException(std::string const &string)

The equivalent of the KL throw statement. The first variation takes a printf-style format string and a list of arguments, the second takes a string length and a pointer to raw character data, and the third takes an STL std::string.

Note

You must use the Fabric::EDK::throwException function to throw an exception from within an extension; using the C++ throw keyword will not work.

Older Extensions (before kl2edk)

New in version 1.13.0.

Some users may have older extensions which are not yet using kl2edk. For these users there is an additional #define which must be declared before calling IMPLEMENT_FABRIC_EDK_ENTRIES. An example of this define can be seen here:

#define FABRIC_EDK_EXT_MyExtensionName_DEPENDENT_EXTS \
  { \
    { "MyDependentExt", 1, 5, 2, NULL }, \
    { 0, 0, 0, 0, 0 } \
  }

This #define specifies the version of extensions on which the current one depends as a static array terminated by an entry which is all zeros. In the above example the MyExtensionName extension depends on version 1.5.2 of the MyDependentExt extension. When matching extensions Fabric will match any extension in the minor version range greater than or equal to the specified version, so in this example MyDependentExt >= 1.5.2 and < 1.6.0 will match.

The values passed in each row of the DEPENDENT_EXTS define are: