Profilo di jiyu人生路漫漫,快乐先行FotoBlogElenchi Strumenti Guida

Blog


14 maggio

嵌入式程序员应知道的基本问题-C语言(zz)

来源:21ICbbs  作者:lhf

C语言测试:想成为嵌入式程序员应知道的0x10个基本问题

其中少量灰色的文字是我添加的,表达一些我的看法,很不成熟,希望朋友们指正。

C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为带面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。
从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?这个愚蠢的问题吗?如要你答出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上而不上在嵌入式系统上。如果上述任何问题的答案是“是”的话,那么我知道我得认真考虑我是否应该去做这份工作。
从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者C语言的水平。不管怎么样,看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮住。这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。


预处理器(Preprocessor)

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)


#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
? #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)


? 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
? 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
? 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。


2 . 写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。


#define MIN(A,B) ((A) <= (B) ? (A) : (B))

这个测试是为下面的目的而设的:
? 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
? 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
? 懂得在宏中小心地把参数用括号括起来
? 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

least = MIN(*p++, b);


3. 预处理器标识#error的目的是什么?


如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。


死循环(Infinite loops)
4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:

while(1)
{
?}

 do{

...

} while (1)

一些程序员更喜欢如下方案:

for(;;)
{
?}

 

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto

Loop:
...
goto Loop;


应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

数据声明(Data declarations)

5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )

答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer

typedef int (*PFUNCTION)(int)

PFUNCTION pfun;
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer

typedef int (*PFUNCTION)(int)

PFUNCTION pfun[10];
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?


Static
6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
? 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
? 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
? 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。


Const

7.关键字const有什么含意?
我只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?

const int a;
int const a;
const int *a;
int * const a;
int const * a const;   //    int const * const a;

/******/
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
? 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
? 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
? 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。


Volatile

8. 关键字volatile有什么含意?并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
? 并行设备的硬件寄存器(如:状态寄存器)
? 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
? 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
? 一个参数既可以是const还可以是volatile吗?解释为什么。
? 一个指针可以是volatile 吗?解释为什么。
? 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}

下面是答案:
? 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
? 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
? 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:


int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}


由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}

位操作(Bit manipulation)

9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
? 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
? 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
? 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:


#define BIT3 (0x1 << 3)
static int a;

void set_bit3(void) {
a |= BIT3;
}
void clear_bit3(void) {
a &= ~BIT3;
}

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。


访问固定的内存位置(Accessing fixed memory locations)

10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;

A more obscure approach is:
一个较晦涩的方法是:

*(int * const)(0x67a9) = 0xaa55;

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

中断(Interrupts)

11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}

这个函数有太多的错误了,以至让人不知从何说起了:
? ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
? ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
? 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
? 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。


代码例子(Code examples)

12 . 下面的代码输出是什么,为什么?

void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 ”>6”。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。 因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。

13. 评价下面的代码片断:

unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */

对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:

unsigned int compzero = ~0;

这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧…

动态内存分配(Dynamic memory allocation)
14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?
这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这么一个小节目:
下面的代码片段的输出是什么,为什么?

char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
else
puts("Got a null pointer");
puts("Got a valid pointer");

所以在这个时候,一定要使用pc-lint来检察代码规范性,起码pc-lint能检查出if-else语句格式不对,要求开发人员对格式进行修改,那样的话,看起来就方便多了。

这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是“Got a valid pointer”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。


Typedef 
15 Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

#define dPS struct s *
typedef struct s * tPS;

以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

dPS p1,p2;
tPS p3,p4;

第一个扩展为

struct s * p1, p2;

.
上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。

晦涩的语法

16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c;
c = a+++b;

这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:

c = a++ + b;

因此, 这段代码持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。
好了,伙计们,你现在已经做完所有的测试了。这就是我出的C语言测试题,我怀着愉快的心情写完它,希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年,我就不做现在的工作,也需要找一个。

让你明白什么是ERP(zz)

ERP(Enterprise Resource Planning)企业资源计划系统,是指建立在信息技术基础上,以系统化的管理思想,为企业决策层及员工提供决策运行手段的管理平台。
一天中午,丈夫在外给家里打电话:"亲爱的老婆,晚上我想带几个同事回家吃饭可以吗?"(订货意向)
妻子:"当然可以,来几个人,几点来,想吃什么菜?"
丈夫:"6个人,我们7点左右回来,准备些酒、烤鸭、番茄炒蛋、凉菜、蛋花汤......。你看可以吗?"(商务沟通)
妻子:"没问题,我会准备好的。"(订单确认)
妻子记录下需要做的菜单(MPS计划),具体要准备的东西:鸭、酒、番茄、鸡蛋、调料......(BOM物料清单),发现需要:1只鸭蛋,5瓶酒,4个鸡蛋......(BOM展开),炒蛋需要6个鸡蛋,蛋花汤需要4个鸡蛋(共用物料)。
打开冰箱一看(库房),只剩下2个鸡蛋(缺料)。
来到自由市场,妻子:"请问鸡蛋怎么卖?"(采购询价)
小贩:"1个1元,半打5元,1打9.5元。"
妻子:"我只需要8个,但这次买1打。"(经济批量采购)
妻子:"这有一个坏的,换一个。"(验收、退料、换料)
回到家中,准备洗采、切菜、炒菜......(工艺线路),厨房中有燃气灶、微波炉、电饭煲......(工作中心)。妻子发现拨鸭毛最费时间(瓶颈工序,关键工艺路线),用微波炉自己做烤鸭可能来不及(产能不足),于是阅览室在楼下的餐厅里买现成的(产品委外)。
下午4点,电话铃又响:"妈妈,晚上几个同学想来家里吃饭,你帮忙准备一下。"(紧急订单)
"好的,你们想吃什么,爸爸晚上也有客人,你愿意和他们一起吃吗?"
"菜你看着办吧,但一定要有番茄炒鸡蛋,我们不和大人一起吃,6:30左右回来。"(不能并单处理)
"好的,肯定让你们满意。"(订单确定)
鸡蛋又不购了,打电话叫小贬送来。(紧急采购)
6:30,一切准备就绪,可烤鸭还没送来,急忙打电话询问:"我是李太,怎么订的烤鸭还不送来?"(采购委外单跟催)
"不好意思,送货的人已经走了,可能是堵车吧,马上就会到的。"
门铃响了。"李太太,这是您要的烤鸭。请在单上签一个字。"(验收、入库、转应付账款)
6:45,女儿的电话:"妈妈,我想现在带几个朋友回家吃饭可以吗?"(呵呵,又是紧急订购意向,要求现货)
"不行呀,女儿,今天妈已经需要准备两桌饭了,时间实在是来不及,真的非常抱歉,下次早点说,一定给你们准备好。"(哈哈,这就是ERP的使用局限,要有稳定的外部环境,要有一个起码的提前期)
送走了所有客人,疲惫的妻子坐在沙发上对丈夫说:"亲
爱的,现在咱们家请客的频率非常高,应该要买些厨房用品了(设备采购),最好能再雇个小保姆(连人力资源系统也有接口了)。
丈夫:"家里你做主,需要什么你就去办吧。"(通过审核)
妻子:"还有,最近家里花销太大,用你的私房钱来补贴一下,好吗?"(最后就是应收货款的催要)
现在还有人不理解ERP吗?记住,每一个合格的家庭都是生产厂长的有力竞争者!
07 maggio

关于汇编语言内存寻址方式浅析(转)

指令操作数的寻址方式
  一、与数据有关,寻找参加操作的数据(8种---->7种基本方式+字符串寻址)
  
  ★涉及的一些概念
  DISP/16位:AX(累加器),BX(基侄寄存器),CX(计数器),DX(数据与地址寄存器),SP(堆栈指针),BP(基址指针),SI(源变址器),DI(目的变址器)
  DISP/8位:AH(高8位),AL(低8位),BH,BL,CH,CL.DH,DL
  IP指令指针,存放代码段中的偏移地址;EA偏移地址,段内相对地址,有效地址
  CS代码段,SS堆栈段,DS数据段,ES附加段
  堆栈:方便事项程序要求保留和恢复有关信息的特殊存储部件,是一种数据结构。
  pws(状态):CF(进位),PF(奇偶),SF(符号),OF(溢出),ZF(零),DF(方向),IF(中断),TF(跟踪)
  PA:20位物理地址 ;MOV传送
  
  1、立即寻址
  用来表示常数
  MOV AX,1946H;1946H(立即数)-->AX
  
  2、寄存器寻址
  MOV AX,CX ;cx-->ax
  
  3、直接寻址
  MOV BX,[1000H] ;[1000H]→EA,操作数默认DS
  MOV AX,ES:VAR ;越段前缀
  
  4、寄存器间接寻址
  EA=BX∨SI∨DI∨BP;∨表示或者
  a、PA=DS×16+{BX∨SI∨DI}
  b、PA=SS×16+BP
  c、PA=越段寄存器×16+{BX∨SI∨DI∨BP}
  
  5、寄存器相对寻址
  直接变址寻址
   ┍BX┑
  EA=├SI┥+{DISP}
   ├DI┥
   ┕BP┚
  mov ax,count[si] ;ea=count+si,(ea)→ax,约定为DS
  mov al,es:string[bp] ;越段前缀
  
  6、基址变址寻址
  使用数组和表格
  EA={BX∨BP}+{SI∨DI}
  pA=DS×16+BX+{SI∨DI}
  pA=SS×16+BP+{SI∨DI}
  mov cx,es:[bx][di]
  mov ax,[bx][si] ;ea=bx+si,ea→ax
  
  7、相对基址变址寻址
  EA={BX∨BP}+{SI∨DI}+{DISP}
  mov ax,array[bx][si] ;ea=array+bx+si,ea→ax
  
  8、字符串寻址
  movsb ;([si])=>([di]),si+1=>si,di+1=>di
   ;源=>目,可理解为mov [di],[si]

