Using Surface Dial in Desktop Application with using C++.

Using Surface Dial in my apps.
It’s easy in UWP environment. But it’s not easy in Desktop environment.
WRL technology is required to use Surface Dial in desktop applications (e.g : Games) developed in C/C++.
But WRL became a legendary technology. MS dislike old technology like WRL.

I spent a all day for getting ‘ABI::Windows::Storage::Streams::IRandomAccessStreamReference’ object.
Finally, It was possible to check the RuntimeClass name using C++/CX code.
クソMS。
Anyway I succeeded.

radialcontroller_desktop_crop

Ok.I will explain how to do it.

First, Let’s see the sample code in github.
https://github.com/Microsoft/Windows-classic-samples/tree/master/Samples/RadialController

The code is not complicated. The important thing here is how to change the menu item icon.
Looking at the code, …

HRESULT DeviceListener::AddMenuItemFromKnownIcon(_In_ HSTRING itemName, _In_ RadialControllerMenuKnownIcon icon)
{
	// Get menu items
	ComPtr<Collections::IVector<RadialControllerMenuItem*>> menuItems;
	RETURN_IF_FAILED(_menu->get_Items(&menuItems));

	// Create item
	ComPtr<IRadialControllerMenuItem> menuItem;
	RETURN_IF_FAILED(_menuItemStatics->CreateFromKnownIcon(itemName, icon, &menuItem));

	// Set Callback
	RETURN_IF_FAILED(menuItem->add_Invoked(
		Callback<ITypedEventHandler<RadialControllerMenuItem*, IInspectable*>>(this, &DeviceListener::OnItemInvoked).Get(),
		&_menuItem2Token));

	// Add item to menu
	RETURN_IF_FAILED(menuItems->Append(menuItem.Get()));

	// Log new item
	wchar_t message[2000];
	swprintf_s(message, 2000, L"Added %s to menu\n", WindowsGetStringRawBuffer(itemName, nullptr));
	WriteDebugStringW(DEBUG_OUTPUT_TYPE_DEBUG_CONSOLE,message);

	return S_OK;
}

If you want to change the icon, add another such function.

HRESULT DeviceListener::AddMenuItemFromUnknownIcon(_In_ HSTRING itemName, _In_ ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStreamReference> icon)
{
	// Get menu items
	ComPtr<Collections::IVector<RadialControllerMenuItem*>> menuItems;
	RETURN_IF_FAILED(_menu->get_Items(&menuItems));

	// Create item
	ComPtr<IRadialControllerMenuItem> menuItem;
	HRESULT hr = _menuItemStatics->CreateFromIcon(itemName, icon.Get(), &menuItem);

	RETURN_IF_FAILED(hr);

	// Set Callback
	RETURN_IF_FAILED(menuItem->add_Invoked(
		Callback<ITypedEventHandler<RadialControllerMenuItem*, IInspectable*>>(this, &DeviceListener::OnItemInvoked).Get(),
		&_menuItem2Token));

	// Add item to menu
	RETURN_IF_FAILED(menuItems->Append(menuItem.Get()));

	// Log new item
	wchar_t message[2000];
	swprintf_s(message, 2000, L"Added %s to menu\n", WindowsGetStringRawBuffer(itemName, nullptr));
	WriteDebugStringW(DEBUG_OUTPUT_TYPE_DEBUG_CONSOLE, message);

	return S_OK;
}

The question is how to get ComPtr icon.
In C++/CX, you can write this code.

	Windows::ApplicationModel::Package^ package = Windows::ApplicationModel::Package::Current;
	Windows::Storage::StorageFolder^ folder = package->InstalledLocation;

	String^ path = folder->Path;
	create_task(folder->GetFileAsync(L"miku_icon.png")).then([this](Windows::Storage::StorageFile^ file)
	{
		Windows::Storage::Streams::RandomAccessStreamReference^ streamRef = Windows::Storage::Streams::RandomAccessStreamReference::CreateFromFile(file);

	});

Then, in WRL, how should it be done?

Object of ABI::Windows::Storage::StorageFile and Object of ABI::Windows::Storage::Streams::RandomAccessStreamReference are required.

But it is impossible to call class static method directly in WRL.
You need to get the object with the suffix “Statics” and call the method through it.

Object of ABI::Windows::Storage::IStorageFileStatics required for calling static method of ABI::Windows::Storage::StorageFile.
And Object of ABI::Windows::Storage::Streams::IRandomAccessStreamReferenceStatics required for calling static method of ABI::Windows::Storage::Streams::RandomAccessStreamReference.
Get a object of ABI::Windows::Storage::IStorageFileStatics as follows.

HRESULT	hr;
	ComPtr<IActivationFactory> pFactory;
	hr = Windows::Foundation::GetActivationFactory(Microsoft::WRL::Wrappers::HStringReference(RuntimeClass_Windows_Storage_StorageFile).Get(), &pFactory);
	if (S_OK != hr)
		__debugbreak();

	ComPtr<ABI::Windows::Storage::IStorageFileStatics> pStorageFileStatics;
	hr = pFactory.As(&pStorageFileStatics);
	if (S_OK != hr)
		__debugbreak();

