regsvr32で登録するCOMオブジェクトをシステムに登録せずに自分のプログラムで使用する方法 補足

この記事の補足です。

ちょうど書くことが無くなった上にもしかして勘違いするかも、と思った項目に関しての説明と思ってください。

CoCreateInstanceが実際やっていることとは?

対象記事には「CoCreateInstanceをシミュレートする」と書いていますが、もちろんこれは完全なシミュレートでは全然ありません。かなり機能を省略しています。

実際にやっていることを列挙してみると

  1. システムに登録されているCOMから対象のCLSIDを持つDLLをいったんロードする(CLSIDが同じ物が存在するときは内部優先度順。ここでロードされるDLLの範囲をCLSCTX_INPROC_SERVERなどで定める)
  2. 対象DLLからDllGetClassObject関数のエントリポイントを取得
  3. DllGetClassObjectを使って対象のIIDのインターフェイスを作成してみる
  4. 成功した場合はDLLをCOM管理領域にロードしたまま作成されたインターフェイスを返す(DLLロード時にCoLoadLibraryを使うことでDLLはCOM管理領域で管理される。この場合のCOM管理領域の役目はDLLの参照カウント管理)
  5. 失敗したときはそのDLLを解放し、次の優先度を持つDLLをロードし直して2.に戻る
  6. すべてのDLLを検索して存在しないときは失敗する

となります。

手動ロードしたときにDLLが参照するCoCreateInstanceについて

手動ロードを行うとそのままではCoCreateInstanceはole32.dllを指したままになります。

記事の例では

  • DLL内部にあるインターフェイスはすべて自己で参照を完結させている

もしくは

  • 基本的にシステム内に必ず登録されているような物しか参照していない(これがCoInitializeが「ほとんど」必要と言っている意味)

と言う仮定があるためある程度は動くのですが、この手動ロードしたDLLが別の手動ロードされるべきDLLのインターフェイスを使用する、みたいなパターンでは作成が不可能になってしまいます。

これを解消し、かつ手動ロードを成功させるためには

手動ロード時にDLL内部から呼び出されれるCoCreateInstanceの呼び出しを乗っ取って一度本体が実装している仮のCoCreateInstanceを呼び出すようにし、

この中で本来のCoCreateInstanceの呼び出し+手動ロード側のDLLのインターフェイス作成を行う

と言う手順が必要になります。

簡単なコードを示すなら

class CCOMManager{
publi:
	・・・
	HRESULT CreateCOMInstance(REFCLSID rclsid,REFIID riid,LPUNKNOWN pUnkOuter,void **lplpObject); //インスタンスの作成
	HRESULT LoadCOMDLL(const TCHAR *lpFileName); //DLLのロード
	HRESULT UnloadCOMDLL(void); //DLLのアンロード
	BOOL HookCoCreateInstance(HMODULE hModule); //CoCreateInstanceの呼び出しをhookする(自身でも可)
protected:
	・・・
	typedef HRESULT (STDAPICALLTYPE *LPFNCOCREATEINSTANCE)(REFCLSID rclsid,LPUNKNOWN pUnkOuter,DWORD dwClsContext,REFIID riid,LPVOID FAR* ppv);
	HRESULT CreateDLLInstance(REFCLSID rclsid,REFIID riid,IUnknown *lpUnknownOuter,void **lplpObject); //内部でロードされているDLLから作成
	static HRESULT WINAPI CoCreateInstanceStub(REFCLSID rclsid,LPUNKNOWN pUnkOuter,DWORD dwClsContext,REFIID riid,LPVOID FAR* ppv); //hook時に呼び出されるCoCreateInstance
private:
	・・・
	static CCOMManager *m_lpCOMManager;
	static LPFNCOCREATEINSTANCE m_fnCoCreateInstance;
};
CCOMManager *CCOMManager::m_lpCOMManager = NULL;
//本来のCoCreateInstanceの呼び出し先アドレスを保存
CCOMManager::LPFNCOCREATEINSTANCE CCOMManager::m_fnCoCreateInstance = &::CoCreateInstance;
//関数の呼び出し先アドレスを乗っ取る
BOOL HookFunction(HMODULE hModule,const TCHAR *lpModuleName,const CHAR *lpProcName,FARPROC fnMethod);
HRESULT CCOMManager::CreateCOMInstance(REFCLSID rclsid,REFIID riid,LPUNKNOWN pUnkOuter,void **lplpObject)
{
	HRESULT hr;
	if(lplpObject == NULL) return E_POINTER;
	
	hr = m_fnCoCreateInstance(rclsid,pUnkOuter,CLSCTX_INPROC_SERVER,riid,lplpObject);
	if(hr == S_OK) return hr;
	hr = CreateDLLInstance(rclsid,riid,pUnkOuter,lplpObject);
	return hr;
}
BOOL CCOMManager::HookCoCreateInstance(HMODULE hModule)
{
	return HookFunction(hModule,TEXT("ole32.dll"),"CoCreateInstance",(FARPROC)&CCOMManager::CoCreateInstanceStub);
}
HRESULT WINAPI CCOMManager::CoCreateInstanceStub(REFCLSID rclsid,LPUNKNOWN pUnkOuter,DWORD dwClsContext,REFIID riid,LPVOID FAR* ppv)
{
	return m_lpCOMManager != NULL ? m_lpCOMManager->CreateCOMInstance(rclsid,riid,pUnkOuter,ppv) : m_fnCoCreateInstance(rclsid,pUnkOuter,dwClsContext,riid,ppv);
}

のような管理クラスを作るのが手っ取り早いです。

一部実装していませんが、この中で一番難しいのはエントリポイント乗っ取りの処理であるHookFunctionの実装ですね。

この記事などで微妙に紹介されているように実行ファイルに関する知識が必要になります。

自分が実装したライブラリではちゃんとその辺の処理がされているので大丈夫なのですが・・・。

というように、かなりテクニックが必要

こんな物を使うことになるようならかなりの状態であると推測されますね。

まあ、頑張ってください、とだけ言っておきます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL