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

[M] Prelude to K.O. (4.5) + 12楼(4.75)

[ 14748 查看 / 31 回复 ]

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

前面的四回把架构的关键特征进行了抽象的描述,本来不打算写第五回的(因此这回叫4.5),但是...
在过去的一个多星期我埋头苦干,将CAS代码从头到尾的梳理了一遍。因此KO部分的工作暂停了下来... XD

更改的地方比较的多:很多功能被规范化,无数细小的错误(和潜在错误)得到了更正,部分核心代码简化... (还好以前写得算整齐,不然就晕了... =v=)
尽管总的架构没有太大改变,但是我觉得还是有必要作一下文档工作,避免以后自己忘了 :D

主要的更改点有:
1. CAS消息扩展功能的规范化
2. 线程和线程池的包装完善化
3. 意外对象(Exception Object)的完善化


--- CAS 消息 ---

听说Delphi 2007的Together更加的稳定,于是我也开始学着玩玩,发现这东西确实不错 =v=
Model支持与IDE完美的集成,只要几点鼠标,20秒下面的图就出来了 :P
照着讲...



* 增加BusMSG接口。
话说本次修订前,其实CAS总线并没有真的支持完全自定义的消息实例,因为发送消息的方法接受的是以BaseMSG为基类的对象... :P
后来我在一边讲的时候意识到了这一点,于是现在添加一个BusMSG接口,并且使得发送消息方法接受实现BusMSG接口的消息,真正的支持消息的自由实现。
(增加一个接口,而不是扩展原来的BaseMSG接口的原因是功能分离,用户模块仅需要看见对它们有意义的接口,不用关心总线内部发生的事情)

* 实现了前面提到的,较为通用"跨模块调用"(Inter-module call, IMC)消息支持。
本次修订之前,对于IMC的支持是硬写在消息队列核心的,因此不具有自定义实现以及扩展性。
现在添加一个MSG_Event接口,为消息在总线的传输添加通用的事件,然后将IMC消息支持实现在这些事件的基础上。
这样,不仅自定义性得到满足(可以重新实现自己的IMC消息),而且也具有比较好的扩展性(利用事件支持,还可以实现一切其它的功能)。

* 将扩展消息模单独块化。(上图中不可见)
本次修订之前,扩展消息和基本消息挤在一个模块里面。现在基本消息模块仅含有必须的消息接口,减少不需要扩展消息的模块的代码量。

* 优化扩展消息的实现。(上图中不可见)
可复制消息和可序列化消息的代码经过重新梳理和测试,效率和可靠性都得到提高。

--- 线程和线程池 ---

* 重写了线程回收代码
本次修订之前,对于需要强制回收的线程使用的是TerminateThread,这种方法不能有效回收系统资源,而且也不能给目标线程回收用户资源的机会。
现在则采用了改变线程上下文的方法,并且分两步回收线程: 首先强制生成一个例外,如果一定时间内还不能中止,再强制让线程自我中止。
若第一种方法成功,所有的资源应该得到完美的回收 (当然,前提是执行的程序有良好的意外处理机制);
第二种方法能够回收系统分配的线程资源以及部分已知的用户资源 (尽管不能完全回收,但是比TerminateThread什么都回收不了的好 =v=)。
另外,顺带重新包装了OS的一写同步对象,以保证必要时能够及时唤醒等待中的线程。

* 为工作线程增加接口
修订的过程中,我突然想起,分配线程给用户模块的时候用的居然是类.... |||||| 现在改正为接口了,同时对于线程对象的中止条件进行了完整的测试,保证不论线程以何种方式结束(自杀或者他杀 :P),都不会导致线程对象和线程池出问题。

Delphi包装的线程拥有良好的自定义性能。因为支持纯虚函数指针,任何类实现线程方法都可以通过工作线程成为一个独立的执行体,没有任何对象继承的要求。

--- 意外对象 ---

Delphi的另一个特色就是对意外处理有优良的包装,同过抛出意外处理异常情况能够提供比返回值有用很多倍的信息,而且随时随地,想抛就抛。

本次修订新增了一个独立的意外对象(而不是继承自默认的意外对象),特点就是,能够自动叠加(Stack)其它的意外对象,形成完整的"意外链",对于代码的除错非常的有用,特别是在没有源代码或者调试器的情况下。
简单的讲,就是在这个意外对象被抛出的时候,如果当前已经有一个已经抛出的对象,则新的意外对象将旧的自动"链接"到自己内部。

这个功能在许多语言里面几乎是无法想象的。比如Java中新的意外将会覆盖掉旧的意外;C++则更狠,如果抛出新的意外时旧还没释放,程序强制调用ExitProcess,世界太平...|||| (这就是为什么C++强烈不建议在Destructor中抛出异常)

