KeyFC欢迎致辞,点击播放
资源、介绍、历史、Q群等新人必读
KeyFC 社区总索引
如果你找到这个笔记本,请把它邮寄给我们的回忆
KeyFC 漂流瓶传递活动 Since 2011
 

[M] Prelude to K.O. (4)

[ 17044 查看 / 45 回复 ]

K.O.(KeyFC Open Translation Toolset) 是目前正在开发的“开放版本”汉化工具的代号。
其中的汉化部分工具试验性的采用了代号为Chobits Application Server(CAS)的架构。
这个结构的特点为,程序全部由基于消息传递的模块动态组合而成。

前面几回介绍了CAS架构的理念,内部结构,消息的接口、定义及传递。
至此,各位所了解的CAS还处于"空想模块化"阶段——理论上写出模块化的程序是可行的。
那么具体的实现上呢?这就是今天要谈的内容: CAS模块的结构、封装和程序的组装

--- CAS模块 ---

从理论模块化到实际模块化,CAS选择了一个最直接的方法:一对一"物理"映射。
也就是说,每一个CAS的"功能模块"都映射到一个实际的文件上。
文件的格式自然也就是"动态加载库",在Windows下叫DLL(Dynamic Loading Library),在Linux下叫SO(Shared Object)。

目前CAS仅有Windows的版本;但因为整个CAS除了少数地方使用同步对象外,并没有其他平台相关的代码,因此能够被很容易的移植到Linux下。
其中核心组件Lock-free队列我为了在8CPU环境下验证正确性已经实现在Linux下了(但是由于Linux pthreads的Condition同步对象缺少Timed Wait(带时限的等待)方法,暂时没有写出带锁同步队列作性能比较)。
有时间的话,我会尝试包裹Posix Thread中的同步对象(支持Timed Wait),这样移植CAS应该就没有什么大碍了。
(虽然Interface在CAS的使用是兼容COM规范的,但是其用途并不COMish,因此不影响Linux移植。)

书归正传,那么,也就是说模块的加载需要靠"动态加载库"文件(以下简称DLL)的接口来进行了。
(下面的部分将涉及一些抽象程序代码和具体API)

--- CAS模块封装 ---

因为世界上没有未卜先知,因此CAS模块的接口必须提供一个固定的方法集合才能够被正确的加载。
不过,因为每个人都有自己写加载方法的自由,因此这里讲的只能算"官方推荐",供使用者参考(当然,作为"官方使用者",K.O.系列的程序将遵循这里介绍的接口)


I: 首先要解决的问题是,怎样知道加载的DLL是合法的CAS模块?
因此,我规定每个CAS模块DLL都需要导出(Export)一个函数: ChobitsModuleSignature
这个函数返回一个64字节的记录(没有硬性规定意义,可以是代码的MD5,或者是数码签名等),目的是让加载方能够作选择性的加载。


II: 接下来,需要确定的是加载的模块使用的是兼容的接口和消息定义。
使用动态加载库的一个著名的问题就是"DLL地狱",由于同一个DLL的不同版本可能使用不同的接口,如果管理不善可能导致各种奇怪的问题。
为了解决好这个问题,我规定每个CAS模块DLL都需要导出(Export)另一个函数: ChobitsModuleInfo
这个函数返回多种信息,包括模块名称、GUID、消息队列长度、需要多少工作线程等等;
其中有一条信息(32位整数),称作"兼容总线版本",详细的定义如下:

* 高8位: 总线运行级别
  在前面的介绍中,我有意模糊了总线的具体功能,现在给出更清晰的定义。目前定义的总线有三个运行级:
  0. 静态总线: 仅提供了模块注册、查询和消息传送支持等基本功能;总线内部没有线程调度,因此不执行消息投递。好处是轻载(light weight),适用于消息较少的应用环境。(K.O.系列程序使用的就是这个总线)
  1. 投递总线: 顾名思义,就是增加了消息投递支持。适用于消息较密集的应用环境。(由于涉及到事件顺序,加上前段时间的总线代码重构(code re-factor),目前仍然在编写/调试中...)
  2. 管理总线: 在前一个级别的基础上,增加了一个管理线程,用于执行管理工作(总线统计信息收集、消息队列阻塞检测、失效队列回收;当然最重要的是接受队列注册、注销请求——有了这个功能,就可以使用消息来注册消息队列,这也就意味着,可以由一个模块来加载另一个模块)。这个总线适用于较大型的服务和应用程序,因为它可以自动支持一个动态的执行环境(比如动态的替换一个模块的库文件到新的版本,而不需要重新启动整个应用程序)。
  因为每一个运行级提供的支持都是前一个运行级的超集(Super-set),因此,自然的,兼容低级别总线的模块可以加载到高级别总线上,反过来则不行。

