2016-12-31 1 views
0

У меня есть дилемма. Моему графическому интерфейсу на C++-приложении требуется реализовать функции перетаскивания. В то же время я конвертирую это приложение Win32 в UWP для отправки в Windows Store. Но есть одна проблема:Как совместить RegisterDragDrop, RoInitialize работать вместе в одном потоке?

Для реализации перетаскивания и падение мне нужно вызвать эти два метода:

OleInitialize(NULL); 
//... 
HRESULT hr = RegisterDragDrop(hMainWnd, pDropTarget); 

и инициализировать WinRT материал для работы с Windows Store, мне нужно позвонить:

HRESULT hr = RoInitialize(RO_INIT_MULTITHREADED); 

К сожалению OleInitialize инициализирован COM в single-thread apartment и RoInitialize требует многопоточной модели, в то время как RegisterDragDrop не может функционировать без вызова OleInitialize.

Любая идея, как его решить? (кроме перемещения RoInitialize и всего кода WinRT в рабочий поток, что усложнит ситуацию.)

+1

это может быть неправильно, но с рядом код перетаскивания все еще работал ok 'if (0 <= OleInitialize (0)) CoUninitialize(); RoInitialize (RO_INIT_MULTITHREADED); '- да непарные вызовы -' OleInitialize' с 'CoUninitialize'. – RbMm

+0

@ RbMm: Хм, да, похоже, это сработало. Благодарю. Хотя, пожалуйста, объясните, что вы там делаете? – c00000fd

+3

РеестрDragDrop недоступен в UWP, так что вы уже на неправильной ноге. Для UWP используйте CoreDragDropManager.TargetRequested. –

ответ

2

Raymond Chen в своем обычном снисходительном способе довольно хорошо критикует вещи, но не предлагает никаких проблем с существующей проблемой. Я отправляю это в основном для более поздней самореференции, и если кто-то еще споткнется на эту же проблему. Я просто провел несколько дней, пытаясь решить эту ошибку, так что, возможно, это сэкономит время для кого-то другого.

Проблема

Во-первых, это машинный код Win32 (не .NET или C++/CX.) Это C++ с опрыскиванием WRL для облегчения обработки WinRT/COM вещи.

В моем случае у меня есть приложение Win32 GUI, которое реализует перетаскивание файлов в основное окно. Таким образом, чтобы инициализировать его, нужно сделать это из главного потока, прямо при запуске приложения:

OleInitialize(NULL); 
//... 
HRESULT hr = RegisterDragDrop(hMainWnd, pDropTarget); 

OleInitialize вызова выше будет инициализировать COM для основного потока использовать single-thread apartment, который необходим для RegisterDragDrop, чтобы добиться успеха , Без него функция drag-and-drop не будет работать.

Затем скажите, что вы решили преобразовать это приложение Win32 в UWP с помощью конвертера Microsoft Project Centennial для включения в хранилище Windows 10.

Когда приложение будет преобразовано и перечислено в магазине в соответствии со схемой пробной лицензии, вы будете использовать следующую логику, чтобы проверить, есть ли у пользователя пробная версия или активированная (то есть купленная) копия приложения. Вы начнете его как таковой:

//Init COM for WinRT 
RoInitialize(RO_INIT_MULTITHREADED); 

ComPtr<IStoreContextStatics> pStoreContextStatics; 
if(SUCCEEDED(RoGetActivationFactory(
    HStringReference(L"Windows.Services.Store.StoreContext").Get(), 
    __uuidof(pStoreContextStatics), 
    &pStoreContextStatics)) && 
    pStoreContextStatics) 
{ 
    //Get store context for the app 
    ComPtr<IStoreContext> pStoreContext; 
    if(SUCCEEDED(pStoreContextStatics->GetDefault(&pStoreContext)) && 
     pStoreContext) 
    { 
     //Got store context 
     //.... 

    } 
} 

, а затем, если вам нужно знать испытание против активированного состояния приложения, используя this logic, вы бы назвали:

