我已经能够使用清单,特别是MSBuild任务GenerateApplicationManifest,以便我们的主应用程序使用隔离COM。我可以创建我需要的DLL中实现的所有COM对象,而无需在我的客户端计算机上注册DLL。但是,我很贪心......
我们的应用程序套件还有一些通过COM调用的独立应用程序。对于这些,据说你不能EXE到EXE隔离的COM。严格来说,这是事实,但我已经有90%的方式,而在其他论坛上,我看到其他人提供线索,以便在那里完成剩下的工作。
对于我的EXE服务器,我在清单中有一个条目,其中包含EXE服务器的名称和该条目中的子条目,以便当ATL服务器调用LoadRegTypeLib()
时,呼叫将成功。这很有效。
当然,棘手的部分是你不能在客户端应用程序清单中为EXE服务器添加一个条目,并期望CoCreateInstance()
成功(通过启动服务器EXE并执行COM所做的所有其他工作。)
我可以假装很多,因为我知道要启动什么EXE服务器。我可以致电CreateProcess()
,然后在客户端应用中调用WaitForInputidle()
,让我的服务器为客户端应用中的CoCreateInstance()做好准备。
如果我在客户端应用中呼叫CoCreateInstance()
并要求IDispatch
接口,则呼叫成功,我可以呼叫Invoke()
,一切正常。
现在这里有贪婪的部分......
IDispatch工作得很好,但我希望能够通过我从IDispatch派生的双接口进行调用。我想这样做,因为我有很多代码编写,语法更简单,异常处理已经存在。
但是,当我在QueryInterface()
接口上为双接口调用IDispatch
时,我会收到E_NOINTERFACE返回。我在服务器EXE中的ATL服务器对象中设置了断点,并且可以确认在服务器端找到接口并返回S_OK。所以,似乎某种程度上接口无法被编组回客户端。
所以,问题是,如何让我的自定义/双界面的QueryInterface()
成功?我已尝试在客户端清单(和服务器清单)中使用<comInterfaceProxyStub>
和<comInterfaceExternalProxyStub>
的各种组合来尝试和封送接口,但我仍然看到我的客户端中的E_NOINTERFACE
返回
几年前,我看到Hans Passant在一个不同的论坛上发表评论,可能需要一个单独的代理/存根DLL来编组接口,但没有太多细节。
甚至可以在免注册的情境中解决这个问题吗?是否有必要创建代理/存根库?如果是这样,我的客户端应用程序(和/或服务器应用程序和/或代理/存根DLL)中的清单条目会是什么样的?
答案 0 :(得分:2)
如果您有代理/存根DLL,请将其包含为file
元素,并为其处理的每个接口添加子comInterfaceProxyStub
元素(不要忘记threadingModel
属性)。
如果您有一个类型库,请将其包含为带有子file
元素的typelib
元素,并为类型库中的每个接口添加comInterfaceExternalProxyStub
元素(don&#39 ; t忘记tlbid
属性),其中proxyStubClsid32
是自动编组:"{00020424-0000-0000-C000-000000000046}"
。
例如,如果您使用标准编组(代理/存根DLL):
<assembly ...>
<file name="myps.dll">
<comInterfaceProxyStub iid="{iid1}"
name="IMyDualInterface1"
baseInterface="{00020400-0000-0000-C000-000000000046}"
numMethods="8"
proxyStubClsid32="{proxyStubClsid32}"
threadingModel="Free"
/>
</file>
</assembly>
如果您使用类型库编组:
<assembly ...>
<file name="mylib.tlb">
<typelib tlbid="{tlbid}"
version="1.0"
helpdir=""
/>
</file>
<comInterfaceExternalProxyStub iid="{iid2}"
baseInterface="{00020400-0000-0000-C000-000000000046}"
numMethod="8"
name="IMyDualInterface2"
tlbid="{tlbid}"
proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
/>
</assembly>
实际上,comInterfaceExternalProxyStub
适用于任何其他已注册(非隔离)代理/存根。例如:
<assembly ...>
<comInterfaceExternalProxyStub iid="{iid2}"
baseInterface="{00000000-0000-0000-C000-000000000046}"
numMethod="4"
name="IMyInterface3"
proxyStubClsid32="{proxyStubClsid32}"
/>
</assembly>
其中,在这种情况下,{proxyStubClsid32}
是注册的代理/存根CLSID。
如果我的内存对我有用,那么当Windows XP仍然受支持时,我已成功尝试使用comInterfaceExternalProxyStub
代理/存根DLL中的接口,然后声明相应的comClass
元素在proxy / stub file
元素中,实际上不需要comInterfaceProxyStub
元素。
但是,这不是一个好习惯,comInterfaceExternalProxyStub
应该只用于外部代理/存根,因为它的记录方式,听起来像是允许COM基础结构在隔离的CLSID中查找激活所需的代理/存根时。
答案 1 :(得分:0)
嗯,我最终能够到达那里......
诀窍是在我的客户端EXE中<comInterfaceProxyStub>
条目下有一个<file>
,并且在服务器EXE中<comInterfaceExternalProxyStub>
条目下有一个<assembly>
。 / p>
总结一下,我有2个EXE和1个ProxyStub DLL:MFCDialog.exe(客户端),ExeServer2.exe(服务器)和ExeServer2PS.dll(代理存根DLL)。 ExeServer2最初是一个ATL向导生成的EXE服务器,带有代理/存根。代理/存根DLL有点神秘。我根本没碰过它。它没有唯一的源文件,只是从MIDL编译为ExeServer2(EXE服务器项目)生成的一些文件。
在调整ExeServer2.exe和MFCDialog.exe的清单文件后,我可以在手动启动服务器后编组界面。
MFCDialog.exe.manifest的重要部分:
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<file name="ExeServer2PS.dll">
<comInterfaceProxyStub iid="{2985957C-3067-4361-A010-23735F13E4B9}" name="IMyServer2" numMethods="8" baseInterface="{00020400-0000-0000-C000-000000000046}" proxyStubClsid32="{2985957C-3067-4361-A010-23735F13E4B9}" threadingModel="Both"/>
</file>
<!-- unimportant stuff like DPI, UAC, ComCtrl32 removed-->
</assembly>
在上面,您可以注意到不需要服务器类型库的条目。 iid是IMyServer2的uuid,baseInterface是IDispatch的uuid,proxyStubClsid32与IMyServer2接口的uuid相同 - 即使它在技术上是CLSID而不是IID。这就是ATL如何产生它。
ExeServer2.exe.manifest的重要部分:
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<file name="ExeServer2.exe">
<typelib tlbid="{50142018-B402-4FDE-B085-67ABCC128526}" version="1.0" helpdir=""/>
</file>
<comInterfaceExternalProxyStub name="IMyServer2" iid="{2985957C-3067-4361-A010-23735F13E4B9}" tlbid="{50142018-B402-4FDE-B085-67ABCC128526}" proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"/>
</assembly>
在上面,有两个重要部分......首先是typelib条目,以便ATL服务器可以连接到它们的类型库。第二个是外部代理存根条目。 iid是IMyServer2的uuid,tlbid是服务器的类型库(ExeServer2),proxyStubClsid32是默认的自动代理存根CLSID。
以下是启动Exe服务器的代码(ATL服务器不需要特殊参数):
BOOL SpinUpExe(CString strExeName)
{
STARTUPINFO info;
ZeroMemory(&info, sizeof(info));
info.cb = sizeof(info);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
TCHAR szDir[MAX_PATH];
GetModuleFileName(0, szDir, MAX_PATH);
CString strDir(szDir);
strDir = strDir.Mid(0, strDir.ReverseFind(_T('\\')));
CString sExe = strDir + CString(_T("\\")) + strExeName;
BOOL bSuccess = CreateProcess(sExe, NULL, NULL, NULL, FALSE, 0, NULL, strDir, &info, &pi);
if (!bSuccess)
{
DWORD dw = GetLastError();
_com_error err(dw);
}
else
{
WaitForInputIdle(pi.hProcess, 5000);
}
return bSuccess;
}
以下是对测试代码的按钮单击的响应:
void CMFCDialogDlg::OnExeServer2()
{
CLSID clsid;
HRESULT hr = CLSIDFromProgID(L"ExeServer2.MyServer2", &clsid);
if (FAILED(hr))
{
_com_error err(hr);
OutputDebugString(err.ErrorMessage());
}
CComDispatchDriver lpDisp;
hr = lpDisp.CoCreateInstance(__uuidof(ExeServer2Lib::MyServer2));
if (hr == REGDB_E_CLASSNOTREG)
{
SpinUpExe(_T("ExeServer2.exe"));
hr = lpDisp.CoCreateInstance(__uuidof(ExeServer2Lib::MyServer2));
}
if (FAILED(hr))
{
_com_error err(hr);
AfxMessageBox(err.ErrorMessage());
}
else
{
ExeServer2Lib::IMyServer2Ptr lpServer;
try
{
lpServer = lpDisp.p;
}
catch (_com_error e)
{
AfxMessageBox(e.ErrorMessage());
}
if (lpServer)
{
_bstr_t bstrtName = lpServer->Name;
CString strMsg = CString(_T("From IMyServer: ")) + (LPCTSTR)bstrtName;
AfxMessageBox(strMsg);
}
else
{
_variant_t vRet;
hr = lpDisp.GetPropertyByName(L"Name", &vRet);
if (FAILED(hr))
{
_com_error err(hr);
AfxMessageBox(err.ErrorMessage());
}
else
{
CString strMsg = CString(_T("From IDispatch: ")) + (LPCWSTR)vRet.pbstrVal;
AfxMessageBox(strMsg);
}
}
}
}