* 中间12位: 总线的主版本号
  前几回说到了,总线和模块之间唯一需要交互的两处就是: 模块消息接口——用于发送和接收消息;基本消息接口——用于处理消息的传递过程。
  同一个主版本号的总线将使用相同的模块消息接口和基本消息接口。换句话说,就是一旦模块消息接口和基本消息接口发生任何改变,主版本号将会提升。
  这也就意味着,兼容不同主版本号总线的模块一定不能被加载,不论谁高谁低。

* 后面12位: 总线的兼容版本号
  如果总线发生了一些细小的修订,但是没有更改模块消息接口或者基本消息接口,那么兼容版本号将会提升。
  因为接口并未改变,所以兼容旧版本总线的模块将可以使用在新的总线上;但是,因为兼容新总版本总线的模块将会期待总线一定程度上的变化,因此不能用于旧版本的总线。

最后举一下例子,以免有人看晕。以下版本号的格式为: [运行级别] [主版本号] [兼容版本号]
如果总线版本为:
2 2 2
模块兼容版本为:
2 2 2 可以加载
1 2 2 可以加载  (期待运行级别低可以)
3 2 2 不可以加载 (期待运行级别高不行)
2 2 1 可以加载  (兼容前一个修订版本可以)
2 2 3 不可以加载 (兼容后一个修订版本不行)
x y z 只要y不是2就不可以加载 (主版本不一致)


III: 接下来就是模块特定导出函数了。
在目前使用的规范中,只有两种不同的模块: 总线模块(是的总线也可以是一个单独的DLL :D) 和 应用模块
总线模块导出两个函数: CMB_Request (请求总线) 和 CMB_Drop (丢弃总线)
服务模块导出三个函数: CAM_Attach (接驳总线),CAM_Detach (脱离总线) 和 CAM_Finalize (结束应用)
(注意,因为目前CAS的应用仅仅在总线运行级别0上的,因此更高级别的总线以及对应服务模块*可能*会有更多的导出函数。但是这里已经列出的函数及接口将保持不变)

* 先谈总线模块 (Chibots Bus Module, CMB):

CMB_Request 需要一个总线ID参数,返回一个总线管理接口(Interface)。(是的,如果你愿意,同一个应用程序里面可以存在多个总线。总线之间是相互独立的,没有内置的交互能力,因此怎么使用嘛就是你的事了=v=)
如果指定ID的总线不存在则创建一个新的;如果不能创建(达到个数上限),则返回空指针。

CMB_Drop 需要一个总线ID参数。其功能是将符合ID的总线的内置记录清除,这样下一次请求同样ID时,将创建一个新的总线。
注意,因为总线是通过管理接口引用的,因此也是引用计数的。调用CMB_Drop和总线是否被释放没有任何关系。(当然,推荐合理的使用方法为即将释放总线前调用CMB_Drop,然后将管理接口置空,总线将被自动释放)

* 然后是服务模块 (Chobits Application Module, CAM):

模块加载程序在调用了前面提到的ChobitsModuleInfo,检查版本符合之后,应该根据同时返回的其他信息为正在加载的模块收集资源。(比如,使用返回的名称和GUID在总线上注册一个消息接口;同时生成要求数量的线程)
准备完毕后,调用服务模块的CAM_Attach,将资源递交给模块。
这时,模块应该做的事情有:
* 初始化内部结构
* 记录传递的消息接口
* 将传入的线程(一般来说,只有一个)分配到指定的任务(一般来说,是消息处理循环)


