Paper Plane xUI(PPx)のText Moduleに自作のコマンドを追加する

この記事では、PPxの[PPx Text Module](http://toro.d.dooo.jp/slppx.html#ppxtext)に自作のコマンドを追加する方法を紹介します。
また、公式HELPにも[PPx Moduleについて](http://toro.d.dooo.jp/ppxhelp.html#moduledev)が書かれているので合わせてご覧ください。

# 自作のコマンドを追加する手順

## 1.実装

PPx Text ModuleのPPXTEXT.Cを開きます。
ここでは単純に「Hello」という文字列を返すだけのコマンド「hello」を作成してみます。

```c:PPXTEXT.C
BOOL PPXAPI ModuleEntry(PPXAPPINFOW *ppxa,DWORD cmdID,PPXMODULEPARAM pxs)
{
	if ( cmdID == PPXMEVENT_FUNCTION ){
		省略
		if ( ((pxs.command->commandhash == 0) || (pxs.command->commandhash == 0xc914d34f)) && !wcscmp(pxs.command->commandname,L"HELLO")){
			wcscpy(pxs.command->resultstring, L"Hello");
			return TRUE;
		}
		省略
	}
}
```

*2016/12/14:commandhashについてを修正*

commandhashの計算はPPxの「PPD_CMD.C:GetModuleNameHash」関数が行っています、自前で計算してもいいのですが、「*commandhash」というコマンドが用意されているのでこちらを利用します。

**※自身の設定が消えないように、適用前にバックアップを取るか、追加取り込みをしてください**

```:PPx.CFG
KC_main	= {	; PPcメイン窓
^H	,*commandhash %"commandhash"%{commandname%}
}
```

commandhashを計算するショートカットキーを登録しておき、「hello」を計算します。

![commandhash.png](https://qiita-image-store.s3.amazonaws.com/0/51728/1a34e736-6301-7d3b-dd20-3f6a29338f69.png)

この時の「0xc914d34f」が、helloコマンド(大文字のHELLO)のcommandhashになります。
また、この他にもppxcmds.plのNameHashでも調べる事ができます。


## 2.ビルド

保存後、PPx Text Moduleをビルドします。
その際、makefileをVC++でビルド設定へ変更しています。
*※僕の場合は、VC++2015 x86 Native Build Tools Command Promptを利用しています。*

*2016/12/13追記*
また、別途defファイルも用意します。

```:PPXTEXT.DEF
LIBRARY PPXTEXT
EXPORTS
  ModuleEntry @1
```

```:MAKEFILE
UseDebug = 0	# 1 なら debug 指定
UseBCC = 0	# 1 なら BCC32 , 0 なら CL
UseIlink = 0	# 1 なら ilink32(BCC 5.5以降) を使用
VCTOOLKIT = 1	# VC TOOL KIT を使うなら 1 に、VS2008なら 0 に
```

```:cmd
E:\path\to\PPXTXSRC> nmake  
```

## 3.実行

生成されたPPXTEXT.DLLをPPxディレクトリへコピーし、以下の設定を書いてみます。

**※自身の設定が消えないように、適用前にバックアップを取るか、追加取り込みをしてください**

```:PPx.CFG
KC_main	= {	; PPcメイン窓
^T	,%OB echo %*hello()
}
```

上記のコマンドを実行し、「Hello」と表示されれば成功です。

![ppx_module.png](https://qiita-image-store.s3.amazonaws.com/0/51728/ee529723-25aa-9b40-9ab3-cc977694063b.png)


# PPx Text Moduleのソース解説

以下は内部の話です、コマンド追加手順とは無関係ですので読み飛ばしても構いません。


まずは、PPx Text ModuleのPPXTEXT.Cを開きます

```c:PPXTEXT.C
BOOL PPXAPI ModuleEntry(PPXAPPINFOW *ppxa,DWORD cmdID,PPXMODULEPARAM pxs)
{
	if ( cmdID == PPXMEVENT_FUNCTION ){
		// ====================================================================
		if (省略 "NOWDATETIME")){
		    省略
		}
		if ( ((pxs.command->commandhash == 0) || (pxs.command->commandhash == 0xf75d5755)) && !wcscmp(pxs.command->commandname,L"CLIPPEDTEXT")){
			int len = 0;

			if ( OpenClipboard( ppxa->hWnd ) != FALSE ){
				HGLOBAL hG;
				WCHAR *text;

				hG = GetClipboardData(CF_UNICODETEXT);
				text = GlobalLock(hG);
				len = wcslen(text);
				if ( len > (CMDLINESIZE - 16) ) len = CMDLINESIZE - 16;
				memcpy( pxs.command->resultstring,text,len * sizeof(WCHAR) );
				GlobalUnlock(text);
				CloseClipboard();
			}
			pxs.command->resultstring[len] = '\0';
			return TRUE;
		}

	...
}
```

既存コマンドの「CLIPPEDTEXT」を例に解説します。
ここで、大事なのは「!wcscmp(pxs.command->commandname,L"CLIPPEDTEXT")」「commandhash == 0xf75d5755」「pxs.command->resultstring」の三つです、それぞれ順を追って説明します。

## !wcscmp(pxs.command->commandname,L"CLIPPEDTEXT")

これは見た目で分かる通り、「*clippedtext」コマンドの大文字との一致チェックです、処理までは追えてませんが、pxs.command->commandnameには呼び出したコマンドを大文字化した文字列が入っています。


## commandhash == 0xf75d5755

*2016/12/14:commandhashについてを修正*

これは"CLIPPEDTEXT"から生成されたハッシュ値です、予めpxs.xxx->commandhashを調べることでコマンド名の全体の検索速度が上がります。


## pxs.command->resultstring

こちらは戻り値です、PPx Text Module側では単にメモリに書き込んでいますが、書き込める量は幾つでしょう、もう少し探ってみます。

PPx側のPPD_MLD.Cを検索すると

```c:PPD_MDL.C
void FunctionModule(EXECSTRUCT *Z)
{
	省略
#ifndef UNICODE
		WCHAR 省略,destW[CMDLINESIZE];
		#define DESTBUF destW
#else
		#define DESTBUF Z->dst
#endif
	省略
	function.resultstring = DESTBUF;
}
```

という処理を見かけます、ANSI時はWCHARの[CMDLINESIZE]配列ですね。
ここでUNICODE時のDESTBUFをさらに探ります、Z->dstはZ->DstBufを指したポインタであり、

```c:PPD_CMD.C
// Z の初期化 -----------------------------------------------------------------
void ZInit(EXECSTRUCT *Z)
{
	省略
	Z->dst = Z->edit.EdBottom = Z->DstBuf;
	省略
}
```

Z->DstBufは[CMDLINESIZE * 2]の配列

```c:PPD_DEF.H
typedef struct {
	省略
	TCHAR DstBuf[CMDLINESIZE * 2];	// 変換後の内容を保存する一時領域
} EXECSTRUCT;
```

CMDLINESIZEは1024なので

```c:PPCOMMON.H
#define CMDLINESIZE 0x400		// コマンドラインの大きさ
```

resultstringはANSI、UNICODE共に2048バイト分書き込める事になります、それ以上書き込もうとするとセグフォ(Segmentation fault)が発生します。

ただし、*2にしてるのは安全マージンを持たせているため、実際に書き込む際はCMDLINESIZE分だけ書き込むようにします。