最新のAB5では、(一時的に)ABのインタフェースがCOMインタフェースとして使えなくなった(AB開発日記 (2007/10/16))とあるので、調べてみました。その結果、次の2点がCOMと異なるため、非互換性を生んでいると分かりました。

  1. 仮想関数表 (vtbl)がCOMの規定する位置
  2. This引数の扱い

今日は、1つ目のvtblについてです。

まずは調べるために用いたABコードからです。

Interface ITest
	Sub Proc1()
	Sub Proc2()
	Sub Proc3()
End Interface

Declare Sub GetTestObj Lib "td.dll" Alias "_GetTestObj@4" (ByRef t As Any)

Sub Test()
	Dim t = Nothing As ITest
	DebugBreak()
	GetTestObj(t)

'	SetDWord(VarPtr(t), GetDWord(VarPtr(t)) - &h0c)

	t.Proc1()
	t.Proc2()
	t.Proc3()
End Sub

Test()

このプログラムが参照するtd.dllはこの記事の最後に載せています。

SetDWordの行、早くもネタバレです。理由は後で明らかにしますが、このプログラムではその行のコメントアウトを外すだけで、動きます。

DebugBreak関数は、ABのDebug文と同じような効果がありますが、リリースコンパイルでも無効にならないという違いがあります。


これをリリースコンパイルして、できたEXEをダブルクリックなどで実行します。DebugBreakでWindowsが強制終了しにかかってきますが、強制終了のダイアログから、すかさずデバッガとしてVisual Studioを起動します。Visual Studioでは、自身が認識できるデバッグ情報がないため、ただの逆アセンブルを表示してきます。下はt.Proc1()呼出に関わる部分の抜粋です。

0044B0B2  mov         eax,dword ptr [ebp+4]
0044B0B8  push        eax
0044B0B9  mov         ecx,dword ptr [esp]
0044B0C0  mov         edx,dword ptr [ecx+0Ch]
0044B0C3  mov         ecx,dword ptr [ecx+8]
0044B0C6  push        ecx
0044B0C7  call        dword ptr [edx]

COMのインタフェースへのポインタは、vtblへのポインタへのポインタです。つまり、インタフェース変数から2回参照剥しした先がvtbl、ようするに関数ポインタの配列であるとして扱っていれば正解です。しかし、このコードでは、tと思しきebp+4の内容をeaxに送った後、1回目の参照剥しで[ecx+0Ch]とオフセット0Chを加えてしまっています。このためCOMの規定するvtblの位置を外してしまっています。現在のインタフェースからvtblまでの構造

それを逆手に取ったのが、冒頭のコメントアウト、SetDWord(VarPtr(t), GetDWord(VarPtr(t)) – &h0c)です。オフセット分を予め引いておけば、ちょうどいい位置にvtblがやってくるわけです。ただし、this引数がとんでもないことになるので、殆どのメソッドは呼び出した途端に落っこちるでしょう(今回テストに使用したインタフェースの実装では、thisに全く触れないため落っこちません)。

これをどうにかするには、インタフェースの実体の配置を変えて、0Chを加えることなくvtblにアクセスできればよいわけです。提案するインタフェースからvtblまでの構造


最後に、今回使用したVisual C++製DLLです。VC++固有の機能は殆ど使っていないので、他のコンパイラでもほとんど修正をせずに使えるはずです。

//td.cpp
//cl /ZI td.cpp /link /DLL /out:td.dll
//mt -manifest td.dll.manifest -outputresource:td.dll;2
#include <stdio.h>
#include <windows.h>

void msg(void* t, PCTSTR s);

class Test
{
public:
	virtual void WINAPI Proc1()
	{
		msg(this, "Proc1");
	}
	virtual void WINAPI Proc2()
	{
		msg(this, "Proc2");
	}
	virtual void WINAPI Proc3()
	{
		msg(this, "Proc3");
	}
	virtual void WINAPI Proc4()
	{
		msg(this, "Proc4");
	}
};

Test o;

extern "C" __declspec(dllexport)
void WINAPI GetTestObj(Test*& t)
{
	t = &o;
}

BOOL WINAPI DllMain(HINSTANCE, DWORD, void*)
{
	return TRUE;
}

void msg(void* t, PCTSTR s)
{
	char buf[1024];
	_snprintf(buf, sizeof buf, "%s, this: %p, &o: %p", s, t, &o);
	MessageBoxA(0, buf, "", MB_OK);
}

このコードはCOMインタフェースの体をなしていませんが、Visual C++はCOMのためクラスに何か小細工を仕組んではいないので、vtblについて調べたいだけの今回はこれでも構わないのです。


スポンサード リンク

この記事のカテゴリ

  • ⇒ ABとCOMの狭間
  • ⇒ ABとCOMの狭間