c++ - 如何结合RegisterDragDrop,RoInitialize在一个线程中一起工作?

标签 c++ windows winapi com windows-runtime

我有两难选择。我基于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以他通常的居高临下的方式擅长批评事物,但无法解决现有问题。我主要将其发布,以供日后参考,以防万一其他人也遇到同样的问题。我只花了几天时间尝试解决此错误,因此也许可以节省其他人的时间。

问题

首先,这是本机Win32代码(没有.NETC++/CX。)它是C++,上面撒了WRL,以便于处理WinRT / COM内容。

就我而言,我有一个Win32 GUI应用程序,该应用程序将文件拖放到其主窗口中。因此要启动它,需要在应用程序启动时从主线程执行此操作:

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


上面的OleInitialize调用将初始化主线程的COM以使用single-thread apartment,这是RegisterDragDrop成功所必需的。没有它,拖放功能将不起作用。

然后,假设您决定使用Microsoft的Project Centennial转换器将此Win32应用程序转换为UWP,以包含在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应用商店购买了该应用的许可证。反过来,您的应用程序逻辑调用上面的代码以查看应用程序是否已激活,但是您得到的是nActive=0nTrial=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_MODEdocumentation for it与Windows帮助(F1)选项一样有用:


  先前对RoInitialize的调用指定了并发模型
  此线程为多线程单元(MTA)。这也可能表明
  从中性线程单元到单线程的转变
  公寓已经发生。


之前打过什么电话?任何人都可以使用的唯一参数是RO_INIT_MULTITHREADED

因此,我开始更深入地研究,并通过消除过程发现,更早的OleInitialize调用是RoInitialize失败并导致上述事件级联的原因。

因此,我当时正要在这里提出问题。

在侧面请注意,错误缠身的WinRT库(ref1ref2ref3ref4ref5)没有迹象表明在RoInitialize之后以及内部某处的所有调用中都存在问题由于SKU COM初始化,无提示地无法检索应用程序的single-thread apartment

破解/解决方法

正如RbMm在上面的注释中所建议的那样,执行以下操作将是可行的,但这是完全未记录的行为:

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

CoInitializeEx(NULL, COINIT_MULTITHREADED);


因此,如果您不希望您的应用由于没有明显的原因而崩溃,那么我将不使用它。



我所采用的解决方案是将所有WinRT COM内容(上面列出的代码:第二和第三代码段)移动到单独的工作线程中。从那里可以正常工作。问题是在主线程和该工作线程之间编组调用。它是可行的,但需要一些工作,即使用mutexesevents进行同步访问等。

因此,如果有人找到更简单的解决方法,请发布您的解决方案。我将其标记为答案。

关于c++ - 如何结合RegisterDragDrop,RoInitialize在一个线程中一起工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41404779/

相关文章:

c++ - 所有类的 pretty-print ,支持基于范围的 for 循环

c++ - 广度优先搜索返回不正确的数量

c++ - 在 C++ 中使用标准库函数名作为标识符是否有效?

php - 是否可以在 Windows 中的 PHP 和 C 之间共享内存?

Windows FFMPEG - 将 PNG 转换为持续时间为 n 秒的 WMV

c++ - 从 COLORREF[] 中绘制 DeviceContext

c++ - 如何在 C++ 中检查程序是否安装了 Qt

windows - git push origin master 拒绝用户 X,其中 x 不是本地 git 配置中的用户

c++ - 创建 WIN32 应用程序(面向对象)(C++)

c# - P/Pnvoke SetFocus 到一个特定的控件