我对寻址的一些分析看看对大家对他的理解有没有帮助。
  
  物理地址(PA)=段地址+偏移地址(EA)
   =段寄存器的内容×16+偏移地址
   =约定默认段地址×16+偏移地址
   =越段地址×16+偏移地址
  
  1、立即寻址
  A→B,A就是PA(物理地址)可以直接给B
  2、寄存器寻址
  A→B,A(寄存器)的值是PA可以给B
  3、直接寻址
  A→B,A是EA,EA=确定的寄存器的值,由EA得到PA,然后给B
  4、寄存器间接寻址
  A→B,A是EA,EA=变动的寄存器的值,由EA得到PA,然后给B
  5、寄存器相对寻址
  A→B,A是EA,EA=变动的寄存器的值+DISP的值,由EA得到PA,然后给B
  6、基址变址寻址
  A→B,A是EA,EA=变动的寄存器的值+另一变动的寄存器的值,由EA得到PA
  
  ,然后给B
  7、相对基址变址寻址
  A→B,A是EA,EA=变动的寄存器的值+另一变动的寄存器的值+DISP的值,
  
  由EA得到PA,然后给B
  8、字符串寻址
  A→B,A源PA,然后给B(目的pA)
  
  理解寻址方式的关键是EA的值是什么和怎样由EA得到PA,方式4,5,6,7容易混淆,我这样解释可能不科学,但能达到明白它的意思就行。4和3的区别是EA=变动的寄存器的值,5在4的基础上加了DISP的值,6相当于2个5相加(EA=变动的寄存器的值+另一变动的寄存器的),7相当于5+6,哈哈,都什么和什么呀。
06 maggio

让Editplus支持汇编语言语法高亮显示

-- 2006.5.6 By A.TNG
 
最近在看汇编,发现好多知识都还给老师了,得恶补回来。写了几行代码,发现SourceInsight感觉好不爽,还是用回最喜欢的Editplus。不过突然发现ep不支持汇编语言的语法高亮显示,很是郁闷,心想,如此强大的ep怎能如此弱,找了找ep的各个设置,发现在:工具-〉参数设置-〉文件-〉设置和语法 中有个可以设置语法高亮显示的选项,里头已经有了:cpp css html java js jsp ...。马上在网搜索了一个ASM的语法高亮设置,贴出来,分享一下,同时也感谢原作者的工作。
 
所需要做的是,打开ep,至对应的设置面板,选择添加,在描述中输入:ASM,在文件扩展名中输入:asm,然后在语法文件一栏选择对应的stx文件就好了。(asm的stx文件,就是把下面的东东保存为you_decide_the_name.stx)
 
 
#TITLE=ASM
; ASM syntax file written by kylin.
; This file is required for EditPlus to run correctly.
#DELIMITER=,(){}[]-+*/=~!&|<>?:.
#QUOTATION1='
#QUOTATION2="
#CONTINUE_QUOTE=n
#LINECOMMENT=;
#ESCAPE=\
#CASE=y
#PREFIX3=$
#PREFIX4=@
#PREFIX5=%
#NUMBER_PATTERN=asm
#SPECIAL_STX=asm
#KEYWORD=Reserved words
db
dw
dd
dq
mov
movsx
movzx
xchg
push
pusha
pushad
pop
popa
popad
in
out
xlat
lea
lds
les
lfs
lgs
lss
lahf
sahf
pushf
pushfd
popf
popfd
add
sub
adc
sbb
inc
dec
mul
div
imul
idiv
cbw
cwd
cwde
cdq
cmp
neg
daa
das
aaa
aas
aam
aad
and
or
xor
not
test
shl
sal
rol
ror
rcl
rcr
shld
shrd
movs
movsb
movsw
movsd
cmps
cmpsb
cmpsw
cmpsd
scas
scasb
scasw
scasd
lods
lodsb
lodsw
lodsd
stos
stosb
stosw
stosd
ins
insb
insw
insd
outs
outsb
outsw
outsd
jmp
jc
jnc
jz
je
jnz
jne
js
jns
jo
jno
jp
jpe
jnp
jpo
ja
jneb
jae
jnb
jb
jnae
jbe
jna
jg
jnle
jge
jnl
jl
jnge
jle
jng
jcxz
jecxz
loop
loopz
loope
loopnz
loopne
ret
retn
retf
int
into
iret
iretd
set
clc
stc
cmc
cld
std
cli
sti
hlt
wait
esc
lock
nop
bt
btc
btr
bts
bsf
bsr
bound
enter
leave
lar
lsl
lgdt
lidt
sgdt
sidt
ltr
str
lmsw
smsw
lldt
sldt
arpl
clts
verr
verw
DW
DD
DQ
MOV
MOVSX
MOVZX
XCHG
PUSH
PUSHA
PUSHAD
POP
POPA
POPAD
IN
OUT
XLAT
LEA
LDS
LES
LFS
LGS
LSS
LAHF
SAHF
PUSHF
PUSHFD
POPF
POPFD
ADD
SUB
ADC
SBB
INC
DEC
MUL
DIV
IMUL
IDIV
CBW
CWD
CWDE
CDQ
CMP
NEG
DAA
DAS
AAA
AAS
AAM
AAD
AND
OR
XOR
NOT
TEST
SHL
SAL
ROL
ROR
RCL
RCR
SHLD
SHRD
MOVS
MOVSB
MOVSW
MOVSD
CMPS
CMPSB
CMPSW
CMPSD
SCAS
SCASB
SCASW
SCASD
LODS
LODSB
LODSW
LODSD
STOS
STOSB
STOSW
STOSD
INS
INSB
INSW
INSD
OUTS
OUTSB
OUTSW
OUTSD
JMP
JC
JNC
JZ
JE
JNZ
JNE
JS
JNS
JO
JNO
JP
JPE
JNP
JPO
JA
JNEB
JAE
JNB
JB
JNAE
JBE
JNA
JG
JNLE
JGE
JNL
JL
JNGE
JLE
JNG
JCXZ
JECXZ
LOOP
LOOPZ
LOOPE
LOOPNZ
LOOPNE
RET
RETN
RETF
INT
INTO
IRET
IRETD
SET
CLC
STC
CMC
CLD
STD
CLI
STI
HLT
WAIT
ESC
LOCK
NOP
BT
BTC
BTR
BTS
BSF
BSR
BOUND
ENTER
LEAVE
LAR
LSL
LGDT
LIDT
SGDT
SIDT
LTR
STR
LMSW
SMSW
LLDT
SLDT
ARPL
CLTS
VERR
VERW

#KEYWORD=Register
flat
stdcall
casemap
none
dup
proto
call
local
invoke
eax
ax
ah
al
ebx
bh
bl
bx
ecx
cx
ch
cl
edx
dx
dh
dl
esi
si
edi
di
ebp
bp
esp
sp
carry
overflow
parity
sign
zero
true
false
FLAT
STDCALL
CASEMAP
NONE
DUP
PROTO
CALL
LOCAL
INVOKE
EAX
AX
AH
AL
EBX
BH
BL
BX
ECX
CX
CH
CL
EDX
DX
DH
DL
ESI
SI
EDI
DI
EBP
BP
ESP
SP
CARRY
OVERFLOW
PARITY
SIGN
ZERO
TRUE
FALSE
#KEYWORD=Statements
386
model
option
data
const
stack
code
proc
endp
struc
ends
end
include
includelib
if
else
elseif
endif
while
endw
repeat
break
continue
until
null
386
MODEL
OPTION
DATA
CONST
STACK
CODE
PROC
ENDP
STRUC
ENDS
END
INCLUDE
INCLUDELIB
IF
ELSE
ELSEIF
ENDIF
WHILE
ENDW
REPEAT
BREAK
CONTINUE
UNTIL
NULL
#KEYWORD=Description
ds
cs
es
ss
fs
gs
addr
offset
byte
word
dword
ptr
DS
CS
ES
SS
FS
GS
ADDR
OFFSET
BYTE
WORD
DWORD
PTR
#KEYWORD=Symbols
=
:
@
(
)
,
.
;
/
+
-
*
%
#
01 maggio

今天才知道,一个磁盘的扇区大小为512B

软盘、硬盘都是这样,那么来说,处理起来是一样的。

不过又看到一则新闻,稍微老一些,2006-3-26。

德国IT新闻网站Golem.de报道,“国际磁盘驱动器、器材及原料协会”(IDEMA)已经同意将硬盘扇区大小由目前的512Byte增加到4096Byte,这将导致硬盘存储密度和容量的进一步提升。

该报道称更大的扇区可以实现更健全的错误修正机制。采用4KB扇区的硬盘将在今年晚些时候上市,但目前我们还没有哪些厂商将推出这种硬盘及其容量大小的消息。

世界变化就是快啊。

29 aprile