新CAS的意外对象不仅可以提供文字和数字反馈,还可以提供出现意外的类(不仅仅是名字,而是一个类的引用),方法的名称,以及更加有用的信息: 一个意外分类代码。
这个分类代码表示了出现这个意外大致的原因,已经可能的问题。例如:
User类意外表示这个错误是由于程序员不小心引起的,比如提供了一个超过数组范围的编号;
Functional类意外表示这个意外是功能的一部分,比如,如果用户选择通过意外而不是返回值通知消息发送超时,将可能收到Functional类的意外;
Panic类意外表示可能出现了一些不寻常的问题,比如系统内存耗尽;
Internal类意外表示可能总线的代码有问题,这个时候就需要通知我来解决啦! :D

------

最后,来一张消息总线的全景。



(注意,程序未正式发布前,你所看到的东西都有可能改变.....尽管几率比较小......)
最后编辑Prz 最后编辑于 2007-06-09 13:57:52
本主题由 管理员 深海蓝空 于 2007/6/9 14:49:31 执行 设置精华/取消 操作
分享 转发
飛べない翼に、意味はあるんでしょうか?
TOP

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

不做评价...直到开放SDK.
TOP

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

原帖由 dwing 于 2007-6-2 20:36:00 发表
不做评价...直到开放SDK.




唔,对了,目前来说,CAS对于其它语言的兼容性并没有作太多妥协。

1. 对于一些Pascal原生的对象,CAS模块是将其作为基本元素直接传递的。比如,String对象,集合(set),记录类型(record)等。

2. Delphi提供的标准核心构件,也是被当作基本元素直接传递的。比如,文件(File),流(Stream)等。
(以上两点呼应我曾经提到过的,CAS仅仅是按照COM规范使用Interface实现模块代码分离,而不是完全的为COM而COM)

3. 比较重要的是,CAS模块与总线、模块和模块之间的过程调用没有Exception Firewall。
这样做的好处是能够为模块编写者提供全面的Debug信息——前提是,如果模块能安全的够接受异常对象并展示出其内部含有的信息;
但是,就像我上面提到的,不是所有的语言都对异常有完善的支持,尤其是对于C++来说,任何时候都可能抛出异常几乎是不能接受的.....


至于提供跨语言兼容性嘛,我还不太确定什么时候会这么做。或许直到Haeleth (RLDev的作者)把RLDev 用C++ 重写的那一天吧.... ||||||||
飛べない翼に、意味はあるんでしょうか?
TOP

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

原帖由 Prz 于 2007-6-3 0:00:00 发表
唔,对了,目前来说,CAS对于其它语言的兼容性并没有作太多妥协。

1. 对于一些Pascal原生的对象,CAS模块是将其作为基本元素直接传递的。比如,String对象,集合(set),记录类型(record)等。

2. Delphi提供的标准核心构件,也是被当作基本元素直接传递的。比如,文件(File),流(Stream)等。
(以上两点呼应我曾经提到过的,CAS仅仅是按照COM规范使用Interface实现模块代码分离,而不是完全的为COM而COM)

3. 比较重要的是,CAS模块与总线、模块和模块之间的过程调用没有Exception Firewall。
这样做的好处是能够为模块编写者提供全面的Debug信息——前提是,如果模块能安全的够接受异常对象并展示出其内部含有的信息;
但是,就像我上面提到的,不是所有的语言都对异常有完善的支持,尤其是对于C++来说,任何时候都可能抛出异常几乎是不能接受的.....


既然这样,我就不期望什么了,果然是太依赖delphi本身提供的各种"便利".
我还是去研读跨语言的COM好了.

--------------------

另外,关于C++的异常.以下代码在很古老的VC6中就运行无误了.
包括构造函数异常,析构函数异常,嵌套异常,异常中的异常.

#include <stdio.h>
class C
{
public:
C(bool b) {if(b) throw new int(1);}
~C()  {throw new int(2);}
};
void func()
{
try{C c0(true);}
catch(int *e)
{
  printf("%d\n",*e); 
  C c1(false);
}
}
void main()
{
try{func();}
catch(int *e)

  printf("%d\n",*e); 
}
}

输出:
1
2
最后编辑dwing 最后编辑于 2007-06-03 10:19:17
TOP

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

