徒然開発記

例外中の例外 [2004/11/18]

C++では、例外を伝播中に別の例外を投げることは出来ません。

class A
{
  public:
    ~A() { throw 1; }
};

int main(void)
{
    {
        A a;
        throw "x";
    }

    return 0;
}

例外「"x"」を投げると、aがスコープアウトしてaのデストラクタが実行され、そこで例外「1」が投げられます。
例外「"x"」は何処にも捕捉されていない為に有効で、そこに例外「1」が追加されるのだから、どっちが正しい例外なんだ〜、となるわけです、たぶん。
そして、よく解らないから終わらせてしまえ、と強制終了になります(set_terminate()で変更できるんですけど)

で、Javaの場合はどうなるんだろうと思ったわけですな。

public class AAA
{
    public static void main(String[] args)
    {
        try {
            throw new ArithmeticException("a");
        } finally {
            throw new NullPointerException("b");
        }
    }
}

コンパイルエラーになりました。流石にコレはまずい様です。

そこで、コンパイラを誤魔化すために、別のクラスで例外を投げるようにしてみました。

public class AAA
{
    public static void main(String[] args)
    {
        try {
            throw new ArithmeticException("a");
        } finally {
            BBB.x();
        }
    }
}

public class BBB
{
    static public void x()
    {
        throw new NullPointerException("b");
    }
}

今度はコンパイルも正常終了したので、早速実行してみると

Exception in thread "main" java.lang.NullPointerException: b
        at BBB.x(BBB.java:5)
        at AAA.main(AAA.java:11)

ArithmeticExceptionの方はどこかに行ってしまいました。
そこで、こんな事をしてみました。

public class AAA
{
    public static void main(String[] args)
    {
        try {
            try {
                throw new ArithmeticException("a");
            } finally {
                BBB.x();
            }
        } catch (ArithmeticException e) {
            System.out.println("ArithmeticException の例外");
        }
    }
}

public class BBB
{
    static public void x()
    {
        throw new NullPointerException("b");
    }
}

実行結果は、こう。

Exception in thread "main" java.lang.NullPointerException: b
        at BBB.x(BBB.java:5)
        at AAA.main(AAA.java:11)

うわっ!本当にArithmeticExceptionは何処かに行っているっぽい。

そこで、もう1つ。

public class AAA
{
    public static void main(String[] args)
    {
        try {
            try {            
                try {
                    throw new ArithmeticException("a");
                } finally {
                    BBB.x();
                }
            } catch (NullPointerException e1) {
                System.out.println("NullPointerException の例外");
            }
        } catch (ArithmeticException e2) {
            System.out.println("ArithmeticException の例外");
        }
    }
}

public class BBB
{
    static public void x()
    {
        throw new NullPointerException("b");
    }
}

結果

NullPointerException の例外

IMEを無効にする [2004/11/17]

IMEを無効にするには次の関数を呼びます。

::ImmDisableIME(NULL);

(imm32.libをリンクするのを忘れずに。imm.hをインクルードしなければならないけど、こっちはwindows.hからインクルードされている)

ヘルプに依れば、ウィンドウが作られる前に呼ばなければならないそうなので、起動時に呼んでおけばOKでしょう。

ただし、コレを使うには、WINVER が 0x040A以上でなければいけません。

Windowsは新しいOSが出るたびにAPIが増えていて、ImmDisableIME()もその過程で追加されたAPIです(IEをインストールする時に、こっそりと追加されるAPIもありますが。そういうのは、ServicePack等で配布して欲しいものです)

WINVERと各OSの対応は次の通り

WINVER_WIN32_WINNT_WIN32_WINDOWS
950x04000x0400
980x04100x0410
Me0x05000x0500
NT40x04000x0400
20000x05000x0500
XP0x05010x0501
Server 20030x05020x0502

NT系と95系の2系統が別々に管理されているために、非常に扱いづらい構成になっています。

ところで、WINVER= 0x040Aってどんな状態なんでしょうか。
Win95かNT4.0に、IEのどれかのバージョンをインストールした状態だと思うんですけど。
WINVERからの逆引き(現存する全てのWINVERと、その構成の一覧)ってどこかに無い物でしょうか。

それは兎も角

Visual C++6では、何もしないとWINVER = 0x0400になるので、そのままでは、ImmDisableIME()を使えません(コンパイルエラーになる)

そこで、こんなコードをヘッダファイルの先頭の方に書いておきましょう。

#ifndef WINVER
#define WINVER 0x040A
#endif
#if WINVER < 0x040A
#undef WINVER
#define WINVER 0x040A
#endif

さて、

このままだと(WINVER = 0x0400の)Win95とNT4の人は使えません。

そこで、違う方法を考えます。

::ImmAssociateContext(hWnd, NULL);

コッチは、ウィンドウハンドルが必要なので、ウィンドウを作った後で呼びます。
しかも、ImmAssociateContext()の戻り値を保存しておけば、 元に戻すことも出来ます。

HIMC hImcOld = ::ImmAssociateContext(hWnd, NULL);  // IME無効

......

::ImmAssociateContext(hWnd, hImcOld);              // IME有効

ImmDisableIME()ImmAssociateContext()の違いは、IMEを無効にする単位です。

ImmAssociateContext()だと、ウィンドウの上にエディットボックスを大量に貼り付けたら、その全てでImmAssociateContext()を実行して1つずつ無効にしなければなりません。
ImmDisableIME()だと一度に全部無効に出来ますけど、一旦無効にしたら、プログラムを終わらせるまで有効に戻すことは出来なさそうなので、特定のエディットボックスだけIMEを有効にする事は不可能です。

ImmDisableIME()スレッド単位スレッドが終わるまで無効のまま
ImmAssociateContext()ウィンドウ単位自由に有効/無効が切り替えられる

しかし、
APIの名前やファイル名が「IMM」で、ハンドルが「IMC」で、処理対象が「IME」なんですよね。
どれかに統一して欲しい所です。

DirectXで画像を拡大・回転表示 [2004/11/10]

前回、変に拡大されてロードされない様にしたんですが、今回は、ちゃんと拡大縮小して表示させてみます。

DirectX9のID3DXSprite::Draw()には、X,Y方向の拡大率や、Z軸回りの回転角が指定できたんですが、Update summer 2003 から無くなってしまいました。

代わりにID3DXSprite::SetTransform()で設定します。
3Dで座標の変換を行なうのと同じ行列を用意して、ID3DXSprite::Draw()で表示する前にID3DXSprite::SetTransform()で変換行列を設定します。

float scale = 0.5f;

D3DXMATRIX matrix;
D3DXMatrixScaling(&matrix, scale, scale, 1);
d3d_sprite->SetTransform(&matrix);

::SetRect(&rect, 0, 0, width, height);
d3d_sprite->Draw(texture, &rect, 
                 &D3DXVECTOR3(width/2, height/2, 0),
                 &D3DXVECTOR3(width/2/scale, height/2/scale, 0), 0xffffffff);

画面の真ん中を中心として、半分の大きさで表示しています。
(with, height) = 画面の大きさ = 画像の大きさです。

指定しているベクトルが複雑です。

色々と試したところ、次の様な計算が行われているみたいです。

Y = M(X - C + P)

X : 変換前の座標
Y : 変換後の座標
M : SetTransform()で指定する変換行列
C : Draw()の第3引数。画像の中心
P : Draw()の第4引数。画像の表示位置

晶紀は、絶対にバグだと思っているんですよ、この計算式。
本当は『Y = M(X - C) + P』にする予定なんじゃなかったんでしょうか。
そうじゃなきゃ、CとPを別々に指定する意味が全くありませんから。

そんなの大した問題じゃ無いじゃんとか言っている人、出直しです。

ID3DXSprite::Draw()での座標変換がおかしい所為で、画像を回転して表示するのが非常に面倒なんですよ。

float angle = 3.14/8.0; // 45度

D3DXMATRIX matrix_r;
D3DXMatrixRotationZ(&matrix_r, angle);

D3DXMATRIX matrix_t;
D3DXMatrixTranslation(&matrix_m, width/2, height/2, 0);

D3DXMATRIX matrix;
D3DXMatrixMultiply(&matrix, &matrix_r, &matrix_t);

d3d_sprite->SetTransform(&matrix);

::SetRect(&rect, 0, 0, width, height);
d3d_sprite->Draw(texture, &rect,
                 &D3DXVECTOR3(width/2.0f, height/2.0f, 0),
                 NULL, 0xffffffff);

画面の真ん中を中心として、右に45度くらい回転して表示します。
(with, height) = 画面の大きさ = 画像の大きさです。

回転してから移動するのと、移動してから回転するのでは、結果が大きく違って来ます。
中心で回転した画像を任意の場所に表示するには、回転してから移動しないといけません。
Y = M(X - C) + P』形式ならMに回転行列、Pに表示位置を指定するだけで済むのに、『Y = M(X - C + P)』になっている所為で、Mに回転してから移動する行列を指定しなければならなくなっているのです。

意外とUpdate Summer 2004で直っていたりして。

そう言えば、D3DXMatrixMultiply()のヘルプに、『Out = M1 * M2』って書いてあるけど『Out = M2 * M1』じゃないかな…。
まぁ、左に指定してある(第2引数の)変換行列から先に適用されるって覚えておけば良いんですけどね。

メッセージループ [2004/11/9]

一定時間ごとに何かの処理しようとします。
タイマーを起動してWM_TIMERメッセージを処理すれば良いんですが、タイマーは使いません。

要はメッセージループでGetMessage()としてメッセージが来るまで待つんじゃなくて、PeekMessage()でメッセージが無くても処理しようということです。

while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

これが、

do {
    if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    } else {
         何か。
    }
} while (msg.message != WM_QUIT);

こうなります。

PeekMessage()でメッセージが無かった場合、whileでの条件判断が無駄な気がしなくも無いのですが、大抵このように書いてあるので、それに従います。

さて、

PeekMessage()を使って、上の様に書くと、何もしていなくても「何か」の処理を繰り返し実行することになるので、CPUに負担がかかります。
他のアプリケーションは遅くなるし、電気は沢山喰うし、部屋は暑くなるし、良い事がありません。

一定時間経つまでスリープしましょう。

DWORD last = ::GetTickCount();