Petri网简介(转载)

    Petri网是对离散并行系统的数学表示。Petri网是1960年代由卡尔·A·佩特里发明的,适合于描述异步的、并发的计算机系统模型。Petri网既有严格的数学表述方式,也有直观的图形表达方式,既有丰富的系统描述手段和系统行为分析技术,又为计算机科学提供坚实的概念基础。
    由于Petri网能够表达并发的事件,被认为是自动化理论的一种。研究领域趋向认为Petri网是所有流程定义语言之母。

  • 经典Petri网
    经典的Petri网是简单的过程模型,由两种节点:库所和变迁,有向弧,以及令牌等元素组成的。

    • Petri网的结构
      (1) Petri网的元素:
      • 库所(Place)圆形节点
      • 变迁(Transition)方形节点
      • 有向弧(Connection)是库所和变迁之间的有向弧
      • 令牌(Token)是库所中的动态对象,可以从一个库所移动到另一个库所。
        (2) Petri网的规则是:
      • 有向弧是有方向的
      • 两个库所或变迁之间不允许有弧
      • 库所可以拥有任意数量的令牌
    • 行为
      如果一个变迁的每个输入库所(input place)都拥有令牌,该变迁即为被允许(enable)。一个变迁被允许时,变迁将发生(fire),输入库所(input place)的令牌被消耗,同时为输出库所(output place)产生令牌。
      • 变迁的发生是原子的
      • 有两个变迁都被允许的可能,但是一次只能发生一个变迁
      • 如果出现一个变迁,其输入库所的个数与输出库所的个数不相等,令牌的个数将发生变化
      • Petri网络是静态的
      • Petri网的状态由令牌在库所的分布决定
    • Petri网的形式化定义
      一个经典的Petri网由四元组(库所,变迁,输入函数,输出函数)组成。任何图都可以映射到这样一个四元组上,反之亦然。
    • Petri网流程建模
      一个流程的状态是由在场所中的令牌建模的,状态的变迁是由变迁建模的。令牌表示事物(人,货物,机器),信息,条件,或对象的状态; 库所代表库所,通道或地理位置;变迁代表事件,转化或传输。
      一个流程有当前状态,可达状态,不可达状态。
    • 经典Petri网的局限性
      • 没有测试库所中零令牌的能力
      • 模型容易变得很庞大
      • 模型不能反映时间方面的内容
      • 不支持构造大规模模型,如自顶向下或自底向上

  • 高级Petri网
    为了解决经典Petri网中的问题,研究出了高级Petri网,在以下方面进行了扩展:

    • 令牌着色
      一个令牌通常代表具有各种属性的对象,因此令牌拥有值(颜色)代表由令牌建模的对象的具体特征,如一个令牌代表一个工人(张三,28岁,经验3级)。
    • 时间
      为了进行分析,我们需要建模期间,延迟等,因此每一个令牌拥有一个时间戳,变迁决定生产出的令牌的延迟。
    • 层次化
      构造一个复杂性与数据流图相当的Petri网的机制。子网是由库所,变迁和子网构成的网络。
    • 时序
      增加时序逻辑的定义,更好的描述行为过程。

  • Petri网的应用
    Petri网的应用非常广泛,以下是Petri网比较常用的几种应用:

    • 软件设计
    • 工作流管理
    • 工作流模式
    • 数据分析
    • 并行程序设计
    • 协议验证

彩信之我见

-- 2006.4.28 By A.TNG


1、废话。
搞IT总是让人感觉没有前途,来公司实习,越来越觉得前途渺茫,直到突然有一天,醒悟过来,我原来不是属于IT行业的,而是属于通信行业。看看sina上科技版首页,俨然的把IT业界与电信分开来,于是觉得很欣慰,自己原来也不是那么“挨踢”。

爱立信、诺基亚、摩托罗拉、西门子、阿尔卡特、朗讯、北电、华为和中兴,哪家不是世界巨头,哪家不令各大学高材生趋之若鹜。她们都是属于电信行业。因此,就算是进了排名最后的中兴,“挨起踢”来,都让人觉得舒服,或者说,人家放个屁都是香的。

完了,跑题了。最后跑一句:珍爱生命,远离··。

2、关于彩信。
其实跟彩信结缘也是在去了上海之后,本科毕业以前,不了解什么是彩信,就听说过SMS/MMS,知道肯定是两个不同的东西,听说过发彩信巨爽,图片声音样样都有,还有就是发一条,要花1RMB,掐指头算了一下,合10条短信呢,算了,还是省省吧。直到二月份去了上海,知道自己做的email协议栈只是一个很小的组件,还是依附于彩信的,于是开始对彩信有种莫名的崇拜。

还有一点,就是觉得彩信组的人都是大牛,人也特别好,感觉好亲切:)

3、说说彩信
其实到现在为止,发彩信的人还是很少,首先是终端设备支持不够。中国人穷,对于手机是能用就用,只有极少数人看到出新手机,不管多贵都去买一个尝鲜,大多数人用的还是那种比较老式,对彩信支持都不够好的手机。

然后是网络支持不够,凭借现在2G的网络或者是CDMA的网络,对于发送小的彩信,还是不错的,来个小图片,来段短声音,都还凑活。但是据彩信组的大牛们说,在现在GPRS的网络上,超过80K的彩信,就极有可能发送不成功;移动的兄弟们说,我们的GSM网络就支持100K的数据,超过这个量,说断就断,不会事先跟你打招呼的。其实啊,各大厂商也就是把宝压在了3G的网络上头,在那个上头发彩信,感觉爽极了。

说实话,彩信还有有她的缺陷,在能发送的情况下,彩信中心是不会保存发送的彩信的,因为她不是一个邮件服务器,不过她也有很有趣的地方,彩信可以通过网关,并且重新编码,发到用户的邮箱里头。

彩信发到邮箱,需要各个方面的支持,网关需要支持,能对彩信进行解码和MIME封装,需要支持SMTP把彩信发到邮件服务器上,用户的客户端(Outlook和Foxmail)把彩信型邮件接收下来,也需要显示上的支持,不过我试过Outlook Express 6.00.2800.1807和Foxmail 5.0.800.0都不支持对SMIL的解析,所以也完全没有达到显示彩信的效果。唉,其实也很惭愧,我觉得既然OE和Foxmail都不支持,我们做的东西也不需要支持那么好,照着葫芦画瓢就好了。

4、个人感觉
虽然现在手机上的邮件用的人还是很少,不过我仍然希望手机邮件有一个美好的未来。从功能上说,彩信和邮件都能提供很完善的多媒体服务,而且邮件的历史远比彩信要长,使用的人更多,如果能把手机和邮件完美结合,潜在的客户群体肯定很大。邮件也有她的缺点,她属于被动式,用户需要主动去收取,才能够知道是否有新邮件,而彩信属于PUSH型,能主动通知用户是否有彩信到。不过在这一点上,加拿大有一家公司联合电信运营商推出了“Push Mail-黑莓”服务,来弥补邮件被动式的缺点,“Push Mail”是建立与现有的POP机制之上的,应该是更加的完善。令人高兴的是联通公司也提出了“红草莓”服务,跟那个“Push Mail”类似。

个人感觉,邮件这个东西,发展到现在,很多方面都很完善了,ESMTP、POP3、IMAP4和MIME这些协议都有比较稳定的代码支撑着,可是邮件和手机结合的同时,要充分考虑手机的特点,手机在控制性和显示方面都有天生的缺陷,因此在显示邮件时,如何充分的利用MIME协议的特点,并且最大限度的满足用户的需求,才是难点。其实显示邮件方面Lotus Notes感觉是做得最好的,而OE和Foxmail要差一些。


22 gennaio

对AEECallback结构体及其基础函数的分析

----2006.1.22

typedef struct _AEECallback AEECallback;
struct _AEECallback
{
 AEECallback *pNext;  //保留,并且调用程序不得修改此成员
 void        *pmc;  //保留,并且调用程序不得修改此成员
 PFNCBCANCEL  pfnCancel;  //回调被取消时,指向回调处理程序所调用函数的指针。调用程序必须将此指针设置为 NULL。
 void        *pCancelData; //传递给 pfnCancel 的数据。 调用程序不得修改此成员。
 PFNNOTIFY    pfnNotify;  //AEE 调用的回调函数。调用程序必须将此指针设为指向 AEE 回调处理程序所调用的函数。
 void        *pNotifyData; //传递给 pfnNotify 的数据,调用程序必须将此指针设为指向须传递给 pfnNotify函数的数据。
 void        *pReserved;  //保留,并由回调处理程序使用此成员
};
该结构体,前两个参数pNext和pmc都是不可修改的,是系统维护其值;pfnCancel和pCancelData是在取消回调时使用的;pfnNotify和pNotifyData是在回调时使用的;最后一个pReserved是保留参数,可以存储任何数据。

AEECallback_Setup函数:
1、通过CALLBACK_Cancel宏调用了pcb中的pfnCancel函数。
2、给pfnCancel赋值,给pCancelData赋值
3、给pReserved赋值,其值是pfnCancelNotify
备注:猜想该函数的功能就是为pcb结构体赋值,按次序分别是pfnCancel、pCancelData、pReserved

static void AEECallback_Setup(AEECallback *pcb, PFNCBCANCEL pfnCancel,
                              void *pvCancel, PFNSCHEDNOTIFY pfnCancelNotify)
{
   CALLBACK_Cancel(pcb);
   pcb->pfnCancel   = pfnCancel;
   pcb->pCancelData = pvCancel;
   pcb->pReserved   = (void *)pfnCancelNotify;
}

AEECallback_Fire函数:
1、把参数强制类型转换为AEECallback类型
2、把pfnCancel赋值为0
3、调用pfnNotify函数
备注:猜想该函数的功能就是调用notify函数

