问题
I have been able to use manifests and especially the MSBuild task GenerateApplicationManifest so that our main application uses Isolated COM. I can create all the COM objects implemented in DLLs that I need without having to register the DLLs on my client machine. But, I'm greedy...
Our application suite also has some separate applications that are invoked through COM, generally. For these, it is said that you can't do EXE to EXE isolated COM. Strictly speaking, that is true, but I have gotten 90% of the way and on other forums I have seen others giving clues to get the rest of the way there.
For my EXE server, I have a entry in the manifest with the name of the EXE server and a sub entry in that entry so that when the ATL server calls LoadRegTypeLib()
, the call will succeed. That works.
Of course, the tricky part is that you cannot put a entry for the EXE server in the client application manifest and expect CoCreateInstance()
to succeed (by launching the server EXE and doing all the other stuff COM does.)
I am able to fake quite a bit because I know what EXE server to launch. I can call CreateProcess()
and then call WaitForInputidle()
in the client app to get my server ready for CoCreateInstance() in the client app.
If I call CoCreateInstance()
and ask for the IDispatch
interface in the client app, the call succeeds and I can call Invoke()
and everything works.
Now here comes the greedy part...
It's all well and good that IDispatch works, but I want to be able to call through my dual interfaces derived from IDispatch. I want to do that because I have lots of code written that way and the syntax is simpler and the exception handling is already there.
However, when I call QueryInterface()
for the dual interface on my IDispatch
interface, I get an E_NOINTERFACE return. I have set breakpoints in my ATL server object in the server EXE and can confirm that on the server side, it finds the interface and returns S_OK. So, it seems like somehow the interface is not able to be marshalled back to the client.
So, the question is, How can I get the QueryInterface()
for my custom/dual interface to succeed? I've tried various combinations of using <comInterfaceProxyStub>
and <comInterfaceExternalProxyStub>
in my client manifest (and server manifest) to try and marshal the interface, but I am still seeing the E_NOINTERFACE
return in my client.
I saw a comment by Hans Passant from several years ago in a different forum about maybe needing a separate proxy/stub DLL to marshal the interface, but there wasn't much detail.
Is it even possible to solve this in a registration free context? Is it necessary to create a proxy/stub library? If so, what would the manifest entries look like in my client application (and/or server app and/or proxy/stub DLL)?
回答1:
If you have a proxy/stub DLL, include it as a file
element with a child comInterfaceProxyStub
element for each interface it handles (don't forget the threadingModel
attribute).
If you have a type library, include it as a file
element with a child typelib
element, and add a comInterfaceExternalProxyStub
element for each interface in the type library (don't forget the tlbid
attribute), where proxyStubClsid32
is the automation marshaler: "{00020424-0000-0000-C000-000000000046}"
.
For instance, if you use standard marshaling (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>
If you use type library marshaling:
<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>
In fact, comInterfaceExternalProxyStub
applies to any other registered (non-isolated) proxy/stub. For instance:
<assembly ...>
<comInterfaceExternalProxyStub iid="{iid2}"
baseInterface="{00000000-0000-0000-C000-000000000046}"
numMethod="4"
name="IMyInterface3"
proxyStubClsid32="{proxyStubClsid32}"
/>
</assembly>
where, in this case, {proxyStubClsid32}
is a registered proxy/stub CLSID.
If my memory serves me right, back when Windows XP was still supported, I've successfully tried using comInterfaceExternalProxyStub
for interfaces in a proxy/stub DLL and then declaring the respective comClass
elements in the proxy/stub file
element, effectively not needing the comInterfaceProxyStub
element.
However, this is not a good practice, comInterfaceExternalProxyStub
should really be used only for external proxy/stubs, as the way it is documented, it sounds like the COM infrastructure is allowed to not look for in isolated CLSIDs when activating the required proxy/stub.
回答2:
Well, I was able to to get there eventually...
The trick was in my client EXE to have a <comInterfaceProxyStub>
under a <file>
entry, and in the server EXE to have a <comInterfaceExternalProxyStub>
under an <assembly>
entry.
To summarize, I had 2 EXEs and 1 ProxyStub DLL: MFCDialog.exe (the client), ExeServer2.exe (the server), and ExeServer2PS.dll (the proxy stub DLL). The ExeServer2 was originally an ATL Wizard generated EXE server with a proxy/stub. The proxy/stub DLL is sort of mysterious. I have not touched it at all. It has no unique source files, just some files generated from the MIDL compile for ExeServer2 (the EXE server project).
After tweaking the manifest files for ExeServer2.exe and MFCDialog.exe, I can marshal the interface after I manually startup the server.
Important part for 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>
In the above, you can notice that an entry for the server typelib is NOT needed. The iid is the uuid of IMyServer2 and the baseInterface is the uuid of IDispatch, and the proxyStubClsid32 is the same as the uuid of the IMyServer2 interface--even though it's technically a CLSID and not IID. That is how ATL generates it.
Important part of 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>
In the above, two important parts... First, the typelib entry so that the ATL servers can connect to their typelib. The second is the external proxy stub entry. The iid is the uuid of IMyServer2, tlbid is the type library for the server (ExeServer2), and proxyStubClsid32 is the default automation proxy stub CLSID.
Here is the code to spin up the Exe server (ATL servers need no special arguments):
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;
}
And following is the response to the button click that tests the code:
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);
}
}
}
}
来源:https://stackoverflow.com/questions/41134925/consuming-exe-server-through-quasi-isolated-com