do {
    if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        略
    } else {
        何か。
        ::Sleep(間隔 - (::GetTickCount() - last));
        last = ::GetTickCount();
    }
} while (msg.message != WM_QUIT);

非常に乱暴な気がしますが、こんな感じでしょうか。

ところが、これだとメッセージの処理も一定間隔でしか処理できません。メッセージの応答は悪くなるし、それに押されて「何か」の処理も遅れます。
「やる事があるなら、いつまでも寝てるな」です。

そこで、一定時間スリープするけれど、メッセージが来たら直ぐ復帰するAPIがあると便利ですね。
まぁ、あるんですけどね。

DWORD next = ::GetTickCount() + 間隔;

do {
    if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        略
    } else {
        if (::GetTickCount() >= next) {
            何か。
            next += 間隔;
        }

        ::MsgWaitForMultipleObjects(0, NULL, FALSE,
                                    next - ::GetTickCount(), QS_ALLINPUT);
    }
} while (msg.message != WM_QUIT);

さてさて

普通のアプリケーションならコレで良いんですが(って言うか、普通のアプリケーションならWM_TIMERで十分)、ゲームの場合、そうは行きません。

ゲーム中に、メッセンジャーの呼び出しが入ってダイアログが表示された。
ゲームのウィンドウはフォーカスが外れたけど、敵機は勝手に動いていて、いつの間にかやられていた。
なんて事になったら最悪です(スペースキーを連打していて、何かの警告メッセージを見る前に消してしまったってのも最悪ですが…)
そこで、フォーカスが外れたら、タイマーを止めたいのです。

bool active;
DWORD next;
DWORD rest;

==================================================

switch (message) {
  case WM_ACTIVATE:
    if (LOWORD(wParam) != 0) {
        active = true;
        next = ::GetTickCount() + rest;
    } else {
        active = false;
        rest = next - ::GetTickCount();
    }
    break;
}

==================================================

rest = 間隔;
next = ::GetTickCount() + 間隔;
active = false;

do {
    if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        略
    } else {
        if (active) {
            if (::GetTickCount() >= next) {
                何か。
                next += 間隔;
            }

            ::MsgWaitForMultipleObjects(0, NULL, FALSE,
                                        next - ::GetTickCount(), QS_ALLINPUT);
        } else {
            ::WaitMessage();
        }
} while (msg.message != WM_QUIT);

一応、フォーカスが外れる時に、残り時間を保存してますけど、復帰時に一定間隔待つようにしても問題なさそう。

(注)待ち時間がマイナスになる場合の事は、考慮していません。

DirectXで画像ファイルロード [2004/11/9]

DirectXで画像ファイルを表示しようとする場合、D3DXCreateTextureFromFile()関数が便利です。

LPDIRECT3DDEVICE9 d3d_device;
LPDIRECT3DTEXTURE9 texture

...

D3DXCreateTextureFromFile(d3d_device, filename, &texture);

これで画像ファイルのロードが完了するので、ID3DXSprite::Draw()で表示してやればOKです。

LPD3DXSPRITE d3d_sprite;

...

d3d_sprite->Begin(D3DXSPRITE_ALPHABLEND);
RECT rect;
::SetRect(&rect, 0, 0, width, height);
d3d_sprite->Draw(texture, &rect, NULL, &D3DXVECTOR3(0, 0, 0), 0xffffffff);
d3d_sprite->End();

DirectX3の頃は、画像ファイルを表示するだけでも凄く面倒で、そこで挫折した記憶が…。
それに比べれば、ものすごく簡単です。

ところが、やっぱりココにも落とし穴…。

画像ファイルをロードすると言っても、これはテクスチャとしてロードしているのです。
テクスチャは、諸々の都合上(おそらく処理速度)で、2のべき乗の大きさのものしか扱えません。640x480の画像ファイルをロードしても、テクスチャは1024x512の大きさになります。

まぁ、テクスチャの大きさが1024x512だろうが、ちゃんと640x480の画像ファイルはロードできるんですが、なんと、640x480の画像データを1024x512に拡大してくれちゃっていました。
てっきりテクスチャの左上640x480の範囲にロードされているものだと思っていました(今までテストに使ってきた画像が、多少変形しても気付かないタイプの画像だったから…)

3Dで使うなら、この方が都合が良いんでしょうけど、2Dで使う場合は、ちょっと困り物です。
対策を考えましょう。

拡大されないように最初から1024x512の画像にしておきますかね。

で、ヘルプを良く見てみると

この関数D3DXCreateTextureFromFileD3DXCreateTextureFromFileExW(pDevice, pSrcFile, D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, ppTexture)と等しい。

と書いてあるじゃありませんか。 ここになんとなく秘密がありそうです。

D3DXCreateTextureFromFileEx()のヘルプを見ると

イメージをフィルタリングする方法がD3DX_DEFAULTなら、D3DX_FILTER_TRIANGLE | D3DX_FILTER_DITHERと等しい。

とあります。そして

D3DX_FILTER_TRIANGLE:ソースイメージ内の各ピクセルが、転送先イメージに等しく反映される。

意味はさっぱり解りませんが、なんとなくサイズが一致するように拡大縮小している様な気がします。

そして、

D3DX_FILTER_NONE:スケーリングまたはフィルタリングを行わない。

というのがありました。

つまり、画像をロードする時は、

D3DXCreateTextureFromFileEx(d3d_device, filename,
                            D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0,
                            D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_FILTER_NONE,
                            D3DX_DEFAULT, 0, NULL, NULL, &texture);

とすれば、良いのです。これで、変に拡大されることはなくなりました。
しかし、引数の数が多くて解り辛いなぁ…。

画像データをアーカイブしたり暗号化したりしている場合は、D3DXCreateTextureFromFileInMemoryEx()を使うのが良いんでしょうね(独自形式の画像データじゃなければ)

αチャンネル付きのPNG形式がちゃんと扱えるので、素材作りの手間はかなり軽減できそうです。

JavaでExcelを操作 [2004/11/9]

JavaでMS-ExcelとMS-Wordのファイルを操作します。
そのために、Jakarta POIを使います。
MS-Wordについては、まだ開発途中らしいですけど。

昔、JavaからExcelファイルを操作するのに、COMでExcelを操作するDLLをC++で書いて、JNIで呼び出していたんですよ。しかもExcelの操作内容は先に(Javaで)テキストファイルに書き出しておいて、DLLがそのテキストファイルを読みながらExcelを操作するんです。
しかし、COMによる関数呼び出しって意外と遅いんですなぁ。その所為で、アライメントの調整とかセルの結合は行なわないようにして処理速度を稼いでいたりしました(流石に罫線を出力しないのは仕様的に無理だった)

でも、POIなら直接ファイルを作るから、意外とコッチの方が速かったりするかも。
Excelがインストールされていない環境でも使えますし。

閑話休題。

こんな感じのコードを書きます。

// ファイルロード
HSSFWorkbook book = new HSSFWorkbook(new FileInputStream(ファイル));

// 1つ目のシート取得
HSSFSheet sheet = book.getSheetAt(0);

// B3のセルを取得
HSSFRow row = sheet.getRow(2);            // 縦方向
if (row == null)
    row = sheet.createRow(2);
HSSFCell cell = row.getCell((short)1);    // 横方向
if (cell == null)
    cell = row.createCell((short)1);

// セルに書く
cell.setCellValue(10);

// セルに式を書く
cell.setCellFormula("$B3+$B4");

// セルを読む
System.out.println(cell.getNumericCellValue());

// 保存
book.write(new FileOutputStream(ファイル));

セルの取得の部分が意外と面倒です。そのあたりは、開発者も解っていたんでしょう。

HSSFCell cell = HSSFCellUtil.getCell(HSSFCellUtil.getRow(2, sheet), 1);

こうやって書くことも出来ます。
ただ、これをやると、

java.lang.NoClassDefFoundError: org/apache/commons/lang/exception/NestableException

となるんですよね。
解決するには、JakartaのCommons Langライブラリをインストールする必要があります。

そうそう、セルに日本語を書きたい場合は、コレが必要です。

cell.setEncoding(HSSFCell.ENCODING_UTF_16);

セル毎に必要かとも思ったけど、1回やればOKっぽいですにゃ〜。

DirectXでスクリーンモード変更 [2004/11/5]

ウィンドウモードとフルスクリーンモードを動作中に切り替えます。

といっても、CreateDevice()する前に設定するのと同じように)D3DPRESENT_PARAMETERSの値をそれぞれのモード用に設定して、Direct3DDevice9Reset()するだけですけどね。簡単です。

LPDIRECT3DDEVICE9      d3d_device;
D3DPRESENT_PARAMETERS  d3d_pp;

    ...


::ZeroMemory(&d3d_pp, sizeof(d3d_pp));
if (is_window_mode) {
    d3d_pp.Windowed = TRUE;
    ...
} else {
    d3d_pp.Windowed = FALSE;
    ...
}

d3d_device->Reset(&d3d_pp);

ところが、意外な落とし穴。
フルスクリーンモードからウィンドウモードへ変換すると、ウィンドウモードなのにウィンドウの枠やタイトルバーが表示されません。

ウィンドウは枠やタイトルバーがある分だけウィンドウの中身の部分(クライアント領域)よりも大きいのですが、フルスクリーンモードにした時に、ウィンドウの大きさがフルスクリーンモード時の画面の大きさに変更になってしまっているからです。多分そうです。

そして、どういうわけだか、枠を無視してウィンドウの中身を描いてしまっているので、枠が表示されないように見えるのです。たぶん。

これを解決するには、フルスクリーンモードに変換する前に、ウィンドウの位置と大きさを覚えておいて

RECT window_rect
GetWindowRect(hWnd, &window_rect);

ウィンドウモードに戻った時に、Direct3DDevice9Reset()を行なった後で、ウィンドウの大きさを設定しなおせばOK

if (is_window_mode) {
    SetWindowPos(hWnd, HWND_NOTOPMOST,
                 window_rect.left, window_rect.top,
                 (window_rect.right - window_rect.left),
                 (window_rect.bottom - window_rect.top),
                 SWP_SHOWWINDOW);
}

ところで、フルスクリーンモードで動作しているプログラムを終了すると、他に開いていたウィンドウが左上の方に集まって小さくなっているプログラムを見たことがありませんか?

市販のゲームでも、時々そんな仕様のものを見かけます。

