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

CoreObjects/GoliahCore17:纯 C 语言中的高效 OOP || 2

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.86/5 (3投票s)

2018年12月30日

CPOL

5分钟阅读

viewsIcon

4917

downloadIcon

87

在纯粹、简洁、高效、纯净的 ANSI C 中实现具有 OOP 概念的 Canvas

引言

在本文中,我将着重展示如何在纯粹的 C 语言中轻松采用 OOP 编码。为此,我从头开始编写了一个简单的类库,用于渲染一些几何形状。

背景

这应被视为《CoreObjects/GoliahCore17:纯 C 语言中的高效 OOP》的自然延续。其中介绍的概念假定您已知晓,但继续阅读并不需要。

路由

本文将以引导式的方式,介绍随附示例中的关键点。

我实现了一个图形环境 (Canvas),能够收集和渲染一些基本的几何形状。

形状 (也称为 Entity) 的集合实现细节对用户来说是完全隐藏的。因此,只暴露了用于基本插入、删除和向前遍历的 抽象接口

为了允许在任何设备上进行渲染,渲染过程是将所有形状提交给一个渲染接口 (CanvasRenderer),该接口在设备本身之外实现。

目标

示例的目标是实例化一个 Canvas,添加一些形状,然后使用实现 CanvasRenderer 接口 的代理对象,在任意渲染设备上渲染这些形状。

main 的内容应该大致如下所示:

struct CanvasEntity* ent;
struct CanvasRenderer* renderer = (struct CanvasRenderer*)ConsoleRenderer_build(rendererRegion);
struct Canvas* canvas = Canvas_build(0);
ent = (struct CanvasEntity*)CanvasPoint_build(0,10,11,ColorModelARGB32_make(12,13,14,15));
canvas->invoke->Entities->insert(canvas, ent);
ent = (struct CanvasEntity*)CanvasRectangle_build(0,10,11,12,13,14,ColorModelARGB32_make(15,16,17,18));
canvas->invoke->Entities->insert(canvas, ent);
ent = (struct CanvasEntity*)CanvasCircle_build(0,10,11,12,ColorModelARGB32_make(15,16,17,18));
canvas->invoke->Entities->insert(canvas, ent);
canvas->invoke->Canvas->render(canvas, renderer);
CoreObject_free(canvas);

Canvas 类概览

首先,概览一下层次结构。

Class Hierarchy

Canvas 形状

形状的层次结构基于 抽象CanvasEntity 类。插入了一些其他 抽象 类,为子类提供额外的行为。

实体

CanvasEntity 是形状层次结构的基础类。它提供了一些公共属性,并将操作定义为纯方法。

属性收集在 CanvasEntityAttributes Attributes 成员中。

  • Visible
  • Selected
  • StrokeWidth
  • StrokeColor

操作由 CanvasEntityProtocol 定义。

  • render
  • getBoundingBo
  • 翻译
  • rotate
  • scale

EntityLocated

CanvasEntityLocatedGeometricsLocation2D Location 成员中添加了二维位置信息。

Point 和 Circle

CanvasPointCanvasCircle 是基于 CanvasEntityLocated 的具体类。

CanvasCircleGeometricsCircleExtent Extent 成员中添加了 radius 属性。

每个类都实现了其 CanvasEntityProtocol 的特有版本。

EntityTilded

CanvasEntityTilted 扩展了 CanvasEntityLocated,并在 GeometricsTilt Tilt 成员中添加了一个旋转角度。

Square、Rectangle、Ellipse 和 Triangle

CanvasSquareCanvasRectangleCanvasEllipseCanvasTriangle 是基于 CanvasEntityTilted 的具体类。每个类都添加了一些属性来完成其形状定义(请参阅代码),并实现了其 CanvasEntityProtocol 的特有版本。

Canvas 类

Canvas 类充当所有其他类的容器和粘合剂。即便如此,它也不是一个最终的具体类,因为根据要求,我们需要一个完全隐藏且可透明替换的数据结构来收集形状。

因此,Canvas 实际上是作为其子类 CanvasImplementation 实例化的,它嵌入了一个简单的链表来收集形状。它还提供了一个 CanvasEntityIterator 的实现,即 CanvasEntityListIterator,用于遍历列表,并实现了完整的 CanvasEntityCollectionProtocol

CanvasRenderer 类

CanvasRenderer 是一个 抽象 接口,它公开了渲染每个已知形状的方法,如下所示:

  • renderPoint
  • renderCircle
  • renderSquare
  • renderRectangle
  • renderEllipse
  • renderTriangle

必须为任何设备实现此接口,或以我们希望渲染 canvas 内容的方式实现。

在本示例中,渲染通过 ConsoleRenderer 类实现,该类仅将提交给它的形状信息打印到控制台。

关注点

除了可以或多或少设计得很好的层次结构之外,我想在此重点介绍如何在纯 C 语言中管理这些类。在此问题中,我想重点关注两个方面:

  • 宏辅助
  • 动态分配

宏辅助

