CUIAutomation
COM クラス オブジェクトのメソッドを、アクティブ/Windows スクリプト アプリケーションを介して読み込んで実行するスクリプトに公開したいと考えています (スクリプト エンジンを実装していません。具体的には「JScript」エンジンを使用しています)。IDispatch
スクリプト ホストは通常、 を実装するオブジェクトを自動的に公開できますが、CUIAutomation
クラスは を実装しませんIDispatch
。QueryInterface
オブジェクトへのIDispatch
ポインターを呼び出すには、 を返しE_NOINTERFACE
ます。
以下で詳しく説明する私の質問全体は、基本的にこれに要約されます。実装されていないオブジェクトのディスパッチを実装することは可能IDispatch
ですか? 可能であれば、オブジェクトのコクラスの型情報を持つことは、必要な (そしておそらく十分な) 要件であるに違いありません。可能であれば、以下で説明するように、そうしようとして何が問題になっていますか? 私の代替手段は何ですか?
ITypeInfo
前述のように、私の解決策は、コクラスの型情報 ( ) を取得する必要がある場合CUIAutomation
、理論的には、コクラスのオブジェクトを実装しなくても、 likeおよびIDispatch
のメソッドを介してランタイム ディスパッチを実行できるはずであるという私の仮説を中心にしています。実際には、 を実装し、オブジェクト (または適切な型情報と組み合わせることができるもの) をラップし、メンバーのディスパッチをラップされたオブジェクトに委任する独自のクラスを設計します。ITypeInfo
GetIDsOfNames
Invoke
IDispatch
CUIAutomation
IUnknown
それを実装するモジュールへのパスを見つけ、次の手順を使用することで、少なくともCUIAutomation
コクラスの型情報を読み込むことに成功しました。これはすべて Windows レジストリにあります。LoadTypeLib
(注:呼び出しが成功したかどうかをチェックするアサーションがあります(S_OK
またはERROR_SUCCESS
などと比較することにより-成功のコードに依存します)が、簡潔にするために、スニペットでのエラーチェックを省略します-呼び出しが戻りをチェックしない場合説明されているように、値の周りには常にアサーションがあります)
/// Return zero if and only if successful
int LoadTypeInfo(LPOLESTR szCLSID, ITypeInfo * * ppTypeInfo) {
HKEY hRegKeyCLSIDs;
RegOpenKeyEx(HKEY_CLASSES_ROOT, "CLSID", 0, KEY_READ, &hRegKeyCLSIDs); /// Only need to do this once through application lifetime, but here for context
HKEY hRegKeyCLSID;
RegOpenKeyEx(hRegKeyCLSIDs, szCLSID , 0, KEY_READ, &hRegKeyCLSID);
BYTE data[MAX_PATH];
DWORD cbData = sizeof(data);
RegGetValueW(hRegKeyCLSID, L"InprocServer32", NULL, RRF_RT_REG_SZ, NULL, data, &cbData);
ITypeLib * pTypeLib;
LoadTypeLib((LPOLESTR)data, &pTypeLib);
return (pTypeLib->GetTypeInfoOfGuid(CLSID, ppTypeInfo) == S_OK);
}
委任DispatchProxy
クラスは次のように設計されています。
class DispatchProxy: public IDispatch {
private:
IUnknown * pUnknown;
ITypeInfo * pTypeInfo;
public:
DispatchProxy(IUnknown * pUnknown, ITypeInfo * pTypeInfo): pUnknown(pUnknown), pTypeInfo(pTypeInfo) {
/// `pUnknown` is the object that doesn't implement `IDispatch` and `pTypeInfo` is the type information for objects like what `pUnknown` points to.
}
/// Omitting `AddRef` and `Release` -- these are rather standard.
HRESULT STDMETHODCALLTYPE DispatchProxy::QueryInterface(REFIID riid, void * * ppvObject) {
if(ppvObject == nullptr) {
return E_POINTER;
}
else
if(riid == IID_IUnknown || riid == IID_IDispatch) {
*ppvObject = this;
((IUnknown *)*ppvObject)->AddRef();
return S_OK;
}
else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
}
/// NOT returning any type information -- explanation below, if you're surprised
HRESULT STDMETHODCALLTYPE DispatchProxy::GetTypeInfoCount(UINT * pctinfo) {
*pctinfo = 0;
return S_OK;
}
HRESULT STDMETHODCALLTYPE DispatchProxy::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo ** ppTInfo) {
if(iTInfo != 0) return DISP_E_BADINDEX;
_ASSERTE(*ppTInfo == NULL);
return E_NOTIMPL; /// Even though type information for the object being delegated to, is available, obviously, I am unsure whether it technically is valid for `DispatchProxy`, which may have a completely different, incompatible, layout. Granted, `E_NOTIMPL` isn't part of the contract for this method, but like I said -- I am unsure about this one.
}
HRESULT STDMETHODCALLTYPE DispatchProxy::GetIDsOfNames(REFIID riid, LPOLESTR * rgszNames, UINT cNames, LCID lcid, DISPID * rgDispId) {
return pTypeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId); /// Returns S_OK, all good. Also tried `DispGetIDsOfNames(pTypeInfo, rgszNames, cNames, rgDispId)` with same result
}
HRESULT STDMETHODCALLTYPE DispatchProxy::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr) {
return pTypeInfo->Invoke(pUnknown, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); /// Fails with `E_NOTIMPL`. Also tried `DispInvoke(pUnknown, pTypeInfo, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr)` with same result
}
};
これに関連して、スクリプトがオブジェクトCUIAutomation
(スクリプト) でメソッドを呼び出す前に、クラスのオブジェクトのようなオブジェクトへの参照を取得する方法が必要です。VBScript の関数や当時の Internet ExplorerのようcreateObject
に、「グローバル」実装オブジェクトでメソッドを公開することにより、スクリプトが指定された CLSID の COM オブジェクトを作成できるようにします。指定された CLSID で識別される COM クラスのオブジェクトを作成するために使用します。IDispatch
CreateObject
new ActiveXObject(progID)
CoCreateInstance
HRESULT Global::CreateObject(VARIANT * pvCLSID, VARIANT * pvResult) {
_ASSERTE(V_VT(pvCLSID) == VT_BSTR);
CLSID CLSID;
CLSIDFromString(V_BSTR(pvCLSID), &CLSID);
IUnknown * pUnknown;
CoCreateInstance(CLSID, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, &pUnknown);
IDispatch * pDispatch;
HRESULT hResult = pUnknown->QueryInterface(&pDispatch);
if(hResult != S_OK) {
_ASSERTE(hResult == E_NOINTERFACE);
ITypeInfo * pTypeInfo;
if(LoadTypeInfo(V_BST(pvCLSID), &pTypeInfo)) { /// No type information was available -- not much choice but to return the created object as `IUnknown`
V_VT(pvResult) = VT_UNKNOWN;
V_UNKNOWN(pvResult) = pUnknown;
return S_OK;
} else {
pDispatch = new DispatchProxy(pUnknown, pTypeInfo);
}
}
if(pvResult) {
V_VT(pvResult) = VT_DISPATCH;
V_DISPATCH(pvResult) = pDispatch;
}
return S_OK;
}
スクリプトはCUIAutomation
オブジェクトを作成し、次のように新しいDispatchProxy
ラッピングへの参照を取得できます。
uiautomation = createObject("{ff48dba4-60ef-4201-aa87-54103eef594e}");
GetRootElement
その後、オブジェクトのメソッド (ここでは ) を呼び出すことができるはずです。
uiautomation.GetRootElement(/* parameters */);
残念ながら、pTypeInfo->Invoke
すべての中心にある呼び出しが返されますE_NOTIMPL
。それが当面の課題です。
実装されていないものとその理由は? メンバー ID ( dispIdMember
) は前に書いたものと一致しpTypeInfo->GetIDsOfNames
、後者は を返すS_OK
ので、少なくともそれによると、メンバー ID は有効です。パラメータの形式もそれとは何の関係もないと思います-pTypeInfo->Invoke
もしそうなら、呼び出しから別のエラーコードが予想されます。
write as type information count を作成し、結果として書き込みをGetTypeInfoCount
行っても、後続の呼び出しの結果には影響しません。それでも失敗します。1
pTypeInfo
GetTypeInfo
ITypeInfo::Invoke
また、コクラス自体の情報ではなく、元のコクラスオブジェクトで取得した実際のIUIAutomation
インターフェイスの型情報 (pTypeInfoDefaultInterface
以下のスニペット) を使用してみました。 ITypeInfo
ITypeInfo::Invoke
HREFTYPE hRefType;
pTypeInfo->GetRefTypeOfImplType(0, &hRefType);
ITypeInfo * pTypeInfoDefaultInterface;
pTypeInfo->GetRefTypeInfo(hRefType, &pTypeInfoDefaultInterface);
インターフェイスまたはコクラスの型情報が使用されているかどうかに関係なく、効果は同じです -- をITypeInfo::Invoke
返しますE_NOTIMPL
。
私は何を間違っていますか?COM やディスパッチに関する重要な情報を見逃していませんか? また、どのような型情報が役立ちますか? 私は IDL ファイルを作成しません。これDispatchProxy
は COM サーバーの一部ではなく、アプリケーションの厳密な内部クラスです。Visual C++ で確認できる仮想関数テーブルを確認し、型情報についても調査を行いGetFuncDesc
ました。入力内容はしっかりしているように見えます。名前とパラメーターの型と数がすべて揃っています。私が呼び出そうとしているすべての予想されるメソッド。ポインターは有効で、使用可能です。
少なくともGetRootElement
、オブジェクトへのポインターへのポインターを期待し、そのようなタイプのパラメーターを渡すことさえできないスクリプトからそのようなメソッドをディスパッチすることが原因である可能性があることを認めます。しかし、ドキュメントによると、ITypeInfo::Invoke
おそらくE_INVALIDARG
orDISP_E_EXCEPTION
を返す必要があります。
私も をCreateStdDispatch
いじってみましたが、2 つのことが気になりました。CreateStdDispatch
次に、どこからどのディスパッチが行われ、どのポインターがどの引数として使用されるのか正確にはわかりません。ここでの慣用的な代替案でない限り、それは私の実際の質問ではないと思いますが、それが私の場合に役立つ場合は、それが正確に何をし、どのようにプラグインするかについての説明を得るためです.