フルスクリーンモードにして画面サイズを小さくしたから、その画面サイズに収まるように他のウィンドウのサイズを調整したんだと思うんですけど、結構困り物です。

解決方法は、
フルスクリーンモードにする時(デバイスを作成したり、リセットする前)に、ウィンドウのスタイルを変更することです。

SetWindowLong(hWnd, GWL_STYLE, WS_POPUP | WS_SYSMENU | WS_VISIBLE);

もちろん、ウィンドウモードにする時には、元のスタイルを指定しておかないと駄目です。

というわけで、本日の成果。
コマンドライン引数で指定した画像ファイルを表示する。
リターンキーでウィンドウモードとフルスクリーンモードが切り替わる。
ソースファイル一式

フルスクリーンモードで他のアプリケーションに切り替えてから元に戻すと、画面が表示されないんですよね。
復帰時にデバイスをリセットするんだけど、それが完了する前にWM_PAINTメッセージが飛んできて描画が完了したことになっているんだろうか。

そう言えば、書き忘れていた…。
サンプルプログラムは Visual C++ .NET 2003 + DirectX 9 SDK Update summer 2003で作ってます。
(Summer 2004をインストールしよう思いつつも、していない。スプライト関係がまた変更になったそうですし)
しかも、「とりあえず動けばいいや」なので非常に見づらいです。コメントも書いてないし。

DirectXで画像表示 [2004/10/28]

「DirectXを覚える事にしました」

と、今まで何回もそう言いながら、言った回数だけ挫折しているんですけどね。
「今度こそは」と、何度も禁煙に失敗している喫煙者の様です

そこで、今回は趣向を変えて、「画像が表示できればOK」と割り切ることにしました。

キーやマウスの入力は、DirectInputは使わずに、普通のアプリケーションと同じ様にウィンドウメッセージのハンドラで処理することにしてしまえば、あとは、画像さえ表示できれば「じゃんけんくえすと」程度の物は作れるはずです。
(「じゃんけんくえすと」程度の物を作るのにDirectXを使う必要があるのかは、この際置いておくとして………)

Cマガジンの2004年3月号に、DirectXで2D画像を扱う特集が載っているので、早速サンプルプログラムを見てみました。

まぁ、こんな感じなら画像表示は簡単に出来そうだと思ったのですが
………
………
………

Cマガジンのサンプルプログラムを、アイコン化して元に戻したら、エラーで落ちました。

いつの間にやらスプライトがNULLを指していました。
で、どこをどう修正していいのやらさっぱりわかりません。
早くも挫折の危機です。

DirectXの何が難しいって、アプリケーションの切り替えをした時に、ロストしたりしなかったり、リセットだけでいいのか作り直しなのかが、はっきりしないんですよね。

チュートリアルにはそんな事書いて無いし、サンプルプログラムはやたらとしっかり出来ているので見通しが悪いし。

とにかく色々と調べた結果、アイコン化から復帰する時に

pD3DDevice->Reset(&d3dppApp);
RELEASE(pSprite);
D3DXCreateSprite(pD3DDevice, &pSprite);

としているのが悪い(スプライトの作成に失敗している)事がわかり、

pSprite->OnLostDevice();
pD3DDevice->Reset(&d3dppApp);
pSprite->OnResetDevice();

と書き直すことで解決しました。

ウィンドウサイズ変更時に必要な処理は、こんな感じですかね。

Direct3DDevice9Reset()
D3DXSpriteデバイスのリセット前にOnLostDevice()、リセット後にOnResetDevice()
Direct3DTexture9D3DPOOL_MANAGEDで作成しているなら不要

というわけで、本日の成果。
コマンドライン引数で指定された画像ファイルを表示するだけ。
ソースファイル一式

ところで、RegisterClassEx()に指定するWNDCLASSEX型のhCursorフィールドにNULLを指定すると、カーソルがずっと砂時計のままだということに気が付きました。

friend で template な関数 [2004/10/28]

役に立たないプログラミングTipのページで公開中の順列を列挙するクラスはVisual C++ 6の環境で作成とテストを行なっています。

このクラスは、簡単に書くと、こんな構造をしています。

template<typename T>
class Permutation
{
    ....

    friend bool operator==(const Permutation<T>&, const Permutation<T>&);
};

template<typename T>
bool operator==(const Permutation<T>& a, const Permutation<T>& b)
{
    // Permutationクラスのプライベートメンバ変数を色々と比較する。
}

で、このクラスを次の様に使用します。

Permutation<int> a(〜);
Permutation<int> b(〜);

if (a == b) {
    ....
}

“Visual C++ 6の環境で作成とテスト”をしたので、当然VC++6では正常に動作してくれます。

さて、

このソースファイルをVisual C++ 7.1(.NET 2003)へ持っていってコンパイルしてみると、リンク時に

bool operator==(Permutation<int> const&, Permutation<int> const&)が見つかりません。

というエラーになりました。

なんじゃそりゃ〜。

色々と調べた結果、

    friend bool operator==<T>(const Permutation<T>&, const Permutation<T>&);

と書かないといけないらしいのです。
そうしないとbool operator==(const Permutation<T>& a, const Permutation<T>& b)をテンプレート関数と認識しないんだとか(参考)。

それにしても、コンパイルエラー(プライベートメンバを参照しました)ではなくリンクエラーになるところが、不思議です。 gcc(g++)でも、リンクエラーなんですよね。

でも、VC++7.1だと上の様に書くとコンパイルエラーになるんですよ。
一体、どうしろっていうんですかっ!

問題になっているのはfriendなので、これさえ使わなければOKなんですよね。

(方法1) メンバ変数をパブリックにする。
これは、まぁ問題外でしょう。

(方法2) 比較をするメンバ関数を作る。
書き換えの手間を考えたらクラスメンバにするのが楽そうです。

template<typename T>
class Permutation
{
    ....

  public:
    static bool equal(const Permutation<T>& a, const Permutation<T>& b)
    {
        // 元々 operator==(const Permutation<T>&, const Permutation<T>&)にあったもの
    }
};

template<typename T>
bool operator==(const Permutation<T>& a, const Permutation<T>& b)
{
    return Permutation::equal(a, b);
}

abstract で final なクラス [2004/8/18]

Javaはどんな場合でも必ずクラス(インターフェースも含む)にしなければなりません。

でも、関数として欲しい(わざわざクラスにするまでも無い)場合も結構あったりします。

そういう時は、ダミーのクラスを作って、そこのクラスメソッドして実装することで実現します。

class Func
{
    static void func1() { ...;}
}

使う時は、

    Func.func1();

です。

さて、このダミーのクラスFuncは、関数を集めただけの、C++で言えばnamespaceみたいなクラスですから、インスタンスを作る意味は全く無いし、派生させる意味もありません。

いっそのこと、インスタンスを作れない様にしてしまった方が、安全です。

Javaでインスタンス生成を禁止するにはクラスにabstract修飾子を付ける事で実現できます。
派生を禁止するにはfinalです。

で、

abstract final class Func
{
}

とすると、コンパイラに怒られた。
「abstractとfinalは一緒に使うな」

そこで、この手のクラスで一番有名なMathクラスのソースを見てみると

public final strictfp class Math
{
    private Math() {}
}

おぉ。なるほど。

abstractで「派生して使いなさいよ」と言っているのに、finalで「派生は禁止」じゃ矛盾してますからね。
う〜ん、細かい。

修飾子 strictfp [2004/8/18]

Javaの標準クラスMathのソースを見てみると…

public final strictfp class Math
{
}

strictfpなどという見知らぬ修飾子が付いている………。

これは、「実数をIEEE754形式で表しなさい」という指定だそうな。

実数の表現方法には色々な種類があって、CPUに依って一番得意な方法は異なるんです。

通常、JVMは、処理が速くなるようにCPUにとって一番速い方法で実数を表現するようになっていると思うんですが、そうすると、(実数の)表現方法の精度その他諸々の事情により発生する誤差で、環境によって値が微妙に違ってきてしまうのです。

「Write Once, Run Anywhere」を理想(あくまで理想)として掲げているJavaとしては納得がいかなかったんでしょう。

しかしIEEE754って倍精度で64bitなんですけど、最近のCPUって80bitとか128bitとかで実数を表すから、無理にIEEE754にしない方が速くて正確という事に……。

ところで、速度を犠牲にしてまで、どんな環境でも実数の値をきっちり同じにしないといけない場合ってどんな時だろう…。
そういう時って、浮動小数点表現の誤差すら許せない様な場合なんじゃ…。

Javaでメッセージボックス [2004/8/16]

VisualBasicのMsgBoxInputBoxの様なものをJavaで表示したかったんですが、APIマニュアルを見ても、それらしいのが見当たらないので、JDialogクラスを派生して自分で作ってしまいました。

もじへんB」では自作のMessageBoxを使っているので、「もじへんB」のjarファイルを展開するとMessageBox.classが出てきます。
さらに、これを逆コンパイルすると、どんな実装になっているのかもわかります(といっても、ボタンとラベルを貼っているだけだけど…)

Javaは逆コンパイルがしやすい言語なんで、かなりオリジナルに近いコードを作ってくれて、面白いです。
晶紀はjadという逆コンパイラを使っています。

先日、

Iterator it = list.iterator();
while (it.hasNext())
{
    System.out.println(it.next());
    System.out.println("-----");
}

を逆コンパイルしたら

for (Iterator it = list.iterator(); it.hasNext(); System.out.println("-----"))
{
    System.out.println(it.next());
}

こんなコードになりました。
間違って無いけど、珍妙だなぁ。 forの3つ目にSystem.out.println("-----")があるのって。

閑話休題。

ところが、「『こんなのがあったら便利だな』と思う物は、既に誰かが作っている」の法則通り、Java標準クラスのJOptionPaneにメッセージボックスの様なものがありました。

こんなクラス名じゃ、絶対に判らないって。

とりあえず、メッセージボックスを表示するには、 JOptionPaneshowMessageDialog()を使えばよいそうです。

import javax.swing.*;

public class MessageBoxTest extends JFrame
{
    public MessageBoxTest()
    {
        setVisible(true);

        JOptionPane.showMessageDialog(this, "メッセージ");
    }

    public static void main(String[] args)
    {
        new MessageBoxTest();
    }
}