static void AEECallback_Fire(void *pvCxt)
{
   AEECallback *pcb = (AEECallback *)pvCxt;

   pcb->pfnCancel = 0;
   pcb->pfnNotify(pcb->pNotifyData);
}

AEECallback_CancelNotify函数:
1、从pReserved指针中取出保存的pfnCancelNofity,并且强制类型转换,猜想该成员的职责就是保存一些需要保存的指针
2、把pfnCancel置为0
3、调用pfnCancelNotify函数
备注:该函数其实就是要调用其中的CancelNotify函数,只是该函数指针保存在了void类型的pReserved成员里头,需要进行强制类型转换,注意的一点是在调用notify类函数的时候都把pfcCancel置为了0,不知道是何用意

static void AEECallback_CancelNotify(AEECallback *pcb)
{
   PFNSCHEDNOTIFY pfnCancelNotify = (PFNSCHEDNOTIFY)pcb->pReserved;

   pcb->pfnCancel = 0;
   pfnCancelNotify(pcb->pCancelData, AEECallback_Fire, pcb);
}

AEECallback_SchedNotifyWait函数:
1、先通过AEECallback_Setup函数来填充pcb结构体
2、再调用pfnSched函数
备注:不明白这个函数什么意思

void AEECallback_SchedNotifyWait(AEECallback *pcb, void *pSchedNotifyObj,
                                 PFNSCHEDNOTIFY pfnSched,
                                 PFNSCHEDNOTIFY pfnCancel)
{
   AEECallback_Setup(pcb, AEECallback_CancelNotify, pSchedNotifyObj, pfnCancel);
   pfnSched(pSchedNotifyObj, AEECallback_Fire, pcb);
}

16 gennaio

关于AEEClsCreateInstance中nSize的奇怪问题

AEEClsCreateInstance函数的功能是用来创建接口类,具体可以见Blog中《开发BREW扩展类》一文。在该函数的实现代码中,有一个奇怪的nSize,他是需要创建的接口类申请空间的大小,但是代码中对于该nSize的赋值十分有意思,对于以下的分析比较合理,贴出来分享一下。

转自:http://expert.imobile.com.cn/bbs

作者:东方欲晓

if(nSize < sizeof(ExtensionCls))

nSize += sizeof(ExtensionCls);
if((pMe = (ExtensionCls *)MALLOC(nSize +
sizeof(VTBL(IExtensionCls)))) == NULL )
return ENOMEMORY;

ExtensionCls是用户定义的class结构,所以创建的内存nsize至少应该等于这个,
if(nSize < sizeof(ExtensionCls))

nSize += sizeof(ExtensionCls);
是进行合理性检查,防止传入的size不合理(小于class的大小),如果不合理则在nsize的基础上加一个完整的class的内存大小,保证能正常创建class(通常传入的nsize就是sizeof(appclass),所以这一句不会走)
pMe = (ExtensionCls *)MALLOC(nSize +
sizeof(VTBL(IExtensionCls))))

进行真正的内存分配,此时除了给class分配内存外,还要为vtbl分配内存,所以分配的大小为nSize +
sizeof(VTBL(IExtensionCls))

这样分配完后,在内存中,class之后就紧接着是一块vtbl区域,接着代码中会为这块vtbl区域初始化,即将其函数指针指向确切的函数地址,最后再将class中的pvt指针指向该vtbl,从而完成了整个class的初始化。

14 gennaio

开发BREW扩展类

看到一篇文章,觉得比较有指导意义,译过来,希望给大家有些帮助,能力有限,有错误的地方还望大家指出来。共同学习,共同进步。

原文地址:
https://brewx.qualcomm.com/bws/content/gi/common/appseng/en/knowledgebase/docs/extensions.htm

1 简介:
扩展类可以扩充BREW的功能,通常来说,扩展类都是以动态的形式存在并且以OTA方式提供下载,然而在某些情况下,OEM制造商会在他们的设备中添加了一些静态的扩展类,例如:OEM制造商经常会给BREW标准接口类IAddrBook添加许多额外的功能,他们通常会创建自己的IAddrBook接口,并且把新增的额外功能提供给开发者。

2 静态 VS 动态
静态扩展类通常是被包含在OEM制造商的SW版本中,不由第三方开发者提供。从另外一个角度看来,动态扩展则是由OTA方式下载。移动应用和它所需要的扩展类是同时被下载到手机上的(除非手机上已经存在了所需的扩展类),通过MIF文件提供的依存列表信息把所需要的扩展类下载到手机当中。每一个动态扩展都会有一个相关联的引用计数,该引用计数负责监视手机上所有应用对于该扩展类的依赖关系,如果引用计数减少到0,则该扩展类将被从手机内存中移除。静态扩展则不是,它没有引用计数,也永远不会被从手机上删除。

3 特性
扩展类不包含权限设置,它的权限级别继承于父应用,例如:如果某应用使用了一个具有文件操作功能的扩展类,那么该应用就是扩展类的父应用,除非父应用的MIF文件中设置了允许文件操作的权限,否则该扩展类的所有文件操作都会失败,这一点对于扩展来开发人员来说十分重要。
与权限级别相似的是,扩展类中所有对于文件的操作都是通过父应用的上下文来执行的,如此来看,对于父应用不可见的文件,扩展类也是无法操作的。
扩展类即可以是保护类型也可以是未保护类型。对于保护类型来说,如果父应用的MIF文件中的依存关系列表中有显示罗列出该扩展类,那么父应用就可以创建并使用它,反之,则不行;对于非保护类型则可以被所有应用使用,不用考虑该应用是否明确声明了依存关系,这样的扩展类通常被加上一个MIME类型的标记,应用只需要通过外壳查找目标MIME类型的句柄,就可以使用它的功能了。

4 MIF文件
扩展类和应用一样需要MIF文件,然而,他们却在根本上有所不同。首先,一个扩展类的MIF文件不需要定义一个applet,只需要在MIF编辑器的"Externsions"标签栏中的"Exported Classes"中设置其class ID即可。保护类型的属性也在该标签栏中设置。
如果一个应用的运行以来与一个扩展类,那么在该应用的MIF文件中必须把扩展类的class ID设置为依赖,如果没有此类依赖关系,该扩展类将不会在下载应用的时候被一同下载,当该应用运行的时候就会失败。如果扩展类是可选的,就可以不用添加依存关系,然而应用必须能够处理扩展类不存在的情况。

5 声明扩展类的结构体
第一步,开发扩展类首先需要声明虚函数表。
typedef struct IDemoExtension IDemoExtension;
AEEINTERFACE(IDemoExtension)
{
        INHERIT_IQueryInterface(IDemoExtension);
        int (*DisplayTime)(IDemoExtension *po);
        int (*DisplayDate)(IDemoExtension *po);
};

第一行声明了一个IDemoExtension的结构体,该结构体是一个apple结构体,将会返回给正在初始化的应用,其中的宏AEEINTERFACE声明了一个扩展类的虚函数表,从上面的声明看出,虚函数表中有5个函数。BREW的每一个接口类都定义了一个类似于上面定义中的宏INHERIT_XXX,用它来把已继承的函数添加到已存在的函数表中,至少,BREW强烈推荐如上例所示,从IQueryInterface派生而来。宏INHERIT_IQueryInterface添加三个函数到函数表中:AddRef, Release, QueryInterface。我们的例子中添加了DisplayTime和DisplayData两个函数。
下一步我们定义宏来索引我们函数表中的函数
#define IDEMOEXTENSION_AddRef(p)
 AEEGETPVTBL((p),IExtensionCls)->AddRef((p))
#define IDEMOEXTENSION_Release(p)
    AEEGETPVTBL((p),IExtensionCls)->Release((p))
#define IDEMOEXTENSION_QueryInterface(p)
    AEEGETPVTBL((p),IExtensionCls)-> QueryInterface((p),(clsid),(pp))
#define IDEMOEXTENSION_DisplayTime(p)
 AEEGETPVTBL((p),IDemoExtension)->DisplayTime((p))
#define IDEMOEXTENSION_DisplayDate(p)
 AEEGETPVTBL((p),IDemoExtension)->DisplayDate((p))
上面的宏可以供父应用来调用扩展类的函数,宏AEEGETPVTBL能准确地指向函数表,并能方便的调用到函数表中的函数,需要注意的是AEEGETPVTBL是同AEEINTERFACE配合起来使用的,我们一旦声明好虚函数表,那么就应该定义扩展类结构体了。

以上代码的第一行把虚函数表插入了扩展类的结构体定义之中,虚函数表必须总是被定义成为结构体的第一个成员,m_nRefs变量也必须在结构体定义中声明,一个扩展类在应用中将会数次被引用,这个变量用来保存在应用中被引用的总次数,AddRef和Release函数是用来对该变量进行增减操作的,一旦m_nRefs变为0,该扩展类就会释放所有占用的资源。结构体中接下来的三个成员是扩展类的可选成员,可以在扩展类中添加任何所需要的成员变量。
6 定义函数
定义的第一个函数必须是AEEClsCreateInstance,REW总是调用该函数来创建该class的接口,下面的代码表明了普遍实现的方法。
int AEEClsCreateInstance(AEECLSID ClsId, IShell *pIShell,
       IModule *po, void **ppObj)
{
   *ppObj = NULL;
   if( ClsId == AEECLSID_MYEXTENSION) {
  return MyExtension_New(sizeof(IDemoExtension), pIShell, po,
           (IModule **)ppObj);
   }
   return EFAILED;
}
我们的函数首先会检查由BREW传入的参数class ID,如果检查通过了,函数就尝试创建和初始化需要的资源。

