どうやら AllocateSharedMemory がちょうど目的に適うもののようだけれど、2000 か XP 限定になってしまう。
Specifying Attributes of Variables 読め、私。
int sweeeet_shared_variable __attribute__((section("shared"), shared)) = 0;注意点:
某かの参考になるかと思って BBShelf のソースコードを見たら驚いた。インデントがスペース 3 個って何! 初めて見たよ。
簡単な英文法チェッカ。結構便利。
こういうのはちゃんとした理論に基づいてるというのは解るけれど、実際に動いているのを見ると何だか魔法みたいだ。それを云うと正規表現のパターンマッチングとか、コンパイラとかも同じようなものだけど。
C で try / except / finally のような例外処理ができたらなー。多重ループの内側から全てのループの外へ抜けるためにフラグを用意するようなまどろっこしさ。
#define TRY(X) if (setjmp((X)->jump_env) == 0) #define EXCEPT else #define RAISE(X) longjmp((X)->jump_env, !0)
とでもして。
typdef struct {
jmp_buf jump_env;
const char* error_message;
} CONTEXT;
void
rule_Command(CONTEXT* context)
{
...
if (am_i_sleeping())
RAISE(context);
...
}
void
rule_Script(CONTEXT* context)
{
TRY(context) {
...
rule_Command(context);
if (something_wrong())
RAISE(context);
...
} EXCEPT {
notice(context->error_message);
}
}いや、やっぱ気持ち悪い。
長らく ``Under development'' に居座ってた BBDropFTP がリリースされました。
本家 0.0.90 以降か xoblite bb1 以降でなければ動作しません。bbLean ではプラグインが利用している API がないのでロードできません。
ああ、思ったより時間がかかってしまった。という訳でもうちょっとましになった BBMouseGesture 及びその動作に必要なライブラリ。詳細は BBMouseGesture.rc を参照。前回のものと大した違いはありませんが。
BBController 0.07 がリリースされました。主な変更点は以下の通り。
exec() は約一名から需要があったようなので追加されたようです。0.06 は公開されていませんが、exec() が追加されていうというだけのようです。
いやしかし。確かに loop なり label @WindowClass なりは半ば冗談のつもりで BBKontroller に取り込みましたが、ほとんどジョークで実装した sound と --file を取り込まれるとは思わなかったです。
そのゲームってどう考えても R(ザー
何というか、こう、context を読めと。
ゲームには基本的な流れがあり、その中での駆け引きなり何なりを楽しむもの。そのゲームのコンセプトやバックグラウンドを見ると、「他のプレイヤーと助け合い、または競い合いながら冒険を繰り広げ」るのが基礎。冒険の過程での (プレイヤーの分身である) キャラクターの経験 / 成長を「モンスターとの戦闘」においているので、「戦ってモンスターを倒さなければ意味がない」のは当然のこと。弱いモンスターもいれば強いモンスターもいる。キャラクターはまだ駆け出しのひよっこなのだから、相手にできるのは弱いモンスターだけ。弱いといってもそれはモンスター全体からすれば弱いのであって、駆け出しのキャラクターにとっては十分強敵でしょう。その戦闘に具体的な描写はありませんが、代わりダメージやその他の要素の組み合わせで表現されているのです。
ただ、最初のうちはその戦闘に駆け引きも何も感じられません。それは当然でしょう。あなたの好きなスポーツを思い浮かべてください (観戦するのではなく自らプレイするものを。例えばテニスとします)。ある程度ゲームを楽しむためにはそれなりの修練が必要です。さらに極めるためにはより多くの修練が必要です。ルールもあまり知らず、初めてラケットを握ったばかりでテニスというゲームにおける「駆け引き」ができますか? まずはラケットの握り方から始めなければならないでしょう。それからラケットの振り方、球出し、サーブやレシーブが一通りできるようになってからでないとそもそもゲームになりません。
筆者が体験したゲームであってもそれは同じことです。聖職者あるいは他の職業に転職するまでの段階はその基礎部分です。転職できたとしても、それはまだある程度ゲームができるようになったというだけのことで、まだその先があるのです。しかし誰もが極めるまでプレイする訳ではありません。ある程度プレイして、肌に合わないと思えばやめればいいし、楽しければ続ければいいし、極めたければ極めればいい。
筆者は自ら進んで (「面白そうだ、プレイしてみたい」などと思って) そのゲームを始めた訳ではないだろうし、それ以前に「ゲーム」をプレイしない人種だと思われる。だから「ゲーム」をプレイする人種には斜め 135 度くらい傾いた見解の記事に見える訳だ。
でも「自分より弱いモンスターを叩きつぶす心理」はいくらなんでもずれすぎ。
(以下邪推)
また、そのゲームはプレイするためにある程度準備が必要なはず。クライアントのダウンロードとインストール、アカウントの取得、料金の支払い。それは筆者自ら行ったのでしょうか。誰かがお膳立てした環境でプレイしているように思えるのですが。そうでないとそのゲームの背景を知らないような文にはならないと思うのですが。
そもそも本当にそのゲームについて調べたんでしょうか。
ゲームによっては1カ月に何時間プレーしても1500円と安いことだ。
それはネットゲームにおいては中堅クラスの料金です。PS 某は 1100、F 某は 1280、G 某は 640、D 某は 1000、E 某は 980、P 某は 1200、L 某は 2000、U 某は 1980 (昔の数字なので今は変わっているかも知れませんが、大幅に違うということはないでしょう)。
まあ、それ以前に、
やり始めて3カ月、私には何がおもしろいのか、さっぱり分らない。
プレイしていることすら怪しい訳ですが。
(初めてのプレイから3 ヶ月経ったということでしょうけど)
技術的には共有セクションとファイルマッピングは同一のものらしい。Section Objects and Views を見ると、
「「セクションオブジェクト」は共有され得るメモリーのある部分を表す。プロセスは自分のメモリアドレス空間の一部 (メモリセクション) を他のプロセスと共有するためにセクションオブジェクトを利用することができる。セクションオブジェクトは、プロセスがファイルを自分のメモリアドレス空間にマップすることを可能にするためのメカニズムも提供する」
ということであり、File-backed and Page-file-backed Sections を見ると、
「全てのメモリセクションは共有されるべきデータを一時的または恒久的に格納できるディスクファイルによってサポート (「書き戻 ("backed")」) される。あなたがあるセクションを作成する際、そのセクションが書き戻されるファイルを指定することができる。そのようなセクションは「ファイルバックド (file-backed)」セクションと呼ばれる。書き戻すファイルを指定しなかった場合、そのセクションはシステムのページングファイルに書き戻され、そのセクションは「ページファイルバックド (page-file-backed)」セクションと呼ばれる。ファイルバックドセクションのデータは恒久的にディスクに保存することができる。ページファイルバックドセクションのデータは恒久的にディスクに保存されることはない」
ということなので、ディスクに書き戻されるかどうかの違いみたい。
プロセス間でデータを共有するにはどうすればいいか、のまとめ。
ざーっと検索してみたところ、方法としては以下の 3 つがあるらしい。
共有セクションが一番自然 (というか楽)。VC++ ならリンカの /SECTION オプション とか#pragma を使うと簡単にできるようなのですが、VC++ 使ってないのでアウト。gcc だとどうすればいいんだか。
ファイルマッピングは mmap(2) と同じこと (どちらも使ったことないけど)。誰かが CreateFileMapping でファイルマッピングオブジェクトを作り、それ以外のプロセスは OpenFileMapping でファイルマッピングオブジェクトを探す。共有するデータのアクセスには、まず MapViewOfFile で自分のアドレス空間に引っ張ってきて、要らなくなったら UnmapViewOfFile で削除する、と。ただし、グローバルフックで情報を共有する場合、OpenFileMapping や MapViewOfFile などの初期化作業はどうにかできるものの、後始末 (UnmapViewOfFile) が不可能 (初期化はフックプロシージャが初めて呼び出されたときに行えばいいものの、後始末は「フックの解除」が何時行われるか解らないから)。
じゃあ管理用のウィンドウを作って SendMessage などでメッセージを送受信してどうにかすればできないことはないけど何かアレ。
どの手段を取るにせよ、きっちり排他制御しないと危険。
どうでもいいことを思いついた。
(0) 適当な拡張子 (取り敢えず .style としよう) を決める。
(1) Explorer の「ツール > フォルダ オプション > ファイルの種類」で「新規」を選び、「ファイルの拡張子」に (0) で決めた拡張子を入力して OK を押す。
(2) 「登録されているファイルの種類」から (0) で決めた拡張子を選択し、「詳細設定」を押す。
(3) 「新規」を選び、「アクション」に適当な名前 (「Apply」や「スタイルを適用する」) を入力し、「アクションを実行するアプリケーション」に「PATH_TO_BBKONTROLLER\BBKontroller.exe setstyle "%1"」と入力する。
(4) OK を連打する。
BB4W が起動していれば、拡張子が .style のファイルを開くとそれが適用されます。
(だからどうした)
MOUSEHOOKSTRUCTEX の定義がヘッダファイルにない!
typedef struct {
MOUSEHOOKSTRUCT MOUSEHOOKSTRUCT;
DWORD mouseData;
} MOUSEHOOKSTRUCTEX, *PMOUSEHOOKSTRUCTEX;
こういう風に書かれると、通常のデータは ((MOUSEHOOKSTRUCTEX*)wparam)->MOUSEHOOKSTRUCT.some_member のようにアクセスしなければならないように見える。でも
The members of a MOUSEHOOKSTRUCT structure make up the first part of this structure.
ということは
typedef struct {
/* members of MOUSEHOOKSTRUCT */
POINT pt;
HWND hwnd;
UINT wHitTestCode;
ULONG_PTR dwExtraInfo;
/* unique member of MOUSEHOOKSTRUCTEX */
DWORD mouseData;
} MOUSEHOOKSTRUCTEX, *MOUSEHOOKSTRUCTEX;実際にはこのような定義ということか。かなりアレだと思うが、上記の構造体を自分で定義して、MouseProc で受け取った LPARAM を MOUSEHOOKSTRUCTEX* にキャストしたものの mouseData を垂れ流してみたところ、どうやらこれでも構わない模様。X2 を押すと 0x0020000、ホイールを上下に動かすと 0x00780000 や 0xFF880000 (使っているのはボタンタイプのホイールなので 120 や -120) が渡されている。
WM_MOUSE_LL / LowLevelMouseProc を使っても情報は取得できるけど、そうすると根本的な動作すら NT 4.0 SP3 以降限定になってしまうのでアレ。
それに LowLevelMouseProc は a new mouse input event がスレッドの input queue にポストされる際に呼び出されるので、LowLevelMouseProc の中で mouse_event を呼び出すとシステム巻き添えで死ぬと思われる (mouse input は local mouse driver や mouse_event の呼び出しから発生するから)。MoueProc の場合はアプリケーションが GetMessage や PeekMessage を呼び出す際 (かつマウスメッセージがある場合) に呼び出されるので大丈夫……だと思う
(以下頭が混乱しているので注意)
(MouseProc が呼び出した mouse_event でポストされたイベントは MouseProc が終了するよりも早く GetMessage で取り出される可能性はない。GetMessage が呼び出されたときに MouseProc が呼び出されるので、呼び出し元に制御が戻っていないから。メッセージキューはスレッドごとに存在するのでマルチスレッド云々が絡んでも無問題 (PeekMessage は解らない。いや、PeekMessage は即座に制御が戻ってくるとはいえ、MouseProc の呼び出しから戻ってこない限りは PeekMessage も戻ってこないので同じことか) [1]。そもそもマウスやキーボード絡みのイベントは一旦システムのメッセージキューに送られ、その後現在アクティブなウィンドウ (を生成したスレッド) のメッセージキューに送られるので、二つ以上のスレッドから並行 / 並列して MouseProc が呼び出されることはない……多分ないと思う)。
[1] つまりこういう制御の流れになるから。
GetMessage
PeekMessage MouseProc mouse_event
|*| | | | |
|*| | | | |
|*-------------->*| | |
| | |*| | |
| | |*-------------->*|
| | | | |*|
| | |*<--------------*|
| | |*| | |
|*<--------------*| | |
|*| | | | |
|*| | | | |全然関係ないけど、人間の指が標準で六本とか七本とかだったら標準的なマウスのボタンの数もそれに比例して増えたりしたのだろうか。五本なら最大でも五個までしか扱えないし。いや、でも数が増えても使いこなせないだろうから大して変わらないか。
mouse_event で使う X ボタン関連の定数。片っ端から適当な値を指定して試し、以下の値で動作を確認しました (実験したのは Windows XP)。
MOUSEEVENTF_XDOWN 0x0080 MOUSEEVENTF_XUP 0x0100
(以下余談)
4 以上ボタンのあるマウスの話。私はそういうマウスを持ってないし実物を見たこともないので 4 つ目以降のボタンを扱うときに少々困る。で、先程 mouse_event を使えばエミュレートできることに (ようやく) 気付いた。
しかしヘッダファイルに X ボタン関連の定義が見つからない。
WM_XBUTTONUPWM_XBUTTONDOWNXBUTTON1XBUTTON2これらは BB4W のソースコードに書かれていたし、実験してみたら正しいようだった。
だけど MOUSEEVENTF_XDOWN と MOUSEEVENTF_XUP が見つからない。grep してみると以下のものは見つかるのですが (値の部分は見やすいように変えてるので実際の出力とは違います)。
MOUSEEVENTF_MOVE 0x0001 /*1*/ MOUSEEVENTF_LEFTDOWN 0x0002 /*2*/ MOUSEEVENTF_LEFTUP 0x0004 /*4*/ MOUSEEVENTF_RIGHTDOWN 0x0008 /*8*/ MOUSEEVENTF_RIGHTUP 0x0010 /*16*/ MOUSEEVENTF_MIDDLEDOWN 0x0020 /*32*/ MOUSEEVENTF_MIDDLEUP 0x0040 /*64*/ MOUSEEVENTF_WHEEL 0x0800 MOUSEEVENTF_ABSOLUTE 0x8000 /*32768*/
検索してみると値を書いてあるところがいくつか見つかるのですが、
MOUSEEVENTF_MOVE 0x0001 MOUSEEVENTF_LEFTDOWN 0x0002 MOUSEEVENTF_LEFTUP 0x0004 MOUSEEVENTF_RIGHTDOWN 0x0008 MOUSEEVENTF_RIGHTUP 0x0010 MOUSEEVENTF_MIDDLEDOWN 0x0020 MOUSEEVENTF_MIDDLEUP 0x0040 MOUSEEVENTF_WHEEL 0x0080 /*!*/ MOUSEEVENTF_XDOWN 0x0100 /*!*/ MOUSEEVENTF_XUP 0x0200 /*!*/ MOUSEEVENTF_ABSOLUTE 0x8000
こんな感じ。値が違う。実際にこの値で試してみたところ、どれも正しくなかったし (ただ、この定数が書かれていたところは VB 関連のところばかりだったのでそれが関係して値が違う……にしてはおかしいよな。値は &Hxxx と書かれていたけど、あれは 16 進数の表記のはず)。
という訳で 14 日 のアレな奴をそれなりに体裁を整えてみたもの。詳細は同梱の BBMouseGesture.rc 参照 (euc-jp, lf)。
Amazon.co.jp から注文してた品が到着。Compilers: Principles, Techniques, and Tools。翻訳よりも原書の方が楽しいです。
BBMouseGesture:
デスクトップ上でなければジェスチャーが認識されないことと、ろくに割り当てれるコマンドがないこと以外はそれなりに。
欠点:
File mapping とやらがそれっぽい。
先ず file mapping object を作成するために CreateFileMapping を呼び出す (このとき適当に名前をつけておく)。そして CreateFileMapping が返した file mapping object へのハンドルを、そのプロセスのアドレス空間にファイルの view を作成するために MapViewOfFile を呼び出すときに使う。MapViewOfFile は file view へのポインタを返す。
次に上記と同じ file mapping object を使うために、上記でつけたものと同じ名前を用いて OpenFileMapping を呼び出す。そして同様に MapViewOfFile を呼び出して file view へのポインタを得る。
で、使わなくなった file view は UnmapViewOfFile でアンマップする。同様に file mapping object は CloseHandle でクローズする。これらの呼び出しの順序はどうでもいい: 先に file mapping object をクローズしても、アンマップされていない file view が残っていれば生き残ったまま。最後の file view がアンマップされたときにクローズされる。
さて、それをどう読み書きしろと。ポインタ経由で読み書きすればいいだけの話のようだけど、それってすんえg−めんどkさい。読み書きの際にやたらと例外が起こる (可能性がある) ようだし。
取り敢えず、ファイル (手抜きパースのための書式) から動的に設定 (LeftWorkspace か RightWorkspace を割り当てれるだけ) を読み込めるようにしてみたが。
あれだ、スレッド (というかプロセスか) が異なってると単純にグローバル変数に置くだけでは設定が共有されない訳だ。設定のロード / アンロードは BB4W のプロセスで行われるから、BB4W の上 (つまりデスクトップとか) でないと実質ジェスチャーが効かない。
つまりプロセスを超えても情報を共有しなければならない訳だが、それにはどういう方法があるんだか。仲介役ウィンドウを作成して PostMessage、SendMessage、WM_COPYDATA とかいうアホな方法しか思い浮かびません。というか他の方法を知らない。少し調べてみます。
あー、後、なかったことにしてたイベントの再生が一部おかしい気がする。
めも。4 つ目や 5 つ目のボタンのこと。
WM_XBUTTONUP (0x020B)WM_XBUTTONDOWN (0x020C)wParam の上位ワードが XBUTTON1 (0x0001)wParam の上位ワードが XBUTTON2 (0x0002)gcc on Cygwin で該当する定義がないこと、WM_XBUTTONDOWN 等の説明を見ると「上位ワードはどのボタンがダブルクリックされたかを示す」とあるけどそれは意味が違わないかということ。
定数調べるのと、それが正しいのか確認するのに苦労した。自分で確認しようにも 5 ボタンマウスなんて持っていないですから。
Littlewitch の通販がー! 何か素敵なことになってるー!
先日までと違い、クレジットカード、銀行振込、代金引換が可能に。駄目だ、そんなことになったら買うしかないじゃないか (郵便局に行くのは面倒。大した手間じゃないけど)。
再生云々は解決。今のところ問題は起きてないけど、そのうち問題起きそうな気がする。
どのボタンのシングルクリックでも、一旦スタックに積んでからジェスチャーか否か判断しているため、ボタンが押されたときに何らかの動作をするものはボタンを離したときに動作するようになってしまう。
例えばアクティブなウィンドウを切り替えるためにクリックした場合、ボタンを押したとき切り替わるのだけれど、これがボタンを離したときに切り替わるようになってしまう (正確にはボタンを押しても離すまでは押されなかったことにされ、ボタンを離すと「ボタン押す」「ボタン離す」をしたということにされる)。
タイムアウトを導入しても少々アレな操作感だろうな。一部のボタンは押してもジェスチャーの開始とみなさないようにすればある程度は解決するが。
あ"ー、ひょっとして mouse_event って即座に戻ってきますか。mouse_event、MouseProc、mouse_event、MouseProc の無限ループを想像していたのだけれど、非同期なら状態を表す変数を追加したところで無意味か。
ああ、そうそう。これは不具合と見るか微妙な点だが、mouse_event で MOUSEEVENTF_LEFTXXX または MOUSEEVENTF_RIGHTXXX を送ると、それは物理的に左あるいは右にあるボタンに対応するイベントが送られる。例えば、Windows 側でマウスの左右のボタンを入れ替えてる設定にしてると、mouse_event で MOUSEEVENTF_LEFTXXX を送ると WM_RBUTTONXXX が発生する。
上記の無限ループの回避だけど、グローバル変数一つ作って状態を感知すればいいか。しかし、mouse_event とかはどのウィンドウへイベントを送ってるんだ? フックプロシージャを呼び出したスレッドに属するウィンドウだとは思うが、場合によってこの解決策が無効になったりしないだろうか。一つのスレッド内で完結してればいいんだが。
ジェスチャーを、(1) 何かボタンを押す; (2) マウスを動かす (これは省略可); (3) ボタンを離すか別のボタンを押す; というパートに分けている。で、今のコードがシングルクリックもジェスチャーとして扱っている。
さすがにそれはアレなのでシングルクリックはジェスチャーとして扱わないようにしているのだけれど、問題はそのクリックをどう再生するか (再生しないとクリックがなかったことにされるのでマウスで操作できなくなる)。マウスイベントを起こすには mouse_event を使えばいいのだけれど、単純に XButtonDown、XButtonUp を再生すると、再生したマウスイベントを自分自身が捕まえてしまう。これで何が起きるかというと、シングルクリック発生、ジェスチャーとして扱わないのでそれを再生、シングルクリック発生、ジェスチャーとして扱わないのでそれを再生、シングルクリック発生、ジェスチャーとして扱わないのでそれを再生、しんぐr(ザー
一回ハングアップしかけたのは秘密。
あるアプリケーション (例えば rxvt) は PM_NOREMOVE を付けて PeekMessage してくる。PeekMessage でメッセージが存在するかどうか確認してから GetMessage するのだ。で、MouseProc に渡される nCode が HC_ACTION か HC_NOREMOVE か考慮せずに処理している場合、そのアプリケーション上でマウスを操作すると MouseProc が Peek と Get の計 2 回呼び出されることになる。
つまり、左ボタンを押すと 2 回押されたと判断してしまう訳だな、これが。nCode == HC_ACTION 以外は処理しないことにすれば Ok。
int stack[13];
int sp = -13;
#define arraysizeof(a) (sizeof(a) / sizeof((a)[0]))
int push(int v)
{
if (arraysizeof(stack) - 1 <= sp)
return 0;
sp++;
stack[sp] = v;
return !0;
}和訳: sizeof の型は size_t; size_t は夢符号整数、じゃなくて無符号整数; int は unsigned に格上げされる; sp が負である限り上記条件式は真。
あー、bbLean だと @BBCfg.plugin.load PLUGIN_NAME でプラグインのロード / アンロードができるか。プラグインのテスト時にピン留めしたメニューをいちいちマウスで操作してたよ。コマンドラインからなら BBKontroller 使えばいい。BBKeys 使うほどでもないが、かといってマウスは使いたくない。
WW&F、面白い。気が付いたらもう三話まで進んでた。
そういえば、話の根本に関わるような選択肢がまだ出てこないな。二週目以降に出てくるのか、それともまともな選択肢が存在しない (例えば終ノ空) か。
ああ、そうそう。選択肢がまともに選べないのはどういうことなんだろう。カーソルを移動してもフォーカスが最も下の選択肢に移動するんですが。序章から三話予告までの間では予告でしか選択肢が出てこず、しかもそれはどうでもいいものだったので、選択肢が選べないのは演出の一つかと思っていたのだけれど、本編の途中で出てきた選択肢も選ぶことが不可能に近かったので演出でなくアレ (どれ) なのかという疑いが。
公式サイト見ても何もないし。
そうそう。スレッド間でグローバル変数が共有されていないため、CallNextHookEx で渡すフックハンドルがかなりアレなままだ (BB4W のスレッドから呼び出された場合は有効なフックハンドルを渡しているが、それ以外のスレッドから呼び出された場合は初期値の NULL を渡している)。
ただ、これは別に NULL を渡しても問題ないらしい (Win32API 質問箱の 245 から 258 を参照) ので、常に NULL を渡すようにしてもいいかな。
さて、クリックするとグローバル変数に格納されているフックハンドルの値が何故か変更される現象についてだが、まずは以下のログを見てもらおう。
BBMouseGesture: beginPlugin BBMouseGesture: G_hhook: 0x13E5040F, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x13E5040F, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x13E5040F, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_gesture_input_stack: BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_hhook: 0x00000005, GetCurrentThreadId: 0x00000E2C BBMouseGesture: G_gesture_input_stack: L BBMouseGesture: endPlugin BBMouseGesture: UnhookWindowsHookEx BBMouseGesture: フック ハンドルが無効です。
右ボタンか左ボタンが押されると値が変更される。しかし変数 (G_hhook) を代入している場所は SetWindowsHookEx 呼び出し時以外にない。
ここで G_hhook の変化後の値に注目してみる。0x00000005 って微妙な値は何なんだ。右ボタンが押されると必ずこの値に変更される。左ボタンの場合は 0x00000004 だ。
ふと思って G_gesture_input_stack (ジェスチャーの入力を溜めておくところ) 周りを見直してみると原因が解った。
G_gesture_input_stack の型はジェスチャーの入力の種類 (上下左右等) を表す enum にしていたのだけれど、4 と 5 がちょうど左ボタンと右ボタンを表す定数と一致している。G_gesture_input_stack に溜められ、ジェスチャー終了と共にその内容を確認 / 関連する動作を実行、という形にしているのだが、G_gesture_input_stack の初期化がジェスチャー終了後にしか行っていなかった。G_gesture_input_stack の中で有効な一番上の値の位置を示すインデックス (G_gesture_input_stack_pointer) の初期値は、わざと -13 という妙な値にしていた。で、フック開始後、最初のジェスチャー開始時に妙な初期値のままのインデックスを用いて入力をスタックに積んだため、逆方向のバッファオーバーランらんらーんらーんらーん。
かなりナイスな位置に上書きしてくれた。楽しい。
ちなみにバイナリを調べてみると、
$ nm BBMouseGesture.dll | grep _G_ 10004060 b _G_gesture_input_stack 10003004 d _G_gesture_input_stack_pointer 10004040 b _G_gesture_state 10004030 b _G_hhook 10004020 b _G_hwnd_core 10004050 b _G_mouse_state 10003000 d _G_option_notice
enum は (多分) int と同じバイト数、そして利用している環境での int は 4 バイトなので、
13 * 4 == 0x0C * 0x04
== 0x0C << 2
== 0x30
&(G_gesture_input_stack[0]) - &G_hhook == 0x10004060 - 0x10004030
== 0x00000030傑作だ。
何故か UnhookWindowsHookEx するときに「無効なフックハンドル」と怒られる。しかしフックは生きてる。何故だ。
どうなんだろうな。beginPlugin で SetWindowsHookEx し、endPlugin で UnhookWindowsHookEx してるのだけれど、その際、フックハンドルはグローバル変数に格納……してるのが悪いのかな。(1) beginPlugin が呼ばれたときと別のスレッドから endPlugin が呼ばれる; (2) UnhookWindowsHookEx に渡されるフックハンドルは未初期化; (3) UnhookWindowsHookEx は失敗するが、フックは生きたまま。
仮にそうだとしよう。ならば何故このエラーが今まで起きなかったのか。フックハンドルの格納云々の部分は変わってないのに。
試しにフックをかける / 外すコードだけを残してみたところ、BB4W 以外のスレッドからフックプロシージャが呼ばれるとグローバル変数に格納されているフックハンドルの値が未初期化なのは確認できた。しかし endPlugin が BB4W 以外のスレッドから呼ばれることはありえないと思うんだが。メニューから動的にロード / アンロードするにしろ、restart するにしろ、endPlugin は BB4W 以外のスレッドから呼び出されないはず。
BBMouseGesture: beginPlugin BBMouseGesture: G_hhook: 0x19390329, GetCurrentThreadId: 0x00000A3C BBMouseGesture: G_hhook: 0x19390329, GetCurrentThreadId: 0x00000A3C BBMouseGesture: G_hhook: 0x19390329, GetCurrentThreadId: 0x00000A3C BBMouseGesture: G_hhook: 0x19390329, GetCurrentThreadId: 0x00000A3C BBMouseGesture: G_hhook: 0x00000000, GetCurrentThreadId: 0x00001538 BBMouseGesture: G_hhook: 0x00000000, GetCurrentThreadId: 0x00001538 BBMouseGesture: G_hhook: 0x00000000, GetCurrentThreadId: 0x00001538 BBMouseGesture: G_hhook: 0x00000000, GetCurrentThreadId: 0x00001628 BBMouseGesture: G_hhook: 0x00000000, GetCurrentThreadId: 0x00001628 BBMouseGesture: G_hhook: 0x00000000, GetCurrentThreadId: 0x00001628 BBMouseGesture: G_hhook: 0x19390329, GetCurrentThreadId: 0x00000A3C BBMouseGesture: G_hhook: 0x19390329, GetCurrentThreadId: 0x00000A3C BBMouseGesture: G_hhook: 0x19390329, GetCurrentThreadId: 0x00000A3C BBMouseGesture: endPlugin
ちなみにフックの解除に失敗してフックが生き残ったままになると、一度ログオフしない限りフックを解除できない。まあ、ログに吐かせたハンドルの値を直接放り込めば解除できそうな気がしないでもないが。
WW&F、序章まで進めての感想。
FlipRL と FlipLR (左クリックの後右クリック、等) は楽だけど、GestureUp、GestureDown とかはどう判断したらいいんだ。判断するためのパラメータって何があるよ。
贅沢を云えばきりがないので、取り敢えず Opera と同等のレベルのジェスチャーの判断はできるようにしたい。
あと exclusion が要るな。上記のテスト版だと Opera の上でジェスチャーすると Opera とプラグインの両方の動作が実行される。無条件に片方を優先するとそれはそれで不便なので、ジェスチャーによって優先順位を変える、とか。面倒。
MouseProc の説明見たら書いてた。
nCode が 0 未満ならフックプロシージャは CallNextHookEx を呼び出して、その戻り値をそのまま返さなければならない (must)。
nCode が 0 以上でフックプロシージャがメッセージを処理しない場合、CallNextHookEx を呼び出してその戻り値をそのまま返すことが強く推奨される; そうでないと WH_MOUSE フックを設定した他のアプリケーションはフックの通知 (hook notifications) を受け取らないため、結果として誤った動作をするかも知れない。フックプロシージャがメッセージを処理した場合、非 0 の値を返すことでシステムが対象のウィンドウプロシージャへメッセージを渡すことを妨害してもよい (may)。
……らしいです。はい。ということは特定条件下 (メッセージの送り先がコア、かつジェスチャー実行後) で WM_RBUTTONUP を受け取った場合に非 0 を返せばいいのか。
激しくテスト版。マウスの右ボタン押しつつ左ボタンを押すと左の、マウスの左ボタン押しつつ右ボタンを押すと右のワークスペースへ移動します。あとボタンを押しつつカーソルを他のウィンドウに移動させるとアレなことになるのもご愛嬌 (クラッシュはしませんが)。
デスクトップ上で操作するとメニューが表示されてしまうのはご愛嬌。
いや、やっぱり果てしなく面倒。現在の状態をどう保存 / 参照しろと。各所でフックプロシージャが呼ばれるということは、ウィンドウを跨いでジェスチャーすることは不可能。
んじゃー情報保持用のウィンドウでも作っておいて SendMessage でもしろと。でも何かそれはアレだよな。プロセス間でデータを共有するにはどうすればいいのさ。
いやまあ、それは後で解決すればいいや。
むぅ。フックプロシージャをプラグインと同じ DLL に突っ込んでると、その中で BB4W の API 使っている場合、他のウィンドウへフックしているメッセージが送られる際に「Blackbox.exe が見つかりません」となる。それは当然だし解決方法は解っているので FindWindow で誤魔化して後回し。
で、問題は BB4W 以外のウィンドウ上で動作しない……と思ったらそれは当然だ。(多分) DLL は別のプロセスから参照されていて、状態を保持している変数はプロセスローカル。
いや、それは違うな。コアへのウィンドウハンドルをグローバル変数にキャッシュしてるが、それはプロセスローカル。キャッシュは BB4W のプロセスだけ有効であって、それ以外のプロセスではキャッシュされていない。よって BB4W 以外のプロセスへのメッセージのフック時には存在しないウィンドウへ PostMessage しているので動作しない。
試しに必要時にコアへのウィンドウハンドルを探すようにしたら動いた。
これでお膳立てはできたに等しい。後は GestureUp や FlipForward 等の判断さえできれば Ok。
あー、そういえばフックしたメッセージをなかったことにするにはどうすればいいんだろう。これができないとデスクトップ上でジェスチャーした後にメニューが表示されて困るんだが。
取り敢えず今日はもう頭回らないので続きは明日。
いや、別に分けなくてもいいか。どうせプラグイン自体が DLL だし、自身にまとめればいい。
ところで、フックとは直接関係ないのだけれど、プラグインのデバッグのために、WM_COPYDATA 経由で受け取った文字列を表示する単体の実行ファイルを作って、それ経由で情報を表示させようとしているのだけれど、リアルタイムで表示されたりされなかったりする。表示されない場合は、表示の仕方からしてフルバッファリングされてるようなのだけれど、再現条件が掴めないので何が原因か解らない。
と思ったら解ったような解らないような。何もオプションを付けずにコンパイルしたら思った通りに動作した。ということは -mwindows とかその辺りが原因なのか。
今試してみたら -mwindows または -mno-cygwin を付けてコンパイルしたものは駄目だった。
一先ず thread-specific フックはかけれた。次、グローバルフック。
面倒だなー。DLL ロードして、その中の関数の位置を取得して。で、CallNextHookEx するときに自分のフックハンドルを渡す訳だが、それってどこから持ってくればいいんだ。フックハンドルはフックの設定 / 解除側が持っていて、フックプロシージャがある DLL ではそんなものは見ることはできない。VC++ だと #pragma でどうにかできるようなのですが、VC++ なんて持ってないし、第一 #pragma は Hack や Rogue を起動するためのもの。
設定 / 解除側に get_hook_handle とか作っておけと。
めも: -Wl,--out-implib=libMODULE.dll.a
目的は他のウィンドウ上であってもマウスジェスチャーを機能させることなので、結局フックを使わなければならないか。しかもグローバルフック。
取り敢えずフックをかけることから始めるか。
ぬぅ。SetCapture を使えばウィンドウの範囲外のマウスイベントも取得できるものの、それはキャプチャーするウィンドウでマウスボタンを押してから、ボタンが離されるまでの間限定の模様。しかも他のウィンドウの上でボタンを押すとキャプチャーは解除されるし。
何となく WM_CAPTURECHANGED を受信した際に再度 SetCapture するように (つまりキャプチャーの解除を抑制) してみたところ、BB4W を何故かマウスで操作することができない。
何故かと思って SetCapture の説明を見直したら原因が解った。SetCapture は指定されたウィンドウが属するスレッドのマウスイベントをキャプチャーする。で、BB4W はシングルスレッドで動く。つまり BB4W が受け取るべきマウスイベントを全て実験用のプラグインが奪ってしまうため、BB4W がマウスで操作できない状態に陥ってしまっていた。
実際には SetCapture を再設定しなくても同じ状態になっているのだけれど、その場合は他のウィンドウをクリックすればキャプチャーが解除されるので気付きにくかっただけの模様。
実験用のプラグインのロード / アンロードはメニューから行っているのですが、そのメニューが操作できなくて笑えた。メニューをキーボードで操作しようとしても、そのためにはまずメニューにフォーカスを移さなくてはならず、しかしそのフォーカスはどう移せと (該当メニューはピン留めしていた)。
まあ、どっちにしろマウスキャプチャーは使えないのですが。
システムは通常、マウスイベントが発生した際にカーソルのホットスポットを含んでいるウィンドウへマウスメッセージを送る。アプリケーションはこの振る舞いを SetCapture 関数を用いることで指定されたウィンドウへマウスメッセージを回すように変更することができる。指定されたウィンドウは、アプリケーションが ReleaseCapture を呼ぶか、別の capture window を指定するか、あるいはユーザーが別のスレッドによって作られたウィンドウをクリックするまでの間、全てのマウスメッセージを受け取る。
マウスキャプチャーが変更される際、システムは WM_CAPTURECHANGED メッセージをマウスキャプチャーを失ったウィンドウに送る。メッセージの lParam パラメータはマウスキャプチャーを得たウィンドウへのハンドルを指す。
フォアグラウンドウィンドウだけがマウスの入力をキャプチャーできる。バックグラウンドウィンドウがマウスの入力をキャプチャーをしようとした場合、ウィンドウの見える範囲内にカーソルのホットスポットがある場合に発生したマウスイベントしか受け取らない。
マウスの入力をキャプチャーすることはウィンドウがカーソルがウィンドウの外へ移動してもマウスの入力を全て受け取らなければならない場合に有用である。例えば、マウスボタンが押されてから離されるまでの間のカーソルの位置を追跡する場合などである。
スレッドは GetCapture 関数を使ってどのウィンドウがマウスをキャプチャーしているかどうかを調べることができる。もしそのスレッドのウィンドウのどれもマウスをキャプチャーしていない場合、GetCapture はそのウィンドウへのハンドルを探す。
bbEdgeFlip は画面の左右端に透明なウィンドウを作成して、WM_MOUSEMOVE が来るとワークスペースを変えている。で、WM_MOUSEMOVE はカーソルが自分の上にないと来ない。つまり、画面全体を覆う透明なウィンドウを作成すればいい。
……いい訳がない。本当に透明なウィンドウ (透明度 0) を作成すると WM_MOUSEMOVE は来ないし、かといって限りなく透明なウィンドウ (透明度 1) を作成すると重い。しかも WM_MOUSEMOVE が確実に拾えるようにウィンドウを最前面に持ってきたから実質マウスで操作不能に陥る (クリックしても、それは透明なウィンドウをクリックすることになるのでアプリケーションの切り替えなどができない)。
じゃあカーソルの位置に関係なくマウスの動きを感知するにはどうすればいいんだ。
特定のバージョンの Windows にしかない API を使う場合は windows.h を #include する前に WINVER や _WIN32_WINNT を定義しておく。
Opera みたいなマウスジェスチャーってどう実装してるんだ。Opera の場合、マウスジェスチャーの設定は
これらの組み合わせに何らかの機能を割り当てているけれど、それはどう判断してるんだ。WM_MOUSEMOVE を監視して、移動量に応じて上下左右を判断してるのか。
それならフックなんて要らないよな。
I've released BBKontroller 0.1.4.
sound command to play .wav file.sound コマンドを追加してみた。あーあ、バイナリがすごいサイズになってるよ。一応、BBController よりは低いけど、20KB は大きすぎ。確か最初はどちらも 7KB 前後だった気がする。
$ ll *.exe *.dll -rwx------ 1 kana なし 7168 Mar 11 23:08 BBKontroller-Proxy.dll -rwx------ 1 kana なし 22016 Mar 11 23:08 BBKontroller.exe -rwx------ 1 kana なし 17920 Mar 11 23:08 BBMayuKon.dll
というか BBController は何であんなにサイズが大きいんだ。試しに strip してみたら 30KB に減ったけど、それでも大きいよな。
第六話「0 歩進んで 0 歩下がる」
token = strtok(s, DELIMITERS); /* FIXME: not thread-safe */
while (token != NULL) {
char** tmp;
tmp = (char**)realloc(av, sizeof(char*) * (ac+1));
if (s == NULL) return 0;
av = tmp;
av[ac] = strdup(token);
if (av[ac] == NULL) return 0;
++ac;
}和訳: 次のトークン読めよ。
ちなみに実行すると無限ループかつメモリイーターなので死ねます。緩やかに蝕まれるのでまだマシでしたが (秒速 10M とか食われたことがある。その場合は気付いても対処するには遅すぎる)。
function ToggleOption(opt_name)
let s:s = "if &" . a:opt_name . "\n"
let s:s = s:s . " let &" . a:opt_name . " = 0\n"
let s:s = s:s . "else\n"
let s:s = s:s . " let &" . a:opt_name . " = 1\n"
let s:s = s:s . "endif"
execute s:s
unlet s:s
endfunctionこれで、
:call ToggleOption("OPTION")とすれば OPTION の on/off を切り替えられる。取り敢えず以下のように割り当ててみたり。
map \ :call ToggleOption("wrap")<CR>vim すくりぷとは触ったことないに等しいのでこれでいいのかどうか。
「こんなミスをしてしまったのでメモってやる!」第四話。
buf = NULL;
if (buf == NULL) {
error_handling();
}和訳: 何だその条件式は。
「こんなミスをしてしまったのでメモってやる!」第五話。
char* buf; buf = (char*)malloc(sizeof(char) * (size + 1)); size++; buf[size] = _no; /* (i) */
和訳: (i) の時点で有効な添字の範囲は 0 <= index < size。バッファオーバーらんらんらーんらーんらーん。
I've released BBKontroller 0.1.2.
loop command like BBController.--file option to read any commands from a specified file.指定されたファイルからコマンドを読み込めるようにしてみた。やろうと思えば、
#!/usr/local/bin/BBKontroller
loop 2
setstyle PATH_TO_STYLES\clovers
softpause 2
setstyle PATH_TO_STYLES\hydrangea
softpause 2
endloopこういうファイルを書いて実行できてしまう。
char* buf;
char* tmp;
buf = NULL;
size = 0;
while (_no < "AAAAAAAAAARRRGGGG! ARGV! ARGC!") {
something_a();
tmp = (char*)realloc(buf, sizeof(char) * (size+1+1)); /* (i) */
buf[size] = *_no;
something_b();
++size;
}和訳: (i) の次に以下の行が足りない。
buf = tmp;
I've released BBKontroller 0.1.0. Fully recoded.
初めてこんなミスをしてしまったのでメモってやる!
C 言語にて:
new_vector = (VECTOR_ELEM**)realloc(vector, vector->size + 1);
動く訳がない。正しくは:
new_vector = (VECTOR_ELEM**)realloc( vector, sizeof(vector->elems[0])
* (vector->size + 1) );BBController 0.04b がリリースされました。
loop と endloop の追加 (loopn xxx で n 回 xxx を実行)label() の追加 (label(xxx) で xxx をツールバーのラベルに表示。@WindowClass とすれば該当するウィンドウのタイトルを表示)ということらしいのですが、少なくとも bbLean 1.12 では loop 以外動きませんでした。
そもそもどうやって他プロセスから直接 bro@m を送れるんだろう?
ちなみに、ブランチによってまちまちですが、コアのウィンドウプロシージャは WM_COPYDATA を処理したりしなかったりです。bbLean は WM_COPYDATA を受け取るのですが、
case WM_COPYDATA:
{
int w = ((PCOPYDATASTRUCT) lParam)->dwData;
int i = ((PCOPYDATASTRUCT) lParam)->cbData;
void *data = ((COPYDATASTRUCT *) lParam)->lpData;
if (w >= BB_MSGFIRST && w < BB_MSGLAST)
MainWndProc (hwnd, w, *(WPARAM *) data, (LPARAM) ((WPARAM *) data + 1));
return TRUE;
}このように受け取ったデータを少し細工してから自分自身で再び処理しています。それを利用して本体に WM_COPYDATA すっ飛ばして bro@m を送る、という芸当はできそうにないし。
いやまあ、WM_COPYDATA で送る文字列の先頭に sizeof(WPARAM) バイト分だけ詰め物すればできなくはないだろうけど、不確実な上に気持ち悪い。
bbEdgeFlip 0.0.1 がリリースされました。
マウスカーソルを画面の左 / 右端に持っていくとワークスペースを左 / 右に切り替えるプラグイン。
便利な人には便利そう。私の場合、基本的にマウスは大抵画面隅に放置プレイしてるので使えないですが。
Littlewitch、どうにかしてる。
今、次回作「Quartett!」の体験版を少し起動してみたのですが、前作「白詰草話」に比べて磨きがかかってるというか何というか。
その、なんだ、あまりに滑らか過ぎて怖い。1 倍速の自動送りにしてると、既にゲームではなくムービーになっている。「一定時間操作しなかったらボタン / マウスカーソルを消去」できる (このオプションは白詰草話にはなかったと思う) ので、もう何と云っていいやら。
ああ、どうしよう。買いそう。
I've released BBKontroller 0.0.4. Added some commands.
Python の文字列オブジェクトの実装を見てたのですが、C の文字列から Python の文字列オブジェクトを作成する関数が、
op = (PyStringObject *)
PyObject_MALLOC(sizeof(PyStringObject) + size * sizeof(char));
if (op == NULL)
return PyErr_NoMemory();
PyObject_INIT_VAR(op, &PyString_Type, size);
op->ob_shash = -1;
op->ob_sstate = SSTATE_NOT_INTERNED;
if (str != NULL)
memcpy(op->ob_sval, str, size);
op->ob_sval[size] = '\0';
となっていて、これは単に PyObject_MALLOC で必要な分だけメモリを確保して、そこに元と成る文字列をコピーしてるだけです。
問題は、PyStringObject の定義が、
typedef struct {
PyObject_VAR_HEAD
long ob_shash;
int ob_sstate;
char ob_sval[1];
} PyStringObject;
つまり、PyStringObject で最低限必要な分 + コピーする文字列の分のメモリを連続した領域に確保しておいて、ob_sval を size + 1 の長さの配列として扱っている、ということ。
……いやいや、確かに char* ob_sval という風にして、それを別に割り当てるよりは遥かにスマートなのだけど、こういう使い方をしていいのかな?
一見して問題ないように思えるけれど (実際 Python を使ってて問題が起きたことはないけれど)、これは C の言語仕様上問題ないという保証はあるのかどうか。