SyntaxHighlighter

2012年1月1日日曜日

NPAPI プラグインの作り方 for Windows その5

NPAPI プラグインの作り方 for Windows その5

今回は、サンプルの簡単な説明をします。

なお、今回でひとまず NPAPI プラグインの作り方は終了です。要望があればコメント欄に書いてください。気が向けば対応します。

これまでと同じく今回のサンプルは、GitHub のページからダウンロードできます。
この中の modified_files/npruntime/plugin.cpp が主に説明する対象です。

HTML ファイルからプラグインの呼び出し

テスト用の HTML ファイル test.html からプラグインを参照しているのは以下の箇所です。

1
<embed id="embed1" type="application/mozilla-npruntime-scriptable-plugin" width=600 height=40>

embed タグはプラグインをロードするために使われるタグで、type 属性にプラグインの種類を指定することで、目的のプラグインをロードします。

type 属性には、プラグインの MIME type を指定します。
サンプルの場合は、application/mozilla-npruntime-scriptable-plugin になっています。

このプラグインの MIME type は、Windows の場合はリソースファイル nprt.rc 内で定義されています。

オリジナルのプラグインを作成する場合は、以下の MIMEType の箇所を好きなものに変更してください。
もちろん、変更した場合は test.htmlembed タグの方も同じ文字列に変更してください。

なお、MIME type の仕様としては、独自の種類を定義する際は application/x- で始まる文字列にすることが推奨されているようです。

1
2
3
4
5
6
7
VALUE "LegalTrademarks", "\0"
VALUE "MIMEType", "application/mozilla-npruntime-scriptable-plugin\0"
VALUE "OriginalFilename", "nprt.dll\0"
VALUE "PrivateBuild", "\0"
VALUE "ProductName", "npruntime scriptable example plugin\0"
VALUE "ProductVersion", "1, 0, 0, 1\0"
VALUE "SpecialBuild", "\0"

複数の MIME type に対応する場合は、| で区切ればよいようです。

なお、HTML ファイルからの呼び出しには直接は関係ありませんが、ProductName でプラグインの名前を、FileDescription でプラグインの説明を記述するなどが決められています。

また余談ですが、Windows 向けの NPAPI ではこのようにリソースファイルで MIME type を指定しますが、Unix などでは、np_entry.cpp 内の NP_GetMIMEDescription 関数で返す文字列で MIME type を指定します。
逆に言えば、Windows 環境では NP_GetMIMEDescription で返される文字列の値は、ブラウザに参照されることはありません。

プラグイン内で使われる変数

NPVariant

NPAPI プラグインの中では、JavaScript 側とやりとりをする際に、NPVariant という型がよく出てきます。

この型は、npruntime.h 内で以下のように定義されています。

1
2
3
4
5
6
7
8
9
10
11
// npruntime.h より
typedef struct _NPVariant {
    NPVariantType type;
    union {
        bool boolValue;
        int32_t intValue;
        double doubleValue;
        NPString stringValue;
        NPObject *objectValue;
    } value;
} NPVariant;

type フィールドには、現在 value に保持している値の種類が格納されており、value にその値が保持されます。

Windows の COM を扱う際に登場する VARIANT 型と同様なものだと考えればよいと思います。

NPVariant 型の変数に値を代入したり値を読み出したりする際は、BOOLEAN_TO_NPVARIANTNPVARIANT_TO_BOOLEAN マクロを使うとよいと思います。

変数の動的確保と解放

JavaScript 側とやりとりをする場合、特に文字列などをメソッドなどの戻り値として返す場合、その文字列の領域を動的にメモリ上に確保する必要があります。

通常の C 言語であれば、mallocfree などを呼び出しますが、NPAPI プラグインでは NPN_MemAllocNPN_MemFree を用いることになります。

ちなみに、前回の記事でサンプルの修正をしたのは以下の部分でした。

1
2
3
4
5
-   STRINGZ_TO_NPVARIANT(_strdup("foo return val"), *result);
+   const NPUTF8 *ret_str = "foo return val";
+   NPUTF8 *s = (NPUTF8 *)NPN_MemAlloc(strlen(ret_str) + 1);
+   strcpy(s, ret_str);
+   STRINGZ_TO_NPVARIANT(s, *result);