ComPtr<IAsyncOperation<StoreAppLicense*>> p_opAppLic; 
if(SUCCEEDED(pStoreContext->GetAppLicenseAsync(p_opAppLic)) && 
    p_opAppLic) 
{ 
    ComPtr<IAsyncOperationCompletedHandler<StoreAppLicense*>> p_onAppLicCallback = 
     Callback<Implements<RuntimeClassFlags<ClassicCom>, IAsyncOperationCompletedHandler<StoreAppLicense*>, FtmBase>>(
     [](IAsyncOperation<StoreAppLicense*>* pOp, AsyncStatus status) 
    { 
     if (status == AsyncStatus::Completed) 
     { 
      ComPtr<IStoreAppLicense> pAppLicResult; 
      if(SUCCEEDED(pOp->GetResults(&pAppLicResult)) && 
       pAppLicResult) 
      { 
       BYTE nActive = -1; 
       BYTE nTrial = -1; 
       pAppLicResult->get_IsActive(&nActive); 
       pAppLicResult->get_IsTrial(&nTrial); 

       //Get app's store ID with SKU 
       HString strStoreId; 
       pAppLicResult->get_SkuStoreId(strStoreId.GetAddressOf()); 

       if(nActive == 1 && 
        nTrial == 0) 
       { 
        //Activated, or purchased copy 
       } 
       else if(nActive == 1 && 
        nTrial == 1) 
       { 
        //Trial copy 
       } 
       else 
       { 
        //Error -- store returned some gibberish 
       } 
      } 
     } 

     return S_OK; 
    }); 

    if(SUCCEEDED(p_opAppLic->put_Completed(p_onAppLicCallback.Get()))) 
    { 
     //Success initiating async call 
    } 
} 

Итак, если вы сделайте все это, ваше приложение, преобразованное в UWP, будет вести себя очень странно. Вот пример. Скажите, что пользователь покупает лицензию для приложения через Windows Store.В свою очередь, ваша логика приложения вызывает код выше, чтобы узнать, активировано ли приложение, но то, что вы получаете, это nActive=0 и nTrial=1. Тогда, если вы проверите strStoreId, это будет ваш идентификатор магазина приложения, но без SKU. WTF !?

Я знаю, это действительно сбивает с толку. В стороне, позвольте мне объяснить. Когда вы сначала указываете свое приложение в магазине Windows, ему будет присвоен идентификатор магазина. Что-то вроде: ABCDEFG12345. Затем, если вы отправляете какие-либо последующие обновления первой версии того же приложения, они добавят к нему номер SKU, что приведет к изменению идентификатора всего приложения до ABCDEFG12345/0010, затем ABCDEFG12345/0011 для следующего обновления. на.

Ну, код WinRT, указанный выше, вернет мой идентификатор магазина приложений как ABCDEFG12345 без привязки к нему SKU. Это было неправильно, поскольку это было третье обновление для первой версии приложения. И таким образом, любые дополнительные атрибуты для этого идентификатора магазина приложения также были неправильными.

Так что была проблема, что я столкнулся с ...

Причина

Все головная боль, которую я описал выше была вызвана моим бездействием проверить код результата возвращается из первого RoInitialize вызова. Я смог бы поймать эту проблему гораздо быстрее, если бы я сделал это:

//Init COM for WinRT 
if(FAILED(RoInitialize(RO_INIT_MULTITHREADED))) 
{ 
    //WinRT COM initialization failed 
    //Go scratch your head why.... 
} 

В этом случае RoInitialize потерпит неудачу с кодом ошибки RPC_E_CHANGED_MODE. documentation for it является полезным в качестве справки для Windows варианта (F1):

Предыдущего вызов RoInitialize указана модель параллелизма для этого потока, как многопоточный апартамент (MTA). Это также может указывать , что произошла замена квартиры с нейтральной резьбой на однопоточную квартиру .

Какой предыдущий звонок? Единственный параметр, с которым можно позвонить, - RO_INIT_MULTITHREADED.

Итак, я начал копать глубже и в процессе устранения обнаружил, что вызов OleInitialize был причиной того, почему RoInitialize не удалось и вызвал каскад событий, описанных выше.

