2016-12-14 11 views
1

Я смог использовать манифесты, и особенно задачу MSBuild GenerateApplicationManifest, чтобы наше основное приложение использовало изолированный COM. Я могу создать все COM-объекты, реализованные в DLL, которые мне нужны, без необходимости регистрации DLL на моей клиентской машине. Но, я жадный ...Потребление EXE-сервера через квазиизолированный COM

В нашем наборе приложений также есть отдельные приложения, которые вызывается через 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 при активации требуется прокси/заглушка.

+0

Для моего простого тестового приложения у меня есть только один объект с одним двойным интерфейсом. В вашем примере у вас есть элементы с (в пределах ) и (в пределах ). Нужна ли мне одна запись? Я использовал ATL для генерации EXE с помощью PS DLL, и интересно, что CLSID для прокси-сервера (найденный при проверке реестра) совпадает с CLSID для интерфейса. –

+0

Вам нужно всего лишь использовать один из подходов. Если ваш двойной интерфейс использует стандартную маршалинг (прокси/заглушку DLL), используйте 'comInterfaceProxyStub'. Если ваш двойной интерфейс использует маршалинг библиотеки типов (файл TLB или встроенный в ресурс DLL) или любой другой зарегистрированный (неизолированный) прокси-сервер, используйте 'comInterfaceExternalProxyStub'. Я отредактирую ответ, чтобы отразить это двумя отдельными примерами. – acelent

+0

Поскольку вы указываете, что CLSID прокси/заглушки совпадает с IID, (скорее всего, это должно быть так, что вы используете стандартное маршалинг, так что у вас есть прокси/заглушка DLL. Используйте 'comInterfaceProxyStub'. – acelent

0

Ну, я был в состоянии попасть в конце концов ...

Хитрость была в моем клиенте EXE, чтобы иметь <comInterfaceProxyStub> под <file> в записи, а на сервере EXE, чтобы иметь <comInterfaceExternalProxyStub> под <assembly> входа ,

Подводя итог, у меня было 2 EXE и 1 ProxyStub DLL: MFCDialog.exe (клиент), ExeServer2.exe (сервер) и ExeServer2PS.dll (DLL-заглушка прокси-сервера). ExeServer2 изначально был создан сервером EXE с ATL Wizard с прокси/заглушкой. 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); 
     } 
     } 
    } 
} 
+0

Несколько вещей: 1) вы должны либо использовать прокси/заглушку, либо библиотеку типов для данного интерфейса во всех манифестах, а не P/S в одном манифесте, а tlb в другом; 2) двойной интерфейс - это тот, который основан на 'IDispatch', методы которого также доступны через встроенную виртуальную таблицу, он должен быть совместим с автоматизацией, он должен использовать маршалер библиотеки типов вместо прокси-сервера/заглушки; 3) использование COM-изоляции с серверами EXE является неровным, я бы не полагался на 'CoGetClassObject' или' CoCreateInstance', чтобы работать надежно даже после того, как 'WaitForInputIdle' успешно возвращается с учетом бесконечного времени. – acelent