これは、本来であれば NPN_MemAlloc でメモリを確保すべきなのですが、_strdup で文字列用のメモリを確保してしまっていることが問題でした。

NPN_MemAlloc, NPN_MemFree などは、プラグインロード時にブラウザから関数ポインタとして渡されます。
そのため、プラグイン作成者側には、その内部でどのようにメモリが管理されているかを仮定することはできません。

そのため、NPN_MemAlloc 内部で malloc と同様のものを呼んでいるのであれば、サンプルを修正しなくても動作するかもしれませんが、そうでない場合は、ブラウザ側が戻り値の文字列を NPN_MemFree しようとしたタイミングでクラッシュすることになります。

プラグインのメソッドの呼び出し

ここでは、プラグインのメソッドの呼び出し方法と、新しいメソッドの追加方法について書きます。

プラグインのメソッドの呼び方

プラグインのメソッドを呼ぶには、以下のように embed 要素を取得し、その要素に対してメソッドを呼び出します。

1
2
3
4
// <embed id="embed1" type="application/mozilla-npruntime-scriptable-plugin" width=600 height=40> の要素を取得
var elem = document.getElementById("embed1");
// 例えば foo 関数を呼ぶ。
elem.foo();

プラグインのメソッドの定義

呼び出されるメソッドは、以下のように定義されます。

ここでは、実際にサンプルで定義されている foo メソッドについて説明します。

HasMethod の対応

まず、HasMethod 関数の対応をします。

この関数は、特定の名前のメソッドを持っているかどうかを判定するための関数です。

サンプル内では、下記のようになっている箇所です。

1
2
3
4
5
bool
ScriptablePluginObject::HasMethod(NPIdentifier name)
{
  return (name == sFoo_id || name == sDiskSize_id);
}

ここで、sFoo_id は、CPlugin::CPlugin で初期化されていますが、"foo" という文字列を表すものだと思ってください。

上の例では、sFoo_id が表す foo という名前か、sDiskSize_id が表す diskSize という名前の文字列なら true を返すことになります。

メソッドの実体の定義

メソッドの実体は、以下のような関数内で定義されています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
bool
ScriptablePluginObject::Invoke(NPIdentifier name, const NPVariant *args,
                               uint32_t argCount, NPVariant *result)
{
  if (name == sFoo_id) {
//    printf ("foo called!\n");
 
    NPVariant docv;
    NPN_GetProperty(mNpp, sWindowObj, sDocument_id, &docv);
 
    NPObject *doc = NPVARIANT_TO_OBJECT(docv);
    // ...
    return true;
  }else if (name == sDiskSize_id){
    // 簡単なエラーチェック
    if (argCount != 1 || !NPVARIANT_IS_STRING(args[0])){
      BOOLEAN_TO_NPVARIANT(false, *result);
      return true;
    }
 
    NPString drive = NPVARIANT_TO_STRING(args[0]);
 
    ULARGE_INTEGER free_size, total_size;
    // 日本語などもサポートするなら MultiByteToWideChar などを使う。
    std::string d(drive.UTF8Characters, drive.UTF8Length);
    if (!GetDiskFreeSpaceEx(d.c_str(), &free_size, &total_size, NULL)){
      BOOLEAN_TO_NPVARIANT(false, *result);
      return true;
    }
 
    INT32_TO_NPVARIANT((int32_t)(total_size.QuadPart/(1024*1024)), *result);
    return true;
  }
 
  return false;
}

この Invoke 関数は、プラグインに対してメソッドが呼ばれたときに、メソッド名や引数の配列などとともに呼び出されます。

そこで、この関数の中で、メソッド名に応じて処理を分けてやることで、各メソッドの処理を実装することができます。

上の例では、もともと存在した foo メソッドに加え、新規に diskSize というメソッドを追加した場合です。

このように、いくらでも自由にメソッドを追加することができます。

もちろん、数が多くなればメソッド名とコールバックをハッシュに格納するなどして、効率化すべきでしょう。

プロパティの場合も、HasProperty, GetProperty, SetProperty 関数の内部で同様に実現されています。

ひとまずこれで簡単な NPAPI プラグインの作成方法の説明を終わります。
質問などがあれば、コメント欄までお願いします。

0 件のコメント:

コメントを投稿