跳转至内容

Aros/开发者/文档/库/OOP

来自Wikibooks,开放世界中的开放书籍
Aros维基书籍的导航栏
Aros用户
Aros用户文档
Aros用户常见问题
Aros用户应用程序
Aros用户DOS Shell
Aros/用户/AmigaLegacy
Aros开发文档
Aros开发者文档
从AmigaOS/SDL移植软件
Zune初学者指南
Zune .MUI类
SDL初学者指南
Aros开发者构建系统
特定平台
Aros x86完整系统HCL
Aros x86音频/视频支持
Aros x86网络支持
Aros Intel AMD x86安装
Aros存储支持IDE SATA等
Aros Poseidon USB支持
x86-64支持
摩托罗拉68k Amiga支持
Linux和FreeBSD支持
Windows Mingw和MacOSX支持
Android支持
Arm Raspberry Pi支持
PPC Power Architecture
其他
Aros公共许可证

oop.library允许驱动程序使用OO(面向对象设计)。HIDDs是AROS中OO用法的另一个示例。

仅供讨论 - OOP二进制对象模型?

[编辑 | 编辑源代码]

二进制对象模型的论证。能够从在安装时静态编译的虚拟机中使用C++和其他编程语言的能力,尤其是在您希望在此过程中放弃JIT的情况下。

此外,我还需要将任何OS特定结构或变量的使用包装在一个也在安装时链接的库中。这可能只是一个托管的AROS。

但Amiga风格的库在任何有意义的方式上都不是对象。AmigaOS中的面向对象设计并非以库为中心,而是以其他结构为中心。转向更OO的方向可能很有趣,但向库添加接口并不能实现这一点。

为了将OOP作为二进制对象模型来实现,我获得了Marius Schwarz的OOP4a源代码。其中存在一些严重的缺陷。它使用线性搜索来查找命令的名称,并且不支持接口继承。我知道如何在.library中实现Java风格的接口,但是对线性名称查找的依赖使得这些接口无法使用。

当我开始制作我的Object.library实现时,我决定接口继承优于多重继承,因为它不会产生可怕的菱形依赖关系。我使用哈希表而不是线性搜索来进行名称查找,并用C而不是68k汇编编写它。我不太确定AROS如何在没有FD文件的情况下处理其库头文件,但我意识到它们在AROS上必须与处理器无关。

这让我们回到了主题:二进制对象模型。OOP4a在68k AmigaOS上的libs:classes/中使用子目录来指示它附带的原型类库的继承结构层次结构。这将使命名空间在库之间保持分离,以便任何对象都不会干扰另一个对象的名称(在合理的范围内)。我打算为libs:interfaces/目录中的每个接口使用一个单独的接口文件,该文件将成为所有接口的全局存储库,除了直接继承接口(在OS 4中称为“主”接口)。即使我的最初计划是使用哈希表进行名称查找,直接继承接口也可以包含在其定义库的同一目录中。

OOP4a中继承的实现方式是在库的全局区域中以A6的正偏移量保存父链接。这意味着,当打开类库时,将访问其父目录并打开父库,从而启动递归,一直到遇到空值的根类。在我的Object.library实现中,Object.library类无论如何都是根类,从而消除了父类可能没有名称查找等的某些特殊情况。

oop.library正在执行其中一些操作,但我确信这是正确的方法。我确实喜欢Amiga LVO表而不是哈希表,以避免在加载期间进行名称绑定。我可能可以使用一个哈希表用于目录,然后在每个目录中使用LVO表。

我认为库的OS 4接口实际上也正在执行此操作。它允许在一个库中拥有不同的接口,每个接口都有自己的名称和LVO表。

但这确实要求开发人员在编译库期间告知哪种方法或函数位于LVO表的哪个位置。

重新创建OS 4风格的接口表很容易。让我烦恼的是,您需要一个指向接口的指针,位于任何寄存器中(最好在PPC和其他寄存器丰富的处理器上为每个接口使用不同的寄存器)。我假设库的基本指针也将包含在接口结构中。

在听到反对哈希表名称查找的意见后,我可以说打开这样的库会很慢。同样,继承链也会更慢,因为它必须对直接继承的每个阶段执行名称查找,直到解析为止。这让我们来到了一个大问题:如果接口不仅仅是一个名称,我们该如何存储它们?胖指针?通过对继承的每个阶段使用相同的哈希值来加快哈希查找速度?

框架不应该需要名称改编方案,而是使用libs:中的子目录来组织命名空间依赖关系并支持Java风格的接口继承。缺点是我计划使用名称的哈希表,而不是像AmigaOS那样需要FD或SFD文件来初始化其vtable。

