1

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

我们的应用程序套件通常还有一些通过 COM 调用的单独应用程序。对于这些,据说不能对EXE隔离COM做EXE。严格来说,这是真的,但我已经完成了 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)中的清单条目会是什么样子?

4

2 回答 2

2

如果您有代理/存根 DLL,请将其作为一个file元素包含在它处理的每个接口的子comInterfaceProxyStub元素中(不要忘记该threadingModel属性)。

如果你有一个类型库,将它作为一个file带有子typelib元素的元素包含进来,并为类型库中的每个接口添加一个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 中使用 for 接口,然后comClass在代理/存根file元素中声明相应的元素,实际上不需要该comInterfaceProxyStub元素。

但是,这不是一个好的做法,comInterfaceExternalProxyStub实际上应该只用于外部代理/存根,因为它被记录的方式,听起来 COM 基础设施在激活所需的代理/存根时不允许在隔离的 CLSID 中查找。

于 2016-12-14T15:42:06.600 回答
0

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

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

总而言之,我有 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 服务器可以连接到它们的 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);
         }
      }
   }
}
于 2016-12-14T21:53:04.430 回答