Таким образом, я был здесь, чтобы задать вопрос здесь.

Обратите внимание на стороне, что ошибки охваченной WinRT библиотека (ref1, ref2, ref3, ref4, ref5) не дала мне никаких признаков проблемы во всех вызовах следующих RoInitialize и где-то внутри тихо не удалось получить приложение SKU из-за single-thread apartment Инициализация COM.

Hack/Обход

Как было предложено RbMm в комментариях выше, делая следующий будет работать, но это полностью документированы поведение:

if(SUCCEEDED(OleInitialize(0)) 
{ 
    CoUninitialize(); 
} 

CoInitializeEx(NULL, COINIT_MULTITHREADED); 

Так что, если вы не хотите, чтобы ваше приложение чтобы начать рушиться без видимой причины, я бы не использовал его.

Решение

Мое решение, что я пошел с было переместить все WinRT COM вещи (код я перечислил выше: 2-й и 3-й сегменты кода) в отдельный поток. Там будет отлично. Вопрос: marshalling звонки между вашим основным потоком и этим рабочим потоком. Это выполнимо, но требует некоторой работы, то есть с использованием mutexes и events для синхронного доступа и т. Д.

Так что если кто-нибудь найдет для вас более легкое решение, отправьте свое решение. Я буду отмечать это как ответ.

+0

Вам не нужны мьютексы и события, чтобы маршировать интерфейсы WinRT через границы квартир. Как описано в разделе [Threading and Marshaling] (https://msdn.microsoft.com/en-us/library/windows/apps/hh771042.aspx), * «в подавляющем большинстве случаев экземпляры классов Windows Runtime, например стандартные объекты C++, могут быть доступны из любого потока. Такие классы называются «agile». » – IInspectable

+1

(Примечание: это может быть неточно.) Он предложил решение« CoreDragDropManager ». 'RegisterDragDrop()' все еще работает в вашем конвертированном пакете приложений, потому что он не использует никаких функций Windows Runtime/UWP. Конвертер Desktop App только что сделал пакет AppX вокруг вашего настольного приложения, предназначенного только для Win32. Теперь, когда вы используете функции Windows Runtime/UWP, вам нужно «RoInitialize()», а «RegisterDragDrop()» больше не применяется. – andlabs

+0

@andlabs: Возможно, «CoreDragDropManager.RegisterDragDrop» будет работать в приложении Win32, преобразованном в UWP. Понимаете, причина, по которой я использую этот процесс конверсии, так это то, что мое приложение все еще может работать в ОС до Windows 10. Плюс просто предоставление мне «CoreDragDropManager.TargetRequested» потребует нескольких часов исследований, обучения и тестирования без конкретного примера. Поэтому вопрос заключается в том, почему вы оставите «RegisterDragDrop»? Просто чтобы компенсировать что-то еще. – c00000fd

0

Вы спросили, почему сначала следует вызвать OleInitialize(), затем следует CoUnintialize, а затем повторно запустите COM через CoInitializeEx, и он безопасен, посмотрите на код перезаписанного OLE-сервера в WINE, https://github.com/wine-mirror/wine/blob/master/dlls/ole32/ole2.c он близок к «реальной вещи», , OleInitialize вызывает CoInitializeEx сам с COINIT_APARTMENTTHREADED и не выполняется перед выполнением инициализаций, специфичных для OLE, при сбое CoInitializeEx. Нет причин для отказа, поскольку код OLE может работать также в режиме MULTITHREADED. Помните, что MULTITHREADED означает, что вызывающий абонент должен позаботиться о синхронизации/блокировке, в то время как с APARTMENTTHREADED COM libray будет обрабатывать его для кода. Поэтому, если вы убедитесь, что вы не вызываете OLE-код, например dragdrop и буфер обмена, из нескольких потоков, тогда проблем нет. Сохранение всего пользовательского интерфейса в главном потоке сделает это. Как вы уже должны писать многопотоковый код самостоятельно, используя запрошенный режим MULTITHREADED.

У меня проблема с фильтрами/драйверами directshow, которые блокируют процесс, когда COM инициализируется с помощью APARTMENTTHREADED, даже когда directshow вызывается из потока с THREADED, в то время как основной поток пользовательского интерфейса работает в APARTMENTTHREADED.

Uninitializing COM после инициализации OLE, а затем повторного инициализации COM с MULTITHREAED во время запуска в основном потоке пользовательского интерфейса, вы обойдете ошибку в OleInitialize. Это лучшее решение, чтобы убедиться, что все работает хорошо.

+0

Я нашел случай, когда комбинация OleInitialize с COINIT_MULTITHREADED приводит к сбою: если IDsObjectPicker вызывается в потоке с этой комбинацией, это приведет к сбою, когда диалог будет закрыт и объект будет выпущен. Во всех версиях Windows (тестирование W2K, XP, Win10). Стек будет переполняться рекурсивным вызовом той же функции. –

1

решение проблемы с IDsObjPicker Я упоминал в комментарии ealier, быстрый код, который я написал только сейчас.

Использование кода ниже, как:

TDsObjPicker lv_PickInfo; 

    memset(&lv_PickInfo, 0, sizeof(TDsObjPicker)); 

    Sec_InitDsObjPicker(&lv_PickInfo, &lv_InitInfo); 

    Sec_InvokeDsObjPicker(&lv_PickInfo, 0, &lv_oData); 

Решение это запустить диалог в другом потоке и инициализации нить без комбинации Ole + Com:

// command codes 
#define DSOPCMD_EXITTHREAD 1 
#define DSOPCMD_INITIALIZE 2 
#define DSOPCMD_INVOKE  3 



// parameters of object picker via thread 
typedef struct tagDsObjPicker 
{ 
    // thread handle 
    HANDLE   hThread; 

    // events 
    HANDLE   hCmdEvt; 
    HANDLE   hRdyEvt; 

    // commands 
    UINT   CmdCode; 
    HRESULT   hResult; 

    // command parameters - DSOPCMD_INITIALIZE 
    DSOP_INIT_INFO *InitInfo; 

    // command parameters - DSOPCMD_INVOKE 
    HWND   hWnd; 
    IDataObject **oData; 

    // 
} TDsObjPicker; 




DWORD CALLBACK _Sec_DsObjPickerThread(VOID *in_Param) 
{ 
    /* locals */ 
    HRESULT   lv_hCreateResult; 
    HRESULT   lv_hResult; 
    TDsObjPicker *lv_PickInfo; 
    IDsObjectPicker *lv_oPicker; 


    // get info structure 
    lv_PickInfo = (TDsObjPicker*)in_Param; 

    // init COM 
    CoInitializeEx(NULL, COINIT_MULTITHREADED); 

    // preclear object pointer 
    lv_oPicker = NULL; 

    // create instance of picker 
    lv_hCreateResult = CoCreateInstance(
    CLSID_DsObjectPicker, NULL, CLSCTX_INPROC_SERVER, 
    IID_IDsObjectPicker, (VOID**)&lv_oPicker); 

    // while thread is not aborted 
    while (lv_PickInfo->CmdCode != DSOPCMD_EXITTHREAD) 
    { 
    // wait for command event 
    if (WaitForSingleObject(lv_PickInfo->hCmdEvt, INFINITE) == 0) 
    { 
     // what command? 
     switch (lv_PickInfo->CmdCode) 
     { 
     // call init 
     case DSOPCMD_INITIALIZE: 
     { 
      // call object 
      if (lv_hCreateResult) 
      lv_hResult = lv_hCreateResult; 
      else 
      lv_hResult = lv_oPicker->Initialize(lv_PickInfo->InitInfo); 

      // done 
      break; 
     } 

     // call invoke 
     case DSOPCMD_INVOKE: 
     { 
      // call object 
      if (lv_hCreateResult) 
      lv_hResult = lv_hCreateResult; 
      else 
      lv_hResult = lv_oPicker->InvokeDialog(lv_PickInfo->hWnd, lv_PickInfo->oData); 

      // done 
      break; 
     } 

     // other command codes 
     default: 
      lv_hResult = E_FAIL; 
      break; 
     } 

     // store result 
     lv_PickInfo->hResult = lv_hResult; 

     // notify caller 
     SetEvent(lv_PickInfo->hRdyEvt); 
    } 
    } 

    // destroy the picker object 
    if (lv_oPicker) 
    lv_oPicker->Release(); 

    // cleanup COM 
    CoUninitialize(); 

    // leave the thread 
    return 0; 
} 



VOID Sec_DoneDsObjPicker(TDsObjPicker *in_PickInfo) 
{ 
    // is thread created? 
    if (in_PickInfo->hThread) 
    { 
    // set command code 
    in_PickInfo->CmdCode = DSOPCMD_EXITTHREAD; 

    // trigger the thread to process the code 
    SetEvent(in_PickInfo->hCmdEvt); 

    // wait for thread to finish 
    WaitForSingleObject(in_PickInfo->hThread, INFINITE); 

    // close thread handle 
    CloseHandle(in_PickInfo->hThread); 
    } 

    // close event handles 
    if (in_PickInfo->hCmdEvt) CloseHandle(in_PickInfo->hCmdEvt); 
    if (in_PickInfo->hRdyEvt) CloseHandle(in_PickInfo->hRdyEvt); 

    // clear 
    memset(in_PickInfo, 0, sizeof(TDsObjPicker)); 
} 




HRESULT Sec_InitDsObjPicker(TDsObjPicker *in_PickInfo, DSOP_INIT_INFO *in_InitInfo) 
{ 
    /* locals */ 
    DWORD lv_TID; 


    // thread not yet created? 
    if (!in_PickInfo->hThread) 
    { 
    // create events 
    in_PickInfo->hCmdEvt = CreateEvent(0,0,0,0); 
    in_PickInfo->hRdyEvt = CreateEvent(0,0,0,0); 

    // if ok 
    if (in_PickInfo->hCmdEvt && in_PickInfo->hRdyEvt) 
    { 
     // create the thread 
     in_PickInfo->hThread = CreateThread(
     0, 0, _Sec_DsObjPickerThread, in_PickInfo, 0, &lv_TID); 
    } 

    // failed? 
    if (!in_PickInfo->hThread) 
    { 
     // cleanup 
     Sec_DoneDsObjPicker(in_PickInfo); 

     // return with error 
     return E_OUTOFMEMORY; 
    } 
    } 

    // store parameter 
    in_PickInfo->InitInfo = in_InitInfo; 

    // set command code 
    in_PickInfo->CmdCode = DSOPCMD_INITIALIZE; 

    // trigger the thread to process the code 
    SetEvent(in_PickInfo->hCmdEvt); 

    // wait for result 
    WaitForSingleObject(in_PickInfo->hRdyEvt, INFINITE); 

    // return the result 
    return in_PickInfo->hResult; 
} 

HRESULT Sec_InvokeDsObjPicker(TDsObjPicker *in_PickInfo, HWND in_hWnd, IDataObject **out_oData) 
{ 
    /* locals */ 
    MSG lv_Msg; 


    // thread not yet created? 
    if (!in_PickInfo->hThread) 
    return E_FAIL; 

    // store parameters 
    in_PickInfo->hWnd = in_hWnd; 
    in_PickInfo->oData = out_oData; 

    // set command 
    in_PickInfo->CmdCode = DSOPCMD_INVOKE; 

    // trigger the thread 
    SetEvent(in_PickInfo->hCmdEvt); 

    // process messages of this thread while picker runs in other thread until event 
    while (MsgWaitForMultipleObjects(1, &in_PickInfo->hRdyEvt, 0, INFINITE, QS_ALLINPUT) != 0) 
    { 
    // get next message 
    while (PeekMessage(&lv_Msg, 0,0,0, PM_REMOVE)) 
    { 
     // translate/dispatch the message 
     TranslateMessage(&lv_Msg); 
     DispatchMessage(&lv_Msg); 
    } 
    } 

    // return the result 
    return in_PickInfo->hResult; 
} 
Смежные вопросы