「はい」「いいえ」のボタンにしたければshowConfirmDialog()、文字を入力するならshowInputDialog()です。

これらの仲間にshowInternal***Dialog()があるんですが、コッチを使うと、ダイアログが開くんじゃなくて、指定した親ウィンドウの中に表示されます。

import javax.swing.*;

public class MessageBoxTest extends JFrame
{
    public MessageBoxTest()
    {
        setVisible(true);

        JOptionPane.showInternalMessageDialog(getContentPane(), "メッセージ");
    }

    public static void main(String[] args)
    {
        new MessageBoxTest();
    }
}

余談。

「『こんなのがあったら便利だな』と思う物は、既に誰かが作っている」の法則以外にも、「『こんなのが必要だ』と思っている物は、誰も作っていない」の法則もあったりします。

JavaでZIPファイルを作る [2004/5/31]

JavaにはZIPファイルをアクセスするためのクラスが標準で用意されています。Jarファイルが実質ZIPファイルと同じだから当然と言えば当然ですけど…(J2SE 1.5には、別のフォーマットのJarファイルがあるそうですが…)

で、肝心のZIPファイルを操作するクラスですが、ZipInputStreamZipOutputStreamです。

ではAPIドキュメントを参考に、ZIPファイルを作成するプログラムを作ってみます。

import java.io.*;
import java.util.zip.*;

public class ZipTest
{
    static public void main(String[] args) throws Exception
    {
        BufferedInputStream input = new BufferedInputStream(
                                        new FileInputStream(new File(args[0])));

        byte[] buff = new byte[1024];
        int len;

        ZipOutputStream output = new ZipOutputStream(
                                     new FileOutputStream(new File("test.zip")));
        output.putNextEntry(new ZipEntry(args[0]));

        while ((len = input.read(buff)) != -1) {
            output.write(buff, 0, len);
        }

        output.closeEntry();
        output.close();
    }
}

ちなみに、エラー処理は“全く”していません。

さてさてさて、

JavaでZIPファイルを作る方法をネットで検索すると、同じようなプログラムが見つかるんですが、「この方法だとうまくいかない」と書いてあったりします。
putNextEntryをする前に、“CRC”と“圧縮後のデータサイズ”をZipEntryにセットしなくてはならないのだとか。
圧縮する前に“圧縮後のデータサイズ”なんか分かる筈がないので、「2回圧縮しろ」なんて書いてあります。

あのですねぇ………。一体全体、誰が何をどう考えたら、そんなわけわかめな仕様になるんですか。
ちょっと考えれば、それがどんな間抜けな事だという事が判る思うんですけど。
とりあえず、そんな話は見なかったことにして、早速、上のプログラムを実行します。

出来上がったzipファイルを展開ソフトで展開します。

ちゃんと展開できました。
ほらね。
試しに、Windows XP標準搭載の機能でも展開してみます。こっちも問題なし。

通常ZIPファイルは、データの前に圧縮後のデータサイズがあるけど、ZipOutputStreamで作ったZIPファイルはデータの後ろに圧縮後のデータサイズがあるんですよね。

きっと、ファイル以外の出力先にも対応できるようにZIPフォーマットが拡張されたけど、展開ソフトの方が、それに対応していなかったとか、そういうことじゃないのかにゃ〜。

Javaのバージョンが上がって、修正されたのかも知れませんけど、まぁ、ちゃんとZIPファイルが出来たから、それでいいや。

インストールメモ(OpenSSL編) [2004/5/27]

某ソフトに公開鍵暗号(ぶっちゃけRSA)を実装するために、半年以上かけて多倍長整数演算クラスを作ってきたんですが、RSAに必要な素数生成がものすごく遅いのです。
せいぜい200bitの素数を作るのが限界で、300bitの素数を作ろうとすると、帰ってきません。
まともにRSAで暗号しようとすると、キーは1024bit以上と言われています。つまり512bitの素数が必要です。

そこで、自作の多倍長整数演算クラスは、さっぱり捨てて、OpenSSLに含まれるライブラリを使うことにしました。

ところが、このOpenSSL、make一発とは行きません。
中々の曲者です。

まず、Makefileを作らなければならないのです。
Unixなんかでは、それぞれで環境が違うからImakefileからMakefileを作ったりしますけど、Visual C++用のMakefileは最初から用意されていたりする場合が多いんですよね。

perl Configure VC-WIN32

さっそく躓きます。
「perlが要るの〜〜〜っ!」

perlはインストールしていないので、コードを読んで手作業でMakefileの作成を………諦めます。

perlのインストールしてある所(すなわち仕事場のマシン)でMakefileだけ作成してきます(他にも幾つかのヘッダファイルを作っているみたいだったけど)

さて、OpenSSLでは高速化のため、幾つかのファイルはアセンブラで書いてあります。
さらにはアセンブラを使わないバージョンのソースも用意されています。
そこで、アセンブラ使うか使わないかをユーザに選択させてくれます。

もちろん少しでも早いほうが良いのでアセンブラを使用します。
Windows用の場合、MASMとNASMの選択が出来ます。
MASMをつかうなら

ms\do_masm

これ、バッチファイルなんですけど、なんと、この中でもperlを使います。
アセンブリコードは共通のアセンブリ言語みたいなもので書いて、それを元に各アセンブラ毎のソースに変換するという事をしています。

またしても、perlのインストールしてある所(すなわち仕事場のマシン)まで持って行き、アセンブリソースを生成してきます。

これで漸くコンパイルに必要なファイルが揃いました。

スタティックライブラリを作るには

nmake -f ms\nt.mak

DLLを作る場合は

nmake -f ms\ntdll.mak

です。

ところが、途中でコンパイルが止まります。
アセンブラに失敗しているのです。
Makefileを良く見るとアセンブルに使用するのはmlというコマンドです。
それが無いと言っています。
(まぁ、MASMはインストールしていないので、無いと言えば無いのですが、Visual C++に含まれていると思っていました)

試しに、これをclに変えてみます(clはVisual C++のコンパイラ)

失敗します。

なんで?

MASMってMicrosoft assembler でしょ。
どうして、Visual C++で通らないんですかっ!

仕方ないので、MASM32をインストールします。

漸くコンパイルが完了しました。

後は、inc32にインクルードパスを、out32にライブラリパスを通せば終了です。

しかし、キー長1024bitのRSAで暗号化しても、全然遅く無いですね。
なんで自作の多倍長整数演算クラスは、あんなにも遅いんだろうか…。

タスクバーの並べ替え [2004/5/26]

タスクバー(Windowsの下側に付いているアレです。人によっては左だったり上だったりしますが…)には、表示中のウィンドウが並んでいます。

これって、起動順に並んでいるんですが、自由に並べ替えたいとか思った事、ないですか?
「右クリックしてドラッグしたら移動できるかもっ」と思ったりしたのですが、全然ダメでした。 そこで、タスクバーの並べ替えをするプログラムを作ります。

タスクバーもウィンドウの一種なので、何とかしてウィンドウハンドラを取得し、何かのメッセージを送りつければ順番を変えられるかも、と予測。
早速調べてみると………。

はぅっ!

Win32APIのShowWindowSW_HIDEをすると、タスクバーから消えることを利用して簡単に出来ることがわかりました。

以下、その手順。

  1. EnumWindows()で、ウィンドウを列挙。
  2. ShowWindow()SW_HIDEして、ウィンドウを消す。
  3. 並べたい順番で、ShowWindow()SW_SHOWして、消したウィンドウを復帰。

参考:http://www.codeguru.com/Cpp/controls/toolbar/miscellaneous/article.php/c5495/

インストールメモ(Tomcat編) [2004/5/8]

Servletの実験のためにTomcatをインストールします。

と言っても、インストーラ付きのWindows用バイナリを入手し、実行して、画面の指示に従うだけなので、メモしておく事なんて、特に無かったりして…。

Windows XPの検索コンパニオンを無効にする [2004/4/15]

Windows XPには色々な機能が搭載されました。
余計なことをして鬱陶しくて重くなっただけの機能も一杯搭載されました。

その中で、利用頻度が高いものの1つに検索コンパニオンがあります。
ファイルを検索するのに、何故にアニメーションを見せ付けられなければならないのかが解りません。「初心者でも簡単に使えるように」と言う事の様ですが、設定箇所が吹き出しになっているだけで、何も変わりませんけど。
MS-Officeのイルカにする質問で最も多いのが「イルカを消す方法」だったという調査結果があったりなかったりするくらい、検索にアニメーションは不要なんです。

まぁ、検索コンパニオンのアニメーションくらいは設定項目があるので簡単に無効に出来るんですが、出来ない人もそれなりに居る様ですね。

それは兎も角、

アニメーションが出なくなってもXPの検索には困った仕様があります。
Windowsの検索には、テキストファイル中の文字列を検索する機能があるんですが、XPの検索はコレが変更になりました。
XPの検索コンパニオン
上の画像を良く見てください。
ファイルに含まれる単語またはが対象なのです。Windows2000までは『文字列』だったのに。
この所為で、意外と使い物にならない検索になってしまいました。

ところが、レジストリをいじると従来の検索に切り替わると言うことが判明しました。
そのレジストリは

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState\Use Search Asst

これに"no"という値を設定すれば、昔の検索になります。
XPの検索コンパニオンOFF

でも、ちゃんと「含まれる文字列」となっているのに単語単位で文字の検索してるっぽいんですよねぇ。
困った仕様(世間一般ではバグと呼ぶ)です。

さらに検索コンパニオンが有効になっていると、エクスプローラのフォルダで右クリックをして検索をしても、
エクスプローラのメニュー
探す場所がドライブのルートディレクトリになっているという仕様(世間一般ではバグと呼ぶ)も、無効にすれば直ります。

さてさてさて、

レジストリを変更するとなると色々と危険が伴います。
そこで

Set WshShell=WScript.CreateObject("WScript.Shell")

mode = MsgBox("検索コンパニオンを無効にしますか?", vbYesNo, "検索設定")

If mode = vbYes Then
    WshShell.RegWrite "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState\Use Search Asst", "no" , "REG_SZ"

    MsgBox "検索コンパニオンを無効にしました。", , "検索設定"
Else
    WshShell.RegDelete "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState\Use Search Asst"

    MsgBox "検索コンパニオンを有効にしました。", , "検索設定"
End If

