2016-12-14 46 views
1

我已经能够使用清单,特别是MSBuild任务GenerateApplicationManifest,以便我们的主应用程序使用隔离的COM。我可以创建所有在我需要的DLL中实现的COM对象,而无需在客户机上注册这些DLL。但是,我很贪心......通过准消费EXE服务器隔离COM

我们的应用程序套件也有一些独立的应用程序,通常通过COM调用。对于这些,据说你不能EXE来EXE隔离COM。严格地说,这是事实,但我已经获得了90%的道路,并且在其他论坛上我看到其他人提供了线索来获得剩余路线。

对于我的EXE服务器,我在清单中具有EXE服务器的名称和该条目中的子条目,以便当ATL服务器调用LoadRegTypeLib()时,该调用将成功。这样可行。

当然,最棘手的部分是,你不能把在客户端应用程序清单的EXE服务器条目,并期待CoCreateInstance()成功(通过启动服务器EXE和做所有其他的东西COM一样。)

因为我知道要启动的EXE服务器,所以我可以假装很多。我可以拨打CreateProcess(),然后在客户端应用程序中拨打WaitForInputidle(),以使我的服务器在客户端应用程序中可用于CoCreateInstance()。

如果我打电话CoCreateInstance()并要求客户端应用程序中的IDispatch界面,则调用成功,我可以调用Invoke()并且一切正常。

现在,这里是贪婪的一部分...

这一切都很好这IDispatch的工作,但我希望能够通过我的IDispatch来自衍生双接口调用。我想这样做,因为我有很多用这种方式编写的代码,语法更简单,异常处理已经存在。

但是,当我在我的IDispatch接口上拨打QueryInterface()作为双接口时,我得到一个E_NOINTERFACE返回值。我在服务器EXE的ATL服务器对象中设置了断点,并可以确认在服务器端找到了接口并返回S_OK。所以,看起来不知何故界面不能被整理回客户端。

所以,问题是,我怎样才能得到QueryInterface()为我的自定义/双接口成功?我试过在我的客户端清单(和服务器清单)中使用<comInterfaceProxyStub><comInterfaceExternalProxyStub>的各种组合尝试编组界面,但我仍然在客户端看到E_NOINTERFACE返回。

几年前,我在一个不同的论坛上看到Hans Passant发表评论,可能需要一个单独的代理/存根DLL来编组接口,但没有太多细节。

它甚至有可能在无注册的上下文中解决这个问题吗?是否有必要创建一个代理/存根库?如果是这样,那么清单条目在我的客户端应用程序(和/或服务器应用程序和/或代理/存根DLL)中会是什么样子?

回答

2

如果你有一个代理/存根DLL,包括它与它处理每个接口的子​​元素的file元素(不要忘了threadingModel属性)。

如果你有一个类型库,把它作为一个孩子typelib元素的file元素,并添加一个comInterfaceExternalProxyStub元素类型库中的每个接口(不要忘记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元素试图/存根file元素,实际上不需要​​元素。

但是,这不是一个好的做法,comInterfaceExternalProxyStub应该只用于外部代理/存根,就像它被记录的方式,它听起来像COM基础结构被允许不激活时在孤立的CLSID中查找需要的代理/存根。

+0

对于我的简单测试应用程序,我只有一个具有单一双界面的对象。在您的示例中,您具有(在内)和(在内)的元素。我需要每个条目之一吗?我使用ATL来生成带有PS DLL的EXE,有趣的是代理存根(通过检查注册表找到)的CLSID与接口的CLSID相同。 –

+0

您只需使用其中一种方法。如果您的双接口使用标准编组(代理/存根DLL),请使用'comInterfaceProxyStub'。如果您的双接口使用类型库编组(TLB文件或嵌入在DLL资源中)或任何其他注册(非隔离)代理/存根,请使用'comInterfaceExternalProxyStub'。我将用两个单独的例子编辑答案以反映这一点。 – acelent

+0

由于您提到代理/存根的CLSID与IID相同,(最可能)它必须是您使用标准编组的情况,因此您有一个代理/存根DLL。使用'comInterfaceProxyStub'。 – acelent

0

嗯,我能到那里,最终...

诀窍是在我的客户EXE有<file>条目下一个<comInterfaceProxyStub>,并在服务器EXE有<comInterfaceExternalProxyStub><assembly>条目下。总之,我有2个EXE和1个ProxyStub DLL:MFCDialog.exe(客户端),ExeServer2.exe(服务器)和ExeServer2PS.dll(代理存根DLL)。 ExeServer2最初是一个带有代理/存根的ATL向导生成的EXE服务器。代理/存根DLL有点神秘。我没有碰过它。它没有唯一的源文件,只有一些文件从Execl2(EXE服务器项目)的MIDL编译生成。

在调整了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服务器可以连接到他们的typelib。第二个是外部代理存根条目。 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); 
     } 
     } 
    } 
} 
+0

一些事情:1)您应该为所有清单中的给定接口使用代理/存根或类型库,而不是一个清单中的P/S和另一个清单中的tlb; 2)双接口是基于'IDispatch'的方法,这些方法也可以通过本地的vtable访问,它应该是自动化兼容的,它应该使用类型库封送拆分器而不是代理/存根; 3)在EXE服务器上使用COM隔离很脆弱,即使在WaitForInputIdle成功返回给定无限时间之后,我也不会依赖'CoGetClassObject'或'CoCreateInstance'来可靠地工作。 – acelent