接下来,将会有很长一段时间DLL的接口不再有动静。因为所有的交互都通过消息接口在总线上进行...


当由于某种原因(一般来说,是程序结束),消息队列将被从总线取下,模块加载程序应当首先调用CAM_Detach。(如果顺序颠倒,服务模块在发送消息时将会受到一个异常: 队列已经从总线脱离)
收到这个调用的时候,服务模块应该做的事情是:
* 将分配的线程从任务脱离 (不需要终结,它们将会被线程池自动回收)
* 如果必要的话,做出适当清理,为下一个接驳总线的请求做好准备

当CAM_Detach返回后,模块加载程序就可以放心的从总线注销(一般来说是)消息队列了。

最后,当模块加载器需要卸载模块前,应该调用CAM_Finalize,这个时候模块要做的事情就是:
* 停止正在执行的操作(本来这个时候不应该有了,但是以防万一...)
* 释放申请的所有资源

当CAM_Finalize返回后,模块加载程序就可以放心的卸载模块DLL了。


在细节里面滚打了大半圈,让我们重新回到高处,俯瞰CAS架构。

--- CAS程序的组装 ---

因为运行级别3的CAS总线的操作和使用环境与前两个有一定不同,我们分开来看。

前两个级别的CAS总线需要由独立于总线的管理操作控制(加载/卸载应用模块),因此CAS应用程序的(建议)组装结构应该像下图:


(图1) CAS应用程序

* 处理应用程序图形界面(UI),同时负责初始化的线程最先被创建(主线程);
  这个线程创建一个管理线程,用于初始化和终止CAS架构,同时负责响应抽象用户请求。

* 初始化开始,总线模块首先载入,各个模块依次载入并在总线上注册,相应的模块线程被创建;
  初始化最后,第二个UI线程被创建(图中与主线程重合),用于将UI事件转换成抽象用户请求,以及将结果反映到UI上。
  (创建第二个UI线程的原因是,Windows有一套自己的窗体消息机制,(主要)用于和用户输入打交道,因此主线程有自己的消息循环需要处理。)

  什么?你问既然有了Window消息为什么还要CAS?晕...两套消息机制的设计目的以及用途非常不同,其他的(比如多线程同步效率)不说,一个很简单的例子就能说明问题:当你的鼠标划过一个窗口时,这个窗口的消息队列就会接受到成百条鼠标位置的信息,如果这个时候另外几个模块正在通过Window消息队列频繁的通讯的话,这个队列就会更长,导致所有的消息都得不到及时处理,这个时候的现象就是程序的窗口响应迟钝同时后台计算也变慢...可见将图形界面的消息和内部运算的消息混在一起的话,两者的处理都会受到影响。

* 回到刚才说的,所有线程都进入自己的消息循环,程序正式开始工作:
  用户的窗体操作被包装成为高级的应用请求消息,传递到管理线程;
  管理线程的程序将高级请求转换成为一系列的模块功能请求消息,然后分发到各个模块;
  (对于有顺序要求的消息,状态机也好,跨模块调用(IMC)也好,根据个人的风格和期望的效果实现,CAS是一个非常自由的架构! :D)
  根据各个模块反馈的结果,管理线程组装一个UI反馈消息,传递给UI线程,然后由其将消息解释成为图形界面的状态,反馈给用户。

* 最后,程序即将结束,管理线程首先通知各个模块队列取下,接下来依次注销消息队列,然后卸载各个功能模块,总线模块,最后返回;
  而主线程则一直等待线程池将所有线程回收,最后清理资源,终止程序。

注意图中有几条虚线,他们表示的意思是,UI线程和管理线程是作为功能模块挂接到总线上的,但是实际上他们并没有一个单独的DLL文件,因此称为"伪模块"(Pseudo-Module)。



级别2的CAS总线(管理总线)一般应用在大型的服务程序中。因为其本身具有一定的管理能力,因此CAS服务程序的(建议)组装结构应该像下图:


(图2) CAS服务程序