int MyExtension_New(int16 nSize, IShell *pIShell, IModule* pIModule,
                                                   IModule ** ppMod) {
   IDemoExtension*            pMe = NULL;
   VTBL(IDemoExtension) *     modFuncs;
   if( !ppMod || !pIShell || !pIModule )
      return EFAILED;
   *ppMod = NULL;
   // Allocate memory for the ExtensionCls object
   if( nSize < sizeof(IDemoExtension) )
      nSize += sizeof(IDemoExtension);
   // Allocate the module's struct and initialize it. Note that the
   // modules and apps must not have any static data.
   // Hence, we need to allocate the vtbl as well.
   if( (pMe = (IDemoExtension*)MALLOC(nSize +                                      
                              sizeof(VTBL(IDemoExtension)))) == NULL )
      return ENOMEMORY;
   modFuncs = (VTBL(IDemoExtension)*)((byte *)pMe + nSize);
   //Initialize individual entries in the VTBL
   modFuncs->AddRef              = MyExtension_AddRef;
   modFuncs->Release             = MyExtension_Release;
   modFuncs->QueryInterface      = MyExtension_QueryInterace;
   modFuncs->DisplayTime         = MyExtension_DisplayTime;
   modFuncs->DisplayDate         = MyExtension_DisplayDate;
   // initialize the vtable
   INIT_VTBL(pMe, IModule, *modFuncs);
   // initialize the data members
   pMe->m_nRefs      = 1;
   pMe->m_pIShell    = pIShell;
   pMe->m_pIModule   = pIModule;
   // Add References and get IDisplay
   ISHELL_AddRef(pIShell);
   IMODULE_AddRef(pIModule);
   // Create an instance of the IDisplay
   if( ISHELL_CreateInstance(pIShell, AEECLSID_DISPLAY,
                         (void **)&pMe->m_pIDisplay) != SUCCESS )
      return EFAILED;
   // Set the pointer in the parameter
   *ppMod = (IModule*)pMe;
   return AEE_SUCCESS;
}

AddRef, Release和QueryInterface是从IQI接口继承过来的,AddRef负责增加引用计数的数值。

static uint32 MyExtension_AddRef(IDemoExtension* pMe) {
   // Increment reference count
   return ++(pMe->m_nRefs);
}

Release函数负责减少引用计数的数值,当引用计数的值变为0的时候,该实例就会从内存中被释放掉。

static uint32 MyExtension_Release(IDemoExtension* pMe) {
   // Decrement reference count
   if( --pMe->m_nRefs != 0 )
      return pMe->m_nRefs;

   // Ref count is zero.  Release memory associated with this object.
   if( pMe->m_pIDisplay )
      IDISPLAY_Release(pMe->m_pIDisplay);

   // Release interfaces
   ISHELL_Release(pMe->m_pIShell);
   IMODULE_Release(pMe->m_pIModule);
   //Free the object itself
   FREE_VTBL(pMe, IModule);
   FREE( pMe );
   return 0;
}

我们最后一个需要定义的函数就是QueryInterface,这个函数返回一个与扩展类相关联的接口,IWeb接口就是一个例子,当一个应用使用该接口完成一个网页请求的时候,底层的一个TCP套接字被创建,为了能够直接控制这个套接字,应用可以通过调用IWeb_QueryInterface函数,传递参数AEECLSID_SOCKET(AEECLSID_SOCKPORT在BREW3.x中),来获得这个接口,该函数将返回一个指针,下面是QueryInterface的一个具体实现。

static int MyExtension_QueryInterface(IDemoExtension* me,
                               AEECLSID class, void** ppo) {
   switch (class) {
      case AEECLSID_QUERYINTERFACE:
      case AEECLSID_MYEXTENSION:
      case AEECLSID_BASE:
         *ppo = me;
         MyExtension_AddRef(me);
         return SUCCESS;
      case AEECLSID_DISPLAY:
         *ppo = me->m_pIDisplay;
          return SUCCESS;
      default:
         *ppo = NULL;
         return ECLASSNOTSUPPORT;
   }
}

7 静态扩展类

上面介绍的这个例子也可以用来创建静态扩展类,然而静态扩展类可以作为全局变量,有了这个特性,下面的代码可以为静态扩展类构造虚函数表。

static const AEEVTBL(IDemoExtension) gvtIDemoExtension = {
   MyExtension_AddRef,
   MyExtension_Release,
   MyExtension_QueryInterface,
   MyExtension_DisplayTime,
   MyExtension_DisplayDate,
};

以上的代码被放置在函数之外,通常是在一个头文件中,初始化扩展类可以简单的用一行代码搞定。

pMe->pvt = &gvtIDemoExtension;

上面的代码初始化了整个虚函数表,不必对于虚函数表中每个函数都初始化一次,然而上面的代码仅对静态扩展类是有效的,一个动态扩展类的地址是在运行时确定的(于之不同的是,静态扩展类的地址是在编译时确定的),因此,虚函数表必须在运行时声明。

8 关于宏
强烈推荐在以上例子中所使用到的宏,然而在BREW中还有许多其他的可以用来创建扩展类的宏。其中很多宏被包含近来是为了向后的兼容性,一些宏例如:QINTERFACE, GET_PVTBL和DECLARE_IBASE都是十分令人气馁的,他们应该分别被AEEINTERFACE, AEEGETPVTBL和INHERIT_IQueryInterface替代。

9 使用扩展类
扩展类可以像标准的BREW APIs一样被创建和释放,每个函数都可以通过头文件的宏定义来调用,同BREW APIs一样,在编译调用扩展类函数的应用的时候需要包含其头文件,下面的例子叙述了如何在应用中调用扩展类的函数。

if(ISHELL_CreateInstance(pMe->a.m_pIShell, AEECLSID_MYEXTENSION,
              (void **) (&(pMe->pIDemoExtension))) != SUCCESS ) {  
   return FALSE;
}

IDEMOEXTENSION_DisplayTime(pMe->pIDemoExtension);
if(pMe->pIDemoExtension) {
   IDEMOEXTENSION_Release(pMe->pIDemoExtension);
   pMe->pIDemoExtension = NULL;
}

10 通过MIME类型使用扩展类
扩展类可以把其自身注册为一个MIME类型的句柄,它的注册最好通过MIF编辑器来完成。

上面提及的例子展示了一个扩展类注册为MYTIME句柄,MIME类型的注册允许在运行时解决class ID的问题,应用可以通过ISHELL_GetHandler函数找到一个MIME类型的class ID,整个函数会遍历所有的MIF文件,寻找以MIME类型方式注册的class ID。
下面这个例子阐述了一段能创建和显示多种不同格式文件的代码,在此例中,有一个抽象类IReader,一个扩展类可以扩充它的接口来显示多种格式的文件。

// An Application will call this function when it intends to
// open a file
void DisplayFile(MyApp* pMe, char* mime, char* file) {
   AEECLSID myClass;
   // This call will create a handler for MYPDF, MYDOC, or
   // MYXML depending on mime
   myClass = ISHELL_GetHandler(pMe->pIShell, AEECLSID_APP, mime);
   // Create necessary Interface
   ISHELL_CreateInstance(pMe->pIShell, myClass, &pMe->pIReader);

   // Ishell Createinstance
   // Each extension implements DisplayFile, The virtual
   // table will point to the correct corresponding function
   // call.  We are ignoring error checking
   IREADER_DisplayFile(pMe->pIReader, file);
}

DisplayFile是一个被所有扩展类实现的扩充IReader的接口,虚函数表在创建扩展类的时候被初始化,这样就使IREADER_DisplayFile可以索引正确的函数来显示文件。

11 更多信息
BREW 2.x 察看AEE.h
BREW 3.x 察看AEEInterface.h

 

12 gennaio

关于如何利用AEEINTERFACE和QINTERFACE构造自己的类


--2006.1.11

1、关于AEEINTERFACE。

typedef struct _ISample ISample;

AEEINTERFACE(ISample)
{
    INHERIT_IQueryInterface(ISample);

    // add your fun...
    void (* Fun)(ISample* po);
};

struct ISample
{
    const AEEVTBL(ISample)       *pvt;

    // add your var...
    uint32                      m_uRef;
};

宏INHERIT_IQueryInterface的含义是:从基接口IBase继承它的几个关键函数指针AddRef, Rlease, QueryInterface
这样,就很简单的构造出了一个基于AEEINTERFACE的ISample接口,你可以在Fun函数指针后面添加你所需要的函数指针,在m_uRef后面添加你所需要的成员变量,然后用面向对象的眼睛去观察它,那么他就是个类了,虽然他之前放的是个struct。
还有,如果你比较勤快,或者你想模仿一下BREW平台的那些库函数,又或者你不想让别人知道你到底用了哪些成员,你就可以用宏来定义你的那些成员函数,就像BREW平台那样:
#define  ISAMPLE_Fun(p)  AEEGETPVTBL(p, ISample)->Fun((p))
这样,是不是比较cool,不过也有画蛇添足的功能。

2、关于QINTERFACE
example中mediaplayer的源代码实在是比较经典,所以就拿里头的IWindow作为例子吧

typedef struct _IWindow  IWindow;

