SyntaxHighlighter

2014年11月30日日曜日

Open TortoiseSVN for Google Chrome の Native Messaging Host 対応

Open TortoiseSVN for Google Chrome の Native Messaging Host 対応

前に書いたように、Google Chrome には、登録済みのローカルファイルを実行する仕組みがあり、Native Messaging と呼ばれています。
今回は、この具体的な作り方について書きます。

Native Messaging を使う Google Chrome 拡張機能

Chrome 拡張で Native Messaging を使うには、大きく分けて以下の対応が必要です。
なお、公式のページに、まとまった情報があります。

  • JSON 形式でやりとりする実行ファイルの準備
  • 実行ファイルのマニフェストファイルの準備
  • Chrome 拡張機能から、上記の実行ファイルの呼び出し

JSON 形式でやりとりする実行ファイル

やりとりするデータ形式

Native Messaging では、JavaScript 側と実行ファイル側が JSON 形式でデータをやりとりします。
JavaScript 側から渡すデータは JSON 形式にシリアライズされ、実行ファイルの標準入力から渡されます。
逆に、実行ファイル側からは、標準出力に JSON 形式でシリアライズしたデータを出力することで、JavaScript 側にデータを渡すことができます。

ただし、JSON データの長さ 4byte が、後にくる文字列の前に配置されます。

例えば、{"a":1234} というオブジェクトを、実行ファイル側から渡すことを考えます。
この JSON データの長さは 10 (0Ah) byte です。この場合、0A 00 00 00という 4byte が出力され、その後に JSON データが続きます。
サイズを示す先頭の 4byte は、Native Byte Order で渡すので、大抵の PC では Little Endian になります。

データ列の例
0A000000'{''"''a''"'':''1''2''3''4''}'

そのため、受け取る側はまず最初の 4byte を読み、そのサイズ分のデータを続けて読むことになります。

逆に実行ファイル側から出力する場合も同様です。

注意点

Windows 特有の注意点として、「標準入力と標準出力をバイナリモードで開く必要がある」ということがあります。

Windows では、ファイルを処理する際に「テキストモード」と「バイナリモード」があります。
説明は省きますが、標準入力や標準出力は「テキストモード」になっているので、その状態で \n を出力すると \r\n に自動で変換されることになります。
先の例では、0Ah\n なので、以下のようなバイト列になります。

データ列が変換される例
0D0A000000'{''"''a''"'':''1''2''3''4''}'

結果として、データを受け取る Chrome 側は 00000A0Dh つまり 2573byte の JSON データが送られてくると判断し、正しく動かなくなります。

そのため、C 言語であれば、以下のような Windows 依存のコードを実行しておくことで、標準入力や標準出力をバイナリモードにしておく必要があります。

#include <io.h>
#include <fcntl.h>

void set_binary_mode()
{
    _setmode(_fileno(stdin), _O_BINARY);
    _setmode(_fileno(stdout), _O_BINARY);
}

これらに注意して、好きな実行ファイルを作ります。

Open TortoiseSVN for Google Chrome の場合、 open_tortoise_svn_host.exe がその実行ファイルですが、この中では JSON を扱うために json11 を使わせてもらいました。
このライブラリは C++11 向けに書かれており、少し変更するだけで Visual Studio 2013 の C++ コンパイラでもコンパイルできます。

実行ファイルのマニフェストファイルの準備

作成した実行ファイルは、そのままでは Chrome から呼び出すことはできません。
呼び出せるようにするために、マニフェストファイルを作り、そのマニフェストファイルを Chrome に知らせる必要があります。

マニフェストファイル

公式のページにあるように、マニフェストファイルは以下のように定義します。

{
  "name": "com.my_company.my_application",
  "description": "My Application",
  "path": "chrome_native_messaging_host.exe",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"
  ]
}

上記のようなマニフェストファイルを任意のファイル名で作成します。
name は、この Native Messaging 機能の名前を任意で決めて指定します。
path には実行ファイルの名前を指定します。実行ファイルは絶対パスで指定しますが、Windows ではマニフェストファイルがあるディレクトリからの相対パスでも構いません。
type は、現状 stdio しか指定できません。これは上で説明したような JSON 形式のデータを標準入力と標準出力でやりとりすることを意味しています。
allowed_origins には、この Native Messaging Host にアクセスしてよい拡張機能の名前を指定します。この名称は、拡張機能一覧のページ (chrome://extensions/) で ID をチェックすることで分かります。

というか、公式のページの説明を見ましょう。

マニフェストファイルの登録

続いて、作成したマニフェストファイルを Chrome に知らせる必要があります。

これも公式のページに書いてあるように、Windows の場合はレジストリで指定します。
HKEY_CURRENT_USER\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.my_company.my_application にマニフェストファイルの絶対パスを書き込めばよいです。
HKEY_LOCAL_MACHINE 以下でもよいですが、HKEY_CURRENT_USER の下の方が管理者権限が不要なので楽でしょう。

Chrome 拡張機能から、上記の実行ファイルの呼び出し

Native Messaging には、実行ファイルが常駐するタイプと、毎回起動するタイプがあります。
Open TortoiseSVN for Google Chrome は後者のタイプです。

この場合、下記の公式ページの例のように sendNativeMessage を用いて、送信した JSON データと、受け取った JSON データを処理するためのコールバック関数を引数として渡すことで、好きな処理をすることができます。

chrome.runtime.sendNativeMessage('com.my_company.my_application',
  { text: "Hello" },
  function(response) {
    console.log("Received " + response);
  });

面倒になってきたので後半は省略しましたが、作成するのは簡単なのでローカルの実行ファイルと Chrome を連携させたい場合は利用するとよい機能だと思います。

0 件のコメント:

コメントを投稿