前回は、HSP互換C++ライブラリ「HSPPP」の概要を紹介しました。 今回は、HSPPPの中でも特にHSPユーザーにとって大きなパラダイムシフトとなる割り込みハンドラと、「goto廃止」の理由について解説します。

HSPの「割り込み」とは?

HSPには onclickonkey といった命令があり、マウスクリックやキー入力があった瞬間に指定したラベルへジャンプさせることができます。

; HSPの例
onclick goto *OnClicked
stop

*OnClicked
    mes "クリックされました"
    stop

HSPの goto は「一方通行のジャンプ」です。飛んだ後は stop で止まることが多く、元の場所に戻る必要はありません。

HSPPPでの割り込み記述

HSPPPでは、この割り込み機能を ラムダ式(無名関数) を使って再現しました。 構造としては、イベント発生時にシステム側から関数を呼び出す、いわゆる 「コールバック関数」 の仕組みを利用しています。HSPで言うところの 「強制gosub」 に近い挙動になります。

UserApp.cpp の実装例を見てみましょう。

基本的な書き方

HSPPPでは、ジャンプ先のラベルを指定する代わりに、関数(またはラムダ式) を登録します。

// 割り込みハンドラの定義(ラムダ式)
win.onclick([]() {
    mes("クリックされました!");
    return 0; // 関数なので必ずreturnで終わる
});

これだけで、ウィンドウをクリックした時に処理が走ります。

なぜ goto は廃止されたのか?

HSPPPには goto 命令が存在せず、ラベルジャンプによる制御ができません。これには、C++の機能を安全に使うためという絶対的な理由があります。

1. 「一方通行」vs「行って帰ってくる」

HSPの goto は、今やっている処理を放り出してどこへでも飛んでいけます。 しかし、C++のような言語は 「関数を呼び出したら、必ず戻ってくる(return)」 という規律の上で成り立っています。

2. 変数の後始末(デストラクタ)の問題

C++では、ブロック { ... } を抜ける瞬間にデストラクタという後始末機能が働き、メモリやリソースを自動的に片付けてくれます(RAII)。

もしHSPのように goto でブロックの外へ無理やり飛び出してしまうと、この後始末が行われません。 「メモリリーク」や「リソースの食いつぶし」が発生し、最悪の場合はプログラムがクラッシュしてしまいます。

3. だから「関数呼び出し」にする

そこでHSPPPでは、割り込み処理を 「C++の正規の関数(ラムダ式)」 として定義することにしました。

  • HSP: goto で飛んで stop (今の処理は放棄)
  • HSPPP: 関数を呼び出して return (処理が終わったら必ず戻る)

これにより、 「割り込み処理の中で宣言した変数は、処理が終わればきれいに消える」 という安全性が保証されます。HSPPPを使うことで、意識せずとも行儀の良いプログラムが書けるようになるのです。

実践的なサンプルコード

以下は、クリック回数のカウントと、押されたキーのコードを表示するサンプルです。 グローバル変数を使わず、スマートに記述できます。

import hsppp;
using namespace hsppp;

// 状態を保持する変数
int g_clickCount = 0;
int g_lastKeyCode = 0;

int hspMain() {
    auto win = screen({.width = 640, .height = 480, .title = "Interrupt Demo"});

    // [1] クリック割り込み (onclick)
    // ラムダ式の中に処理を閉じ込めることで、ラベル管理が不要になります
    win.onclick([]() {
        g_clickCount++;
        return 0;
    });

    // [2] キー入力割り込み (onkey)
    win.onkey([]() {
        // システム変数 iparam でキーコードを取得可能
        g_lastKeyCode = iparam(); 
        return 0;
    });

    // メインループ
    while (true) {
        win.redraw(0);
        win.color(255, 255, 255).boxf();
        
        win.color(0, 0, 0).pos(20, 20);
        win.mes("Click Count: " + str(g_clickCount));
        win.mes("Last Key: " + str(g_lastKeyCode));

        win.redraw(1);
        await(16);
    }
    return 0;
}

ポイント解説

1. システム変数の取得

HSPでおなじみのシステム変数 iparam, wparam, lparam も関数として実装されています。 上記の onkey の例では、iparam() を呼ぶことで、押されたキーの文字コードを取得しています。

2. エラーハンドリング (onerror)

HSPPPは onerror もサポートしています。 C++の例外機構と統合されており、HspError オブジェクトを通じてエラー番号や発生行を取得できます。

win.onerror([](const HspError& error) {
    // エラー発生時に呼び出される
    mes("Error code: " + str(error.error_code()));
    mes("Line: " + str(error.line_number()));
    return 0;
});

まとめ

HSPPPの割り込み機能は、 「C++の関数呼び出し」 の仕組みを利用しています。 これにより、HSPの手軽さを維持しつつ、C++の強力なメモリ管理機能を安全に使えるようになっています。

「gotoで飛んで終わり」ではなく、「関数を呼んで戻ってくる」スタイルに慣れることで、より堅牢でバグの少ないプログラムが書けるようになります。