| Profilo di jiyu人生路漫漫,快乐先行FotoBlogElenchi | Guida |
|
|
14 maggio 嵌入式程序员应知道的基本问题-C语言(zz)来源:21ICbbs 作者:lhf C语言测试:想成为嵌入式程序员应知道的0x10个基本问题 其中少量灰色的文字是我添加的,表达一些我的看法,很不成熟,希望朋友们指正。 C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为带面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。
1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
这个测试是为下面的目的而设的: least = MIN(*p++, b);
while(1) do{ ... } while (1) 一些程序员更喜欢如下方案: for(;;)
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。 Loop:
数据声明(Data declarations) 5. 用变量a给出下面的定义 答案是: typedef int (*PFUNCTION)(int) PFUNCTION pfun; typedef int (*PFUNCTION)(int) PFUNCTION pfun[10];
7.关键字const有什么含意? const int a; /******/
8. 关键字volatile有什么含意?并给出三个不同的例子。 下面是答案:
long square(volatile int *ptr) 位操作(Bit manipulation) 9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
void set_bit3(void) { 一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。
10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。 int *ptr; A more obscure approach is: *(int * const)(0x67a9) = 0xaa55; 即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。 中断(Interrupts) 11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。 __interrupt double compute_area (double radius) 这个函数有太多的错误了,以至让人不知从何说起了:
12 . 下面的代码输出是什么,为什么? void foo(void) 13. 评价下面的代码片断: unsigned int zero = 0; 对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下: unsigned int compzero = ~0; 这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。 动态内存分配(Dynamic memory allocation) char *ptr; 所以在这个时候,一定要使用pc-lint来检察代码规范性,起码pc-lint能检查出if-else语句格式不对,要求开发人员对格式进行修改,那样的话,看起来就方便多了。 这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是“Got a valid pointer”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。
#define dPS struct s * 以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么? dPS p1,p2; 第一个扩展为 struct s * p1, p2; . 晦涩的语法 16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么? int a = 5, b = 7, c; 这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成: c = a++ + b; 因此, 这段代码持行后a = 6, b = 7, c = 12。 让你明白什么是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网既有严格的数学表述方式,也有直观的图形表达方式,既有丰富的系统描述手段和系统行为分析技术,又为计算机科学提供坚实的概念基础。
如果一个变迁的每个输入库所(input place)都拥有令牌,该变迁即为被允许(enable)。一个变迁被允许时,变迁将发生(fire),输入库所(input place)的令牌被消耗,同时为输出库所(output place)产生令牌。
一个经典的Petri网由四元组(库所,变迁,输入函数,输出函数)组成。任何图都可以映射到这样一个四元组上,反之亦然。 一个流程的状态是由在场所中的令牌建模的,状态的变迁是由变迁建模的。令牌表示事物(人,货物,机器),信息,条件,或对象的状态; 库所代表库所,通道或地理位置;变迁代表事件,转化或传输。 一个流程有当前状态,可达状态,不可达状态。
为了解决经典Petri网中的问题,研究出了高级Petri网,在以下方面进行了扩展:
Petri网的应用非常广泛,以下是Petri网比较常用的几种应用:
彩信之我见-- 2006.4.28 By A.TNG
爱立信、诺基亚、摩托罗拉、西门子、阿尔卡特、朗讯、北电、华为和中兴,哪家不是世界巨头,哪家不令各大学高材生趋之若鹜。她们都是属于电信行业。因此,就算是进了排名最后的中兴,“挨起踢”来,都让人觉得舒服,或者说,人家放个屁都是香的。 完了,跑题了。最后跑一句:珍爱生命,远离中·华·。 2、关于彩信。 还有一点,就是觉得彩信组的人都是大牛,人也特别好,感觉好亲切:) 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、个人感觉 个人感觉,邮件这个东西,发展到现在,很多方面都很完善了,ESMTP、POP3、IMAP4和MIME这些协议都有比较稳定的代码支撑着,可是邮件和手机结合的同时,要充分考虑手机的特点,手机在控制性和显示方面都有天生的缺陷,因此在显示邮件时,如何充分的利用MIME协议的特点,并且最大限度的满足用户的需求,才是难点。其实显示邮件方面Lotus Notes感觉是做得最好的,而OE和Foxmail要差一些。 22 gennaio 对AEECallback结构体及其基础函数的分析----2006.1.22 typedef struct _AEECallback AEECallback; AEECallback_Setup函数: static void AEECallback_Setup(AEECallback *pcb, PFNCBCANCEL pfnCancel, AEECallback_Fire函数: static void AEECallback_Fire(void *pvCxt) pcb->pfnCancel = 0; AEECallback_CancelNotify函数: static void AEECallback_CancelNotify(AEECallback *pcb) pcb->pfnCancel = 0; AEECallback_SchedNotifyWait函数: void AEECallback_SchedNotifyWait(AEECallback *pcb, void *pSchedNotifyObj, 16 gennaio 关于AEEClsCreateInstance中nSize的奇怪问题AEEClsCreateInstance函数的功能是用来创建接口类,具体可以见Blog中《开发BREW扩展类》一文。在该函数的实现代码中,有一个奇怪的nSize,他是需要创建的接口类申请空间的大小,但是代码中对于该nSize的赋值十分有意思,对于以下的分析比较合理,贴出来分享一下。 转自:http://expert.imobile.com.cn/bbs 作者:东方欲晓 if(nSize < sizeof(ExtensionCls)) nSize += sizeof(ExtensionCls); ExtensionCls是用户定义的class结构,所以创建的内存nsize至少应该等于这个, nSize += sizeof(ExtensionCls); 进行真正的内存分配,此时除了给class分配内存外,还要为vtbl分配内存,所以分配的大小为nSize + 这样分配完后,在内存中,class之后就紧接着是一块vtbl区域,接着代码中会为这块vtbl区域初始化,即将其函数指针指向确切的函数地址,最后再将class中的pvt指针指向该vtbl,从而完成了整个class的初始化。 14 gennaio 开发BREW扩展类看到一篇文章,觉得比较有指导意义,译过来,希望给大家有些帮助,能力有限,有错误的地方还望大家指出来。共同学习,共同进步。 原文地址: 1 简介: 2 静态 VS 动态 3 特性 4 MIF文件 5 声明扩展类的结构体 第一行声明了一个IDemoExtension的结构体,该结构体是一个apple结构体,将会返回给正在初始化的应用,其中的宏AEEINTERFACE声明了一个扩展类的虚函数表,从上面的声明看出,虚函数表中有5个函数。BREW的每一个接口类都定义了一个类似于上面定义中的宏INHERIT_XXX,用它来把已继承的函数添加到已存在的函数表中,至少,BREW强烈推荐如上例所示,从IQueryInterface派生而来。宏INHERIT_IQueryInterface添加三个函数到函数表中:AddRef, Release, QueryInterface。我们的例子中添加了DisplayTime和DisplayData两个函数。 以上代码的第一行把虚函数表插入了扩展类的结构体定义之中,虚函数表必须总是被定义成为结构体的第一个成员,m_nRefs变量也必须在结构体定义中声明,一个扩展类在应用中将会数次被引用,这个变量用来保存在应用中被引用的总次数,AddRef和Release函数是用来对该变量进行增减操作的,一旦m_nRefs变为0,该扩展类就会释放所有占用的资源。结构体中接下来的三个成员是扩展类的可选成员,可以在扩展类中添加任何所需要的成员变量。 int MyExtension_New(int16 nSize, IShell *pIShell, IModule* pIModule, AddRef, Release和QueryInterface是从IQI接口继承过来的,AddRef负责增加引用计数的数值。 static uint32 MyExtension_AddRef(IDemoExtension* pMe) { Release函数负责减少引用计数的数值,当引用计数的值变为0的时候,该实例就会从内存中被释放掉。 static uint32 MyExtension_Release(IDemoExtension* pMe) { // Ref count is zero. Release memory associated with this object. // Release interfaces 我们最后一个需要定义的函数就是QueryInterface,这个函数返回一个与扩展类相关联的接口,IWeb接口就是一个例子,当一个应用使用该接口完成一个网页请求的时候,底层的一个TCP套接字被创建,为了能够直接控制这个套接字,应用可以通过调用IWeb_QueryInterface函数,传递参数AEECLSID_SOCKET(AEECLSID_SOCKPORT在BREW3.x中),来获得这个接口,该函数将返回一个指针,下面是QueryInterface的一个具体实现。 static int MyExtension_QueryInterface(IDemoExtension* me, 7 静态扩展类 上面介绍的这个例子也可以用来创建静态扩展类,然而静态扩展类可以作为全局变量,有了这个特性,下面的代码可以为静态扩展类构造虚函数表。 static const AEEVTBL(IDemoExtension) gvtIDemoExtension = { 以上的代码被放置在函数之外,通常是在一个头文件中,初始化扩展类可以简单的用一行代码搞定。 pMe->pvt = &gvtIDemoExtension; 上面的代码初始化了整个虚函数表,不必对于虚函数表中每个函数都初始化一次,然而上面的代码仅对静态扩展类是有效的,一个动态扩展类的地址是在运行时确定的(于之不同的是,静态扩展类的地址是在编译时确定的),因此,虚函数表必须在运行时声明。 8 关于宏 9 使用扩展类 if(ISHELL_CreateInstance(pMe->a.m_pIShell, AEECLSID_MYEXTENSION, IDEMOEXTENSION_DisplayTime(pMe->pIDemoExtension); 10 通过MIME类型使用扩展类 上面提及的例子展示了一个扩展类注册为MYTIME句柄,MIME类型的注册允许在运行时解决class ID的问题,应用可以通过ISHELL_GetHandler函数找到一个MIME类型的class ID,整个函数会遍历所有的MIF文件,寻找以MIME类型方式注册的class ID。 // An Application will call this function when it intends to // Ishell Createinstance DisplayFile是一个被所有扩展类实现的扩充IReader的接口,虚函数表在创建扩展类的时候被初始化,这样就使IREADER_DisplayFile可以索引正确的函数来显示文件。 11 更多信息
12 gennaio 关于如何利用AEEINTERFACE和QINTERFACE构造自己的类
1、关于AEEINTERFACE。 typedef struct _ISample ISample; AEEINTERFACE(ISample) // add your fun... struct ISample // add your var... 宏INHERIT_IQueryInterface的含义是:从基接口IBase继承它的几个关键函数指针AddRef, Rlease, QueryInterface 2、关于QINTERFACE typedef struct _IWindow IWindow; QINTERFACE(IWindow) // add your fun in base struct #define INHERIT_CWindow(iname) \ struct CWindow struct CMainWin IImage * m_pLogo; IWindow就是一个结构体,所有成员都是函数指针,因此也可以称它为接口。INHERIT_CWindow这个宏很重要,继承都靠它了。你可以在上面标注的地方,给基类添加你的数据成员和成员函数。那么当数据成员和成员函数合并,就构成为了CWindow基类。很明显CMainWin是从CWindow派生出来的,如果最好只添加数据成员,如果添加成员函数,就会暴露和成员函数同级的数据成员,那么封装性就会降低了。 如果你还是想模仿BREW平台,那么可以这么写: 在这个应用里头,注意两个宏:DECLARE_VTBL和GET_PVTBL。 大概也就这么多,不过到底是C语言的模拟,很难达到C++面向对象那么完善,不过对于简单的手机应用开发,已经能够胜任了,如果还想研究更加深入的东西,可以读一读高通平台的内部代码。
07 gennaio #pragma 预处理指令详解看《COM技术内幕》,看到一个关于提到objbase.h的文件,于是上里头找找看,有没有些有价值的东西,可是一看发现,写了几个月的c了,还有关键字不认识的,它就是#pragma,上网找了篇资料,贴出来,以后查也方便。
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。 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,如何使两者不冲突 }; 同样的道理适用于: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平台里头有一些比较复杂的宏,关于文中所遇到的宏,列表如下: #define AEEINTERFACE(iname) \ #define AEEVTBL(iname) iname##Vtbl #define INHERIT_IQueryInterface(iname) \ #define INHERIT_IBase(iname) \
-->> typedef struct IICatVtbl IICatVtbl; /** INHERIT_IBase **/ int (*QueryInterface)(IICat *, AEECLSID, void **) void (*IgnoreMaster)(IICat* po); 关于CICat的定义,可以分解为: -->> struct CICat 关于接口函数: #define IICAT_AddRef(p) AEEGETPVTBL(p,IICat)->AddRef((p)) 例如: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、在研读的过程中,发现了一个这样的语句 觉得很奇怪,作者并没有给出IICat的具体实现,但是IICat却实实在在的存在于代码中间。经过仔细察看,发现原来那些函数把IICat作为传入参数,可是在函数中,对于这个参数的处理,使强制转换为CICat类型,这样,才能去访问CICat中IICatVtbl的那些函数指针。 开始一直不明白,大家都提到对于IICatVtbl在结构体中定义的位置,必须放在所有结构体成员的第一位,不然,就无法定位到IICatVtbl中的函数指针。现在,我猜是,由于从IICat转换到CICat,经过了类型转换,其实两者并不是一个类型,但是由于IICatVtbl位于结构体定义的第一位,那么IICat指针所指向的位置也就是IICatVtbl中指针所指向的位置,这样就能保证顺利的访问IICatVtbl中的函数指针。 非常感谢Qinix 斑竹写出了这么好的文章,让大家能从中学习到不少知识,对于文章的理解,还是不够透彻,如果有说得不对的地方,希望大家指正。 关于静态变量与局部变量我们如果正常定一个函数
// same operation
如果我们调用fun函数,那么程序会在动态数据区的栈里头为局部变量申请空间,然后执行相关的代码,进行操作。 但是如果我们进行如下的操作:
int* global_tmp = NULL; void fun() tmp = 1; if (NULL == global_tmp) printf("global_tmp 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读写数据的速度有所提高,从盘面读写数据的速度有所下降。总的来说,差距不是太大,在可以接受的范围内。 |
|
|