通过准隔离COM消耗EXE服务器

时间:2016-12-14 04:35:04

标签: com registration-free-com

我已经能够使用清单,特别是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)中的清单条目会是什么样的?

2 个答案:

答案 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);
         }
      }
   }
}