QINTERFACE(IWindow)
{
   void     (*Enable)(IWindow * po, boolean bEnable);
   void     (*Redraw)(IWindow * po);
   boolean  (*HandleEvent)(IWindow * po, AEEEvent eCode, uint16 wParam, uint32 dwParam);
   void     (*Delete)(IWindow * po);

   // add your fun in base struct
};

#define INHERIT_CWindow(iname) \
   DECLARE_VTBL(iname) \
   CMediaPlayer * m_pOwner; \
   IShell *       m_pIShell; \
   IDisplay *     m_pIDisplay; \
   flg            m_bActive:1
   // add your var in base struct

struct CWindow
{
   INHERIT_CWindow(IWindow);
};

struct CMainWin
{
   INHERIT_CWindow(IWindow);

   IImage *       m_pLogo;
   AEERect        m_rectLogo;
   IMenuCtl *     m_pMainMenu;
   flg            m_bAbout:1;
   // add your var in sub struct
};

IWindow就是一个结构体,所有成员都是函数指针,因此也可以称它为接口。INHERIT_CWindow这个宏很重要,继承都靠它了。你可以在上面标注的地方,给基类添加你的数据成员和成员函数。那么当数据成员和成员函数合并,就构成为了CWindow基类。很明显CMainWin是从CWindow派生出来的,如果最好只添加数据成员,如果添加成员函数,就会暴露和成员函数同级的数据成员,那么封装性就会降低了。

如果你还是想模仿BREW平台,那么可以这么写:
#define IWINDOW_HandleEvent(p, e, w, dw)  GET_PVTBL(p, IWindow)->HandleEvent(p, e, w, dw)

在这个应用里头,注意两个宏:DECLARE_VTBL和GET_PVTBL。
还可以注意一下IWINDOW_HandleEvent这个函数,对于不同的CWindow,就对应不同的XXX_HandleEvent,那么不同的窗体就会体现不同的行为。

大概也就这么多,不过到底是C语言的模拟,很难达到C++面向对象那么完善,不过对于简单的手机应用开发,已经能够胜任了,如果还想研究更加深入的东西,可以读一读高通平台的内部代码。

 

07 gennaio

#pragma 预处理指令详解

      看《COM技术内幕》,看到一个关于提到objbase.h的文件,于是上里头找找看,有没有些有价值的东西,可是一看发现,写了几个月的c了,还有关键字不认识的,它就是#pragma,上网找了篇资料,贴出来,以后查也方便。

 

      在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。 
其格式一般为:    #Pragma Para 
    其中Para 为参数,下面来看一些常用的参数。 

    (1)message 参数 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗 
口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为: 
       #Pragma message(“消息文本”) 
       当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。 
    当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法 
       #ifdef _X86 
       #Pragma message(“_X86 macro activated!”) 
       #endif 
       当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_ 
X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了 
。 
   
   (2)另一个使用得比较多的pragma参数是code_seg。格式如: 
      #pragma code_seg( ["section-name"[,"section-class"] ] ) 
      它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。 

   (3) #pragma once (比较常用) 
      只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。 
   
   (4) #pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。  
     有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。  
   
   (5) #pragma resource "*.dfm"表示把*.dfm文件中的资源加入工程。 *.dfm中包括窗体 
外观的定义。  
    
   (6) #pragma warning( disable : 4507 34; once : 4385; error : 164 ) 
      等价于: 
      #pragma warning(disable:4507 34)  // 不显示4507和34号警告信息 
      #pragma warning(once:4385)        // 4385号警告信息仅报告一次 
      #pragma warning(error:164)        // 把164号警告信息作为一个错误。 
      同时这个pragma warning 也支持如下格式: 
      #pragma warning( push [ ,n ] ) 
      #pragma warning( pop ) 
      这里n代表一个警告等级(1---4)。 
      #pragma warning( push )保存所有警告信息的现有的警告状态。 
      #pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告 
等级设定为n。  
      #pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的 
一切改动取消。例如: 
      #pragma warning( push ) 
      #pragma warning( disable : 4705 ) 
      #pragma warning( disable : 4706 ) 
      #pragma warning( disable : 4707 ) 
      //....... 
      #pragma warning( pop )  
      在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。 
    (7)pragma comment(...)   //objbase.h里头用的最多的就是这个用来导入COM的一些基础库
       该指令将一个注释记录放入一个对象文件或可执行文件中。 
      常用的lib关键字,可以帮我们连入一个库文件。 
 
 
每个编译程序可以用#pragma指令激活或终止该编译程序支持的一些编译功能。例如,对循环优化功能: 
#pragma loop_opt(on)      // 激活 
#pragma loop_opt(off)  // 终止 
有时,程序中会有些函数会使编译器发出你熟知而想忽略的警告,如“Parameter xxx is never used in function xxx”,可以这样: 
#pragma warn —100      // Turn off the warning message for warning #100 
int insert_record(REC *r) 
{ /* function body */ } 
#pragma warn +100            // Turn the warning message for warning #100 back on 
函数会产生一条有唯一特征码100的警告信息,如此可暂时终止该警告。 
每个编译器对#pragma的实现不同,在一个编译器中有效在别的编译器中几乎无效。可从编译器的文档中查看。 

03 gennaio

在BREW平台上读文件的速度远大于写文件的速度

没有在实际的手机上,只是在模拟器上头测试,不过听说在手机上写文件的效率也不够高。
测试环境:BRWE SDK3.1.4
代码:
uint32 lasttime;
uint32 curtime;
uint32 timespan;
//invoke IFILE_SetCacheSize()
{
        uint32 uRtr;
        uRtr = IFILE_SetCacheSize(pIFile, SCS_MAX);
}
lasttime = GETTIMEMS();      //返回当前时间(毫秒)
filesize = IFILE_Write(pIFile, content, length);
curtime = GETTIMEMS();      //返回当前时间(毫秒)
timespan = curtime - lasttime;
大概就是创建ifilemgr和ifile,然后从一个527,660 字节的文本文件中,读取出数据,再写到另外一个文件中。
IFILE_SetCacheSize函数用于设置文件高速缓存操作的大小。 这样便允许调用程序选择缓冲区文件访问权,以提高性能。可是对于这个函数的调用似乎并没有起到太大的作用。IFILE_SetCacheSize函数的返回值是153600,表示高速缓存的大小。
如果调用了IFILE_SetCacheSize函数,则timespan第一次为26593,第二次为26609
如果不调用该函数,timespan的值一直是26609
发现这个函数对于提高性能没有太大的作用。

阅读mediaplayer源代码(first)

第一次阅读mediaplayer源代码
 
2006-1-3
 
mediaplayer源代码是BREW SDK 3.1.4中自带的,一个简单的多媒体应用,2000多行代码,实现了一个支持音频、视频和图片播放,还支持录音的程序。该程序出自高通公司内部开发人员之手,其与高通BREW平台的其他底层应用的实现有异曲同工之妙。该程序的实现充分利用了贯穿于BREW平台的QInterface宏,用C语言巧妙地模仿了面向对象中的多态,继承等特点。感兴趣的朋友,可以在高通的网站上下载最新的SDK安装后,在BREW 3.1.4\sdk\examples\mediaplayer中可以找到对应的源代码。
由于是第一次阅读,很多东西理解还不深刻,叙述难免有偏差,希望大虾们人过留言,帮忙指出错误。
/////////////////////////////////////////////////////////////////////
下面是一些结构体的展开,从阅读代码的角度来说,莫名其妙的宏定义给阅读带来了不少麻烦,但是对于程序开发人员来说,精致的宏定义又给开发带来不小的渐变性,从阅读的角度来说,把宏展开,可以清晰地看到程序设计的脉络,不过在开发的过程中,可以直接使用宏,减少代码的输入量,同时也能保证伴随BREW平台升级所带来的兼容性问题。
原定义
typedef struct _IWindow  IWindow;
QINTERFACE(IWindow)
{
   void     (*Enable)(IWindow * po, boolean bEnable);
   void     (*Redraw)(IWindow * po);
   boolean  (*HandleEvent)(IWindow * po, AEEEvent eCode, uint16 wParam, uint32 dwParam);
   void     (*Delete)(IWindow * po);
};
展开后实际定义
typedef struct _IWindow IWindow;
struct _IWindow

 struct IWindowVtbl *pvt;
};
typedef struct IWindowVtbl IWindowVtbl;
struct IWindowVtbl
{
    void     (*Enable)(IWindow * po, boolean bEnable);
   void     (*Redraw)(IWindow * po);
     boolean  (*HandleEvent)(IWindow * po, AEEEvent eCode, uint16 wParam, uint32 dwParam);
    void     (*Delete)(IWindow * po);
};
//注意,其实以下的两者是一样的。
#define VTBL(iname)   iname##Vtbl
#define AEEVTBL(iname)  iname##Vtbl
 
/////////////////////////////////////////////////////////////////////
定义了枚举类型MPWindow(程序窗口类型)和MPPlayerWin(播放窗口类型:play,record,image)

/////////////////////////////////////////////////////////////////////
关于内部几个关键结构体的创建。
#define INHERIT_CWindow(iname) \
   DECLARE_VTBL(iname) \
   CMediaPlayer * m_pOwner; \
   IShell *       m_pIShell; \
   IDisplay *     m_pIDisplay; \
   flg            m_bActive:1
