Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a modal messagebox in WinRT using native C++

I'm working on a cross-platform C++ SDK at the moment and I have to port our assert handler to WinRT. One part of the process is to display a message box, wait for the user input and trigger a breakpoint when the user selects "debug".

I already got a message box to appear, but I cannot find a way to wait for the message box to appear without leaving the current point of execution.

Here is my code so far.

// 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 ...?

And now I'm stuck here. If I just spin-wait for the callback to be triggered I'm entering an endless loop and the message box does not show at all (at least when I'm calling from the UI-Thread). If I continue execution I'm losing the possibility to trigger a breakpoint at the correct position.

So what I'm looking for is some way to force a redraw or to "busy-wait" for the async call to finish (sth. like "await messadeDialog->ShowAsync()"). I know I could use managed-C++, but I would like to avoid it :)

like image 313
mmpause Avatar asked Nov 03 '22 11:11

mmpause


1 Answers

When you call ShowAsync() to show the popup, the task is scheduled for execution on the UI thread. In order for this task to run, the UI thread must be free to execute it (i.e., it can't be executing other code). If your code is executing on the UI thread and you call ShowAsync(), then you block until ShowAsync() completes, your application will deadlock: the task to show the popup must wait until your code stops running on the UI thread, but your code will not stop running until the task completes.

If you want to wait on the UI thread for an event to happen or for an asynchronous operation to complete, you need to call one of the synchronization functions that pumps the queue so that you don't block the UI thread. For example, take a look at the code in the Hilo project that allows synchronization of an asynchronous operation.

Unfortunately, this still doesn't help you, because Windows Store app UI runs in an Application Single-Threaded Apartment (ASTA), which restricts reentrancy. This is a good thing, because unexpected COM reentrancy is the cause of many of the most horrible of horrible bugs. I don't think there is a way to run the "show the popup" task while your function waits.

However, if this is only for debugging, you can just call MessageBox to show an ordinary message box. It'll show up on the desktop, but your program will definitely wait for the call to complete before continuing execution. Your app won't pass store certification with a call to MessageBox in place, but again, for debug code, it should work fine.

The declaration of MessageBox is #ifdef'ed out by default when building a Windows Store app, but you can declare the function yourself. I wrote an article, "'printf' debugging in Metro style apps" that explains how to do this.


Finally, a quick clarification: there is no "managed C++" for the Windows Runtime. The C++/CX language extensions are syntactically similar to C++/CLI, which targets the .NET Framework and the CLI, but they are semantically different. When using C++/CX, there's no managed code at all and the CLR will not be loaded at runtime. The compiler transforms C++/CX code into equivalent C++ code, then compiles that code. It's all 100% native.

like image 167
James McNellis Avatar answered Nov 12 '22 14:11

James McNellis