CoreObjects/GoliahCore17:用于纯 C 的 *.*





3.00/5 (3投票s)
UWP、COM、C++/WinRT、纯 C 中的 OOP。还有什么?
引言
今天,我将与您分享我进行过的最疯狂的实验之一。
在 UWP 的 Win2D Canvas 控件中渲染上一期的 Canvas。.
这个想法很简单:
- 创建一个基于
C++/WinRT
的UWP
应用。 - 按照说明将
Win2D Canvas
嵌入其中。 - 包含上一期中的
Canvas
代码。 - 制作所需的
COM
接口的"C"
投影。 - 从
C++/WinRT
包装器中提取本地接口实例的指针,以便与"C"
共享。 - 围绕这些接口制作我们的
Renderer
包装器。 - 最后,在任何事件发生时将控制权交还给
"C"
。
但完成起来并不那么简单!
背景
这被视为对“CoreObjects/GoliahCore17:纯 C 的有效 OOP”中首次引入的概念进行“边界之外的探索”,以及对其续集“CoreObjects/GoliahCore17:纯 C 的有效 OOP || 2”的扩展,因此回顾一下这两篇是有益的。Component Object Model
(COM
) 和 C++
基础知识也会有帮助。
目标
最初的计划是创建一个 UWP
应用,作为从“C”应用程序派生的现代前端,就像我们将现代 Windows GUI 引入旧的“C”应用程序一样。
旧的 “C”
应用程序必须基于我们的示例 Canvas
。
新的 GUI 必须由一个 Win2D Canvas
(在 github 上查看)和一些添加和编辑形状的按钮组成。
“C”代码必须保留其核心作用,处理事件和驱动操作。
路由
达到目标所需的步骤。
创建一个基于 C++/WinRT 的 UWP 应用
首先,我们需要为 Visual Studio 2017 安装 C++/WinRT。
我 PC 上安装的实际 VisualStudio 版本如下。
Microsoft Visual Studio Community 2017
Version 15.9.4
VisualStudio.15.Release/15.9.4+28307.222
Microsoft .NET Framework
Version 4.7.03056
安装 C++/WinRT 扩展
- 在“工具”菜单下选择“扩展和更新”,以显示“扩展和更新”对话框。
- 展开左窗格中的“已安装”节点。
- 验证它是否已在中间窗格的列表中安装。
- 为了加快检查速度,请在右侧面板顶部的搜索框中键入
winrt
。 - 如果尚未安装,请展开左窗格中的“联机”节点。
- 在右侧面板顶部的搜索框中键入
winrt
。 - 在中间列表中找到 C++/WinRT 的框。
- 单击下载按钮。
- 按照说明进行操作,直到 VisualStudio 重启。
创建 C++/WinRT 的 UWP 项目
- 在“文件”菜单的“新建”条目下选择“项目”,以显示“新建项目”对话框。
- 在左窗格中,在“已安装”下,展开“Visual C++”节点。
- 选择“Windows 通用”。中间窗格会填充模板列表。
- 选择“空白应用 (C++/WInRT)”。
- 给项目(和解决方案)命名,同时选择包含的文件夹。
- 点击“确定”。
项目已准备就绪,包含一个显示“Click Me”的按钮,以及处理单击所需的所有代码。
您现在可以生成项目,并在几分钟后(i7 3.4Ghz 8MB SSD!!!)单击“Click Me”按钮。
安装 Win2D 程序包
要安装 Win2D
程序包,我遵循了 Win2D 文档中的说明(此处)。
- 在“工具”菜单的“NuGet 程序包管理器”下选择“管理解决方案的 NuGet 程序包...”。
- 在“在线搜索”框中键入 '
Win2D
',然后按 Enter。 - 选择 'Win2D.uwp' 程序包,然后单击“安装”,然后单击“确定”。
- 接受许可协议。
- 单击“关闭”。
嵌入 Win2D Canvas 控件
上面提到的 Win2D
文档仅包含 C#、C++/CX 和 VB 的示例。
幸运的是,C++/WinRT 的方法与 C++/CX 的方法差别不大,因此下面的列表是 C++/CX 示例的改编版本。
- 在“解决方案资源管理器”中双击 MainPage.xaml 以打开 XAML 编辑器。
- 在现有的
xmlns
声明旁边添加Microsoft.Graphics.Canvas.UI.Xaml
命名空间。xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
- 在现有的
Grid
控件内添加一个CanvasControl
。<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>
- 编辑 pch.h 以添加一些
include
。#include "winrt/Microsoft.Graphics.Canvas.UI.Xaml.h"
- 编辑 MainPage.h 以在
MainPage
类中添加以下方法。void canvasControl_Draw(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const& sender, Microsoft::Graphics::Canvas::UI::Xaml::CanvasDrawEventArgs const& args);
- 编辑 MainPage.cpp 以在顶部添加
namespace
。using namespace Microsoft::Graphics::Canvas; using namespace Microsoft::Graphics::Canvas::UI::Xaml; using namespace Windows::UI;
- 接下来添加方法。
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()); }
构建并测试结果。
使 Win2D COM 接口适用于纯 C
- 首先,我意识到对于这个示例,我只需要一个接口,即
IWin2dDrawingSession
。 - 接下来,我在生成的头文件“.\Generated Files\winrt\impl\Microsoft.Graphics.Canvas.0.h”中找到了基本的 COM 接口。
template <> struct abi<Microsoft::Graphics::Canvas::ICanvasDrawingSession> { struct type : IInspectable { {...} }
- 我已手动将此 C++
struct
转换为其“C”对应项。struct IWin2dDrawingSessionProtocol { {...} }
- 为此,我将大量接口所需的辅助结构移植到了“C”中。
- 最后,我创建了一个尽可能符合
CoreObjects
原则的Win2dDrawingSession
。
这是调用结果的示例。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; }
“C”应用程序
“C”应用程序是我们上一期 Canvas
示例的改编。
- 我已将来自“CoreObjects/GoliahCore17:纯 C 的有效 OOP || 2”的示例副本包含在项目文件夹中,并重命名为 GoliahCore17_CoreObjects_Demo2b_Canvas.c。
- 创建了基于
Win2dDrawingSession
的Win2dRender
(参见附件代码)。 - 创建了
App_main
函数。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; }
C++ 端的一点胶水
为了将所有内容连接在一起,我在 MainPage
类的关键点插入了 App_main
调用。
- 添加所需的
COM
接口的魔术“getter”。template<typename Type> struct CoreObject_ComInstance { using type = IUnknown; }; template<typename Type> static CoreTarget CoreObjects_getComInstance(Type const&target) { // Rearrangment of private: // #define WINRT_SHIM(Type) (*(abi_t<Type>**)&static_cast<Type const&> // (static_cast<D const&>(*this))) using IType = typename CoreObject_ComInstance<CanvasDrawingSession>::type; return *(winrt::impl::abi_t<ICanvasDrawingSession>**) &static_cast<ICanvasDrawingSession const&>(target); }
- 添加
Canvas
数据成员。struct MainPage : MainPageT<MainPage> { {...} CoreTarget Canvas; {...} };
- MainPage.cpp 顶部的
Extern
声明。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, ...); }
- 构造函数
MainPage::MainPage() { InitializeComponent(); App_main(&Canvas,App_main_Stage_Load); }
- 析构函数
MainPage::~MainPage() { App_main(Canvas,App_main_Stage_Exit); }
- Win2D Canvas Draw 事件
void MainPage::buttonControl_Click(IInspectable const&, RoutedEventArgs const&) { App_main(Canvas,App_main_Stage_Click); canvasControl().Invalidate(); }
- 按钮单击事件
void MainPage::canvasControl_Draw(CanvasControl const& sender, CanvasDrawEventArgs const& args) { App_main(Canvas, App_main_Stage_Draw, CoreObjects_getComInstance(args.DrawingSession())); }
结果
最终的应用并不完全符合我的预期(原因…请参见下一段),但目前足以满足本文的目的。它包含 Canvas
控件和一个用于添加形状的按钮。
就这样,各位!
麻烦
我必须提到 C++/WINRT
仍处于初步阶段,正如其自身所述。
XamlCompiler warning WMC1502: CppWinRT support is currently in preview and may change in future updates.
除此之外,我不得不经历一堆令人失望的问题...
- 完全重建超过 2 分钟,其中 2 分钟用于运行代码生成器和 PCH。
- 无法为 C 代码定义单独的 PCH。
- XAML 设计器几乎无法使用,因为经常抛出未知的异常。
- 与 XAML 相关的源代码未正确生成或保持同步。
- IntelliSense 迟缓、不正确且非常非常疯狂。
- VisualStudio 滞后,我的 i7 的一个处理器核心保持 100% 的占用率数秒。
- 更不用说缺少一种方法来从 C++/WinRT 类获取包装的 COM 接口。
- 等等。
如何使用源代码
附带的源代码是将代码片段粘贴到一个 C 文件中的集合,以便立即将焦点集中在主题上。该包还包括创建它的 VisualStudio2017 Community Edition 项目和解决方案。随意修改它并享受。
注意:生成前,需要恢复 nuget 包。
待续...
主题的广度无法在一两篇文章中容纳,这只是一个简要概述。如果能引起一些兴趣,我将回到该主题,处理使用 CoreObjects
/ColiahCore17
的 OOP 的其他方面,从普通 PC 到资源非常有限的嵌入式架构。
历史
- 2018/12/26:发布“CoreObjects/GoliahCore17:纯 C 的有效 OOP”
- 2018/12/29:发布“CoreObjects/GoliahCore17:纯 C 的有效 OOP || 2”
- 2019/01/04:发布“
CoreObjects
/GoliahCore17
:用于纯 C 的 *.* ????”(本期) - 2019/01/04:修正了 p. 标题“C++ 端的一点胶水”;添加了“结果” p.;进行了一些小的修复;修正了历史记录中的发布年份!