.. _EAG.sample: Understanding the Sample Extension ======================================= In this section we go through each of the files in the sample extension that you built in the last section. By understanding how these files work and fit together you will understand the basics of how extensions work in |FABRIC_PRODUCT_NAME|. :file:`HelloWorld.fpm.json` --------------------------------------- The only required file in an extension is the extension manifest file; in the sample extension it is the file :file:`HelloWorld.fpm.json`, and its contents are: .. code-block:: json { "libs": "HelloWorld", "code": "HelloWorld.kl" } The extension manifest file must end with the :file:`.fpm.json` suffix, and the part of the filename leading up to :file:`.fpm.json` is by definition the name of the extension. The name of the extension is what is used include the extension using the :samp:`require {ExtensionName};` statement in KL or using the :code:`exts` parameter when creating a |FABRIC_PRODUCT_NAME| core client. The contents of the manifest file are in JSON format (see `http://json.org/ `_), and must be a single JSON object. The two members of the JSON object are :code:`libs` and :code:`code`; both are optional. The value of :code:`libs` is either a string or an array of strings. Each string is the base name of a shared library that is part of the extension. The actual filename of the shared library depends on the platform; the :file:`SConstruct` file that comes with the sample extension takes care of building a shared library with the right filename for the platform it is built on. This means that you can (and should) use the same SConstruct file to build the extension on all the platforms the extension supports. The value of :code:`code` is a filename of a KL source file (including the ``.kl`` extension), or an array of such filenames. All of the provided files are compiled and included into KL code wherever the :samp:`require {ExtensionName};` statement is used to include the extension. |FABRIC_PRODUCT_NAME| detects an extension by finding a :file:`.fpm.json` file somewhere in the extensions path. The extensions path includes all the directories listed in the :envvar:`FABRIC_EXTS_PATH` environment variable, if present, as well as several predefined, platform-dependent directories that contain extensions that are included with the base install of |FABRIC_PRODUCT_NAME|. |FABRIC_PRODUCT_NAME| requires that the files referred to by the :code:`libs` and :code:`code` members in the extension manifest be in the same directory as the extension manifest file itself. For more information about :file:`.fpm.json` files, see :ref:`EAG.fpm`. :file:`HelloWorld.kl` --------------------------------------- The :file:`HelloWorld.kl` source file includes KL source code that is included whenever the :samp:`require {ExtensionName};` statement includes the extension by name from other KL source. For instance, if a KL operator wants to use the functionality provided by the :code:`HelloWorld` sample extension, it should include the statement ``require HelloWorld;`` at the top of the operator code. The contents of the :file:`HelloWorld.kl` file are simple:: function String GetHelloWorldString() = "GetHelloWorldString"; This is a KL statement that refers to a function defined in the native code shared library. The key element here is the ``= "GetHelloWorldString"`` part: this tells KL that the function is called :code:`GetHelloWorldString` in the native code shared library. If this is missing, there will be a linking problem when the library is loaded because by default KL functions have complex internal naming. The ``= "GetHelloWorldString"`` notation is simply telling KL the symbol name in the shared library. Note that a KL source file can also include type definitions (eg. through the `struct` keyword), method definitions, operator definitions, and everything else available in KL. You can use the same ``= "..."`` notation to bind methods to code within the native code shared library. :file:`HelloWorld.cpp` --------------------------------------- The file :file:`HelloWorld.cpp` contains the C++ source code for the native code shared library. Its contents are: .. code-block:: C++ #include "HelloWorld.h" // Automatically generated by kl2edk using namespace Fabric::EDK; IMPLEMENT_FABRIC_EDK_ENTRIES( HelloWorld ) FABRIC_EXT_EXPORT void GetHelloWorldString( KL::String::Result result ) { Fabric::EDK::log("Extension: Enter GetHelloWorldString"); result = "Hello, world!"; Fabric::EDK::log("Extension: Leave GetHelloWorldString"); } The :code:`#include "HelloWorld.h"` line includes the C++ header file with all the definitions for the the |FABRIC_PRODUCT_NAME| EDK (Extension Development Kit). The file :file:`HelloWorld.h` is generated from :file:`HelloWorld.kl` using the :command:`kl2edk` utility; this action is automatically performed by the :file:`SConstruct` build script. The :command:`kl2edk` command generates C++ representations of all custom structure, object and interfaces types used in the KL source files, as well as function prototypes for the required C++ functions. For more information about :command:`kl2edk`, see :ref:`EAG.kl2edk`. The line :code:`using namespace Fabric::EDK;` allows us to reference EDK functions and types without prefixing them with the EDK namespace; this is purely a notational convenience. The :code:`IMPLEMENT_FABRIC_EDK_ENTRIES` line is required to be present exactly once in ever extension. It takes a single parameter which must be the name of the extension. Its purpose is to implement internal functions that are used to load the extension. .. note:: There is a variant of :code:`IMPLEMENT_FABRIC_EDK_ENTRIES` called :code:`IMPLEMENT_FABRIC_EDK_ENTRIES_WITH_SETUP_CALLBACK` that takes a second parameter which is the address of a function to call at the end of the initialization process. It allows you to inject one-time setup code into extensions. The remaining code is the definition of a function that is made available to KL. There are several important points: - Functions made available to KL must be prefixed with the ``FABRIC_EXT_EXPORT`` keyword. The purpose of the keyword is to ensure that the function has an unmangled symbol name and the right calling convention. - Note the use of the :code:`KL::String` type for the first parameter, and note that this is the value that is returned from the function with the KL declaration :code:`function String GetHelloWorldString()`. We will talk more about this in the section on calling conventions. :file:`test.py` --------------------------- The :file:`test.py` file is a Python source file that creates a simple |FABRIC_PRODUCT_NAME| dependency graph and uses it to call the :code:`GetHelloWorldString` function that is provided in the :code:`HelloWorld` extension. The first few lines of the file explicitly add the current directory to the :envvar:`FABRIC_EXTS_PATH` environment variable: .. code-block:: python import os if 'FABRIC_EXTS_PATH' in os.environ: os.environ['FABRIC_EXTS_PATH'] = os.pathsep.join(['.', os.environ['FABRIC_EXTS_PATH']]) else: os.environ['FABRIC_EXTS_PATH'] = '.' Usually a custom extension will be installed at a predefined location that is set in advance in the :envvar:`FABRIC_EXTS_PATH` variable, but it is also possible to set the variable like this. The value of :envvar:`FABRIC_EXTS_PATH` must be modified before the core client is created. The next two lines create a |FABRIC_PRODUCT_NAME| client. We communicate directly with the core of |FABRIC_PRODUCT_NAME| as it is the easiest way to demonstrate the functionality of an extension: .. code-block:: python import FabricEngine.Core as fabric fabricClient = fabric.createClient(); The next several lines create an KL operator. The KL operator calls the :code:`GetHelloWorldString` provided by the extension and stores the result in an :code:`io` parameter that is later bound to a node member: .. code-block:: python opSource = [ "require HelloWorld;", "", "operator entry(io String string) {", " report("KL: Enter entry");", " string = GetHelloWorldString();", " report("KL: GetHelloWorldString returned: " + string);", " report("KL: Leave entry");", "}" ] op = fabricClient.DG.createOperator("op") op.setSourceCode("\n".join(opSource)) op.setEntryPoint("entry") Note the first line :code:`require HelloWorld;`: this statement is required to make available the declaration of the :code:`GetHelloWorldString` function. The require statement essentially includes all of the KL source code that is provided in the extension; in this case, the code in :file:`HelloWorld.kl`. The next few lines create a node with a member of type :code:`String`, binds the operator to the node and then evaluates the node. This causes the :code:`entry` operator to run which in turn calls the :code:`GetHelloWorldString` function in the extension: .. code-block:: python b = fabricClient.DG.createBinding() b.setOperator(op) b.setParameterLayout(['self.string']) node = fabricClient.DG.createNode("node") node.addMember("string", "String") node.bindings.append(b) node.evaluate() The next line verifies that the operator did in fact set the member to the right value: .. code-block:: python print "Python got: " + node.getData("string", 0) Finally, we close the client so that the script will exit: .. code-block:: python fabricClient.close() :file:`SConstruct` --------------------------- The :file:`SConstruct` file is used to build the extension. The only lines of interest are the last ones: .. code-block:: python fabricBuildEnv.Extension( 'HelloWorld', ['HelloWorld.cpp', 'HelloWorld.kl'] ) This line directs :command:`scons` to build an extension named :code:`HelloWorld` using the native code source file :file:`HelloWorld.cpp` as well as the KL source code file :file:`HelloWorld.kl`. The build commands cause the header file :file:`HelloWorld.h` to be generated from the input KL source files using the :command:`kl2edk` tool. The resulting :file:`HelloWorld.h` is then included by :file:`HelloWorld.cpp`.