Introduction
Today, I will tell you about what has become one of my craziest experiment:
Render the Canvas of previous issue in an UWP, Win2D Canvas Control.
The idea was simple:
- Create an
UWP
app backed on C++/WinRT
- Follow instruction to embed a
Win2D Canvas
in it - Include our
Canvas
code from the previous issue - Make a
"C"
projection of the needed COM
interfaces - Extract pointers to native instances of that interfaces from
C++/WinRT
wrappers, to share with "C"
- Make our
Renderer
wrapper around those interfaces - And finally, yield back control to
"C"
on any event
But not so simple to be accomplished!
Background
This is to be considered an "exploration beyond the boundaries" of concepts first introduced in "CoreObjects/GoliahCore17: Effective OOP for plain C" and an extension of its sequel "CoreObjects/GoliahCore17: Effective OOP for plain C || 2", so an overview of both is welcome. Component Object Model
(COM
) and C++
Basics can also help.
The Goal
The original plan was to create an UWP
app working as a modern front-end derived by a "C" application, as if we would bring a modern Windows GUI to a legacy "C" application.
The legacy "C"
application had to be based upon our sample Canvas
example.
The new GUI had to be composed by a Win2D Canvas
(see it on github) and some buttons to add and edit shapes.
The "C" code had to preserve is central role, handling events and driving actions.
The Route
The steps to reach our objective.
Create an UWP App Backed on C++/WinRT
First of all, we need to install the C+++/WinRT for Visual Studio 2017.
The actual release of VisualStudio installed on my PC is the following:
Microsoft Visual Studio Community 2017
Version 15.9.4
VisualStudio.15.Release/15.9.4+28307.222
Microsoft .NET Framework
Version 4.7.03056
To Install the C++/WinRT Extension
- Choose Extensions And Updates under the Tools Menu, to showup the Extensions And Updates dialog.
- Expand the installed node in the left pane.
- Verify if it is already installed in the list on the middle pane.
- To speedup the check, type
winrt
in the search box in the top of right panel. - If it is not installed, expand the online node in the left pane.
- Type
winrt
in the search box in the top of right panel. - Locate the box of C++/WinRT in the middle list.
- Click the download button.
- Follow the instruction up to VisualStudio restart.
To Create a UWP Project for C++/WinRT
- Choose Project under New entry of File Menu to show up the New Project dialog.
- In the left pane, under Installed, expand the Visual C++ node.
- Select Window Universal. The middle pane is filled with a list of templates.
- Select Blank App (C++/WInRT).
- Give a name to the project (and to the solution), choosing also the containing folder.
- Click OK.
The project is ready, with a form containing 1 button saying "Click Me", and all the code needed to handle the click.
You can now build the project and, after some minutes (i7 3.4Ghz 8MB SSD!!!), you can click on the Click Me button.
Installing the Win2D Package
To install Win2D
package, I've followed the instructions in the Win2D documentation (here):
- Choose Manage NuGet Packages for Solution... under NuGet Package Manager of Tools menĂ¹.
- Type '
Win2D
' into the 'Search Online' box, and hit Enter. - Select the 'Win2D.uwp' package and click 'Install', then 'OK'.
- Accept the license agreement.
- Click 'Close'.
Embedding the Win2D Canvas Control
The documentation of Win2D
mentioned above include only examples for C#, C++/CX, and VB.
Fortunately, the C++/WinRT approach is not so much different from the one of C++/CX, so the list below is an adapted version of the C++/CX example:
- Double click on MainPage.xaml in Solution Explorer to open the XAML editor.
- Add the
Microsoft.Graphics.Canvas.UI.Xaml
namespace next to the existing xmlns
statements:
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
- Add a
CanvasControl
inside the existing Grid
control:
<StackPanel Orientation="Vertical"
HorizontalAlignment="Center" VerticalAlignment="Center">
<canvas:CanvasControl x:Name="canvasControl"
Draw="canvasControl_Draw" ClearColor="CornflowerBlue" Width="600"
Height="300" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</StackPanel>
- Edit pch.h to add some
include
s:
#include "winrt/Microsoft.Graphics.Canvas.UI.Xaml.h"
- Edit the MainPage.h to add the following method on
MainPage
class:
void canvasControl_Draw(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const& sender,
Microsoft::Graphics::Canvas::UI::Xaml::CanvasDrawEventArgs const& args);
- Edit the MainPage.cpp to add
namespace
s at the top:
using namespace Microsoft::Graphics::Canvas;
using namespace Microsoft::Graphics::Canvas::UI::Xaml;
using namespace Windows::UI;
- Next add the method:
void MainPage::canvasControl_Draw(CanvasControl const& sender, CanvasDrawEventArgs const& args)
{
args.DrawingSession().DrawEllipse(155, 115, 80, 30, Colors::Black(), 3);
args.DrawingSession().DrawText(L"Hello, world!", 100, 100, Colors::Yellow());
}
Build and test the results.
Making Win2D COM Interfaces Suitable from Plain C
- First of all, I've realized that for this example, I had the needs of only one interface, the
IWin2dDrawingSession
. - Next, I've found the basic COM interface in a generate header ".\Generated Files\winrt\impl\Microsoft.Graphics.Canvas.0.h":
template <> struct abi<Microsoft::Graphics::Canvas::ICanvasDrawingSession>
{ struct type : IInspectable
{
{...}
}
- I've manually converted this C++
struct
in its "C" counterpart:
struct IWin2dDrawingSessionProtocol {
{...}
}
- To do so, I've ported to "C" a constellation of support structure needed by the huge interface.
- Finally, I've created a
Win2dDrawingSession
the more conforming possible to the CoreObjects
principles.
Here is an example of the results in an invocation:
CoreResultCode Win2dRenderer_Renderer_renderRectangle
(CoreTarget target, struct CanvasRectangle* entity) {
struct Win2dRenderer * self = (struct Win2dRenderer *)
CoreObject_getSelf(Win2dRendererType->Class, target);
self->Session->invoke->Win2dDrawingSession->
DrawRectangleAtCoordsWithColorAndStrokeWidth(self->Session
, entity->Location->X-(entity->Extent->Width/2),
entity->Location->Y-(entity->Extent->Height/2)
, entity->Extent->Width, entity->Extent->Height
, ColorModelARGB32_asWindow_UI_Color(entity->Attributes->StrokeColor)
, entity->Attributes->StrokeWidth);
return CoreResultCode_Success;
}
The "C" Application
The "C" application is an adaptation of our Canvas
sample in the previous issue.
- I've included in the project folder and renamed as GoliahCore17_CoreObjects_Demo2b_Canvas.c a copy of the sample from "CoreObjects/GoliahCore17: Effective OOP for plain C || 2"
- Created the
Win2dRender
backed on Win2dDrawingSession
(see attached code) - Created the
App_main
function:
enum App_main_Stage {
App_main_Stage_Load
, App_main_Stage_Exit
, App_main_Stage_Draw
, App_main_Stage_Click
};
CoreResultCode App_main(CoreTarget target, CoreWord16 stage, ...) {
CoreArgumentList argList[1];
if(target == 0) goto __abort;
switch(stage) {
case App_main_Stage_Load:
{
struct CanvasEntity* ent;
struct Canvas* canvas = Canvas_build(0);
ent = (struct CanvasEntity*)CanvasRectangle_build
(0, 155, 115, 80, 30, 14, ColorModelARGB32_make(100, 0, 255, 0));
canvas->invoke->Entities->insert(canvas, ent);
ent = (struct CanvasEntity*)CanvasCircle_build
(0, 155, 115, 80, ColorModelARGB32_make(100, 255, 0, 0));
canvas->invoke->Entities->insert(canvas, ent);
(*(struct Canvas**)target)=canvas;
break;
}
case App_main_Stage_Exit:
{
CoreObject_free(target);
break;
}
case App_main_Stage_Draw:
{
CoreByte rendererRegion[sizeof(struct Win2dRenderer)];
CoreArgumentList_init(argList, stage);
struct CanvasRenderer* renderer = (struct CanvasRenderer*)Win2dRenderer_build
(rendererRegion, (struct Win2dDrawingSession*)CoreArgumentList_take(argList,CoreTarget));
CoreArgumentList_done(argList);
struct Canvas* canvas = (struct Canvas*) target;
canvas->invoke->Canvas->render(canvas, renderer);
break;
}
case App_main_Stage_Click:
{
struct Canvas* canvas = (struct Canvas*) target;
struct CanvasEntityIterator* it=canvas->invoke->Entities->getIterator(canvas);
struct CanvasEntityLocated* ent;
for(ent = 0; it->invoke->next(it, &ent););
GeometricsLocation2D loc = {150, 150};
ColorModelARGB32 color = ColorModelARGB32_make(255,255,0,0);
if(ent) {
loc.X = (ent->Location->X + 30); if(loc.X > 600) loc.X = 0;
loc.Y = (ent->Location->Y-30); if(loc.Y < 0) loc.Y = 300;
color = ColorModelARGB32_make(255, ent->Attributes->StrokeColor.Red - 10, 0, 0);
}
ent = CanvasRectangle_build(0, loc.X, loc.Y, 100, 50, 0, color);
canvas->invoke->Entities->insert(canvas, ent);
break;
}
}
return CoreResultCode_Success;
__abort:
return CoreResultCode_Fail;
}
A Bit of Glue into the C++ Side
To join all together, I've inserted App_main
calls in MainPage
class keypoints.
- Adding the magic "getter" of the needed
COM
interfaces:
template<typename Type> struct CoreObject_ComInstance { using type = IUnknown; };
template<typename Type> static CoreTarget CoreObjects_getComInstance(Type const&target) {
using IType = typename CoreObject_ComInstance<CanvasDrawingSession>::type;
return *(winrt::impl::abi_t<ICanvasDrawingSession>**)
&static_cast<ICanvasDrawingSession const&>(target);
}
- Adding
Canvas
data Member:
struct MainPage : MainPageT<MainPage>
{
{...}
CoreTarget Canvas;
{...}
};
Extern
declarations at the top of MainPage.cpp:
extern "C" {
typedef uint16_t CoreWord16;
typedef const void* CoreTarget;
typedef uint32_t CoreResultCode;
enum App_main_Stage {
App_main_Stage_Load
, App_main_Stage_Exit
, App_main_Stage_Draw
, App_main_Stage_Click
};
CoreResultCode App_main(CoreTarget target, CoreWord16 stage, ...);
}
- Constructor:
MainPage::MainPage()
{
InitializeComponent();
App_main(&Canvas,App_main_Stage_Load);
}
- Destructor:
MainPage::~MainPage() {
App_main(Canvas,App_main_Stage_Exit);
}
- Win2D Canvas Draw Event:
void MainPage::buttonControl_Click(IInspectable const&, RoutedEventArgs const&)
{
App_main(Canvas,App_main_Stage_Click);
canvasControl().Invalidate();
}
- Button Click Event:
void MainPage::canvasControl_Draw(CanvasControl const& sender, CanvasDrawEventArgs const& args)
{
App_main(Canvas, App_main_Stage_Draw, CoreObjects_getComInstance(args.DrawingSession()));
}
The Results
The final app is not exactly what I've expected (due... see the next paragraph), but for now sufficient for the purpose of this article. There are the Canvas
control and a button to add a shape.
That's all folks!
Jams
I have to mention that C++/WINRT
is still at a preliminary stage, as told by itself:
XamlCompiler warning WMC1502: CppWinRT support is currently in preview and may change in future updates.
A part of that, I had to experiment a mess of disappointing issues...
- Over 2 minutes for a complete rebuild, 2 of that to run code generators and PCH
- Impossible to define a separate PCH for the C code
- XAML Designer quite unusable due unknown exceptions frequently thrown
- Source code associated with XAML not correctly generated nor kept in sync
- IntelliSense lazy, incorrect and very very crazy
- VisualStudio lagging, keeping for dozens of seconds one processor core of my i7 at 100% of duty
- Not to mention the leak of a method to get the wrapped COM interface from a C++/WinRT class
- etc.
How to Use the Source Code
The source code attached is an agglomerate of code fragments pasted together in a unique C file, to instantly bring the focus on the subject. The package includes also the VisualStudio2017 Community Edition project and solution under which it was created. Feel free to mess with it and enjoy.
Note: Before you build, you need to restore nuget packages.
To Be Continued...
The vastness of the topic can't be fitted in a couple of articles, this is only a brief overview. If it were to receive some kind of interest, I will return to the topic to deal with some other aspects of the OOP using CoreObjects
/ColiahCore17
, from common PC to embedded architectures with very few resources.
History