サンプルエクステンションの解説

この節では前節でビルドしたサンプルエクステンションに含まれるそれぞれのファイルを一通り見ていきましょう。一連のファイルがお互いどのように機能するかを通し、 Fabric Engine におけるエクステンションがどのように機能するかを理解していきます。

HelloWorld.fpm.json

エクステンションに唯一必須であるファイルは、エクステンションマニフェストファイルです;サンプルでは HelloWorld.fpm.json が該当、具体的内容は以下

{
  "libs": "HelloWorld",
  "code": "HelloWorld.kl"
}

エクステンションマニフェストは、 .fpm.json で終わらせます。 .fpm.json の前にくっつくファイル名はすなわち <エクステンション名> を表します。このエクステンション名が、KLにおける require ExtensionName; 文によるエクステンションインクルードのエクステンション、あるいは Fabric Engine coreクライアント作成時の exts パラメータになります。

マニフェストファイルの内容は JSON形式で単一JSONオブジェクトとして記述します (see http://json.org/) サンプルにおけるJSONオブジェクトは libs and code; これら2つはオプショナルです。

libs の値は、文字列, 文字列の配列 どちらも許容します。どの文字列も、エクステンションをなす共有ライブラリのベース名です。実際の共有ライブラリのファイル名はプラットフォームに依存します。サンプルの SConstruct ファイルが、共有ライブラリのビルドをビルドを実行するプラットフォームに沿う正しいファイル名で扱います。つまりエクステンションがサポートする全プラットフォームに対し、同じ SConstruct ファイルによってビルドを行うことができます。(可能というだけでなく勿論そうすべきです)

code の値は KL ソースファイルのファイル名(文字列単体でも配列でも)です( .kl なエクステンションも含みます)。これらはコンパイルされ KLコードに含まれることになり require ExtensionName; 文によりエクステンションインクルードに使用します。

Fabric Engine ではエクステンションの検出にエクステンションパス中に存在する .fpm.json ファイルを用います。エクステンションパスとは、環境変数 FABRIC_EXTS_PATH に列挙された全てのディレクトリを含みます。このディレクトリには Fabric Engine をインストールした際のプラットフォームに依存した基本エクステンションが存在する、事前定義されたディレクトリを含みます。 Fabric Engine では、 エクステンションマニフェスト中の libscode メンバは同じエクステンションマニフェストと同じディレクトリに存在しなければなりません。

.fpm.json についてのより詳細な情報は エクステンション・マニフェストファイル を参照してください

HelloWorld.kl

HelloWorld.kl ソースファイルは、 require ExtensionName; により、他の KLソースからこのエクステンションを名前で読み込んでくる際にインクルードすることになる、 KL ソースコードです。換言すると HelloWorld エクステンションの提供する機能を、とある KLオペレータで使用したいのであれば、オペレータのコードの一番上に require HelloWorld; とするとインクルードできます。 HelloWorld.kl ファイルの内容は単純です:

function String GetHelloWorldString() = "GetHelloWorldString";

これは KL文であり、ネイティブコードの共有ライブラリで定義された関数を参照しています。一番重要な点は、 = "GetHelloWorldString" です:これは KLに、「この関数はネイティブコード共有ライブラリの中で GetHelloWorldString として呼ばれるものだ」ということを示しています。これを欠いてしまうと、ライブラリをロードした途端、リンク問題をひきおこします。なぜならディフォルトでは KL関数は複雑な内部名を持つためです。 = "GetHelloWorldString" という記法は KLへと共有ライブラリでのシンボル名を単純に伝達しているわけです。

注意点:KLソースファイルは 型定義( struct キーワードを使用)や、メソッド定義、オペレータ定義、その他 KL で可能なことは全てを含めることができます。先ほど同じ = "..." 記法によってメソッドとネイティブコード共有ライブラリでのコードをバインドすることができます。

HelloWorld.cpp

HelloWorld.cpp ソースファイルには ネイティブコード共有ライブラリの 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");
}

#include "HelloWorld.h" の行では C++ヘッダファイル ― Fabric Engine EDK (Extension Development Kit) のための全定義の詰まったヘッダファイルのインクルードを行います。

