メモの日々


2012年08月20日(月) [長年日記]

[windows][c++] Windowsでアクティブウィンドウのタイトルを取得

MinGW-w64を使って、初めてWindowsプログラミングをしてみる。手始めにアクティブウィンドウのタイトルを取得してみた。JNIから使えるライブラリとして実装する。色々難しい。.net frameworkを使えばきっともっと簡単なんだと思う。

APIのリファレンス

APIの一覧を知りたいのだけれど、それが分かるページがよく分からない。一応、

から辿れそうなんだけど、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;
}