* 一般情况下,CAS消息总线将会和主程序形成一个模块;
  主线程创建后,首先创建消息总线(同前一种程序一样,可以创建多个,根据用途而定)
  然后,作为启动(Bootstrap)过程,主线程试图载入预先配置好的一个模块管理模块,注册并挂接;
  接下来,根据需要,主线程可以注册一个伪模块,以便和总线交互。(图中虚线省略了)

* 挂接后,具有管理功能的总线将向模块发出通知消息,模块管理模块捕获这个消息,并开始工作;
  模块管理模块可按照预设定或者某种特定顺序载入各个模块,每个模块独自捕获总线载入通知开始动作。
  注册/注销等消息队列管理操作都将以请求的方式发送给总线,由总线管理线程执行,并且消息队列取下的通知也由总线发出,各个模块应该独立相应。
  因此模块管理模块需要做的操作仅仅是载入,通知挂接,和卸载模块。

* 一般情况下,主线程没有其他事情可做,只需要等待总线的"结束程序"消息(CAS消息或者同步事件);
  然后,首先卸载模块管理模块(在此之前,总线已经通知其队列取下),接下来释放资源,结束程序。

如果需要用户界面,可以将其单独包装在一个模块中予以加载。

可以看得出来,服务程序的流程更加结构化;当然,这主要是因为总线实现了许多附加的功能。


------

现在,坚持跟进的各位应该对CAS架构有了一个初步的了解。如果各位还有什么不清楚的地方,欢迎回复反馈,我在合适的时候将集中解答。

最近正在对底层消息的实现作比较大的改变,主要是软件工程上的,比如统一总线内部对各种消息的处理等;
其中包括前面提到的,将总线对于IMC(跨模块调用)消息的支持通用化,使得基于CAS的开发者能够写出自定义的IMC消息。
当然,这些更改对于前面介绍的抽象概念和高层操作基本没有影响,请各位放心消化。 :P
最后编辑Prz 最后编辑于 2007-05-16 18:17:57
本主题由 管理员 深海蓝空 于 2007/5/17 13:35:24 执行 设置精华/取消 操作
分享 转发
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

...果然写得太多会导致人发晕...

1楼的意见都是针对封装的。
但是我一开始就提到,CAS的主要核心是程序的设计和实现方法,至于如何封装,是使用者个人的风格和程序的功能要求问题;各种不同的封装方法、形式都是完全可以支持的。
这一次的谈内容只是建议的方法,并且每一步的作用都有详细的解释,至于是否必要,就看个人对于最终用户体验的要求了。
比如,1楼提到可以直接扔一个接口了事。这种封装方法虽然也不影响正常情况的功能,但是小心我提到的"DLL地狱"——如果总线或者不同的模块编译时使用的接口不一致,轻则出现内存漏洞,或者程序AV(非法访问),更严重的可能导致模块的"非出错性"错误操作(表面看上去工作正常,实际上做的东西是完全错误的)。


2楼对于消息处理的理解也是有问题的。
其中描述的"窗口显示不了看看你的硬盘是不是狂转吧"仅仅是将一些表面现象机械的联系在一起。
我承认很多情况下“窗口显示不了”时硬盘确实在“狂转",但是,出现这个现象的根本原因还是窗口消息队列的阻塞:处理窗体消息的线程因为阻塞在了一些低速IO操作上,导致不能对窗口重绘事件及时响应。
而且,很多情况下这就是因为使用了我所描述的“不良”的消息处理机制——将程序的内部功能的消息与用户界面的消息混合在一个队列。
(是啊,平时写一次硬盘也就几十个毫秒,没什么大不了;但是关键是时刻就会让你等上几秒到几十秒,就等着窗口翻白吧......)

在CAS架构下,就算硬盘“狂转”,用户界面(窗口)依旧是响应灵活的,就是因为消息处理分离——处理窗口消息的线程只管绘制窗口,也就不会阻塞在低速IO上。
(当然,机器内存不足导致系统产生的大量页面交换的情况除外;不过这种情况下,任何用户程序都会响应迟缓,这就是另外一个问题了...)

(另, 1楼的"想必"是错误的... ^^ Windows版本的CAS是用Object Pascal实现的)


------