Get a object of ABI::Windows::Storage::Streams::IRandomAccessStreamReferenceStatics as follows.

ComPtr<IActivationFactory> pStreamRefFactory;
			const WCHAR* runtime_class_name = L"Windows.Storage.Streams.RandomAccessStreamReference";
			hr = Windows::Foundation::GetActivationFactory(Microsoft::WRL::Wrappers::HStringReference(runtime_class_name).Get(), &pStreamRefFactory);
			if (S_OK != hr)
				__debugbreak();

			ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStreamReferenceStatics> pStreamRefStatic;
			hr = pStreamRefFactory.As(&pStreamRefStatic);
			if (S_OK != hr)
				__debugbreak();

I can not find Class Runtime Name of ABI::Windows::Storage::Streams::IRandomAccessStreamReferenceStatics in header files.
Finally, I found out the Class Runtime Name using the following C++/CX code.

	auto streamRef = Windows::Storage::Streams::RandomAccessStreamReference::CreateFromFile(nullptr);
	auto uri = ref new Windows::Foundation::Uri(ref new String(L"ms-appx:///") + L"Images/Icon_Etc.png");
	auto streamRef = Windows::Storage::Streams::RandomAccessStreamReference::CreateFromUri(uri);
	IInspectable*	pObj = reinterpret_cast<IInspectable*>(streamRef);
	HSTRING str;
	HRESULT hr = pObj->GetRuntimeClassName(&str); // str -> RuntimeClass Name!!!

In this way, it is possible to find Class Runtime Name in WRL using C ++ / CX code.

Below is the complete source code.

void DeviceListener::AddExtMenu()
{
	// Icon's filename for adding
	const WCHAR*	ICON_FILE_NAME = L"\\icon_miku.png";
	const WCHAR*	MENU_NAME = L"Miku";

	HRESULT	hr;
	ComPtr<IActivationFactory> pFactory;
	hr = Windows::Foundation::GetActivationFactory(Microsoft::WRL::Wrappers::HStringReference(RuntimeClass_Windows_Storage_StorageFile).Get(), &pFactory);
	if (S_OK != hr)
		__debugbreak();

	ComPtr<ABI::Windows::Storage::IStorageFileStatics> pStorageFileStatics;
	hr = pFactory.As(&pStorageFileStatics);
	if (S_OK != hr)
		__debugbreak();

	// Open the file.
	WCHAR	wchFullPath[_MAX_PATH] = {};
	GetCurrentDirectory(_MAX_PATH, wchFullPath);
	wcscat_s(wchFullPath, ICON_FILE_NAME);

	HString path;
	path.Set(wchFullPath);

	ComPtr<ABI::Windows::Foundation::__FIAsyncOperation_1_Windows__CStorage__CStorageFile_t> async;
	pStorageFileStatics->GetFileFromPathAsync(path.Get(), &async);

	typedef IAsyncOperationCompletedHandler<ABI::Windows::Storage::StorageFile*> HandlerType;
	auto handler = Microsoft::WRL::Callback<HandlerType>([this, MENU_NAME](IAsyncOperation<ABI::Windows::Storage::StorageFile*>* async, AsyncStatus status)
	{
		HRESULT		hr = S_FALSE;
		ComPtr<ABI::Windows::Storage::IStorageFile> file;
		switch (status)
		{
		case Started:
			break;
		case Completed:
		case Error:
			hr = async->GetResults(&file);
			break;
		case Canceled:
			break;
		}
		if (file.Get())
		{
			ComPtr<IActivationFactory> pStreamRefFactory;
			const WCHAR* runtime_class_name = L"Windows.Storage.Streams.RandomAccessStreamReference";
			hr = Windows::Foundation::GetActivationFactory(Microsoft::WRL::Wrappers::HStringReference(runtime_class_name).Get(), &pStreamRefFactory);
			if (S_OK != hr)
				__debugbreak();

			ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStreamReferenceStatics> pStreamRefStatic;
			hr = pStreamRefFactory.As(&pStreamRefStatic);
			if (S_OK != hr)
				__debugbreak();

			ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStreamReference> streamRef;
			hr = pStreamRefStatic->CreateFromFile(file.Get(), &streamRef);
			AddMenuItemFromUnknownIcon(HStringReference(MENU_NAME).Get(), streamRef);
		}
		return hr;
	});
	async->put_Completed(handler.Get());
}

It was very hard to find document of WRL.
I hope that this posting help developers using Surface Dial with desktop applications using C ++.
May the force with C++ Developers


Using Surface Dial in Desktop Application with using C++.”에 대한 답글 5개

    1. Of course I have looked the source code. That code has no code to change the icon. This post is a way to change the icon.
      By the way, are you Raymond Chen really? I like your book-Old new thing.

      좋아요

댓글 남기기