通过准隔离 COM 使用 EXE 服务器

Consuming EXE server through quasi Isolated COM

我已经能够使用清单,尤其是 MSBuild 任务 GenerateApplicationManifest,以便我们的主应用程序使用 Isolated COM。我可以创建在我需要的 DLL 中实现的所有 COM 对象,而不必在我的客户机上注册 DLL。但是,我很贪心...

我们的应用程序套件也有一些单独的应用程序,通常通过 COM 调用。对于这些,据说你不能做EXE to EXE isolated COM。严格来说,这是真的,但我已经完成了 90% 的事情,在其他论坛上我看到其他人提供线索以完成剩下的事情。

对于我的 EXE 服务器,我在清单中有一个带有 EXE 服务器名称的条目和该条目中的一个子条目,这样当 ATL 服务器调用 LoadRegTypeLib() 时,调用就会成功。行得通。

当然,棘手的部分是您不能在客户端应用程序清单中放置 EXE 服务器的条目并期望 CoCreateInstance() 成功(通过启动服务器 EXE 并执行 COM 执行的所有其他操作) .)

我可以伪装很多,因为我知道要启动什么 EXE 服务器。我可以调用 CreateProcess(),然后在客户端应用程序中调用 WaitForInputidle(),让我的服务器为客户端应用程序中的 CoCreateInstance() 做好准备。

如果我调用 CoCreateInstance() 并在客户端应用程序中请求 IDispatch 接口,调用成功,我可以调用 Invoke(),一切正常。

贪心的部分来了...

IDispatch 可以正常工作,但我希望能够通过派生自 IDispatch 的双重接口进行调用。我想这样做是因为我用这种方式编写了很多代码,而且语法更简单并且异常处理已经存在。

然而,当我在 IDispatch 接口上调用 QueryInterface() 双接口时,我得到一个 E_NOINTERFACE return。我在服务器 EXE 中的 ATL 服务器对象中设置了断点,并且可以确认在服务器端,它找到了接口和 returns S_OK。因此,似乎无法将接口编组回客户端。

所以,问题是,如何让我的 custom/dual 接口的 QueryInterface() 成功?我已经尝试在我的客户端清单(和服务器清单)中使用 <comInterfaceProxyStub><comInterfaceExternalProxyStub> 的各种组合来尝试编组接口,但我仍然看到 E_NOINTERFACE return 在我的客户中。

几年前我在另一个论坛上看到 Hans Passant 的评论,说可能需要一个单独的 proxy/stub DLL 来编组接口,但没有太多细节。

甚至可以在免注册的情况下解决这个问题吗?是否需要创建一个 proxy/stub 库?如果是这样,清单条目在我的客户端应用程序(and/or 服务器应用程序 and/or proxy/stub DLL)中会是什么样子?

如果您有一个 proxy/stub DLL,请将其包含为一个 file 元素,并为其处理的每个接口包含一个子 comInterfaceProxyStub 元素(不要忘记 threadingModel 属性).

如果您有类型库,请将其包含为带有子 typelib 元素的 file 元素,并为类型库中的每个接口添加一个 comInterfaceExternalProxyStub 元素(不不要忘记 tlbid 属性),其中 proxyStubClsid32 是自动化封送拆收器:"{00020424-0000-0000-C000-000000000046}".

例如,如果您使用标准封送处理 (proxy/stub 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 适用于任何其他已注册(非隔离)proxy/stub。例如:

<assembly ...>
    <comInterfaceExternalProxyStub iid="{iid2}"
                                   baseInterface="{00000000-0000-0000-C000-000000000046}"
                                   numMethod="4"
                                   name="IMyInterface3"
                                   proxyStubClsid32="{proxyStubClsid32}"
                                   />
</assembly>

在这种情况下,{proxyStubClsid32} 是注册的 proxy/stub CLSID。


如果我没记错的话,回到仍然支持 Windows XP 的时候,我已经成功地尝试使用 comInterfaceExternalProxyStub 作为 proxy/stub DLL 中的接口,然后声明相应的接口proxy/stub file 元素中的 comClass 个元素,实际上不需要 comInterfaceProxyStub 元素。

然而,这不是一个好的做法,comInterfaceExternalProxyStub 真的应该只用于外部 proxy/stubs,正如它被记录的方式,听起来 COM 基础结构被允许不激活所需的 proxy/stub.

时在独立的 CLSID 中查找

好吧,我最终能够到达那里...

诀窍是在我的客户端 EXE 中有一个 <comInterfaceProxyStub><file> 条目下,而在服务器 EXE 中有一个 <comInterfaceExternalProxyStub><assembly> 条目下.

总而言之,我有 2 个 EXE 和 1 个 ProxyStub DLL:MFCDialog.exe(客户端)、ExeServer2.exe(服务器)和 ExeServer2PS.dll(代理存根 DLL)。 ExeServer2 最初是 ATL 向导生成的 EXE 服务器,带有 proxy/stub。 proxy/stub 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>

在上面,有两个重要部分...首先是类型库条目,以便 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);
         }
      }
   }
}