哦,对了,关于"越来越乱"的解释。
怎么说呢,还是老话:CAS代表的是一个方法、理念,引导使用者根据一定的原则设计程序,同时尽量给予使用者实现的自由。

举个具体的例子:Internet无疑是一个非常成功的架构,我们仔细看看Internet的结构,会发现一个"砂时计" (抽象概念的):

(应用协议)  HTTP FTP SMTP RTSP P2P ......
(传输协议)      TCP UDP ...
(路由协议)        IP
(物理协议)    ETHER ATM FDDI ...
(物理介质) 电话线缆 专用电缆 光纤 无线电 红外线 ......

不论底层还是顶层,看上去都很"乱"(褒义的讲叫"自由"),但是中间有IP坐镇,一切工作就有条不紊。


我设计CAS的时候,也有意的向这个成功的典范看齐:

服务模块 数据库 数学运算 图像处理 文法分析 加密解密 ......
消息分类    请求/回复 限时 功能调用 序列化 ...
通讯机制        消息总线
封装方法      应用程序 服务 ...
支持应用 WEB服务 媒体流传输 个人数据管理 游戏汉化 ......


这个宇宙中最"乱"(自由)的就是人的思想,因此上下两边到底有多少东西会以什么样的形式出现,我根本不企图去猜测,只需要集中精神做好中间的核心就行了。
最后编辑Prz 最后编辑于 2007-05-17 09:24:03
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

QueryInterface是提供了的,但是我在开发中曾经遇到过(可能是)因为通讯模块间Interface版本不一致 (编译两个模块中间修改了Interface的定义) 导致的一些问题,于是我设计了版本校验功能。
我不清楚如果两个使用同一名称、GUID的Interface如果定义不一致到底会产生什么样的问题;根据楼上的描述,似乎不会出现问题,但是我持保留态度。

另外,我曾经提到过CAS的Interface是符合COM特性的,楼上也提到过,使用接口就是为了解决使用不同语言/编译器的程序的通讯问题,因此Pascal和C++的代码的二进制兼容性不需要考虑。

每一种语言都有自己的使用人群,每一个人都是处于自己的一些个人喜好选择语言,因此我对楼上对Pascal的认识表示不屑:

虽然我不喜欢Java,但是我不得不承认,整个工业界正在以一种不可阻挡的趋势转向Java,那么楼上想要发展,我建议趁早抛弃C++转向Java..... (或者被Microsoft的这个#、那个.NET逐渐的诱导到一个他们专有的"Java"世界里去....)


------

我上面说的只是这4次讲述是越来越乱的


我明白你的意思,上一个回复也是针对这个意思回答的:
我认为你觉得乱是因为我介绍的内容的分类越来越多;而我的回答是,一开始讲的是核心,后来逐渐到外层,分类变多是正常的,而且我觉得是开放架构应该体现的优势。

当然也有可能是因为我试图在短短几回里面就把一个反复构思、试验、实现了数年的东西完整的摆出来的结果...=v= 没办法,我这是业余工作,不能占用太多时间......

另外...我不知道楼上从什么地方得知Pascal的语言没有标准化...
(我也没有看到什么地方说过Pascal的语言标准化了就是了... =v= 管它的呢,语言就是语言,标准化与否不是太重要,关键是美就行了)

还有,似乎楼上对于虚函数和抽象函数的担心有点多余:
Pascal是第一个真正支持OOP的高级语言,因此这方面的支持不仅不用担心,而且我认为应该比(标准)C++要早一些... (当然,仅仅是猜想,手里没有历史资料,如果错了也不要打我 :D)
最后编辑Prz 最后编辑于 2007-05-17 10:06:00
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

相对于没有跨平台能力来说,Java就是强。还有其代码可维护性要比C++高得多,这就是Java在业界吃香的原因。

---
GNU编译器也有Pascal的版本,而且是Actively Maintained,好不好用没试过就是了。
Free Pascal也是一个很好的编译器,并且与Delphi语法高度兼容。

---
C/C++我也经常用,只不过只在Linux下(因为当初VS给我的印象极差,虽然听说自从从Borland挖人以后有很大改善,但是我都懒得去试 =v=)

C++里面有很多问题让我头痛,比如:

Pascal可以用下面的方法定义一个指向任意一个类符合接口的成员函数:
TFunc = Function (A: DWORD): DWORD of object;

C++就缺少这种形式的定义,只能用以下两种很“丑”的方法实现:
1. 直接将成员函数Cast成为指针然后赋值;
2. 使用Template,定义一个指向Template类的成员函数的指针;

第一种“丑”是因为需要强行Cast,而且不Type Safe,也就是说如果Cast回接口不一致的函数,程序一样高兴的去执行,然后自我崩坏...
第二种“丑”是因为每定义一个对象,都需要在后面加一行,套用Template产生一个只能指向这个对象的成员函数的类型,一点都不简洁,可用性还差...

如果楼上有什么更美观的解决办法,请不吝赐教。


写程序是一种艺术,因此每个人对于作品的审美观都不同,我喜欢Object Pascal是因为它有些地方(比如上面)很"美",是C++达不到的。
当然我也并不是说C++就不美;事实上我从来就没说过我讨厌C++,有些地方它比Pascal要美得多,比如写循环的语法上就比Pascal精练。
Java也是很"美"的,其纯OOP的语法虽然其实际使用中有的地方会造成语言冗余,这些地方是"丑"的,但是总的来说Java的程序结构就像一串漂亮的玻璃项链。

所以我的观点是,每个存在的语言都有自己的优点和存在的意义,因此仅仅因为自己不使用就否定其存在的价值是不正确的。

---
另外,说老实话,我不认为在目前主流OOP语法架构下,Template有什么不可替代的作用。
Template能够做到的事情,通过合理的设计类、处理继承关系,或者重载操作符,都可以做到。
最后编辑Prz 最后编辑于 2007-05-17 11:10:03
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

这里的TFunc应该是指针吧.如果不是就会觉得很不可理解TFunc到底是什么结构.
如果是指针,这个值就是不固定的,因为许多类都可能符合这个接口,
而这些接口的地址都是不同的.
C++的函数指针只能指向全局或静态函数,指向某个非虚函数也有方法,
但指向虚函数的指针如何实现是不可想象的.
其实我们为什么只要这个成员函数呢,
C++中没遇到这个问题的原因是我们通常使用基类指针,


那么,请给出一个C++的实现,满足如下使用方法:
1. 线程被包装在一个类中(可以命名WorkerThread);
2. 任何一个对象中符合" int (WorkerThread* Thread); "接口的方法都可以被分配给这个"线程对象";
3, 一旦分配,该线程自动调用该对象的方法,并将本"线程对象"实例的指针传递给该方法。
如果不用"丑陋"的Cast和啰嗦的Template,你会发现这个简单的任务对于C++就是Mission Impossible...

而这些接口的地址都是不同的......但指向虚函数的指针如何实现是不可想象的

Object Pascal的设计者们应该是超人了 :D 因为我前面描述的那个Pascal的定义不仅是指向虚函数的,而且是指向"虚类"的...
其实仔细想想,编译器编译你的程序的时候是拥有你的所有类的VMT的,实现对于这种架空的指针的支持,仅仅是做一些偏移量运算而已。

其实,你也不能怪C++的设计者们想不到,C++没有这个东西是因为它的设计理念不同。
C/C++不是一个Strong Type的语言,因此对类型的要求很宽松。
起因要追溯历史到C的诞生了。发明C的Unix编程者们都是Programming Guru,他们的信仰是什么?
"Type checking is for weak minds" (类型检查是为"脑容量有限者"准备的) :D
因此,通过前面我提到的Cast的方法能够实现的操作,C++就肯定不会增加另外一种带类型的方法来解决。

其实,M$的C++里面是有这样的架空指针的,不过有足够的理由相信,这个语言扩展出现在Delphi首席设计师被挖去M$之后... :D
因此可以看出语言总是相互借鉴发展的,因此我告诉你下面的事实你也不要感到意外:

话说回来,"重载操作符"好像只是C++的特性吧-_-|


至少2年前,Delphi的Object Pascal中就增加了操作符重载的支持。
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

赫赫,楼上的实现偷换概念了呢。请注意我说的是"任意类",您的实现仅仅局限在从InterfaceA继承下来的类了。

不继承基类,谁能保证类中同名方法在VTABLE中都是同样的位置?


为什么要保证?有一个好听的术语叫"Compiler Magic"...
你觉得 把一个类的一个方法的地址付给一个指针,这个操作在编译器在处理的时候和静态赋值有什么区别么?
就象我前面说的那样,顶多多计算一下偏移量罢了。
虚函数?没问题,这个虚函数是怎么调用的,这个使用这个函数指针时按同样方法处理就行了。

无类型的思想最早是源于汇编的,

这个是没办法而已吧||||

而C语言是与汇编联系最紧密的高级语言.

赫赫,这又提醒了我,使用Inline汇编,C/C++还有一个做不到的功能Pascal可以。

在一个函数中Inline两段汇编,C/C++没有办法从一段汇编的中间直接跳到另外一段里面。

Pascal Code:

Function A(A: integer): Integer;
Label Jump1;
BEGIN
// 第一段
ASM
  // 一些指令
  JMP Jump1
  // 一些指令
END;

// 一些"高级"代码

// 第二段
ASM
  // 一些指令
Jump1:
  // 一些指令
END;
END;

本来很强大的C Inline汇编在如此关键的地方变得无力,让人觉得很是意外。

(注: "巧合"的是,M$的C++语言在这里也有和Object Pascal相同的扩展...hmm让人觉得意外的亲切呢)
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

以下代码编译通过(VC6+sp6):
void main()
{
        __asm
        {
                jmp next_
        }

        int a=123;

        __asm
        {
next_:
                nop
        }
}


这就是我说的M$的令人熟悉的扩展啊。可惜不是标准化的,没有移植性,拿给GNU编译器就会爆掉。
其实,VC从Delphi学了很多东西才能达到今天的易用程度,比如你敲一个对象,然后加"."旁边出来一个小窗口告诉你所有可用的方法和成员变量。

怎么说呢,学习并不是坏事。昨天翻Google还看到Intel的CPU技术其实很多都是从苏联学来的:
当苏联解体的时候,Intel秘密的收容了苏联国家处理器实验室的一位科学家,于是才有了Pentium I-III系列的CPU——里面用的一些技术(比如 SuperScaler Execution,超标量执行)是苏联80年代就开发出来了的。

------

另外Pascal的开域语句也是特色。

Pascal:

With StructA.StructB.StructC.StructD do
BEGIN
IntA:= 0;
IntB:= 6;
IntC:= 9;
END

C/C++:
StructA.StructB.StructC.StructD.IntA:= 0;
StructA.StructB.StructC.StructD.IntB:= 6;
StructA.StructB.StructC.StructD.IntC:= 9;

丑吧.....当然,如果用非标准化的语法,稍微好一点:
{
// 代码中混合变量声明,C/C++的特色,可惜非标准,现在大多数编译器都会警告
StructD* PtrD = &StructA.StructB.StructC.StructD;
PtrD->IntA:= 0;
PtrD->IntB:= 6;
PtrD->IntC:= 9;
}

还有,Pascal的类型指针自动解析也是很是美的。

Pascal:

// 一个RecordX的实例
StructX: RecordX;
StructX.IntC:= 123;

// 指向一个RecordX的指针
PtrD: ^RecrodX;
PtrD:= @StructX;
PtrD.IntC:= 456; // 一样用"."解析,编译器自动帮忙

C/C++:

RecordX StructX;
StructX.IntC:= 123;

RecordX* PtrD = &StructX;
PtrD->IntC:= 456;

// 一会儿用"."一会儿用"->"多麻烦啊

当要访问一个很复杂的结构中"埋藏"很深的纪录的时候,用C++要交叉写很多"."和"->",如果是在一个并列的条件语句中的话,完全可以把人写晕:
if (((StructD->StructE.StructF->StructG.StructX.IntC > 5) || (StructD->StructE.StructF->StructG.StructX.IntC < 10)) && (StructD->StructE.StructF->StructG.StructX.IntC != 7)) {}
如果用临时变量的话,基本避免不了非标准化(与代码混在一起),因为很多的时候你要先确认深层结构确实存在才能引用。

------

当然C的结构指针运算还是很不错的:
RecordX* PtrD = &StructX;
PtrD++; // 指向下一个邻接的结构

Pascal不能对任意指针作运算,只有一个"丑"一点的解决方案:
// 先定义一个很大的虚序列的指针
Type
StructXArray = Array [0..100000] of StructX;
PStructXArray = ^StructXArray;
Var
PtrD: PStructXArray = @StructX;

// 通过编号来访问下一个邻接的结构
PtrD[1].IntC:= 123;

------

学会了解并欣赏其他语言的优美,这就是我这次和您版聊的目的,我想已经达到了。:D
最后编辑Prz 最后编辑于 2007-05-18 01:23:10
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

其实__asm根本就不是C/C++标准里的,
真正的高级语言标准是不允许嵌入汇编的,这影响到源代码的可移植性.
所以讨论inline汇编是不是标准,是不是扩展毫无意义.


那么,你的意思也就是说,你前面说Object Pascal没有"标准化"也是毫无意义的喽?


即使打开VC的最高警告级别也没有任何警告提示.
我想知道为什么说这是不标准的.

这与下面的写法看起来没有任何不美观的地方


赫赫,果然又在吧M$当成标准了。说老实话,M$的特点就是"不标准",而且非常喜欢把本来标准的东西拿过来乱改。
ISO规范化的C是不提倡将代码和变量声明混合在一起的,因为这样代码可维护性差。你试试用gcc编译就知道了。

前面的帖子我就说过,VC的跨asm段跳转是一个严重不兼容的语言扩展,放在其他编译器下就会爆掉,连这个VC都不会警告,你还期望它警告一个不提倡的使用方法?

不美观是因为多用了一个变量,多写了一行。
如果编译器够聪明,会发现这个变量仅仅是为了展开结构,也就会自动省掉。
但是,这样其实也就相当于把程序运行的效率人为的降低,然后把提升的希望放在编译器上。

VC有VAX做更高级的辅助功能,输入"b"就能智能提示出"bool",输入"class"就自动给出代码框架.
我不知道Delphi是不是有这些功能.如果没有,Delphi不去学就是优点吗?


Delphi上用滥的功能,而且我充分相信也是Delphi首先引入的功能。

或许你应该学学历史,Turbo Pascal从出现到离开主流都是世界首屈一指的IDE开发环境。
C程序员还需要在单独的文本编辑器写程序,然后花数分钟时间编译,最后再链接的时候,TP只需要一个键就可以了。
光标移到一个函数上,一键就可以立刻获得这个函数的接口和相关帮助信息,这个是80年代的事情。那个时候写C的程序员还在来回的翻书吧。
不要忘了,Borland就是靠优秀的开发环境,不断的为快速程序开发创新,才能生存于M$的美元大棒下的。

还有OP的"begin/end"比"{}",":="比"="等等更是麻烦...
其实很难能找到一个比C/C++更简洁的高级语言了.


begin end和{}的区别,大概就是敲键盘上三个键的位置不同罢了。({+} 确实比 b+<回车> 隔的要近一点,你硬要说这个是天大的优势,我也没办法)

至于:=和=嘛,赫赫,请你自己回忆一下,不小心把 == 写成 = 曾经浪费了你多少的时间?


我写过不知多少行代码,很难见到"深层结构",


任何需要处理大量关系数据的场合: 人工智能图像识别, 搜索引擎用的网络爬虫...
我算是对"->"深恶痛绝了..... =v=
最后编辑Prz 最后编辑于 2007-05-18 10:59:33
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

赫赫,是啊,我觉得没有必要"口水战"下去了,原因很简单,这个世界上跟本就没有什么东西是完美,语言也不例外。
从ASM到Perl随便举一种语言我都可以找出一堆比C++好的地方,反之亦然。

最后重申一下我从开始到现在一直的立场:
学会了解并欣赏其他语言的优美


本讨论可以结束了 =v=
最后编辑Prz 最后编辑于 2007-05-18 11:22:34
飛べない翼に、意味はあるんでしょうか?
TOP