为了解决在每个类扩展/接口实现中需要重复大量声明符所带来的固有开销,我引入了预处理器宏的使用,作为类定义过程的组成部分。

类组件定义辅助

用于收集特定类的所有数据组件,并用于其所有子类。

例如,CanvasEntity_cCanvasEntityLocated_c 等。

struct CanvasEntity {
    {…}
    #define CanvasEntity_c
    struct CanvasEntityAttributes Attributes[1];
};

{…}

struct CanvasEntityLocated {

    {…}
    #define CanvasEntityLocated_c CanvasEntity_c\
        struct GeometricsLocation2D Location[1];
    CanvasEntityLocated_c
};

struct CanvasPoint {
    {…}
    CanvasEntityLocated_c
};

在上图中,我们可以看到 CanvasEntity_c 的定义如何嵌入 CanvasEntity 的主体中,它如何被 继承CanvasEntityLocated_c 中,最后,CanvasPoint 如何使用后者来包含所有需要继承的成员。

方法参数列表定义辅助

用于收集函数的部分或全部参数列表声明,并用于加快这些方法的实现速度。

例如,我们可以看到 CanvassEntity_scale_al 的用法。

{…}

#define CanvasEntity_scale_al GeometricsMeasure originX, 
GeometricsMeasure originY, GeometricsMeasure scaleX, GeometricsMeasure scaleY, CoreInt32 scaleUnit
CoreResultCode(*scale)(CoreTarget target, CanvasEntity_scale_al);

{…}

CoreResultCode CanvasPoint_Entity_scale(CoreTarget target, CanvasEntity_scale_al);

{…}

CoreResultCode CanvasCircle_Entity_scale(CoreTarget target, CanvasEntity_scale_al);

{…}
CoreResultCode CanvasSquare_Entity_scale(CoreTarget target, CanvasEntity_scale_al);

CanvassEntity_scale_al 定义在 CanvasEntiryProtocol 中 scale 方法的相同位置,以及所有 scale 实现的声明中。用法相同。

另一个例子是宏对 CanvasEntityTilted_build_balCanvasEntityTilted_build_eal 的使用,它们一起嵌入了一个公共声明,但允许在两者之间插入其他参数。

#define CanvasEntityTilted_build_bal CanvasEntityLocated_build_bal
#define CanvasEntityTilted_build_eal GeometricsMeasure rotationAngle, CanvasEntityLocated_build_eal
struct CanvasEntityTilted* CanvasEntityTilted_init(CoreTarget target, CanvasEntityTilted_build_bal,
CanvasEntityTilted_build_eal);

{…}

struct CanvasRectangle* CanvasRectangle_build(CoreMemoryAddress target, CanvasEntityTilted_build_bal
                      , GeometricsMeasure width, GeometricsMeasure height, CanvasEntityTilted_build_eal);

struct CanvasRectangle* CanvasRectangle_init(CoreTarget target, CanvasEntityTilted_build_bal

                      , GeometricsMeasure width, GeometricsMeasure height, CanvasEntityTilted_build_eal);

动态分配

在此示例中,我为构建器添加了动态分配功能,并提供了一种通用的解除分配方法。

分配

分配通过 CoreObject_build 实用函数完成。

struct CoreObject* CoreObject_build(CoreTarget target, CoreTarget descriptor) {
    struct CoreObject* self = (struct CoreObject*)target;
    const struct CoreObjectDescriptor* type = (const struct CoreObjectDescriptor*)descriptor;
    if(self == 0) {
        self = CoreMemory_allocBytes(type->Size);
    }
    CoreMemory_clearBytes(self, type->Size);
    self->invoke = type->Interface;
    return self;
}

解除分配

通过调用 CoreObject_free 函数并向其提供要释放的实例地址来执行解除分配。该函数所做的只是向实例类的 tell 方法发送一个 CoreObject_tell_Free 消息。实现类有责任最终处理该消息的实际解除分配。

CoreObject_free 函数

CoreResultCode CoreObject_free(CoreTarget target) {
    struct CoreObject* self = (struct CoreObject*)target;
    if(self == 0) return CoreResultCode_Success;
    return self->invoke->Class->tell(target, CoreObject_tell_Free);
}

处理 CoreObject_tell_Free 消息的 tell 函数示例

CoreResultCode Canvas_tell(CoreTarget target, CoreWord16 message, ...) {
    switch(message) {
        case CoreObject_tell_Free:
            if(target) {
                Canvas_Collection_freeAll(target);
                CoreMemory_free((CoreMemoryAddress)target);
            }
    }
    return CoreResultCode_Success;
}

如何使用源代码

随附的源代码是将代码片段整合到一个 C 文件中,以便立即将重点放在主题上。该软件包还包括创建它的 VisualStudio2017 Community Edition 项目和解决方案。请随意修改并享用。

待续...

这个主题的广阔性无法在一两篇文章中容纳,这只是一个简要的概述。如果能引起一些兴趣,我将回到这个主题,来处理使用 CoreObjects/ColiahCore17OOP 的其他一些方面,从通用 PC 到资源非常有限的嵌入式架构。

历史

© . All rights reserved.