从命名的角度来看,主要是用来继承父类,虽然是C不过仍然用C++的思想来设计。CWindow是一个基类,其他所有显示的window都是根据这个基类派生出来的。用INHERIT_CWindow这个宏来完成派生功能。
// Base class of all IWindow objects.
struct CWindow
{
   IWindow vtIWindow;
   CMediaPlayer * m_pOwner;
   IShell *       m_pIShell;
   IDisplay *     m_pIDisplay;
   flg            m_bActive:1;
};
对应创建函数CWindow_New
// Main window: Displays main menu.
struct CMainWin
{
   //从这个地方,我们就简单的认为,CMainWin是从CWindow派生出来的,它继承了CWindow的所有数据成员和IWindow的函数指针
   IWindow vtIWindow;
   CMediaPlayer * m_pOwner;
   IShell *       m_pIShell;
   IDisplay *     m_pIDisplay;
   flg            m_bActive:1;
 
   IImage *       m_pLogo;
   AEERect        m_rectLogo;
   IMenuCtl *     m_pMainMenu;

   flg            m_bAbout:1;  //?为什么会有两个flg,如何使两者不冲突
};

对应创建函数CMainWin_New
同样的道理适用于:CFileListWin(对应创建函数CFileListWin_New)、CPlayerWin(对应创建函数CPlayerWin_New)
CProgCtl这个结构体是表示进程条和标题。
子类的创建函数xxx_new(),会调用父类的创建函数CWindow_New,先创建CWindow,然后再实例化其自身的成员。
/////////////////////////////////////////////////////////////////////
关于消息传递,大概是这样,消息产生,系统调用CMediaPlayer_HandleEvent来处理消息,对于一些可以被处理的消息:EVT_APP_START、EVT_APP_BROWSE_FILE、EVT_APP_STOP、EVT_APP_SUSPEND、EVT_APP_RESUME,函数会调用相关的函数相应消息,对于一些在本函数内无法被处理的消息:EVT_KEY、EVT_COMMAND、EVT_CREATEMEDIA、EVT_CREATEMEDIA_QCP、EVT_COPYRIGHT_END则会调用IWINDOW_HandleEvent来处理消息。
而IWINDOW_HandleEvent只是个宏,实际上它是:
GET_PVTBL(p, IWindow)->HandleEvent(p, e, w, dw)
实际上就是:
((IWindow *)p)->pvt->HandleEvent(p, e, w, dw)
对应的p指针所指向的地址的不同,消息会分发的不同的HandleEvent函数里头,作相应的处理,然后返回TRUE
/////////////////////////////////////////////////////////////////////
HandleEvent函数指针的初始化,对于父类CWindow,其Vtbl的初始化是从CWindow_New函数的第三个参数传递进来的,对于CWindow他无法选择自己的Vtbl。而对于其子类和子类对应的XXX_New函数,首先它会申请一个 VTBL(IWindow)的vtbl,然后用MP_IWINDOW_SETVTBL宏给这个vtbl初始化,然后调用CWindow_New,把整个vtbl当成第三个参数传递进去,那么对应于不同的子类,虽然都调用的是IWindow->HandleEvent函数,不过却有了不同的行为,这就是对面向对象多态的精致模仿。
 
02 gennaio

读《基于COM思想实现AEEINTERFACE》有感

(备注:此文提到的AEEINTERFACE是跟BREW平台相关的)

 

仔细阅读了Qinix的《基于COM思想实现AEEINTERFACE》,彻底被作者折服,作者用极其简单的方法给大家阐述了AEEINTERFACE的实现方式。
不过由于作者在文中,及其文后附带的代码中,对于BREW平台所自带的宏,没有展开,给阅读代码带来了一定困难,于是我把它们展开,在这个过程中,又遇到了一些问题,经过仔细思考,得到一些不成熟的答案,拿出来跟大家分享、讨论一下。

BREW平台里头有一些比较复杂的宏,关于文中所遇到的宏,列表如下:

#define AEEINTERFACE(iname) \
           typedef struct AEEVTBL(iname) AEEVTBL(iname); \
           struct AEEVTBL(iname)

#define AEEVTBL(iname) iname##Vtbl

#define INHERIT_IQueryInterface(iname) \
   INHERIT_IBase(iname); \
   int (*QueryInterface)(iname *, AEECLSID, void **)

#define INHERIT_IBase(iname) \
  uint32  (*AddRef)         (iname*);\
  uint32  (*Release)        (iname*)


因此,关于IICAT的定义,可以分解为:
AEEINTERFACE(IICat)
{
    INHERIT_IQueryInterface(IICat);
    void (*IgnoreMaster)(IICat* po);
};

-->>

typedef struct IICatVtbl IICatVtbl;
struct IICatVtbl
{
 /** INHERIT_IQueryInterface **/

 /** INHERIT_IBase **/
 uint32  (*AddRef) (IICat*);
 uint32  (*Release) (IICat*);
 /** end-INHERIT_IBase **/

 int (*QueryInterface)(IICat *, AEECLSID, void **)
 /** end-INHERIT_IQueryInterface **/

    void (*IgnoreMaster)(IICat* po);
};

关于CICat的定义,可以分解为:
struct CICat
{
    const AEEVTBL(IICat)       *pvt;
    uint32                      m_uRef;
    IShell                     *m_pIShell;  
    ICat*                       m_pCat;             
};

-->>

struct CICat
{
    const IICatVtbl  *pvt;
    uint32                      m_uRef;
    IShell                      *m_pIShell;  
    ICat*                       m_pCat;             
};

关于接口函数:
#define AEEGETPVTBL(p,iname)  (*((AEEVTBL(iname) **)((void *)p)))

#define IICAT_AddRef(p)                                   AEEGETPVTBL(p,IICat)->AddRef((p))
#define IICAT_Release(p)                                  AEEGETPVTBL(p,IICat)->Release((p))
#define IICAT_QueryInterface(p,i,o)                       AEEGETPVTBL(p,IICat)->QueryInterface((p),(i),(o))
#define IICAT_IgnoreMaster(p)                             AEEGETPVTBL(p,IICat)->IgnoreMaster((p)) 

例如:IICAT_AddRef(IICAT)
就是 (*((IICATVtbl **)((void *)IICAT)))->AddRef((IICAT))

关键的几个结构体:CCAT, CICat

当程序运行的时候,ISHELL_CreateInstance会根据CLSID调用IICat_New函数,该函数创建CICat结构体对象,在创建的过程中,首先创建类CCAT的实例,指针赋值给m_pCat,然后会给pvt指针赋值(也就是那个static const gvtIICat),最后分别给m_uRef和m_pIShell赋值。

通过以上的步骤,你分别拥有了CCAT和CICat的实例,那么你就可以通过调用那些以IICAT_开头的函数来完成你需要的功能了,不过接口函数中肯定都回有CICat的实例作为参数,因为你实际调用就是CICat结构体中的函数。

比如说你调用IICAT_IgnoreMaster(p)函数,它实际上是调用的CICat->IgnoreMaster(p),它又实际上是调用的CCAT->IgnoreMaster(p)。

在阅读的时候遇到过两个疑问,现在得到一些不成熟的解释:

1、文中对类、结构体都进行了精巧的设计,那么可以说,这是C和C++混合在一起的,开始一直不明白,这样的代码,如何在纯C的环境下编译通过,因为C是不支持C++的么,后来想通了,正是由于BREW平台是基于COM机制的封装,对于最终客户来说,他所能接触到的就是以下这些函数:IICAT_AddRef、IICAT_Release、IICAT_QueryInterface、IICAT_IgnoreMaster、IICat_New。他们只需要调用这些函数,来完成自己的功能,而无须理解其中的实现,无须控制其中的成员变量,而这样些实现,都是编译成二进制代码(比如dll文件),不需要开发人员再次编译。这样就充分体现了COM机制的优越性。

2、在研读的过程中,发现了一个这样的语句
typedef struct _IICat IICat;

觉得很奇怪,作者并没有给出IICat的具体实现,但是IICat却实实在在的存在于代码中间。经过仔细察看,发现原来那些函数把IICat作为传入参数,可是在函数中,对于这个参数的处理,使强制转换为CICat类型,这样,才能去访问CICat中IICatVtbl的那些函数指针。
这么看来,其实IICat是个假的,什么都不是的结构体,他只是CICat的代言人而已,真正干活的是CICat。(感觉有点《鼠胆龙威》里头的张学友和李连杰,呵呵)。

开始一直不明白,大家都提到对于IICatVtbl在结构体中定义的位置,必须放在所有结构体成员的第一位,不然,就无法定位到IICatVtbl中的函数指针。现在,我猜是,由于从IICat转换到CICat,经过了类型转换,其实两者并不是一个类型,但是由于IICatVtbl位于结构体定义的第一位,那么IICat指针所指向的位置也就是IICatVtbl中指针所指向的位置,这样就能保证顺利的访问IICatVtbl中的函数指针。

非常感谢Qinix 斑竹写出了这么好的文章,让大家能从中学习到不少知识,对于文章的理解,还是不够透彻,如果有说得不对的地方,希望大家指正。

关于静态变量与局部变量

我们如果正常定一个函数


void fun(void)
{
 int tmp;

 // same operation
 // ...
}

 

如果我们调用fun函数,那么程序会在动态数据区的栈里头为局部变量申请空间,然后执行相关的代码,进行操作。
当fun函数执行完毕,tmp变量就会出栈,不复存在。

但是如果我们进行如下的操作:

 

int* global_tmp = NULL;

