65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (3投票s)

2019 年 1 月 4 日

CPOL

6分钟阅读

viewsIcon

7148

downloadIcon

87

UWP、COM、C++/WinRT、纯 C 中的 OOP。还有什么?

引言

今天,我将与您分享我进行过的最疯狂的实验之一。

在 UWP 的 Win2D Canvas 控件中渲染上一期的 Canvas。.

这个想法很简单:

  • 创建一个基于 C++/WinRTUWP 应用。
  • 按照说明将 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 扩展

  1. 在“工具”菜单下选择“扩展和更新”,以显示“扩展和更新”对话框。
  2. 展开左窗格中的“已安装”节点。
  3. 验证它是否已在中间窗格的列表中安装。
  4. 为了加快检查速度,请在右侧面板顶部的搜索框中键入 winrt
  5. 如果尚未安装,请展开左窗格中的“联机”节点。
  6. 在右侧面板顶部的搜索框中键入 winrt
  7. 在中间列表中找到 C++/WinRT 的框。
  8. 单击下载按钮。
  9. 按照说明进行操作,直到 VisualStudio 重启。

创建 C++/WinRT 的 UWP 项目

  1. 在“文件”菜单的“新建”条目下选择“项目”,以显示“新建项目”对话框。
  2. 在左窗格中,在“已安装”下,展开“Visual C++”节点。
  3. 选择“Windows 通用”。中间窗格会填充模板列表。
  4. 选择“空白应用 (C++/WInRT)”。
  5. 给项目(和解决方案)命名,同时选择包含的文件夹。
  6. 点击“确定”。

项目已准备就绪,包含一个显示“Click Me”的按钮,以及处理单击所需的所有代码。

您现在可以生成项目,并在几分钟后(i7 3.4Ghz 8MB SSD!!!)单击“Click Me”按钮。

安装 Win2D 程序包

要安装 Win2D 程序包,我遵循了 Win2D 文档中的说明(此处)。

  1. 在“工具”菜单的“NuGet 程序包管理器”下选择“管理解决方案的 NuGet 程序包...”。
  2. 在“在线搜索”框中键入 'Win2D',然后按 Enter。
  3. 选择 'Win2D.uwp' 程序包,然后单击“安装”,然后单击“确定”。
  4. 接受许可协议。
  5. 单击“关闭”。

嵌入 Win2D Canvas 控件

上面提到的 Win2D 文档仅包含 C#、C++/CX 和 VB 的示例。

幸运的是,C++/WinRT 的方法与 C++/CX 的方法差别不大,因此下面的列表是 C++/CX 示例的改编版本。

  1. 在“解决方案资源管理器”中双击 MainPage.xaml 以打开 XAML 编辑器。
  2. 在现有的 xmlns 声明旁边添加 Microsoft.Graphics.Canvas.UI.Xaml 命名空间。
    xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
  3. 在现有的 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>
  4. 编辑 pch.h 以添加一些 include
    #include "winrt/Microsoft.Graphics.Canvas.UI.Xaml.h" 
  5. 编辑 MainPage.h 以在 MainPage 类中添加以下方法。
    void canvasControl_Draw(Microsoft::Graphics::Canvas::UI::Xaml::CanvasControl const& sender,  
    Microsoft::Graphics::Canvas::UI::Xaml::CanvasDrawEventArgs const& args);
    
  6. 编辑 MainPage.cpp 以在顶部添加 namespace
    using namespace Microsoft::Graphics::Canvas;
    using namespace Microsoft::Graphics::Canvas::UI::Xaml;
    using namespace Windows::UI; 
  7. 接下来添加方法。
    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

  1. 首先,我意识到对于这个示例,我只需要一个接口,即 IWin2dDrawingSession
  2. 接下来,我在生成的头文件“.\Generated Files\winrt\impl\Microsoft.Graphics.Canvas.0.h”中找到了基本的 COM 接口。
    template <> struct abi<Microsoft::Graphics::Canvas::ICanvasDrawingSession>
    { struct type : IInspectable
    {
        {...}
    }
  3. 我已手动将此 C++ struct 转换为其“C”对应项。
    struct IWin2dDrawingSessionProtocol {
      {...}
    }
  4. 为此,我将大量接口所需的辅助结构移植到了“C”中。
  5. 最后,我创建了一个尽可能符合 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 示例的改编。

  1. 我已将来自“CoreObjects/GoliahCore17:纯 C 的有效 OOP || 2”的示例副本包含在项目文件夹中,并重命名为 GoliahCore17_CoreObjects_Demo2b_Canvas.c
  2. 创建了基于 Win2dDrawingSessionWin2dRender(参见附件代码)。
  3. 创建了 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 调用。

  1. 添加所需的 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);
    }
  2. 添加 Canvas 数据成员。
    struct MainPage : MainPageT<MainPage>
    {
        {...}
        CoreTarget Canvas;
        {...}
    };
  3. 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, ...);
    }
  4. 构造函数
    MainPage::MainPage()
    {
        InitializeComponent();
        App_main(&Canvas,App_main_Stage_Load);
    }
  5. 析构函数
    MainPage::~MainPage() {
        App_main(Canvas,App_main_Stage_Exit);
    }
  6. Win2D Canvas Draw 事件
    void MainPage::buttonControl_Click(IInspectable const&, RoutedEventArgs const&)
    {
        App_main(Canvas,App_main_Stage_Click);
        canvasControl().Invalidate();  
    }
  7. 按钮单击事件
    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 到资源非常有限的嵌入式架构。

历史

© . All rights reserved.