このヘッダファイル HelloWorld.h は、HelloWorld.kl を元に kl2edk ユーティリティにより生成されます; この振舞は SConstruct ビルドスクリプトからも自動で行われます。 kl2edk コマンドは、KLソールファイルで使用されている全 <カスタム構造体, オブジェクト, インタフェース型>を C++での表現へと生成します。関数プロトタイプについてもどうように C++関数へと変換されます。 kl2edk についての詳細は kl2edk ユーティリティ を参照してください。

using namespace Fabric::EDK; の行では、EDK関数と型を EDKネームスペース無しに参照できるようにします。これは単に記法を楽にするためです。

IMPLEMENT_FABRIC_EDK_ENTRIES の行は、エクステンションの中で1度だけ記述する必要があるものです。h基数を一つとり、エクステンション名をわたします。エクステンションのロードに必要となる内部関数の実装に使用します。

注釈

IMPLEMENT_FABRIC_EDK_ENTRIES 呼び出しにはいくつか種類があり、 IMPLEMENT_FABRIC_EDK_ENTRIES_WITH_SETUP_CALLBACK とすると引数を二つとるようになります。初期化処理の最後に呼ばれる関数のアドレスをわたします。エクステンション中、1度限りのセットアップ処理を導入できます。

コードの残りでは、KLで利用可能となる関数定義です。重要な点を幾つか:

  • KLでも使用可能な関数にするには、 FABRIC_EXT_EXPORT キーワードを頭につけます。キーワードを使用する目的は、この関数がアンマネージドなシンボル名を含み、正しく呼び出すための規則を持つということを明示するためです。
  • KL::String 型を第一パラメータとして使用する際には注意が必要です。KLの宣言 function String GetHelloWorldString() 関数による戻り値がこの KL::String 型の値を解すことにも注意してください。呼び出し規則についてのセクションでより詳しく記述いたします。

test.py

test.py ファイルは Pythonソースファルです。単純な Fabric Engine dependency graph を作成し、その dependency graphから HelloWorld エクステンションの GetHelloWorldString 関数を呼び出します。

ファイル最初のほうで明示的に、カレントディレクトリを環境変数 FABRIC_EXTS_PATH へ設定します:

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'] = '.'

通常カスタムエクステンションは所定の決められた場所へとインストールします。この場所は環境変数 FABRIC_EXTS_PATH により事前に設定した場所です。ただし、上記のように、Coreクライアント作成前であれば、 FABRIC_EXTS_PATH を設定し使用することが可能です。

次の2行で、 Fabric Engine クライアントを作成します。 Fabric Engine の Coreと直接やりとりをします。エクステンションの機能デモンストレーションするのに最適ですね。

import FabricEngine.Core as fabric
fabricClient = fabric.createClient();

次の数行では、 KLオペレータを作成します。KLオペレータはエクステンションの GetHelloWorldString を呼び、結果を io パラメータに保存、後の工程でノードメンバにバインドします:

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")

code:require HelloWorld;: この行では注意が必要です。この文は GetHelloWorldString 関数の宣言をするために必須です。require文 はエクステンションによって提供されるKLソースコードをまとめてインクルードします。このサンプルの場合、 HelloWorld.kl 中のコードです。

次の数行では、 String 型のメンバを持つノードを作成、そのノードにオペレータをバインド、ノードを評価、しています。つまり entry オペレータを実行し、エクステンションの GetHelloWorldString 関数を呼び出させます。

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()

次の行で、オペレータがメンバを正しい値に設定したかどうか検証します。

print "Python got: " + node.getData("string", 0)

最後にクライアントを閉じ、スクリプトを終了させます。

fabricClient.close()

SConstruct

SConstruct ファイルはエクステンションのビルドに使用します。最重要な箇所は、最後:

fabricBuildEnv.Extension(
  'HelloWorld',
  ['HelloWorld.cpp', 'HelloWorld.kl']
  )

この行では、 scons に「エクステンション名 HelloWorld をネイティブコードソースファイル HelloWorld.cppHelloWorld.kl KLソースコードを使用しビルドせよ」と指示しています。このビルドコマンドにより、入力となるKLソースファイルから kl2edk を使用しヘッダファイル HelloWorld.h が生成されます。結果、 HelloWorld.cpp から HelloWorld.h がインクルードされます。