我陷入两难境地。我的基于GUI的C ++应用程序需要实现拖放功能。与此同时,我正在将此Win32应用程序转换为UWP以提交到Windows应用商店。但是有一个问题:
要实现拖放,我需要调用这两个方法:
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代码移动到工作线程中,这会使事情变得复杂。)
Raymond Chen以他惯常的居高临下的方式擅长批评事情,但没有解决现有问题。我发布这个主要是为了以后的自我引用,以防其他人偶然发现同样的问题。我只花了几天时间试图解决这个bug,所以也许这会为别人节省时间。
首先,这是一个原生的Win32代码(没有.NET
或C++/CX
。)它是C++
与洒上WRL更容易处理WinRT
/ COM
的东西。
在我的例子中,我有一个Win32 GUI应用程序,它实现了将文件拖放到主窗口中。因此,要初始化它,需要从主线程执行此操作,就在应用程序启动时:
OleInitialize(NULL);
//...
HRESULT hr = RegisterDragDrop(hMainWnd, pDropTarget);
上面的OleInitialize
调用将为主线程初始化COM以使用single-thread apartment
,这是RegisterDragDrop
成功所必需的。没有它,拖放功能将无法工作。
然后,假设您决定使用Microsoft的Win32
转换器将此UWP
应用程序转换为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应用商店购买应用的许可证。反过来你的app逻辑调用上面的代码来查看应用程序是否被激活,但你得到的是nActive=0
和nTrial=1
。然后,如果你检查strStoreId
它将是你的应用程序商店ID但没有SKU。 WTF!?
我知道,这真的令人困惑。顺便说一下,让我解释一下。当您首次在Windows应用商店中列出您的应用时,将为其分配一个商店ID。像:ABCDEFG12345
。然后,如果您向同一个应用程序的第一个版本提交任何后续更新,他们将向其添加一个SKU
编号,这将使整个应用程序ID更改为ABCDEFG12345/0010
,然后ABCDEFG12345/0011
进行下一次更新,等等上。
好吧,上面的WinRT代码会将我的应用程序商店ID返回为ABCDEFG12345
而不附加任何SKU
。这是错误的,因为它是应用程序的第一个版本的第三次更新。因此,该应用商店ID的任何其他属性也是错误的。
这就是我面临的问题......
我上面描述的所有头痛都是由于我省略了检查第一次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初始化。
正如RbMm在上面的评论中所建议的那样,执行以下操作会有效,但这是完全没有记录的行为:
if(SUCCEEDED(OleInitialize(0))
{
CoUninitialize();
}
CoInitializeEx(NULL, COINIT_MULTITHREADED);
因此,如果您不希望您的应用程序因为没有明显原因而开始崩溃,我就不会使用它。
我使用的解决方案是将所有WinRT COM
内容(上面列出的代码:第2和第3代码段)移动到单独的工作线程中。它会在那里工作得很好。问题是在主线程和此工作线程之间编组调用。它是可行的,但需要一些工作,即使用mutexes
和events
进行同步访问等。
因此,如果有人发现更容易解决此问题,请发布您的解决方案。我会把它标记为答案。
我之前评论中提到的DatePicker崩溃的解决方案,我刚才写的快速代码。
使用以下代码:
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;
}
你问为什么首先调用OleInitialize(),然后调用CoUnintialize然后通过CoInitializeEx重新启动COM工作并且是安全的,看看WINE中重写的OLE服务器的代码,https://github.com/wine-mirror/wine/blob/master/dlls/ole32/ole2.c它非常接近“真实的东西”。 OleInitialize使用COINIT_APARTMENTTHREADED调用CoInitializeEx本身,并在CoInitializeEx失败时执行OLE特定的初始化之前失败。没有理由失败,因为OLE代码也可以在MULTITHREADED模式下运行。记住MULTITHREADED意味着调用者必须处理同步/锁定,而APARTMENTTHREADED COM库将为代码处理它。因此,如果您确保不从多个线程同时调用像拖放和剪贴板这样的OLE代码,那么就没有问题。保持主线程中的所有UI都可以做到这一点。因为您应该已经使用请求的MULTITHREADED模式自己编写多线程感知代码。
我有一个directshow过滤器/驱动程序的问题,即使在主要UI线程在APARTMENTTHREADED中运行时从具有THREADED的线程调用directshow时,也会在使用APARTMENTTHREADED初始化COM时锁定进程。
在初始化OLE之后取消初始化COM,然后在主UI线程中启动期间使用MULTITHREAED重新初始化COM会使您绕过OleInitialize中的失败。这是确保所有运行良好的最佳解决方案。