さて、やっとたどり着くことができたのですが…
秘訣は、クライアント EXE でエントリの<comInterfaceProxyStub>
下<file>
に、サーバー EXE でエントリの<comInterfaceExternalProxyStub>
下にある<assembly>
ことでした。
要約すると、2 つの EXE と 1 つの ProxyStub DLL がありました。MFCDialog.exe (クライアント)、ExeServer2.exe (サーバー)、および ExeServer2PS.dll (プロキシ スタブ DLL) です。ExeServer2 は、もともと ATL ウィザードによって生成された、プロキシ/スタブを備えた EXE サーバーでした。プロキシ/スタブ DLL は一種の謎です。まったく触れていません。固有のソース ファイルはなく、ExeServer2 (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>
上記では、サーバーの typelib のエントリが不要であることがわかります。iid は IMyServer2 の uuid で、baseInterface は IDispatch の uuid です。proxyStubClsid32 は IMyServer2 インターフェイスの uuid と同じですが、技術的には IID ではなく CLSID です。それが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>
上記では、2 つの重要な部分があります。まず、ATL サーバーがそのタイプ ライブラリに接続できるようにするためのタイプ ライブラリ エントリです。2 つ目は、外部プロキシ スタブ エントリです。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);
}
}
}
}