2012年08月20日(月) [長年日記]
■ [windows][c++] Windowsでアクティブウィンドウのタイトルを取得
MinGW-w64を使って、初めてWindowsプログラミングをしてみる。手始めにアクティブウィンドウのタイトルを取得してみた。JNIから使えるライブラリとして実装する。色々難しい。.net frameworkを使えばきっともっと簡単なんだと思う。
APIのリファレンス
APIの一覧を知りたいのだけれど、それが分かるページがよく分からない。一応、
- Windows Development Reference (→ こっちの方が見やすい。項目が少し違っているなあ。)
から辿れそうなんだけど、APIリファレンスじゃないページも混じっていて頑張らないと見つけられない。日本語訳が
みたい。ここの「Windows 2000」の下をよく探すとカテゴリ毎にリファレンスが見つかる。
アクティブウィンドウを得る
GetForegroundWindowという関数でアクティブウィンドウのハンドルを得られる。これは簡単。
ウィンドウタイトルを得る
GetWindowTextという関数でウィンドウのタイトルを得られる。ウィンドウタイトルを保存する領域が必要になるので、事前にGetWindowTextLengthで文字数を調べる必要がある。
で、GetWindowText()の引数の型がLPTSTRとなっていて、これが難しかった。WindowsのAPIの実装というのはワイド文字版とマルチバイト文字版の2種類があって、その違いを意識しないために文字にはTCHARという型を使っているようである。LPTSTRというのは TCHAR* のことみたい。
で、TCHARがワイド文字(wchar_t)なのかマルチバイト文字(char)なのかは、UNICODEというマクロが定義されているかどうかで決まっている模様。
書いてみたら難しくなかった。
LPTSTRをJavaの文字列へ変換
JNIを使うので、GetWindowText()で得た文字列をJavaの文字列へ変換しないといけない。このときに、TCHARがワイド文字なのかマルチバイト文字なのかで処理を分ける必要がある。
Javaの文字列生成にはNewStringを使う。これに渡すパラメータは「Unicode文字列を参照するポインタ」とある(型はconst jchar*)。Unicode文字列ってのはUTF16のことなのかなあ。
ちゃんと理解していないのだけれど、Windowsのwchar_tはUTF16のようで、TCHARがワイド文字を表す場合はLPTSTRをjchar*へキャストしたものをNewString()へ渡すことでちゃんと文字列を得られた。
TCHARがマルチバイト文字を表す場合はMultiByteToWideCharというWindowsのAPIでワイド文字に変換すれば、上と同じくjchar*へキャストすることでJavaの文字列を得られた。
ワイド文字とマルチバイト文字の選択方法
MinGW-w64のFAQにUnicode applicationsというページがあって、
To compile Unicode applications, define _UNICODE and UNICODE in your files when compiling and add -municode when linking.
とあった。なので、ワイド文字モードにするには、コンパイル時に「-D_UNICODE -DUNICODE」を、リンク時に「-municode」をg++のオプションとして与えればよさそう。これらをしないとマルチバイト文字モードになるようだ。
ソースコード
アクティブウィンドウのタイトルを取得するJNI関数を書いたのでメモしておく。やっつけなので醜いです。あー、このままだとC++の例外発生時にJVMが終了してしまうので、せめて例外をcatchしておく処理が必要だ。
#include <windows.h>
#include <winnls.h>
#include "sample_Windows.h"
JNIEXPORT jstring JNICALL Java_sample_Windows_activeWindow(
JNIEnv *env,
jclass) {
HWND handle = GetForegroundWindow();
if (!handle) return env->NewStringUTF("");
int length = GetWindowTextLength(handle);
if (!length) return env->NewStringUTF("");
LPTSTR buffer = new TCHAR[length + 1];
length = GetWindowText(handle, buffer, length + 1);
jstring result;
#ifdef UNICODE
result = env->NewString((jchar*)buffer, length);
#else
int wide_length = MultiByteToWideChar(CP_ACP, 0, buffer, -1, 0, 0);
if (!wide_length) {
delete[] buffer;
return env->NewStringUTF("");
}
LPWSTR wide_buffer = new WCHAR[wide_length];
if (!MultiByteToWideChar(CP_ACP, 0, buffer, -1, wide_buffer, wide_length)) {
delete[] wide_buffer;
delete[] buffer;
return env->NewStringUTF("");
}
result = env->NewString((jchar*)wide_buffer, wide_length);
delete[] wide_buffer;
#endif
delete[] buffer;
return result;
}