上のコードを、メモ帳でも使って、1字1句間違えることなく書き写し(コピペでも可)、拡張子がvbsとなるように保存しましょう。

ちなみに、XP専用ですので、他のOSでは使わないで下さい。

しかし、Windows Scriptは意外と便利ですね。

フォントの列挙 [2004/4/14]

ワープロソフトでよくあるように、インストールされているフォントの一覧をコンボボックスで表示させます。
とりあえず、インストールされているフォントの一覧が取得できなければ話になりません(というか、これが出来れば完了です)

フォントの列挙にはEnumFontFamiliesEx APIを使います。
これを使うにはコールバック関数を用意する必要があります。

int CALLBACK EnumFontFamExProc(ENUMLOGFONTEX* lpelfe,
                               NEWTEXTMETRICEX* lpntme,
                               int FontType,
                               LPARAM lParam)
{
    MessageBox(lpelfe->elfFullName);
    return TRUE;
}
    CClientDC dc(this);

    LOGFONT lf;
    ::ZeroMemory(&lf, sizeof(LOGFONT));
    lf.lfCharSet = DEFAULT_CHARSET;

    ::EnumFontFamiliesEx(dc.GetSafeHdc(), &lf,
                         (FONTENUMPROC)EnumFontFamExProc, 0, 0);

コールバック関数の型をMSDNの記述通りにしているのに、EnumFontFamiliesExに渡す時にはキャストしないとコンパイルが通らないと言う罠がありました。

これでインストールしてあるフォントの一覧が取得できるんですが、なぜか同じフォントが何回も出てきます。
どうやら、1つのフォントでも言語毎に分かれて取得できるようです。
言語はlpelfe->elfScriptで判ります。
Windows2000とXPで試したんですが、色々な言語のフォントがインストールされています。

C++ の例外 [2004/3/5]

class A
{
  public:
    A(void) { cout >> "コンストラクタ\n" }
    ~A()    { cout >> "デストラクタ\n" }
};

int main(void)
{
    try {
        throw A();
    } catch (A&) {
    }

    return 0;
}

これを実行すると、次の様に表示されます。

コンストラクタ
デストラクタ

何の疑問もありません。

ところが…。

int main(void)
{
    try {
        A a();
        throw a;
    } catch (A&) {
    }

    return 0;
}

こうすると…。

コンストラクタ
デストラクタ
デストラクタ

えっ!?
デストラクタが2回?

その正体は、次の様にしたら判明しました。

class A
{
  public:
    A(void)     { cout >> "デフォルト コンストラクタ\n" }
    A(const A&) { cout >> "コピー コンストラクタ\n" }
    ~A()        { cout >> "デストラクタ\n" }
};
デフォルト コンストラクタ
コピー コンストラクタ
デストラクタ
デストラクタ

例外を投げる時に、コピーを1つ作っているようです。

確かに、「tryブロックから抜ける時にオブジェクトaは解体されるのに、catchブロックで参照していても大丈夫なんだろうか…」とか思っていたんですけど…。

Visual C++ 6のバグ? [2004/1/16]

Visual C++ 6のSTLにはバグが多いそうですが、コンパイラそのものにもあるみたいです。

どういう状況下で発生するのか、はっきりとわからないので、発生した状況だけを書いておきます。

Visual C++ 6で作っていたソースをVisual C++ .NET 2003でコンパイルしたらエラーになったので気付きました。

まぁ、正しいのが動かないわけじゃなく、間違っているのが動いてしまうバグなので、あまり影響は大きくは無さそうです。

オーナードローのCListCtrlの高さを変える [2004/1/14]

CListCtrlをレポートビューで表示します(エクスプローラで詳細表示したものです)

この時の、項目1つ分の高さは、表示しているフォントの大きさで決定します。
なので、SetFont()を使ってフォントを変更すれば、それに合わせて項目の高さも変更されます。

ところが、

LVS_OWNERDRAWFIXEDを指定し、オーナードロー状態で作成すると、フォントを変更しても項目の高さは変わりません(フォントは変わります)

項目の高さを指定するメソッドがあったと思うので探してみても見つかりません。
そのメソッドがあるのはCListBoxの方でした。

項目の高さを変更するには、WM_MEASUREITEMメッセージを受けて、そこで指定します。

ところが、

このメッセージが投げられるのは、コントロールを作った時とコントロールの大きさを変えた時のみなので、SetFont()で大きさを変えたらMoveWindow()を同じ位置と大きさに設定しなおさなければいけません。

参考サイト

関係無いのですが、参考サイトの所で「使い方が解らん。サンプルを載せろ」というコメントが何件かあるんですが、コードをコピペしてクラス名書き換える程度で動くんですけど。
こんな事が出来ないようなら、もっと違う所から勉強し直すべきでしょう。
教えて君は世界中に居るようです。

試用期限の実装 [2003/12/22]

ある事情により、ソフトウェアに試用期限(体験版やシェアウェアに良くあるアレです)を設けることになりました。

とは言っても、いつまで試用できるかの情報を何処かに記録するだけのことなので、この点については大した事をするわけじゃありません。
ファイルに記録するのは、ちょっとアレなので、レジストリに記録します。
試用期限を勝手に書き換えられてしまっては困るので、一応暗号化します。

ところがですねぇ…。

シリアルキーとかのソフトウェア登録情報と違って、試用期限情報は(書き換えが出来なくても)消してしまえば使っていない事に出来るんですよね。
そう思ったら、まともに暗号化する気が失せました。

となると、試用者(使用者の誤字じゃないぞ)に試用期限情報であると気づかれない場所に記録する方法を考えないといけません。
レジストリエディタを起動し、ソフト名(あるいはメーカー名)で検索して見つかるようじゃ問題外です。
かといって、奇抜な名前を付けていたら逆に目立ってしまいます。
葉っぱを隠すには森の中。似た様なものに紛れ込ませたいところです。しかも、同じ様な事を考えている他のソフトと衝突しないようにしなければなりません。

そこで考えたのが、\HKEY_CLASSES_ROOT\CLSID\辺りにGUIDをサブキーとして記録するのはどうでしょうか。これなら多分気付かれないですよね(こんなことやっても大丈夫かな…)
もちろんプログラム中にあるレジストリキーの名前は暗号化しておかなければいけませんが。

などと言っているんですが、結局そこまでしなかったんですけど。

Visual C++ 6のmem_fun1_ref_tバグ発見 [2003/12/19]

Visual C++ 6付属にSTLにはバグが多い事で有名ですが(VC++5は、もっと酷かったと記憶してますが…)、今更ながら、見つけてしまいました。

functionalヘッダで定義されているmem_fun1_ref_tですが、VC++6では、次の様に書かれていました。

template<class _R, class _Ty, class _A>
    class mem_fun1_ref_t : public binary_function<_Ty *, _A, _R> {

    (略)
};

_Tyの後ろに*は不要ですよね。mem_fun1_ref_tなんですから。

(1回でも使えば、コンパイルエラーになるので)発見が難しいバグでもないのに、Service Pack 5を当てた状態でも残っていたのが不思議です(VC++ .NET2003では直っていました)
某検索エンジンで「mem_fun1_ref_t」を検索しても、このバグについては1件もヒットしませんでしたし、殆どの人はSTLportとか使っていたんですかね。

CRichEditCtrlの最終行を表示 (チャットを作るのだ part3) [2003/5/18]

カーソルを末尾に移動文字を追加すれば、“リッチ エディット コントロール”の最後に文字を追加する事は出来るようになりました。

でも、これだと、ただ単純に最後に文字が追加されるだけです。どれだけ文字を追加しても表示位置が変わりません。
やっぱりチャットなので、常に一番最後の行が表示されていて欲しいのです。

CRichEditCtrlクラスには、テキストをスクロールするメソッドLineScroll()があります。これは使えないでしょうか?

結論から言えば、使えない事も無いが、非常に面倒です。

チャットでは、一番最後の行がコントロールの一番下の位置に表示されるのが望ましいのですが、LineScroll()はコントロールの一番上の位置に表示する行を指定しなければならないのです。
つまり、コントロールに表示できる行数を調べなければなりません。
そんな気の利いたメソッドは存在しません。というのも、リッチ エディット コントロールでは文字単位でフォントの大きさを変えられるのです。表示できる行数は一意に決まらないからです。

そこで、逆転の発想。
リッチ エディット コントロールにくっ付いてるスクロールバーにちょっかいをかけます。
スクロールバーの性質上、一番下にスクロールすると、最後の行がコントロールの一番下の位置に表示されるのです。
こんな感じ。

    // 最終行が表示されるようにスクロールする
    edit.PostMessage(WM_VSCROLL, MAKELONG(SB_BOTTOM, NULL));

CRichEditCtrlでカーソルを末尾に移動 (チャットを作るのだ part2) [2003/5/18]

“リッチ エディット コントロール”に文字を追加する事は出来るようになりました。
チャットなので、文字の追加は一番最後にしなければいけません。そうしないと、文章の順番がぐしゃぐしゃになってしまいます。

文字を追加するのはカーソル位置(正確には、選択されている文字)なので、今度は文字の選択を解除して、カーソルを一番最後に移動させます。

EnableWindow(FALSE)としておけば、ユーザがカーソルを移動出来なくなるので、それで良さそうにも見えるのですが、これだと、表示内容のクリップボードへのコピーが出来なくなるので、不許可です(文字が選択出来ないので)

しかし、ヘルプを見てもカーソルを移動させるメソッドは見当たりません。

ヒントは前回にありました。選択された文字を置き換えるメソッドがカーソル位置に文字を追加するのに使えたように、文字を選択するメソッドSetSel()がカーソル移動に使えるのです。
選択範囲の最初と最後を同じにすると、カーソル移動になります。
SetSel()は位置をバイト単位で指定するので、現在表示されている文章の大きさを調べ………る必要はありません。
SetSel(0, -1)が全てを選択する」と言うことがヘルプにかかれています。と言うことは-1は一番最後を表してる?
試してみたら正解でした。
つまり、CRichEditCtrlでカーソルを末尾に移動させるのは、こうなります。

    // カーソルをリッチ エディット コントロールの一番最後に移動
    edit.SetSel(-1, -1);

CRichEditCtrlに文字を追加 (チャットを作るのだ part1) [2003/5/18]

