JNIによるShellExecuteの補足

前回作成したShellExecuteは、「全角文字列が含まれたファイルパスを使うことができない」という制限がありました。
これはファイルパスをUTF-8で渡していたためです。正しくはShift_JISの文字コードをで渡さなければならないため、その修正を施したプログラムを説明します。

プログラムソースはこちらになります。
ShellExecuteサンプル2

ShellExecuteにShift_JISの文字列を渡すために、2種類の方法が考えられます。

1つは、「Java側でShift_JISのバイト列を作成して、それを渡す」という方法です。
JavaでShift_JISのバイト配列を作成

もう1つは、「Java側から渡したStringオブジェクトを、ネイティブ側でShift_JISのchar配列に変換する」という方法です。
ネイティブで、Shift_JISのchar配列を作成

Javaの標準APIには、「指定した文字コードで、byte列を取得するAPI」が用意されているので、前者の方が楽です。ただし、ネイティブメソッドのインターフェースとしては、後者の方が親切です。
呼び出し側からすれば、「Shift_JISのバイト配列に変換して呼び出してください」という仕様は、納得しがたいかも知れません。しかしながら、Java側で変換したほうが圧倒的に簡単に安全に記述できるため、前者を採用してみました。
サンプルコードには、ちょっと手抜きですが、後者の実装も書いてあります。

ネイティブ側の処理

ネイティブ側のロジックは前回とほとんど同じです。

JNIEXPORT jint JNICALL Java_JNISample_shellExecute2
  (JNIEnv *env, jclass obj, jbyteArray file)
{
	jboolean isCopy;
	//SJISバイト文字列を取得
	char* bytes = env->GetByteArrayElements(file, &&isCopy);
	long ret = (long)ShellExecute(NULL, "open", bytes,
		NULL, NULL, SW_SHOWNORMAL);
	if (isCopy == JNI_TRUE){
		env->ReleaseByteArrayElements(file, bytes, 0);
	}
	return (jint)ret;
}

Stringオブジェクトの代わりに、バイト配列オブジェクトが渡されます。このオブジェクトからバイト配列を取得する方法は、UTF文字列を取得するのとほとんど同じです。GetByteArrayElementsと、ReleaseByteArrayElementsを使用します。
これだけです。ネイティブ側にとって都合のよい形で渡す仕様にしたため、ネイティブ側は簡潔になっています。
ReleaseByteArrayElementsの、3つ目の引数は配列バッファの解放方法を指定します。大抵は0になると思います。今回は配列に対して変更を行っていませんが、配列に対して変更を行いReleaseすると、Java側から配列にアクセスしても変更されます。

Java側の処理

Java側の処理は、Shift_JISのバイト配列を作成して、ネイティブメソッドを呼び出すようにします。
注意点として、JavaのAPIでバイト配列を取得しても、NULLターミネイトされていないため、サイズが1byte大きい配列を別途用意し、そこにコピーする必要があります。

byte[] tmp = "c:\\新規フォルダ".getBytes("Shift_JIS");	//SJIS byte配列の作成
byte[] sjis_bytes = new byte[tmp.length + 1];
System.arraycopy(tmp, 0, sjis_bytes, 0, tmp.length);	//配列のコピー
shellExecute2(sjis_bytes);

System.arraycopyメソッドで、容量の大きいバイト配列に詰め替えています。
Javaのbyte配列は生成時に0で初期化されます(これは仕様)。したがってあらためてNULLターミネイトする必要はありません。図にするとこのようなイメージになります。

バイト配列の操作

NULLターミネイトした配列を作る処理も、ネイティブ側で実装してもOKです。
ただ、文字列の長さが不定であることを考慮すると、動的に配列を確保し、開放する必要があります
その点においてもJavaの方が、GC任せで楽なのでJava側で実装しています。

まとめ

JNI:Java Native Interfaceプログラミング―C/C++コードを用いたJavaアプリケーション開発
JNIについて詳しく書かれた貴重な一冊。
JNIプログラミング時に必携の本です。

ネイティブ側にとって都合が良いように、Shift_JISのバイト配列を用意し、呼び出すことで簡単に全角文字を含むファイル名でも実行できるようになりました。
ネイティブ側でShift_JISの文字配列を得る場合には(後者のやり方)、、WideCharToMultiByteというWindows APIを使用します。これはUnicode→Shift_JIS変換ができます。
これを利用して、Java String → Unicode文字列 → Shift_JIS文字列と変換すると、ちょっと処理は長くなりますが、同じように動作しました。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)