赫赫,楼上您并没有"嵌套"啊。是不明白还是又在偷换概念?(估计是后者.... -_-#)

试试下面这段:
----

#include <stdio.h>
class Cx
{
public:
Cx() {throw new int(1);}
~Cx()  {throw new int(2);}
};

void main()
{
try{C c0;}
catch(int *e)
{
  printf("还没有到这里世界就太平了...||||");
}
}

---
当然,我没有玩过M$的C++不知道M$是不是又擅自作了非标准的"修订"。
至少标准的C++是一定会"太平"的....||||||
最后编辑Prz 最后编辑于 2007-06-03 15:59:14
飛べない翼に、意味はあるんでしょうか?
TOP

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

原帖由 Prz 于 2007-6-3 15:55:00 发表

试试下面这段:
----

#include <stdio.h>
class Cx
{
public:
Cx() {throw new int(1);}
~Cx()  {throw new int(2);}
};

void main()
{
try{C c0;}
catch(int *e)
{
  printf("还没有到这里世界就太平了...||||");
}
}


VC6的运行结果:
还没有到这里世界就太平了...||||Press any key to continue

这个异常符合C++标准,为什么说M$擅自作了非标准的"修订"?
TOP

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

哦,可能我简化的过分了点。
(Delphi在类Construct的时候如果有异常,会自动调用Destructor。可能 Static 方式定义的C++不会)

下面这个应该表明我想说的问题:
------

#include <stdio.h>
class Cy
{
public:
Cy() {}
testexcept {throw new int(1);}
~Cy()  {throw new int(2);}
};

void func()
{
Cy c1;
c1.testexcept();
}

void main()
{
try{ func(); }
catch(int *e)
{
  printf("真·还没有到这里世界就太平了...||||");
}
}

------

原因很简单,当一个异常抛出的时候,控制的跳转可能导致一些对象Out of scope。
为了程序逻辑的完整性(以及不产生内存漏洞),对象的Destructor会被自动调用。
但是,如果这个时候Destructor再一次抛出异常的话,怎么办呢?
不管是忽略掉第一个异常还是第二个异常,都不是一个可以接受的解决方案。

C++在这种情况下规定程序强制退出;
Java将其前一个异常扔掉;
Delphi默认动作和Java一样,但是留给编程者自己处理的空间,我选择将异常“串”起来,继续传送。
最后编辑Prz 最后编辑于 2007-06-03 16:54:34
飛べない翼に、意味はあるんでしょうか?
TOP

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

其实析构函数经常只做一些内存和资源释放,这些操作都是安全的,一般情况下都不会也不需要抛出异常.
如果确实要抛出异常,那要这么做:

#include <stdio.h>
class Cy
{
public:
        void testexcept() {throw new int(1);}
        ~Cy()
        {
                try { throw new int(2); }
                catch(int *e) { printf("%d",*e); }       
        }
};

void func()
{
        Cy c1;
        c1.testexcept();
}

void main()
{
        try{ func(); }
        catch(int *e) { printf("%d",*e); }
}

运行结果: 21

---------------------

参考: 对象生死劫 - 构造函数和析构函数的异常
http://blog.csdn.net/tingsking18/archive/2007/03/05/1521296.aspx
最后编辑dwing 最后编辑于 2007-06-04 09:22:12
TOP

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

这些方法我当然知道,不然就不会提出来了。
虽然经过层层的Try Catch包裹可以(基本上)解决问题,但是用起来不爽嘛,感觉像“套中人”......
还有,在图形界面下,没有console给你print,那么就要写自己的Debug Window...... 多线程还需要同步机制,弄得不小心还会死锁一下......

最符合正常人的思维的写法应该是,有问题就扔╰( ̄▽ ̄)╭  ........
到最后一张网接住,看看网到了什么东西。

我个人认为这个才是最体现异常处理的精髓的。
最后编辑Prz 最后编辑于 2007-06-04 15:32:30
飛べない翼に、意味はあるんでしょうか?
TOP

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

当然,我同意在Destructor中扔出异常不是什么好事,我的程序里面也很少用。但是,C++的问题在于:
"不要在Destructor中扔出异常",这点表面上看起来很好做到,但是实际应用中......

虽然保证不出现明确的throw很简单,但是你不能保证一个Destructor仅仅调用其它的Destructor;
如果Destructor调用了一些会抛出异常的方法,那其实是和在Destructor中抛出异常等效的。

这样一来,在Destructor中要使用的方法也就不能轻易的抛出异常了;
在Destructor中要使用的方法要使用的方法也不能轻易的抛出异常了;
在Destructor中要使用的方法要使用的方法.....要使用的方法还是不能轻易的抛出异常了...
.......犹如多米诺骨牌一般哗啦啦.....的倒下去......|||||||||


因此,在一个稍微中型的C++工程里面(特别是多人合作的),不要使用异常几乎成了工业标准!(大型的就更不用说了)
因为一旦某人使用了异常,并且别人对于抛出的异常没有准备,整个程序就会死得很难看,而且要找出问题极其的麻烦....(特别是双重异常造成的直接ExitProcess)

如此一来,"异常处理"这个非常有用的调试机制反倒成了调试软件的绊脚石,岂不是很荒谬么?
飛べない翼に、意味はあるんでしょうか?
TOP