「チャットソフトを作ろう」
急にこんなことを思い付きました。既に高機能なのが沢山存在しているのに…。
いつもの事です。

チャット内容を表示するのに“リッチ エディット コントロール”を使う事に決めました。

はて?
“リッチ エディット コントロール”に文字を追加するにはどうしたら良いのでしょう?
カーソル位置に文字を追加するというメソッドは(ヘルプを見る限り)CRichEditCtrlには見当たりません。

エディット系コントロールにプログラムから文字を入れるにはSetWindowText()を使えばできます。 でも、これは、表示している文字全体を置き換えてしまうので、文字を追加することができません。
それなら…。

    CString mes;
    edit.GetWindowText(mes);
    mes += new_mes;
    edit.SetWindowText(mes);

一旦取り出して、文字を追加したのを設定しなおせば…
あまり美しくありません。

次に目をつけたのはPaste()。クリップボードの追加する文字を入れて、貼り付けをすれば…。これはもっと美しくありません。

そして辿り着いたStreamIn()

typedef RichEditBuff std::deque<char>;

DWORD CALLBACK EditStreamCallback(DWORD dwCookie, LPBYTE pbBuff,
                                  LONG cb, LONG* pcb)
{
    RichEditBuff buff = reinterpret_cast<RichEditBuff>(dwCookie);

    int size = buff.size();

    if (size > cb)
        size = cb;

    RichEditBuff::iterator it = buff.begin();

    for (int i = 0; i < size; i++, it++)
        pbBuff[i] = *it;

    *pcb = size;
    buff.erase(buff.begin(), it);
    return 0;
}

// ===== 略 =====

    char* mes;    // 追加する文字

    RichEditBuff buff;
    buff.insert(buff.end(), mes, mes + strlen(mes));

    EDITSTREAM stream;
    stream.dwCookie = reinterpret_cast<DWORD>(&buff);
    stream.dwError = 0;
    stream.pfnCallback = EditStreamCallback;
    edit.StreamIn(SF_TEXT | SFF_SELECTION, stream);

これでカーソル位置に文字を追加することができました。

所がもっと簡単に実現する方法がありました。

    // リッチ エディット コントロールのカーソル位置に文字を追加
    ReplaceSel(mes);

“選択されている文字を置き換える”メソッドですが、文字が選択されていない場合にはカーソル位置に文字が追加されます。
後は、このメソッドを呼ぶ前に、文字の選択を解除できれば完璧です。

インストールメモ (Apache編) [2002/11/2]

Apache(2.0.43)をWindows2000にインストールします。
本格的なWWWサーバを構築するのではなく、ローカルテスト用サーバにします。
ローカルテスト用なので、普段は無効になっていて必要な時に有効に出来るようにします。

Windows版はバイナリ版があるのでそれを入手し、インストーラを実行します。

仕様許諾やApacheの説明が出た後「Server Information」入力画面になります。
ローカルテスト用サーバなので、ドメインやサーバ名は適当に入力します。 未入力だとインストールできません。
管理者メールアドレスも適当に入力します。〜@〜という形式でないとインストールできません。

そしてサーバの種類を選びます。
ここでは「for All Users. …」の方を選びます(多分デフォルト)
もう1つの方がローカルテスト用には向いていると思ったのですが、こちらだとApacheが起動している間はコマンドプロンプトが表示されたままになるので、ちょっと嬉しくありません。

次の「Setup Type」ではTypicalを選びます。Customにしても良く解りません。

次にインストール先を選択します。ここで選んだディレクトリの下にApache2というディレクトリが出来るので、それを考慮に入れて選択しましょう。

これでインストールは完了です。
ブラウザでhttp://localhost/を指定すると、何か表示されるはずです。

後は、普段はApacheを停止するようにしておきます。
コントロールパネルからサービスを選び、Apache2のスタートアップの種類を「自動」から「手動」へ変えます。

インストールメモ (GraphViz編) [2002/10/28]

GraphViz(1.8.10)をWindows2000にインストールします。
詳しくは知らないけれど、図を書くためのツールです。

これはオープンソースなのですが、一通りのプラットフォーム用のバイナリも配布されています。
Windows版はインストーラ付きなので、ファイルを入手したら、それを実行。適当にインストールディレクトリを選択すれば、インストールは完了です。

が、今回はGraphVizフルセットをインストールするのではなく、その中のdot.exeのみをインストールしますDoxygenから使うのが目的)
Windows版はdot.exe単体での配布もしています。これを入手します。圧縮等は何もしていないので、そのままで適当なディレクトリにコピー。
これだけでは動作しません。一緒に配布されているDLLもインストールします。thirdparty.zipというファイルを展開し、出てきたファイル5つをdot.exeと同じディレクトリに入れます。
そして忘れてはいけないのが、ftd.dll を ft.dll にリネームすること。

これで完了です。

アイコンファイルのフォーマット [2002/9/18]

Windows用のアイコンファイル(拡張子ico)のフォーマットを調べてみました。
(BMPファイルの拡張子をicoに変更するとアイコンとして機能するけど、そういう意味じゃなく…)
icoファイルのフォーマットについてはMSDNに載っていないのですが、昔(Win3.1時代)は書いてあったそうです。

icoファイルは「ヘッダ部」「エントリー部」「データ部」の3つから構成されます(名称は勝手に決めた)
構造は次の通り。

ヘッダ
エントリー0
エントリー1
画像データ0
画像データ1

アイコンファイルは1つのファイルに複数のアイコンを入れることが出来るのです。 16色アイコンと256色アイコンが1つのファイルに入っていたり、普通サイズと小さいサイズの物が1つになっているのはよくあります。

ヘッダ部

struct ICONDIR {
    WORD	idReserved;	// = 0
    WORD	idType;		// = 1
    WORD	idCount;	// エントリーの数
};

ヘッダ部は上記構造体で定義します。
一般的には

struct ICONDIR {
    WORD	 idReserved;	// = 0
    WORD	 idType;	// = 1
    WORD	 idCount;	// エントリーの数
    ICONDIRENTRY idEntries[1];	// それぞれのエントリー
};

こうやって書くそうなのですが、この形式は嫌いなので、使いません。
なぜ嫌いなのかというとsizeofで大きさを調べる時のコードが美しくないからです。どうしても、の場合は大きさ0の配列を使います(処理系依存ですけど)

idReservedは必ず0です。
idTypeは必ず1です。
idCountにはエントリーの数、つまりファイルの中にあるアイコンデータの数になります。

エントリー部

struct ICONDIRENTRY {
    BYTE	bWidth;		// アイコンの幅
    BYTE	bHeight;	// アイコンの高さ
    BYTE	bColorCount;	// 色数
    BYTE	bReserved;	// = 0
    WORD	wPlanes;	// プレーン数
    WORD	wBitCount;	// ビットカウント
    DWORD	dwBytesInRes;	// リソースのサイズ
    DWORD	dwImageOffset;	// 画像データまでのオフセット
};

エントリー部は上記構造体で定義します。

bWidth, bHeight はアイコンの大きさです。 ピクセル単位で指定します。BYTE型(実態はunsigned char)なので255までしか指定できませんが、その以上の大きさの物は普通アイコンとは呼ばれません。
bColorCountはアイコンの色数です。厳密な仕様書が無いので断言は出来ませんが、0, 2, 16のどれかです。0は256色を表します。
bReservedは必ず0です。
wPlanesは通常1です。未使用で0でもOKという話もあります。
wBitCountは画像の1ピクセルを表すのに必要なビット数で、使用できる色数が2色、16色、256色のいずれかである関係上、1, 4, 8のどれかになります。
dwBytesInResは対応する画像データ部の大きさです。
dwImageOffsetは対応する画像データ部のファイル先頭からの位置です。

エントリー部は、ヘッダ部の後にアイコンの数だけ続きます。

画像データ部

画像データ部は「画像ヘッダ部」「パレット部」「表示データ部」「マスクデータ部」の4つから構成されます(名称は勝手に決めた)
構造は次の通り。

画像ヘッダ部

これはBITMAPINFOHEADERと同じ構造をしています。 ただし、いくつか普通のBMPファイルの場合と違う所があります。

biHeightには“画像の高さ×2”の値が入ります。
biCompressionは必ず0です。
biSizeImageには、表示データ部とマスクデータ部の合計サイズが入ります。
biXPelsPerMeter, biYPelsPerMeter, biClrUsed, biClrImportantは必ず0です。

パレット部

普通のBMPファイルと同様にパレットの色がRGBQUAD型の値で必要な数だけ並びます。
ただ、ヘッダ部のbiClrUsedが0なので、必ず全てのパレットが定義されています。

表示データ部

普通のBMPファイルのデータと全く同じです。

マスクデータ部

透明なピクセルを1、不透明なピクセルを0で表した2色画像を普通のBMPファイルのデータと全く同じ方法で表した形式になります。

ちなみに、

ヘッダ部のidTypeを2。
エントリー部のbColorCountを0。
エントリー部のwPlanesにホットスポットのx座標。
エントリー部のwBitCountにホットスポットのy座標。

と言うように変更するとマウスカーソルファイル(拡張子cur)になります。

まぁ、世の中にはアニメーションカーソルとかアニメーションアイコンとかが在ったりしますが、それはまた違うフォーマットをしているそうです。

EnumResourceTypesのエラー [2002/9/6]

Windows用のアプリケーションファイル(俗に言うexeファイル)には、プログラムのコードだけではなく、アイコンやメニューの項目などの情報が一緒にくっついています。
これらの情報をリソースと言います。

アプリケーションファイルに含まれるリソースの一覧を表示させようと、プログラムを組んでみました(ちなみにMFCを使ったGUIアプリケーション)

リソースは「種類」−「名前」−「言語」の順に木構造になっているので、まずは種類を列挙します。

if (!::EnumResourceTypes(NULL, EnumResTypeProc, 0)) {
    ::AfxMessageBox("error");
}

	・
	・
	・

BOOL CALLBACK EnumResTypeProc(HMODULE hModule, LPTSTR lpszType, LONG lParam)
{
    ::AfxMessageBox(lpszType);
    return TRUE;
}

EnumResourceTypes関数を呼び出すと、その第2引数で指定した関数(EnumResTypeProc)が、リソースの種類毎に呼び出される仕組みです。
EnumResTypeProcでは第2引数(lpszType)にリソースの種類が入っています。
後はそれを表示するだけです。

