enumなんとかをfor..inで、その1

for..in文法は、使う側は便利ですが、作る側はコールバック関数のほうが便利です。
そんなわけでC#のyieldからIEnumeratorの類をネイティブでやろうって話です。CreateFiber一発で片付くとか言うの禁止。
これはいつか作った高階関数版、Ada2005風に言うならdownward closure版のEnumWindowsです。使い方はこちら参照

type
	CEachWindow = function(Wnd: HWND; Context: Pointer): Boolean cdecl;

procedure EnumerateWindows(Proc: CEachWindow; Context: Pointer); forward;

...

function EnumWindowsProc(Wnd: HWND; var D: TMethod): LongBool; stdcall;
asm
	mov EDX, D
	mov EAX, TMethod[EDX].Data
	push EAX
	mov EAX, Wnd
	push EAX
	mov ECX, TMethod[EDX].Code
	call ECX
	add esp, 8
	xor al, 1
	cbw
	cwde
end;

procedure EnumerateWindows(Proc: CEachWindow; Context: Pointer);
var
	D: TMethod;
begin
	D.Code := @Proc;
	D.Data := Context;
	EnumWindows(@EnumWindowsProc, LPARAM(@D))
end;

この手のEnum系コールバック関数を、書き換えることなく、for..inで無理やり使えるようにするにはどうすればいいか。
Fiberだと一発ですが、ループのためだけにわざわざ新しくスタックとコンテキスト作って、なんてのはちょっと富豪的に過ぎると思うので、一本のスタックを行ったり来たりすることにします。
for..in側から見れば、初回のMoveNextがesp動かしてスタック上になにか確保しただけの状態。

type
	TWindowEnumerator = class(TObject)
		caller_ebp, caller_eip: Pointer;
		enum_ebx, enum_esi, enum_edi, enum_eip: Pointer;
		FCurrent: HWND;
		function MoveNext: Boolean;
		function GetCurrent: HWND;
		property Current: HWND read GetCurrent;
	end;

function Each(Wnd: HWND; Self: TWindowEnumerator): Boolean cdecl;
asm
	mov edx, Wnd
	mov eax, Self
	push ebp
	mov TWindowEnumerator[eax].FCurrent, edx
	mov TWindowEnumerator[eax].enum_ebx, ebx
	mov TWindowEnumerator[eax].enum_esi, esi
	mov TWindowEnumerator[eax].enum_edi, edi
	mov TWindowEnumerator[eax].enum_eip, offset @Next
	mov edx, TWindowEnumerator[eax].caller_eip
	mov ebp, TWindowEnumerator[eax].caller_ebp
	mov eax, 1
	jmp edx
@Next:
	pop ebp
	mov eax, Self
	mov al, 0
end;

function TWindowEnumerator.MoveNext: Boolean;
asm
	mov edx, TWindowEnumerator[eax].caller_eip
	test edx, edx
	jnz @Resume
	mov TWindowEnumerator[eax].caller_ebp, ebp
	mov edx, [esp]
	mov TWindowEnumerator[eax].caller_eip, edx
	mov edx, eax
	mov eax, offset Each
	call EnumerateWindows
	mov eax, 0
	ret
@Resume:
	mov edx, TWindowEnumerator[eax].FCurrent
	test edx, edx
	jz @End
	mov ebx, TWindowEnumerator[eax].enum_ebx
	mov esi, TWindowEnumerator[eax].enum_esi
	mov edi, TWindowEnumerator[eax].enum_edi
	mov edx, TWindowEnumerator[eax].enum_eip
	add esp, 4
	jmp edx
@End:
	xor eax, eax
end;

function TWindowEnumerator.GetCurrent: HWND;
begin
	Result := FCurrent;
end;

type
	TEnumWindows = record
		function GetEnumerator: TWindowEnumerator;
	end;

function TEnumWindows.GetEnumerator: TWindowEnumerator;
begin
	Result := TWindowEnumerator.Create
end;

問題点は、例外どころかBreakで死ねること。
ダメじゃん。
なお初回のMoveNextではなく、CreateでEnumWindowsに突入した場合、呼び出しもとがその後に例外ハンドラを設定するので、それはそれで微妙なのではあります…が、デストラクタで一旦EnumWindowsに戻すことを考えたらそちらのほうがまだ道はあるか?
…どっちにしろデストラクタが呼ばれるのは例外ハンドラのコンテキストなので、スタックかき回されてしまってますからねえ…。