Использование EXE-сервера через квазиизолированный COM
Я был в состоянии использовать манифесты и особенно задачу MSBuild GenerateApplicationManifest, так что наше основное приложение использует изолированный COM. Я могу создать все COM-объекты, реализованные в нужных мне DLL, без необходимости регистрировать 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. Я установил точки останова в своем объекте сервера ATL в EXE-сервере и могу подтвердить, что на стороне сервера он находит интерфейс и возвращает S_OK. Таким образом, кажется, что интерфейс не может быть передан обратно клиенту.
Итак, вопрос в том, как я могу получить QueryInterface()
для моего пользовательского / двойного интерфейса, чтобы преуспеть? Я пробовал различные комбинации использования <comInterfaceProxyStub>
а также <comInterfaceExternalProxyStub>
в моем клиентском манифесте (и манифесте сервера), чтобы попытаться упорядочить интерфейс, но я все еще вижу E_NOINTERFACE
вернись в мой клиент.
Я видел комментарий Ханса Пассанта от нескольких лет назад на другом форуме о том, что, возможно, нужна отдельная DLL прокси / заглушки для маршалинга интерфейса, но подробностей не было.
Возможно ли вообще решить эту проблему в свободном от регистрации контексте? Нужно ли создавать библиотеку прокси / заглушки? Если так, как будут выглядеть записи манифеста в моем клиентском приложении (и / или серверном приложении, и / или прокси / заглушке DLL)?
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 прокси / заглушки, а затем объявив соответствующий comClass
элементы в прокси / заглушки file
элемент, фактически не нуждающийся в comInterfaceProxyStub
элемент.
Тем не менее, это не очень хорошая практика, comInterfaceExternalProxyStub
действительно должен использоваться только для внешних прокси / заглушек, поскольку, как это задокументировано, похоже, что инфраструктура COM не может искать в изолированных CLSID при активации требуемого прокси / заглушки.
Ну, я смог добраться туда в конце концов...
Трюк был в моем клиенте EXE, чтобы иметь <comInterfaceProxyStub>
под <file>
запись, а на сервере EXE есть <comInterfaceExternalProxyStub>
под <assembly>
запись.
Подводя итог, у меня было 2 EXE-файла и 1 библиотека ProxyStub: MFCDialog.exe (клиент), ExeServer2.exe (сервер) и ExeServer2PS.dll (заглушка прокси-сервера DLL). ExeServer2 изначально был EXE-сервером, созданным мастером ATL, с прокси / заглушкой. 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>
Выше вы можете заметить, что запись для typelib сервера НЕ нужна. Iid - это UUID IMyServer2, а baseInterface - это UUID IDispatch, а proxyStubClsid32 - это то же самое, что и UUID интерфейса IMyServer2 - хотя технически это 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 - это uuid IMyServer2, 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);
}
}
}
}