最新のAB5では、(一時的に)ABのインタフェースがCOMインタフェースとして使えなくなった(AB開発日記 (2007/10/16))とあるので、調べてみました。その結果、次の2点がCOMと異なるため、非互換性を生んでいると分かりました。
- 仮想関数表 (vtbl)がCOMの規定する位置
- 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の位置を外してしまっています。
それを逆手に取ったのが、冒頭のコメントアウト、SetDWord(VarPtr(t), GetDWord(VarPtr(t)) – &h0c)です。オフセット分を予め引いておけば、ちょうどいい位置にvtblがやってくるわけです。ただし、this引数がとんでもないことになるので、殆どのメソッドは呼び出した途端に落っこちるでしょう(今回テストに使用したインタフェースの実装では、thisに全く触れないため落っこちません)。
これをどうにかするには、インタフェースの実体の配置を変えて、0Chを加えることなく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について調べたいだけの今回はこれでも構わないのです。
スポンサード リンク |