5

我目前正在开发一个跨平台的 C++ SDK,我必须将我们的断言处理程序移植到 WinRT。该过程的一部分是显示一个消息框,等待用户输入并在用户选择“调试”时触发断点。

我已经有一个消息框出现,但我找不到等待消息框出现而不离开当前执行点的方法。

到目前为止,这是我的代码。

// Create the message dialog factory

Microsoft::WRL::ComPtr<ABI::Windows::UI::Popups::IMessageDialogFactory> messageDialogFactory;
Microsoft::WRL::Wrappers::HStringReference messageDialogFactoryId(RuntimeClass_Windows_UI_Popups_MessageDialog);

Windows::Foundation::GetActivationFactory(messageDialogFactoryId.Get(), messageDialogFactory.GetAddressOf() );

// Setup the used strings

Microsoft::WRL::Wrappers::HString message;
Microsoft::WRL::Wrappers::HString title;
Microsoft::WRL::Wrappers::HString labelDebug;
Microsoft::WRL::Wrappers::HString labelIgnore;
Microsoft::WRL::Wrappers::HString labelExit;

message.Set( L"Test" );
title.Set( L"Assertion triggered" );
labelDebug.Set(L"Debug");
labelIgnore.Set(L"Ignore");
labelExit.Set(L"Exit");

// Create the dialog object

Microsoft::WRL::ComPtr<ABI::Windows::UI::Popups::IMessageDialog> messageDialog;
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVector<ABI::Windows::UI::Popups::IUICommand*>> messageDialogCommands;

messageDialogFactory->CreateWithTitle( message.Get(), title.Get(), messageDialog.GetAddressOf() );
messageDialog->get_Commands(messageDialogCommands.GetAddressOf());

// Attach commands

Microsoft::WRL::ComPtr<ABI::Windows::UI::Popups::IUICommandFactory> commandFactory; 
Microsoft::WRL::Wrappers::HStringReference commandFactoryId(RuntimeClass_Windows_UI_Popups_UICommand);

Windows::Foundation::GetActivationFactory(commandFactoryId.Get(), commandFactory.GetAddressOf() );

CInvokeHandler commandListener;
commandFactory->CreateWithHandler(labelDebug.Get(), &commandListener, commandListener.m_DebugCmd.GetAddressOf() );
commandFactory->CreateWithHandler(labelIgnore.Get(), &commandListener, commandListener.m_IgnoreCmd.GetAddressOf() );
commandFactory->CreateWithHandler(labelExit.Get(), &commandListener, commandListener.m_ExitCmd.GetAddressOf() );

messageDialogCommands->Append( commandListener.m_DebugCmd.Get() );
messageDialogCommands->Append( commandListener.m_IgnoreCmd.Get() );
messageDialogCommands->Append( commandListener.m_ExitCmd.Get() );

// Show dialog

Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::UI::Popups::IUICommand*>> showOperation;
messageDialog->ShowAsync( showOperation.GetAddressOf() );

// ... and wait for the user to choose ...?

现在我被困在这里。如果我只是旋转等待触发回调,我将进入一个无限循环并且消息框根本不显示(至少当我从 UI 线程调用时)。如果我继续执行,我将失去在正确位置触发断点的可能性。

所以我正在寻找的是某种强制重绘或“忙等待”异步调用完成的方法(比如“await messadeDialog->ShowAsync()”)。我知道我可以使用托管 C++,但我想避免它:)

4

2 回答 2

2

当您调用ShowAsync()显示弹出窗口时,该任务被安排在 UI 线程上执行。为了让这个任务运行,UI 线程必须可以自由地执行它(即它不能执行其他代码)。如果您的代码在 UI 线程上执行并且您ShowAsync()调用ShowAsync()了任务完成。

如果要在 UI 线程上等待事件发生或异步操作完成,则需要调用抽送队列的同步函数之一,以免阻塞 UI 线程。例如,查看Hilo 项目中允许同步异步操作的代码。

不幸的是,这仍然对您没有帮助,因为 Windows 应用商店应用程序 UI 在应用程序单线程单元 (ASTA)中运行,这限制了可重入性。这是一件好事,因为意外的 COM 重入是许多最可怕的错误的原因。我认为没有办法在您的函数等待时运行“显示弹出窗口”任务。

但是,如果这只是为了调试,您可以调用MessageBox显示一个普通的消息框。它会显示在桌面上,但您的程序肯定会等待调用完成,然后再继续执行。您的应用不会通过MessageBox就地调用通过商店认证,但同样,对于调试代码,它应该可以正常工作。

在构建 Windows 应用商店应用程序时,默认情况下 'ed out 的声明,但您可以自己声明该MessageBox函数。#ifdef我写了一篇文章“在 Metro 风格应用程序中进行'printf' 调试”,解释了如何做到这一点。


最后,快速澄清一下:Windows 运行时没有“托管 C++”。C++/CX 语言扩展在语法上类似于 C++/CLI,后者针对 .NET Framework 和 CLI,但它们在语义上有所不同。使用 C++/CX 时,根本没有托管代码,CLR 不会在运行时加载。编译器将 C++/CX 代码转换为等效的 C++ 代码,然后编译该代码。都是 100% 原生的。

于 2012-11-09T18:32:51.387 回答
0

只是对我最终所做的事情的快速跟进(感谢詹姆斯的回答)。

如果从 UI 线程触发了断言(并且应用程序在 STA 中运行),我只需中断并将消息放入调试输出。从 Metro 应用程序触发桌面窗口对我来说似乎是错误的。如果断言是从非 UI 线程触发的,则“模态”框可以完美运行。

#include <ppltasks.h>
using namespace concurrency;

// ...

auto UIDispatcher = Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher;

try
{
    auto uiTask = UIDispatcher->RunAsync( CoreDispatcherPriority::Normal, 
        ref new DispatchedHandler( [&messagePopup, cmds, &result]() 
    {
        try
        {
            create_task(messagePopup->ShowAsync()).then([cmds, &result](IUICommand^ selected) {
                // result is changed depending on which command was selected
            });
        }
        catch (...)
        {
        }
    }));

    // Wait for the user to click

    create_task(uiTask).wait();

    // Sleep until result has been changed
}
catch ( invalid_operation )
{               
    // STA, debugout & break
}

// test on result, etc.

我不知道这是否真的是最好的方法,但它有效:)

您需要将 ShowAsync 分派到 UI 线程,否则在从非 UI 线程调用时会抛出 COM Exception。

我使用了第一个 create_task().then() 因为我很懒 ^^ 并检查用户交互。
当从 STA 调用时 create_task(uiTask).wait() 将引发无效操作(所以我猜 MTA 会正常工作)。
在这种情况下,分派的 ShowAsync 也会通过抛出 COM 异常而失败,因此不会显示任何内容。最后我只是忙着等待盒子被触发。

于 2012-12-19T09:07:44.737 回答