void fun()
{
 int tmp;

 tmp = 1;
 printf("tmp addr is : 0x%08x\n", &tmp);

 if (NULL == global_tmp) 
 {
  global_tmp = &tmp;
 } else
 {
  (*global_tmp)++;
 }

 printf("global_tmp addr is : 0x%08x\n", global_tmp);
 printf("global_tmp addr'addr is : 0x%08x\n", &global_tmp);
}

 

多次调用fun函数,

我发现,存放global_tmp指针的地址,是在编译的时候就确定的,在静态/全局数据区,如果你在fun函数中,给global_tmp赋值,那么global_tmp会指向堆中的一个地址,那么他会一直保存这个地址,直至程序结束退出。但是实际对于fun函数,每次调用,里头的变量都回在栈中分配空间,而随着fun函数的退出,这些空间也会被释放,那么实际上global_tmp就指向了一个未知的值。

所以,对于global_tmp这样的全局指针,所以指向的地址,应该是在堆空间中。

22 dicembre

C专家编程读书笔记(2)

C专家编程读书笔记(2)
2005.12.19
 
1、早用lint,勤用lint,不要等到最后才用lint。lint是软件的道德标准
 
2、关于typedef。
先看一个声明:void (*signal(int sig, void (*func)(int))) (int);
对于它,可以简化为:
typedef void (*ptr_to_func) (int)
ptr_to_func signal(int, ptr_to_func)
对于像以上那个复杂的typedef声明,你大可不必深入的去记忆、研究,只需要把它替代,化简为一个声明,那意义就豁然开朗了。
注意:
①不要在一个typedef中放入几个声明器;
②千万不要把typedef嵌到声明中间部分。
typedef与define的区别:
①可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做。
②在连续声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证。
 
3、数组与指针并不相同,某些情况下,他俩是一样的,不过也存在情况,他俩不一样例如:
文件1: int mango[100];
文件2: extern int * mango;
这是不同的,相当于把整数和浮点数混为一谈。
 
4、Turning实验,人工智能,人机对话,都是十分有意思的东西。
 
5、堆区域用于动态分配的存储,也就是通过malloc(内存分配)函数获得的内存,并通过指针访问。堆中所有东西都是匿名的————不能按名字直接访问,只能通过指针间接访问。
被分配的内存总是经过对齐,以适合及其最大尺寸的原子访问。
堆的末端由一个称为break的指针来标识。当堆管理器需要更多内存时,它可以通过系统调用brk和sbrk来移动指针。一般情况下,不必自己显示调用brk,如果分配的内存容量很大,brk最终会被自动调用。
 
19 dicembre

C专家编程读书笔记(1)

C专家编程读书笔记(1)    2005.12.19
 
1、尽量不要在你的代码中使用无符号类型,以免增加不比要的复杂性。尤其是不要仅仅因为无符号书不存在负值(如年龄、国债)而用它来表示数量。尽量使用int那样的有符号类型,这样在涉及升级混合类型的复杂细节时,不必担心边界情况(如-1被翻译为非常大的正数)。只有在使用位段和二进制掩码时,才可以用无符号数,应该在表达式中使用强制类型转换,使操作数均为有符号或者无符号数。
 
2、这也是为什么C++ 语言令人失望的原因:它对C语言中存在的一些最基本的问题没有什么改进,而它对C语言最重要的扩展(类)却是建立在脆弱的C类型模型上。
 
3、看一段代码:
int main(void)
{
    int pa=0;
    char * avarsc[] = 
    {
        "color monitor",
        "big disk",
        "Cray"
        "on-line drawing routhines",
        "mouse",
        "keyboard",
        "power cables",
    };
    char ** pp;
    pp = avarsc;
 
    printf("%s\n", avarsc[2]); // output Crayon-line drawing routhines
    printf("%s\n", *(pp++)); // output big disk
    scanf("%d", pa);
    return 1;
}
注意字符串数组的定义,最后那个逗号,还有"Cray"后头没有逗号其实avarsc是个字符指针的指针
曾经写过一个这样错误的代码,要为一个字符串: "" 申请空间,应该是STRLEN("\"\"")却写成了STRLEN(""""),编译没有错误,也就没有注意,在后来走查代码的时候,发现了错误的地方,多亏为它多申请了不少空间,不然这又是个难以察觉的内存错误。
 
4、全局变量由C编译程序在动态区之外的固定存储区域中存储。当程序中多个函数都使用同一数据时,全局变量将是很有效的。然而,由于三种原因,应避免使用不必要的全局变量:
①不论是否需要,它们在整个程序执行期间均占有存储空间。
②由于全局变量必须依靠外部定义,所以在使用局部变量就可以达到其功能时使用了全局变量,将降低函数的通用性,这是因为它要依赖其本身之外的东西。
③大量使用全局变量时,不可知的和不需要的副作用将可能导致程序错误。如在编制大型程序时有一个重要的问题:变量值都有可能在程序其它地点偶然改变。
 
5、在编译时分配存储空间的变量称为静态存储变量,定义的静态存储变量无论是做全程量或是局部变量,其定义和初始化在程序编译时进行。作为局部变量,调用函数结束时,静态存储变量不消失并且保留原值。
补充:对于静态全局变量,主要是为了保证唯一性。
补充:关于static的三点正确见解
A、若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
B、若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
C、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
20 ottobre

讨厌BREW

      简单的说,BREW就是一个应用运行环境,安装和支持BREW的手机,用户可以实现下载游戏、应用软件、GPS定位、无线购物等几十种数据服务。

      早就在网上听闻BREW的恶名,今日尝试,果然名不虚传。

      回调机制。简直无法忍受BREW的回调机制,每次跟踪程序,总是会跳到莫名其妙的地方,自己写的还算好,起码能找到,如果是改别人的程序,简直是不可能的,说不定哪个地方就跳到一个神奇的、你想都想不到的地方去了。每次面对VC蹦出来的弹出框,我都想把电脑砸了。

      有了回调机制,于是函数指针开始满天飞。注册一个回调函数,说不定就蹦到哪个文件,哪个旮旯地方,一副超级不友好的界面。

      承认,是我C基础不好,还是特怀念过去J2ME的时代。确实,J2ME慢,不过现在手机上的破CPU上,也不用指望其他程序能跑多快,而且,快慢也不关我的事,让用户去忍受去吧。

     

 
15 ottobre

Linux中Software RAID的实现和性能分析-2

(8)  制作完RAID磁盘分区后,由于做成一个RAID分区的两个物理磁盘分区大小不可能完全一样,一般都会偏小,因此要重新调整文件系统的大小。以/dev/md0为例,需要执行以下命令:

(rescue)#  e2fsck  -f  /dev/md0

(rescue)#  resize2fs  /dev/md0

对其他的RAID磁盘分区也执行同样的命令,然后你就可以重新启动机器了,如果顺利的话,机器就运行在RAID1状态下了。

(9)  收尾工作,你可能希望机器可以从任何一快硬盘启动,因为如果主硬盘出故障,只需要重新启动,就可以继续提供服务,因此,你必须在辅硬盘上把启动程序grub重新安装一遍。

三 分析 1 安全性分析

在使用了RAID磁盘分区后,只要是对该类分区进行的数据写操作,数据会被写到两块硬盘的对应分区上,两块硬盘保持同步更新。这样就保证了数据的安全性,也无需经常备份数据了。制作过程中使用的是一块空余硬盘,RAID1的实现是系统提供的软件方式,节省了RAID卡的开销,以最小的成本实现了我们的目标。

2 效率分析

在选择RAID1方式的时候,一直担心它的效率问题,RAID1只提供数据冗余备份,RAID0能提供较高的数据访问速度,如果选择实现RAID0+1的话,至少需要4块硬盘。成本将大大提高。在制作完RAID1后,马上进行了磁盘读些测验,分析性能。

进行磁盘性能测验是使用Linux自带的工具hdparm。该命令的测试原理是往磁盘分区上反复读写大量数据,并计算时间,以此来测试磁盘性能。在系统下执行该命令,则可以得到硬盘数据读写的测验结果。实验数据见表5:

 

[root@panda ~]# hdparm -Tt /dev/sda1

 

/dev/sda1:

 Timing cached reads:   696 MB in  2.01 seconds = 346.67 MB/sec

 Timing buffered disk reads:  154 MB in  3.01 seconds =  51.10 MB/sec

[root@panda ~]# hdparm -Tt /dev/sdb1

 

/dev/sdb1:

 Timing cached reads:   796 MB in  2.01 seconds = 396.28 MB/sec

 Timing buffered disk reads:  146 MB in  3.03 seconds =  48.21 MB/sec

[root@panda ~]# hdparm -Tt /dev/md0

 

/dev/md0:

 Timing cached reads:   824 MB in  2.01 seconds = 410.22 MB/sec

 Timing buffered disk reads:  130 MB in  3.00 seconds =  43.31 MB/sec

表5 硬盘性能测验数据

从上表的实验数据看出,第一行数据是对主硬盘性能测验的数据,从硬盘cache读写数据的速度是346.67 MB/sec,从硬盘盘面上读写数据的速度是51.10 MB/sec;第二行是对辅硬盘性能测验的数据,从硬盘cache读写数据的速度是396.28 MB/sec,从硬盘盘面上读写数据的速度是48.21 MB/sec;第三行是对RAID磁盘分区性能测验的数据,从cache读写数据的速度是410.22 MB/sec,从盘面读写数据的速度是43.31 MB/sec。可以看出从cache读写数据的速度有所提高,从盘面读写数据的速度有所下降。总的来说,差距不是太大,在可以接受的范围内。