当我查看OOP.library的源代码时,我不喜欢它在运行时执行所有操作的方式。我打算制作Object.library来补充OOP.library。您认为这是否合适,或者应该像OS 4那样直接添加到Exec.library中?我对像OS 4那样做太多的事情的担忧是,OS 4有一个实用程序可以从派生自SFD文件的XML描述符生成C骨架代码。这意味着为除C以外的语言制作一个生成库的编译器,这使得事情变得非常棘手。

为了允许LLVM为多种AROS风格生成代码,我必须用其他代码替换或包装C运行时库的一些函数,以确保它们不会对变量大小和结构大小做出假设。在GCC中,sizeof和offsetof被渲染为宏以获得最小的开销,但这妨碍了可移植性,因此需要用函数来实现它们。

如果我们想更进一步,并使其为所有操作系统生成代码,我们将需要不对结构本身做出任何假设,因此FILE *必须在包装代码中的每个实例中替换为void *(或LLVM表示的i8 *;指向字节的指针),然后仅在目标平台上的第二次链接时恢复为FILE *。对于尝试将跨平台LLVM代码中的C stdio.h头文件包装的早期尝试,您可以查看Mattathias团队在SVN浏览器中启动的项目。

显然,LVO方法在加载时速度更快,并且加载时链接可能很慢——以OpenOffice等应用程序为例,优化加载时链接是加快Linux上应用程序启动的重要部分。但我不确定在大多数情况下它会有多大影响……

也没有任何东西可以阻止支持/使用直接偏移量并提供名称查找。例如,我正在慢慢地开发一个Ruby编译器(用Ruby编写),虽然我尽可能地使用C++风格的vtable,但它也需要支持名称查找,因为如果在Ruby中使用某些结构,则无法在编译时确定将使用的完整方法名称集,并且您还需要支持“send”,这意味着它需要一个名称=>方法映射。所以我有了vtable,但会添加一个哈希表,将名称映射到vtable槽,用于无法在编译时确定偏移量的情况。

例如,向库定义添加一个函数指针,可以在运行时使用它来查询库的元数据,例如名称=> lvo查找,这将是一个相当非侵入性的更改,只会减慢由于某种原因实际需要基于名称查找的应用程序的速度。

至于AmigaOS和AROS中使用的OOP方法,那在另一个层面上慢得惊人……在Amiga硬件上这样做是有道理的,因为它可以做到非常内存高效,但存在可以更快且允许相同扩展性的方法……我不确定这是否重要——对于这两种情况,我们真正应该拥有一些实际的测量和分析数据。

不幸的是,OS4从未引入实际的用例。OS4接口试图模仿一点的是Windows上已知的COM或Mozilla套件中已知的XPCOM。在那里,您拥有可以实现多个接口的对象,所有这些接口都继承自IUnknown。

COM方法QueryInterface在exec中被exec.library/GetInterface替换。与OS4版本相比,COM/XPCOM解决方案可以对我们想要的*任何*接口进行调用。如果存在其他编程语言,则COM风格的对象将在OS 4上实现相同类型的目的。不幸的是,没有。C++类需要接口才能以二进制库格式实现自身。

链接包含一些类似C的伪代码,演示了接口在接口继承链中的实现和用法。

如果您想要了解低级C代码的工作原理,此链接是更高级别的伪代码。

目前没有。据我所知,只有少数系统库使用了除“主”接口之外的其他任何东西(最著名的例子是expansion.library及其pci接口)。

我建议阅读这篇以获取灵感。

协议扩展:一种构建大型可扩展软件系统的方法(Michael Franz博士,1994年)。

协议扩展使用vtable,但允许它们在运行时通过将更改传播到继承链中来动态更新。调用比Amiga/AROS BOOPSI风格的分派快得多——派生类中最派生的类的vtable始终包含要调用的正确方法指针,因此与C++虚函数调用的成本相比,只是一次额外的间接寻址。

缺点是,如果类层次结构很大,则普通的vtable会变得很大,因为每个类的vtable都需要为每种可能的方法分配一个槽。可以通过将API拆分为不继承自单个根类的较小接口来缓解此问题,实际上创建了一个浅的、稀疏的trie方法指针。

当然,如果您想要基于名称查找方法,则仍然存在查找成本,但对于可以针对包含静态方法ID的符号表进行编译的应用程序,您可以获得非常好的性能,并且具有与基于分派函数的OO系统(如BOOPSI)一样好的灵活性(因为只要vtable足够大,您就可以在运行时添加/删除方法,并以分派程序作为完全动态调用的最坏情况的回退)。

(顺便说一句,Franz写了他的博士论文,研究了一种用于体系结构独立二进制文件的方法,称为语义字典编码;最近,他声名鹊起的是跟踪树,与Andreas Gal共同开发,用于TraceMonkey和LuaJIT)。