ちなみにLPTSTR とは文字列を表す型で、char* とほぼ同じです。 何故constになっていないのか気になるところです。

そんなコードを書いて実行してみたところ、
「error」
EnumResourceTypesが失敗しています。
エラー原因を表示させてみると「メモリロケーションへのアクセスが無効」…。
わけわかりません。

悩むこと約1日。

EnumResTypeProc関数のlpszTypeはリソースの種類を表す文字列では無いことが判明。
いや、リソースの種類を表す文字列であることは間違いないけど、それだけじゃないことが判明。

ヘルプをよく読むと、「標準のリソースの種類の時は、次の値を取るよん」って書いてあるじゃないですか。この値はアイコンなら14、メニューなら4とかなり小さな値。
こんな数値を文字列へのポインタと見なして、無理矢理表示しようとすれば、いかに腐れOSと言えども、メモリ保護違反を起こして当然です(OSに依っては、この辺りのアドレスが保護領域になってない可能性もありますが…)

指定されたメモリのアドレス(=場所=ロケーション)は保護されているのでアクセスできません。
………「メモリロケーションへのアクセスが無効」。
納得。

そこで、標準リソースの場合は処理を分ける事で、無事にリソースの種類が表示されました。

しかし、なぜ文字を表示する所でエラー終了せず、EnumResourceTypesがFALSEを返してきたのかは不明。

Excel97とCOM [2002/7/26]

VisualC++で作ったプログラムからExcelを操作することになりました。ちなみにExcel97です。
やろうとしていることは

こんな程度の事はCOMを使って簡単に実現できます。できるはずです。って言うか、出来なきゃおかしいのです。
ところが、色々な問題が発生してきました。

問題となったのは、ユーザがExcelを終了させてしまった場合。

なんと終了したことが検出できない。
MS-Wordは、終了する時に終了イベントを投げてくるので、それを捕捉すればユーザが勝手に終了させたことが検出できるのですが、Excelはプログラムの終了時にイベントを投げてくれません(同じMS-Officeの一員なのに、まるっきり統一感がありません)
そこで、何か操作しようとした時にExcelが終了してしまっていれば、当然エラーが返ってくるだろうということで、これを利用することにしました。
そして、ここに大きな落とし穴があったのです。

  1. ExcelをCOMで起動した場合、そのExcelを終了させてもプロセスが残っている。
  2. プロセスが生きているので、どんな操作をしてもエラーにならない。
  3. やっぱりExcelが終了したことが検出できない。

ただ単に非表示になっているだけかと思い、VisibleプロパティをTRUEにすると、ウィンドウの枠だけが表示され、何をやっても元に戻らない…(COMを解放しないとダメなのです)

全くのお手上げです。

再生するMIDIデータを切り替える [2002/6/23]

(前回のあらすじ)MIDIデータの連続再生ができました。

ゲームでは、戦闘中BGM、街のBGMなど状況によってBGMが切り替わります。 そこで、いくつかのMIDIファイルを切り替えて再生してみることにします。

再生するMIDIデータはMCI_OPENコマンドでしか指定できないので、再生するデータを切り替えるには、MCI_CLOSEコマンドで一旦終了させてMCI_OPENでオープンしなおす必要があります。

    // ----- 終了 -----
    mciSendCommand(open.wDeviceID, MCI_CLOSE, MCI_WAIT, 0);

    // ----- オープン -----
    ::ZeroMemory(&open, sizeof(MCI_OPEN_PARMS));
    open.lpstrDeviceType = "Sequencer";
    open.lpstrElementName = midi_file_name;
    mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
                   (DWORD)&open);

ところが…。

曲の再生は止まるのですが、次の曲が再生されない…

MCI_CLOSEに時間がかかっている所為かと、間にSleep命令を入れてみてもダメ。
しかし、間にMessegeBoxを表示させたらうまくいったのです。

結論。

MCI_CLOSEコマンドとMCI_OPENコマンドの間には、一旦OSへ制御を移さなければならない。
つまり、「MCI_CLOSEコマンドとMCI_OPENコマンドは別のイベントハンドラに書け」ということです。

MCI_OPENコマンドは多少時間がかかるので、MIDI切り替え時に少し処理が止まります。 最初に必要な全MIDIデータをMCI_OPENしておき、再生するデバイスIDを切り替えるようにした方が良いかも。

MIDIを連続で再生しよう [2002/6/23]

(前回のあらすじ)MIDIデータの再生ができました。

MIDIデータをゲームのBGMとして使用しようとする場合、ただデータを再生しただけでは困ります。
データの再生が終了したら、そこでBGMが終わってしまうからです。
これを回避するには、データの再生が終わったら再び最初から再生するようにすれば良いのです。

では、どうやって再生が終わったことを検出したら良いのでしょうか。
一番最初に思い付くのは、定期的に調べることです。MCI_STATUSコマンドを使えば、MCIの状態が調べられます。

でも、この方法では、結構無駄が多くなります。

もっとエレガントかつスマートな方法は無いのでしょうか。 それは、MCIから終了したことを通知してもらう方法です。MIDIデータを再生する時に通知先のウィンドウを指定することで、実現できます(mciSendCommand の第3引数にMCI_NOTIFYを指定する)

#include <mmsystem.h>

MCI_OPEN_PARMS open;
MCI_PLAY_PARMS play;

void play_midi(const char* midi_file_name, HWND wnd)
{
    // ----- オープン -----
    ::ZeroMemory(&open, sizeof(MCI_OPEN_PARMS));
    open.lpstrDeviceType = "Sequencer";         // 再生するものがMIDIであることを表す
    open.lpstrElementName = midi_file_name;     // 再生するMIDIファイル名

    mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
                   (DWORD)&open);

    // ----- 再生 -----
    ::ZeroMemory(&play, sizeof(MCI_PLAY_PARMS));
    play.dwCallback = (DWORD)wnd;               // 終了通知先のウィンドウ
    mciSendCommand(open.wDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)&play);
}

void stop_midi(void)
{
    // ----- 終了 -----
    mciSendCommand(open.wDeviceID, MCI_CLOSE, MCI_WAIT, 0);
}

MCIからの通知はMM_MCINOTIFYメッセージで行われます。 そこでメッセージマップに次の一文を追加します。

ON_MESSAGE(MM_MCINOTIFY, OnMciNotify)

OnMciNotifyはハンドラ関数で、通知を受けるウィンドウのメンバ関数として次の様に定義しておきます。

afx_msg LONG OnMciNotify(WPARAM, LPARAM);

後は、OnMciNotifyに通知を受けた時の処理、今回は「最初から再生し直す」処理を記述すれば完成です。

LONG CMainWnd::OnMciNotify(WPARAM wFlags, LPARAM lDevID)
{
    mciSendCommand(lDevID, MCI_SEEK, MCI_SEEK_TO_START | MCI_WAIT, NULL);
    mciSendCommand(lDevID, MCI_PLAY, MCI_NOTIFY, (DWORD)&play);

    return 0;
}

X68000用の某ゲームでは、BGM用音楽データを、ループコマンドで曲全体を255回ループさせていました(OPMコマンドでは255回までしかループ出来なかった)
1曲の長さが数分あったので、255回のループでは10時間くらいかかるのですが、もちろん10時間以上経過するとBGMは止まります。

つづく

MIDIを鳴らそう [2002/6/23]

プログラム上からMIDIデータを再生するにはMCIを使うのが楽です。
MCIはmciSendCommand APIを使います。

mciSendCommandの第1引数には、デバイスIDを指定します。
デバイスIDはMCI_OPEN(デバイスをオープンする)コマンドで取得できます。
MCI_OPENコマンドの時は、(デバイスIDは使われないのでどんな値でもOKなんですが、通常は)0を指定します。

mciSendCommandの第2引数には、MCIのコマンドを指定します。
MCIコマンドにはデータを再生したり、一時停止したり様々なコマンドがあります。

mciSendCommandの第3第4引数には、MCIコマンドそれぞれに必要な値を指定します。

さて、MCIを使ってMIDIデータを再生するには、次の手順になります。

#include <mmsystem.h>

MCI_OPEN_PARMS open;
MCI_PLAY_PARMS play;

void play_midi(const char* midi_file_name)
{
    // ----- オープン -----
    ::ZeroMemory(&open, sizeof(MCI_OPEN_PARMS));
    open.lpstrDeviceType = "Sequencer";         // 再生するものがMIDIであることを表す
    open.lpstrElementName = midi_file_name;     // 再生するMIDIファイル名

    mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
                   (DWORD)&open);

    // ----- 再生 -----
    ::ZeroMemory(&play, sizeof(MCI_PLAY_PARMS));
    mciSendCommand(open.wDeviceID, MCI_PLAY, 0, (DWORD)&play);

    // 終了まで待つ………

    // ----- 終了 -----
    mciSendCommand(open.wDeviceID, MCI_CLOSE, MCI_WAIT, 0);
}

再生するMIDIデータはファイルしか指定できないので、「普段は圧縮していて必要になったらメモリ上に展開して再生する」という事が出来ないのですが、ただ再生するならこの方法は簡単なのです。

あ、コンパイルする時は、winmm.libも一緒にリンクして下さい。

つづく

窓の作成失敗処理 [2002/6/10]

WindowsでGUIプログラムを作る場合、普通のウィンドウを作るだけなら、CFrameWndクラスを派生させ、LoadFrameメソッドを呼ぶのが簡単です。
それはさておき…
ウィンドウを作成した後で、データを初期化しようと思い、OnCreate関数の中にデータの初期化処理を書きました。
この初期化処理は失敗することがあり、対策として、失敗した時はウィンドウの作成をキャンセルしプログラムが終了する様にします。

OnCreate関数は-1を返せばウィンドウの作成処理がキャンセルになります。。

int CMainWnd::OnCreate(LPCREATESTRUCT cs)
{
    ………

    データの初期化();
    if (初期化失敗) {
        return -1;
    }

    ………

    return 0;
}

ウィンドウの作成に失敗すると、LoadFrame関数はFALSEを返すので、その時はプログラムを終了します。

    if (!LoadFrame(ID_MAINFRAME)) {
        プログラム終了();
    }

