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
для синхронного доступа и т. Д.
Так что если кто-нибудь найдет для вас более легкое решение, отправьте свое решение. Я буду отмечать это как ответ.
это может быть неправильно, но с рядом код перетаскивания все еще работал ok 'if (0 <= OleInitialize (0)) CoUninitialize(); RoInitialize (RO_INIT_MULTITHREADED); '- да непарные вызовы -' OleInitialize' с 'CoUninitialize'. – RbMm
@ RbMm: Хм, да, похоже, это сработало. Благодарю. Хотя, пожалуйста, объясните, что вы там делаете? – c00000fd
РеестрDragDrop недоступен в UWP, так что вы уже на неправильной ноге. Для UWP используйте CoreDragDropManager.TargetRequested. –