我认为我可能会将子目录限制为命名空间解析。除此之外,我之前写的大部分内容看起来都不错。我最初计划拥有LVO以及名称查找。这使我不必为接口文件中的方法名称创建单独的文件。此外,与协议扩展技术不同,我将保留一个根类,因为它只定义了一个“toString”方法,尽管我可能会将该方法重命名为“DebugPrint”。

我认为至少部分原因是为了应对AmigaOS库只能向前版本化并且必须保持100%向后兼容的“问题”。重置接口以删除已弃用的内容的唯一真正方法是在库名称中添加版本,然后您将陷入大量库中。当然,除了AmigaOS专用的库之外,没有其他实际的方法可以做任何其他事情,因此这个想法的实用性可能并不大。我认为它也用于MMU支持等系统特定的东西。

但与一般的系统库一样,它并不是为应用程序编程而设计的解决方案,这就是创建BOOPSI的原因。

请注意,“基于C对象”的机制(即结构体嵌套结构体)也存在许多缺点。这就是glib和gtk+所使用的,经过大量的过度工程,它们设法解决了大多数问题——例如结构体大小增长破坏二进制兼容性(存储一个单独的私有数据块,该块被分配并使用得更像BOOPSI分配对象的方式),添加接口(在运行时从简单函数调用的转换中查找它),属性处理程序、事件等等。但它很大、很慢、难以使用,需要大量的样板代码,而且总的来说不太好……我已经几年没有使用它了,但我认为核心自那时起变化不大。

  • 每个公共函数(以及大多数内部函数,唉)都有一个“转换”宏,它使用树扫描或线性扫描(如果我记得没错)来检查对象的类类型(实际上,大多数对象的“用户”会进行另一次执行此操作的转换)。

同样也会进行检查——因此错误消息会指向源代码……)(并且通常每个成员访问要么也使用宏,要么调用执行此操作的其他函数,或者使用get/set接口)。

  • 私有数据需要类查找(与BOOPSI的数据指针相同),并且还意味着每个对象实例化至少分配了两个内存块。
  • 如果我记得没错,每个“属性”都以一个字符串为键,它必须在全局表中查找该字符串以转换为动态整数,然后需要一个if/then if/else树来处理,因为它不是静态的(即比BOOPSI糟糕得多)。
  • 事件处理是一场噩梦,我不认为任何人值得知道它是如何工作的。

请注意,很多混乱据说是为了支持对除c以外的其他事物的语言绑定。

可以通过更简单的实现来修复其中的一些问题,但您仍然会遇到一些严重的限制,因为它本质上是静态绑定。因此,它仍然难以应对版本控制和二进制兼容性以及动态接口。

x86特别擅长执行糟糕的分支代码,而且CPU本身也很快,所以我怀疑转向另一种过时的技术是否真的值得,除非您获得了很大的收益。当您添加所有必要的负担时,我有一种预感,您可能不会。总是有权衡……

也取决于要解决的问题。例如,“我想用c++编写/调用系统库”与“我想创建一个基于c的应用程序级对象系统”不同。

我对我们当前的oop.library实现不满意。因此,我没有投入任何精力,因为我首先想改进整个OO系统。我发现方法调用期间仍然发生了太多事情。我有一些想法,但没有时间测试我的想法是否有意义。如果有人想看看,我可以尝试写下我粗略且模糊的想法。

参考文献

[编辑 | 编辑源代码]
APTR OOP_NewObject(struct OOP_IClass *classPtr, UBYTE *classID, struct TagItem *tagList) 
OOP_AttrBase OOP_ObtainAttrBase(STRPTR interfaceID) 
OOP_MethodID OOP_GetMethodID(STRPTR interfaceID, ULONG methodOffset) 
void OOP_AddClass(OOP_Class *classPtr) 
void OOP_ReleaseAttrBase(STRPTR interfaceID) 
void OOP_DisposeObject(OOP_Object *obj) 
void OOP_RemoveClass(OOP_Class *classPtr)

OOP_AttrBase OOP_GetAttrBase(STRPTR interfaceID) 
IPTR OOP_GetAttr(OOP_Object *object, OOP_AttrID attrID, IPTR *storage) 
IPTR OOP_SetAttrs(OOP_Object *object, struct TagItem *attrList) 
BOOL OOP_ObtainAttrBases(struct OOP_ABDescr *abd) 
void OOP_ReleaseAttrBases(struct OOP_ABDescr *abd) 
LONG OOP_ParseAttrs(struct TagItem *tags, IPTR *storage, ULONG numattrs, OOP_AttrCheck *attrcheck, OOP_AttrBase attrbase) 
void *OOP_GetMethod(OOP_Object *obj, OOP_MethodID mid) 
华夏公益教科书