これだとデータの初期化に失敗した場合、ウィンドウが表示されること無くプログラムが終了し、何も起こらなかった様に見えるので、失敗した時にはメッセージを出すことにします。

    if (!LoadFrame(ID_MAINFRAME)) {
        MessageBox("失敗");
        プログラム終了();
    }

ところが、このメッセージボックスは表示されません。

ぁぅ〜。

色々と試した結果、
「WM_CREATEメッセージに-1を返すと、CreateWindow APIから返ってきた時点でMessageBox APIが何もしなくなる」
という結論に至りました。

なんと言う、使えない仕様なんでしょう。
結局、OnCreateの中でエラーメッセージを表示することにしました。

int CMainWnd::OnCreate(LPCREATESTRUCT cs)
{
    ………

    データの初期化();
    if (初期化失敗) {
        MessageBox("失敗");
        return -1;
    }

    ………

    return 0;
}

ちなみに、Windows2000Professional + SP2とVC++6です。

iscsymfとiscsym [2002/5/13]

iscsymfとiscsymという関数があります。
関数名が“is”で始まる所から、文字分類の関数じゃないかと思われるのですが、いまいち馴染みの無い関数です(ANSI Cの標準関数では無いようです)

で、この関数は何をする関数かというと、iscsymfは英字とアンダーバーならtrue、iscsymは英字と数字とアンダーバーならtrueを返すのです。
なんとなくC言語の変数名が正しいのかどうか判定するのに使えそうです。
っていうか、そのための関数だそうです(つまり、iscsymfは英字とアンダーバーならtrueではなく、C言語の識別子の1文字目ならtrueを返す関数なのでした)

ちょうどC言語の識別子の判定をする処理が必要になった所だったので、早速使ってみました。

bool check_csymbol(const char* str)
{
    if (!iscsymf(*(str++)))
	return false;

    while (*str != '\0') {
	if (!iscsym(*(str++))) {
	    return false;
	}
    }
    return true;
}

これは、引数に渡した文字列がC言語の識別子、つまり英字またはアンダーバーで始まり、英数字またはアンダーバーが続く文字列ならtrueを返す関数です。

ところが、(Visual C++ 6で、この関数を実装し実行すると)正しい文字列を渡しているのにfalseが返ってくる現象が頻発します。よくよく調べてみると、iscsymfとiscsymの両方ともアンダーバーで0を返しているではありませんか。それって単なるisalphaとisalnumでは…。

結局、こんな関数を作っちゃいました。ぅ〜。

inline bool _iscsymf(int c) {return isalpha(c) || c == '_';}
inline bool _iscsym(int c)  {return isalnum(c) || c == '_';}

Javaでswap [2002/5/10]

2つの変数の値を入れ替える関数(俗に言うswap関数)を作る事になりました。

ClassA a = new ClassA();
ClassA b = new ClassA();
    ...
swap(a, b);

こんなコードを書くと、aとbの内容が入れ替わっている、そんなswap関数を作るのです。出来れば汎用的なものを…。

すると「『Javaでswap関数が出来ない』というのはFAQだよ」などという声が…

そんなことを言われて萌え燃えない様では、漢(≠男)ではありません。
真の「漢(≠男)のプログラマ」というものは不可能を可能にするものなのです。
(ちなみに、晶紀はプログラマではなくて、自称デザイナです)

ステップ1「何も考えずに作る」

void swap(Object a, Object b)
{
    Object temp = a;
    a = b;
    a = temp;
}

何も考えていないので、これは全く意味をなしません。

ステップ2「コピーメソッドを実装する」

入れ替えを行うオブジェクトのクラスに、メンバ変数の内容をコピーするcopyメソッドを実装できれば、この方法でswapは可能になります。

void swap(ClassA a, ClassA b)
{
    ClassA temp = new ClassA;
    temp.copy(a);
    a.copy(b);
    b.copy(temp);
}

これだとcopyメソッドを実装しなければならないので、既存のクラスには適用できません。
汎用的でもなくなります。copyメソッドを持つインターフェースを引数に渡せば汎用性は保たれますが、同じインターフェースを持っているなら違うクラスのオブジェクトを渡してもエラーにはならないので(もちろん正常に動作しない)堅牢性が落ちます。 さらに、メンバ変数を1つずつコピーするため、処理速度の面で不利です。

ステップ3「他のクラスでラップする」

そもそも、オブジェクトの参照を値渡しで渡しているのが問題なわけなので、他のクラスでラップしてしまえば、この問題は解決です。

class SwapWrapper
{
    public Object a;
    public Object b;
}

swap(SwapWrapper wrapper)
{
    Object temp = wrapper.a;
    wrapper.a = wrapper.b;
    wrapper.b = temp;
}

「SwapWrapperにswapメソッドを実装したらダメなの?」という突っ込みが間髪いれず入りましたが(もちろん、OKですよ)、これで変数の入れ替えは出来ます。まったく、誰ですか「Javaでswapは出来ない」なんて言った人は。
ただし、これには大きな落とし穴が…
この関数を呼び出し側は、こんなコードになります。

SwapWrapper wrapper = new SwapWrapper;
wrapper.a = a;
wrapper.b = b;
swap(wrapper);
a = (ClassA)wrapper.a;
b = (ClassA)wrapper.b;

あはははは…。

結論「関数は使わない」

結局のところ、この方法が一番すっきりします。

{
    ClassA temp = a;
    a = b;
    b = temp;
}

このコードを必要な箇所全てに書く。

Javaでマクロは使えないものですかねぇ…。誰かJavaのプリプロセッサ作りませんか?

ファイルアイコンの関連付け [2002/5/8]

Windowsでエクスプローラを起動すると、それぞれのファイルがアイコン表示されます。
このアイコンはファイル名の拡張子に関連付けられています。 (同じ拡張子を持った全く異なる種類のファイルが多数存在する現状で、この仕様には多大なる疑問が湧くところですが、それは兎も角として)自作のソフトウェアで、それが使用しているデータファイルのアイコンを設定することになりました。

別にアイコンが関連付けられていなくても動作には全く問題はありませんし、デフォルトのアイコンが表示されるのが気に入らないのなら各自で設定すれば良いだけの話なので、アイコンの関連付けなどしなくても良いと思うのですが、それを言ってしまったら話が進みません。
早速、アイコンを関連付けてみましょう。

拡張子とアイコンの関連付けの情報はレジストリに保存されています。つまり、レジストリを目的の物に書き換えれば、それで完成です。
が、それでは余りエレガントではありません。
関連付けを行うAPIがきっとあるはずです。

ところが、いくら調べても見つかるのは関連付けを参照するAPIばかり。設定する方のAPIは見つかりません。
ネットで検索したところ「レジストリを直接書き換えるべし」というなんともありがたいお言葉が…。ぁぅ〜。

関連付けを行うキーは、違う拡張子に同じ設定を関連付けられるように、2つに分かれています。
まず、HKEY_CLASSES_ROOTに拡張子(ピリオド付き)のキーを作成し、その値に任意の(半角)文字列を設定します。
そして、HKEY_CLASSES_ROOTに上で設定したキーの値を名前とするキーを作成、その中にDefaultIconというキー名でアイコンのあるアプリケーションを指定するのです。

拡張子extにメモ帳のアイコンを関連付けるには次のような設定をします。

HKEY_CLASSES_ROOT\.ext に "assoc_test_ext"
HKEY_CLASSES_ROOT\assoc_test_ext\DefaultIcon に "c:\windows\notepad.exe,0"

DefaultIconの値にある「,0」は指定した実行ファイル(or DLL)の0番目(つまり最初)のアイコンという意味です。
"assoc_test_ext"の部分は、他と重ならなければ何を設定してもOKです。

CRegKey reg; 
reg.SetValue(HKEY_CLASSES_ROOT, ".ext", "assoc_test_ext");
reg.SetValue(HKEY_CLASSES_ROOT, "assoc_test_ext\\DefaultIcon",
             "c:\windows\notepad.exe,0,0"); 

CRegKeyクラスを使うと、キーを開いたり閉じたりする必要が無くて楽です。

ところが、ちゃんとレジストリを書き換えてもアイコンは変わりません。
試しにマシンを再起動してみると、アイコンは変わりました。つまりレジストリの書き換えは成功していたことになります。
後は、レジストリを書き換えた事、関連付けを変更した事をOS(シェル)に通知するだけです。
こっちは直ぐに見つかりました。

SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); 

これで関連付けを変更した事がシェルに伝わります。
でも、レジストリを変更した事くらい、レジストリを閉じた時点で自動的に再読込して欲しいものです。
(アイコンではなく起動するアプリケーションを設定するにはDefaultIconの変わりにShell\Open\commandを使います)

MFCでDirectX [2002/5/4]

Windows用のプログラミングを始めて、2年になりました。
そろそろ、DirectXにも手を出してみましょう。
といっても、ただ漠然と「DirectXを使ってみよう」と言うわけではなく、「これを作る」という具体的な計画が既にあり、それに「DirectXを使ってみよう」ということなのです。

そのソフトウェアでは、一部に3DCGは使うものの、基本は2Dなので、DirectX7を使うことにします(DirectX8ではDirectDrawが無くなり、2次元画像の扱いが複雑になったと聞きます)。
ウチのマシンには、DirectX8.1 SDKがインストールされているのですが、DirectXはCOM技術を使っているので、DirectX7も全く問題無く使えるはずです。
何か問題が発生したら、某M$社に文句を言いつつ、それから対策を考えましょう

次に決めたことは「MFCを使う」。
DirectXを使うソフトウェアではMFCを使わないのが一般的ですが、やってやれないことはありません。DirectXのサンプルにもMFCを使ったものがあります。
MFCを使うと遅くなると言われますが、今のマシン環境において、それは些細な問題です(それに、そこまでシビアなものを作るつもりも無いし…)
今の世の中、マシンの効率より人の効率です。

と言うことで、本を参考に作ってみました。
意外とあっさり、ウィンドウをフルスクリーンで表示するところまで出来ました。

ところが…
マウスをダブルクリックした時のメッセージWM_LBUTTONDBLCLKが飛んでこない。
ゲームなどのプログラムでは、急いでマウスをクリックした時対策に、WM_LBUTTONDBLCLKのハンドラに普通のクリックの時と同じ処理を行わせると言うことを良くやるのですが、それが出来ないということになります。
まぁ、この原因究明と対策は、とりあえず保留としておこう。