﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Xiao.Zhu C++</title><link>http://www.cppblog.com/zzh/</link><description>Xiao.Zhu C++</description><language>zh-cn</language><lastBuildDate>Thu, 09 Apr 2026 05:37:55 GMT</lastBuildDate><pubDate>Thu, 09 Apr 2026 05:37:55 GMT</pubDate><ttl>60</ttl><item><title>嵌入式程序员应该知道的16个问题</title><link>http://www.cppblog.com/zzh/archive/2008/11/28/68049.html</link><dc:creator>Xiao.Zhu</dc:creator><author>Xiao.Zhu</author><pubDate>Fri, 28 Nov 2008 00:41:00 GMT</pubDate><guid>http://www.cppblog.com/zzh/archive/2008/11/28/68049.html</guid><wfw:comment>http://www.cppblog.com/zzh/comments/68049.html</wfw:comment><comments>http://www.cppblog.com/zzh/archive/2008/11/28/68049.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zzh/comments/commentRss/68049.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zzh/services/trackbacks/68049.html</trackback:ping><description><![CDATA[<div id="art" style="margin: 15px;">
<div>---Sailor_forever分析整理，<a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#115;&#97;&#105;&#108;&#105;&#110;&#103;&#95;&#57;&#56;&#48;&#54;&#64;&#49;&#54;&#51;&#46;&#99;&#111;&#109;">sailing_9806@163.com</a><br>1、预处理器（Preprocessor）... 1<br>2、如何定义宏... 2<br>3、预处理器标识#error的目的是什么？... 4<br>4、死循环（Infinite loops）... 4<br>5、数据声明（Data declarations）... 5<br>6、关键字static的作用是什么？... 6<br>7、关键字const有什么含意？... 7<br>8、Volatile的使用... 9<br>9、位操作（Bit manipulation）... 12<br>10、访问固定的内存位置（Accessing fixed memory locations）... 13<br>11、中断（Interrupts）... 13<br>12、符号扩展的代码例子（Code examples）... 15<br>13、处理器字长导致的数据扩展问题... 16<br>14、动态内存分配（Dynamic memory allocation）... 16<br>15、用Typedef构造复合类型... 17<br>16、晦涩的语法及代码风格... 18<br><br>C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年，我既参加也组织了许多这种测试，在这过程中我意识到这些测试能为面试者和被面试者提供许多有用信息，此外，撇开面试的压力不谈，这种测试也是相当有趣的。 <br><br>从
被面试者的角度来讲，你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗？这是个愚蠢的
问题吗？如要你答出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗？这标志着出题者也许花时间在微机上而不是在嵌入式
系统上。如果上述任何问题的答案是"是"的话，那么我知道我得认真考虑我是否应该去做这份工作。 <br><br>从面试者的角度来讲，一个测试也许能
从多方面揭示应试者的素质：最基本的，你能了解应试者C语言的水平。不管怎么样，看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智
的选择，还是只是瞎蒙呢？当应试者在某个问题上卡住时是找借口呢，还是表现出对问题的真正的好奇心，把这看成学习的机会呢？我发现这些信息与他们的测试成
绩一样有用。 <br><br>有了这些想法，我决定出一些真正针对嵌入式系统的考题，希望这些令人头痛的考题能给正在找工作的人一点帮助。这些问题都是我这些年实际碰到的。其中有些题很难，但它们应该都能给你一点启迪。 <br><br>这个测试适于不同水平的应试者，大多数初级水平的应试者的成绩会很差，经验丰富的程序员应该有很好的成绩。为了让你能自己决定某些问题的偏好，每个问题没有分配分数，如果选择这些考题为你所用，请自行按你的意思分配分数。 <br><br><br><br>1、预处理器（Preprocessor） <br>用预处理指令#define 声明一个常数，用以表明1年中有多少秒（忽略闰年问题） <br><br>#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL（大小写都行，常量后面可以加此标志，宏的命名风格要大写，多个之间用下划线） <br><br>我在这想看到几件事情： <br><br>1) #define 语法的基本知识（例如：不能以分号结束，括号的使用（表达式、参数等要括起来），等等） <br><br>2)懂得预处理器将为你计算常数表达式的值（难道不是替换么，先算再替？会将常数合并），因此，直接写出你是如何计算一年中有多少秒而不是计算出实际的值，是更清晰而没有代价的。 <br><br>3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。 <br><br>4) 如果你在你的表达式中用到UL（表示无符号长整型），那么你有了一个好的起点。记住，第一印象很重要。<br><br>2、如何定义宏<br>写一个"标准"宏MIN ，这个宏输入两个参数并返回较小的一个。<br><br>考点：（表达式、参数等要括起来） <br><br>#define MIN(A,B) （（A） &lt;= (B) ? (A) : (B)) <br><br>这个测试是为下面的目的而设的： <br><br>1)
标识#define在宏中应用的基本知识。这是很重要的。因为在嵌入(inline)操作符变为标准C的一部分之前，宏是方便产生嵌入代码的唯一方法，对
于嵌入式系统来说，为了能达到要求的性能（当然主要是实时性哦，牺牲代码空间换取时间效率），嵌入代码经常是必须的方法。 <br><br>2)三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else（存在条件转移会中断指令流水线）更优化的代码，了解这个用法是很重要的。 <br><br>3) 懂得在宏中小心地把参数用括号括起来 <br><br>4) 我也用这个问题开始讨论宏的副作用，例如：当你写下面的代码时会发生什么事？ <br><br>least = MIN(*p++, b); <br><br><br><br>此处考点：inline函数和宏的区别<br><br>宏
只是将参数完全替换，即MIN(*p++, b)进行宏展开后为（（*p++） &lt;= (b) ? (*p++) :
(b))，如果（*p++） &lt;= (b)成立，则表达式的值为(*p++)，但由于在（*p++）&lt;=
(b)判断过程中改变了p的值，使得此时的? (*p++)非（*p++）&lt;= (b)中的值了，违背了？号表达式的原意。<br><br>但是内联inline函数将进行参数检查，求出参数的值后再将此值带入函数中，因此（（A） &lt;= (B) ? (A) : (B))中的A是一致的。<br><br><br><br>第一部分：宏<br>为什么要使用宏呢？<br>因
为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址，将函数的程序内容执行完后，再返回到转去执行该函数前的地方。这种转移操作要求
在转去执行前要保存现场并记忆执行的地址，转回后要恢复现场，并按原来保存地址继续执行。因此，函数调用要有一定的时间和空间方面的开销，于是将影响其效
率。<br>而宏只是在预处理的地方把代码展开，不需要额外的空间和时间方面的开销，所以调用一个宏比调用一个函数更有效率。<br>但是宏也有很多的不尽人意的地方。<br>1、宏不能访问对象的私有成员。<br>2、宏的定义很容易产生二意性。<br><br>3、宏定义的常量在代码区，很多调试器不能够对其调试<br>我们举个例子：<br>#define square(x) (x*x)<br><br>避免这些错误的方法，一是给宏的参数都加上括号。<br>#define square(x) ((x)*(x))<br><br>第二部分：内联函数<br>从上面的阐述，可以看到宏有一些难以避免的问题，怎么解决呢？<br>内联函数是代码被插入到调用者代码处的函数。如同 #define 宏，内联函数通过避免被调用的开销来提高执行效率，尤其是它能够通过调用（&#8220;过程化集成&#8221;）被编译器优化。<br><br><br>内
联函数和宏很类似，而本质区别在于，宏是由预处理器对宏进行替代，而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数，只是在需要用到的时
候，内联函数像宏一样的展开，所以取消了函数的参数压栈，减少了调用的开销。你可以象调用函数一样来调用内联函数，而不必担心会产生于处理宏的一些问题。<br>声明内联函数看上去和普通函数非常相似：<br>void f(int i, char c);<br>当你定义一个内联函数时，在函数定义前加上 inline 关键字，并且将定义放入头文件：<br>inline void f(int i, char c)<br>{<br>// ...<br>}<br>内联函数必须是和函数体的定义申明在一起，才有效。<br>像这样的申明inline function(int i)是没有效果的，编译器只是把函数作为普通的函数申明，我们必须定义函数体。<br>inline int function(int i) {return i*i;}<br>这样我们才算定义了一个内联函数。我们可以把它作为一般的函数一样调用。但是执行速度确比一般函数的执行速度要快。<br><br><br>当然，内联函数也有一定的局限性。就是函数中的执行代码不能太多了，如果，内联函数的函数体过大，一般的编译器会放弃内联方式，而采用普通的方式调用函数。这样，内联函数就和普通函数执行效率一样了。<br>有上面的两者的特性，我们可以用内联函数完全取代预处理宏。<br><br><br><br>3、预处理器标识#error的目的是什么？ <br>如果你不知道答案，请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子，那么应试者最好希望自己不要知道答案。<br><br><br><br>4、死循环（Infinite loops） <br>嵌入式系统中经常要用到无限循环，你怎么样用C编写死循环呢？ 这个问题用几个解决方案。 <br><br>我首选的方案是： <br><br>while(1) <br><br>{ <br><br><br><br>} <br><br>一些程序员更喜欢如下方案： <br><br>for(;;) （此处的判断效率要低的多，在汇编代码中看看？？？）<br><br>{ <br><br><br><br>} <br><br>这
个实现方式让我为难，因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案，我将用这个作为一个机会去探究他们这样做的基本原理。如果
他们的基本答案是："我被教着这样做，但从没有想到过为什么。"这会给我留下一个坏印象。
（很多时候面试官关注你思考问题的方式，是否留意某些东西善于思考，可能并没有对错，只是偏好而已，比如memset和memcopy以及strcpy都
能拷贝字符串，到底有什么区别呢？看你是否善于比较是否关注细节）<br><br><br><br>第三个方案是用 goto （goto语句在C中是应该尽量避免的，只在处理错误代码时用）<br><br>Loop: <br><br>... <br><br>goto Loop; <br><br>应试者如给出上面的方案，这说明或者他是一个汇编语言程序员（这也许是好事）或者他是一个想进入新领域的BASIC/FORTRAN程序员。 <br><br><br><br>5、数据声明（Data declarations） <br>用变量a给出下面的定义 <br><br>a) 一个整型数（An integer） <br><br>b)一个指向整型数的指针（ A pointer to an integer） <br><br>c)一个指向指针的的指针，它指向的指针是指向一个整型数（ A pointer to a pointer to an integers） <br><br>d)一个有10个整型数的数组（ An array of 10 integers） <br><br>e) 一个有10个指针的数组，该指针是指向一个整型数的。（An array of 10 pointers to integers） <br><br>f) 一个指向有10个整型数数组的指针（ A pointer to an array of 10 integers） <br><br>g) 一个指向函数的指针，该函数有一个整型参数并返回一个整型数（A pointer to a function that takes an integer as an argument and returns an integer） <br><br>h)
一个有10个指针的数组，该指针指向一个函数，该函数有一个整型参数并返回一个整型数（ An array of ten pointers to
functions that take an integer argument and return an integer ） <br><br>答案是： <br><br>a) int a; // An integer <br><br>b) int *a; // A pointer to an integer <br><br>c) int **a; // A pointer to a pointer to an integer <br><br>d) int a[10]; // An array of 10 integers <br><br>e) int *a[10]; // An array of 10 pointers to integers <br><br>f) int (*a)[10]; // A pointer to an array of 10 integers <br><br>g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer // 不是（int x），不需要具体的参数<br><br>h) int (*a[10])(int)（可以从e、g类比得到）; // An array of 10 pointers to functions that take an integer argument and return an integer <br><br>人
们经常声称这里有几个问题是那种要翻一下书才能回答的问题，我同意这种说法。当我写这篇文章时，为了确定语法的正确性，我的确查了一下书。但是当我被面试
的时候，我期望被问到这个问题（或者相近的问题）。因为在被面试的这段时间里，我确定我知道这个问题的答案。应试者如果不知道所有的答案（或至少大部分答
案），那么也就没有为这次面试做准备，如果该面试者没有为这次面试做准备，那么他又能为什么做准备呢？ <br><br><br><br>6、关键字static的作用是什么？ <br>这个简单的问题很少有人能回答完全。在C语言中，关键字static有三个明显的作用： <br><br>1)在函数体内，一个被声明为静态的变量在这一函数被调用过程中维持其值不变（该变量存放在静态变量区）。 <br><br>2) 在模块内（但在函数体外），一个被声明为静态的变量可以被模块内所用函数访问，但不能被模块外其它函数访问。它是一个本地的全局变量。 <br><br>3) 在模块内，一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是，这个函数被限制在声明它的模块的本地范围内使用。 <br><br>大多数应试者能正确回答第一部分，一部分能正确回答第二部分，但是很少的人能懂得第三部分。这是一个应试者的严重的缺点，因为他显然不懂得本地化数据和代码范围的好处和重要性。 <br><br><br><br>考点：在嵌入式系统中，要时刻懂得移植的重要性，程序可能是很多程序员共同协作同时完成，在定义变量及函数的过程，可能会重名，这给系统的集成带来麻烦，因此保证不冲突的办法是显示的表示此变量或者函数是本地的，static即可。<br><br>在Linux的模块编程中，这一条很明显，所有的函数和全局变量都要用static关键字声明，将其作用域限制在本模块内部，与其他模块共享的函数或者变量要EXPORT到内核中。<br><br><br><br>static关键字至少有下列n个作用：<br>（1）设置变量的存储域，函数体内static变量的作用范围为该函数体，不同于auto变量，该变量的内存只被分配一次，因此其值在下次调用时仍维持上次的值；<br><br>（2）限制变量的作用域，在模块内的static全局变量可以被模块内所用函数访问，但不能被模块外其它函数访问；<br><br>（3）限制函数的作用域，在模块内的static函数只可被这一模块内的其它函数调用，这个函数的使用范围被限制在声明它的模块内；<br>（4）在类中的static成员变量意味着它为该类的所有实例所共享，也就是说当某个类的实例修改了该静态成员变量，其修改值为该类的其它所有实例所见；<br>（5）在类中的static成员函数属于整个类所拥有，这个函数不接收this指针，因而只能访问类的static成员变量。<br><br><br><br>7、关键字const有什么含意？ <br>我
只要一听到被面试者说："const意味着常数"（不是常数，可以是变量，只是你不能修改它），我就知道我正在和一个业余者打交道。去年Dan
Saks已经在他的文章里完全概括了const的所有用法，因此ESP(译者：Embedded Systems
Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章，只要能说出const意味着"只读"就可
以了。尽管这个答案不是完全的答案，但我接受它作为一个正确的答案。（如果你想知道更详细的答案，仔细读一下Saks的文章吧。） <br><br>如果应试者能正确回答这个问题，我将问他一个附加的问题：下面的声明都是什么意思？ <br><br>Const只是一个修饰符，不管怎么样a仍然是一个int型的变量<br><br>const int a; <br><br>int const a; <br><br>const int *a; <br><br>int * const a; <br><br>int const * a const; <br><br>本质：const在谁后面谁就不可修改，const在最前面则将其后移一位即可，二者等效<br><br><br><br>前两个的作用是一样，a是一个常整型数。第三个意味着a是一个指向常整型数的指针（也就是，指向的整型数是不可修改的，但指针可以，此最常见于函数的参数，当你只引用传进来指针所指向的值时应该加上const修饰符，程序中修改编译就不通过，可以减少程序的bug）。<br><br><br><br>第四个意思a是一个指向整型数的常指针（也就是说，指针指向的整型数是可以修改的，但指针是不可修改的）。最后一个意味着a是一个指向常整型数的常指针（也就是说，指针指向的整型数是不可修改的，同时指针也是不可修改的）。<br><br><br><br>如果应试者能正确回答这些问题，那么他就给我留下了一个好印象。顺带提一句，也许你可能会问，即使不用关键字 ，也还是能很容易写出功能正确的程序，那么我为什么还要如此看重关键字const呢？我也如下的几下理由： <br><br>1)
关键字const的作用是为给读你代码的人传达非常有用的信息，实际上，声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理
其它人留下的垃圾，你就会很快学会感谢这点多余的信息。（当然，懂得用const的程序员很少会留下的垃圾让别人来清理的。） <br><br>2) 通过给优化器一些附加的信息，使用关键字const也许能产生更紧凑的代码。 <br><br>3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数，防止其被无意的代码修改。简而言之，这样可以减少bug的出现。<br><br><br><br>const关键字至少有下列n个作用：<br><br>（1）欲阻止一个变量被改变，可以使用const关键字。在定义该const变量时，通常需要对它进行初始化，因为以后就没有机会再去改变它了；<br>（2）对指针来说，可以指定指针本身为const，也可以指定指针所指的数据为const，或二者同时指定为const；<br>（3）在一个函数声明中，const可以修饰形参，表明它是一个输入参数，在函数内部不能改变其值；<br>（4）对于类的成员函数，若指定其为const类型，则表明其是一个常函数，不能修改类的成员变量；<br>（5）对于类的成员函数，有时候必须指定其返回值为const类型，以使得其返回值不为&#8220;左值&#8221;。例如：<br>const classA operator*(const classA&amp; a1,const classA&amp; a2); <br>　　operator*的返回结果必须是一个const对象。如果不是，这样的变态代码也不会编译出错：<br>classA a, b, c;<br>(a * b) = c; // 对a*b的结果赋值 <br>　　操作(a * b) = c显然不符合编程者的初衷，也没有任何意义。<br><br><br><br>8、Volatile的使用<br>关键字volatile有什么含意?并给出三个不同的例子。 <br><br>一
个定义为volatile的变量是说这变量可能会被意想不到地改变，这样，编译器就不会去假设这个变量的值了。精确地说就是，优化器在用到这个变量时必须
每次都小心地重新读取这个变量的值，而不是使用保存在寄存器里的备份（由于访问寄存器的速度要快过RAM，所以编译器一般都会作减少存取外部RAM的优
化）。下面是volatile变量的几个例子： <br><br>1) 并行设备的硬件寄存器（如：状态寄存器，通常在头文件中将硬件寄存器地址define为某个意义明确的表达式） <br><br>2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables，即static变量) ；在中断服务程序中修改的供其他程序检测用的变量需要加volatile声明；否则编译器可能对变量更新一次后每次都使用缓存值不再立即更新；<br><br>3) 多线程应用中被几个任务共享的变量（可能被多个线程随时修改） <br><br><br><br>回
答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道，
所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。假设被面试者正确地回答了这是问题（嗯，怀疑是否会是这样），我将
稍微深究一下，看一下这家伙是不是直正懂得volatile完全的重要性。 <br><br>1)一个参数既可以是const还可以是volatile吗？解释为什么。 <br><br>2); 一个指针可以是volatile 吗？解释为什么。 <br><br>3); 下面的函数有什么错误： <br><br>int square(volatile int *ptr) <br><br>{ <br><br>return *ptr * *ptr; <br><br>} <br><br>下面是答案： <br><br>1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。 <br><br>2); 是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。 <br><br>3) 这段代码有点变态。这段代码的目的是用来返回指针*ptr指向值的平方，但是，由于*ptr指向一个volatile型参数，编译器将产生类似下面的代码： <br><br>int square(volatile int *ptr) <br><br>{ <br><br>int a,b; <br><br>a = *ptr; <br><br>b = *ptr; <br><br>return a * b; <br><br>} <br><br>由于*ptr的值可能被意想不到地该变，因此a和b可能是不同的。结果，这段代码可能返不是你所期望的平方值！正确的代码如下： <br><br>long square(volatile int *ptr) <br><br>{ <br><br>int a; <br><br>a = *ptr; <br><br>return a * a; <br><br>} <br><br><br><br>关于volatile关键字在中断函数中的影响实例<br><br><br><br>串口发送数据，中断中对其检测，当中断产生后，置接收标志，主循环中检测此主标志，未用valotile修饰时，编译结果如下：<br><br><br><br>[0xe59f41bc] ldr r4，0x30203378 ; = #0x302096f0<br><br>0x302031b8 [0xe5d40000] ldrb r0，[r4，#0]<br><br>while(!uart1_rxFlag); //uart1_rxFlag为全局变量，在串口接收中断中置1<br><br>0x302031bc [0xe3500000] cmp r0，#0<br><br>0x302031c0 [0x0afffffd] beq 0x302031bc; (Can_Int_Test + 0x17c)<br><br>即
编译器对其进行了优化，读取一次uart1_rxFlag的值之后，将其存放在寄存器r0中，比较后，条件不满足，继续等待，但未重新取存储器中
uart1_rxFlag的值，此时即使中断服务函数中修改了uart1_rxFlag的值，比较处仍然不能发现，就出现了无论如何程序就停在此处的问
题。<br><br><br><br>// 加了volatile关键字后，编译的结果<br><br>302031b4 ldr r4，0x30203378 ; = #0x302096f0<br><br>while(uart1_rxFlag == 0);<br><br>302031b8 [0xe5d40000] ldrb r0，[r4，#0]<br><br>302031bc [0xe3500000] cmp r0，#0<br><br>302031c0 [0x0afffffc] beq 0x302031b8 ; (Can_Int_Test + 0x288)<br><br>添加了关键字后，比较不等，跳转到重新取存储器中的uart1_rxFlag，因此任何时候uart1_rxFlag的值都是最新的。<br><br>一定程度的优化，去掉了读取uart1_rxFlag地址的语句。<br><br><br><br>定
义一个易失性变量，编译器有一种技术叫数据流分析，分析程序中的变量在哪里被赋值、在哪里使用、在哪里失效，分析结果可以用于常量合并，常量传播等优化。
当编译器检查到代码没有修改字段的值，就有可能在你访问字段时提供上次访问的缓存值，这能够提高程序的效率，但有时这些优化会带来问题，不是我们程序所需
要的，特点是对硬件寄存器操作的程序，这时可以用volatile关键字禁止做这些优化。<br><br><br><br>多任务环境下各任务间共享的标志应该加voatile关键字：在多线程访问某字段时，代码希望这些访问能够操作（读取）到字段的最新值，同时写到变量的操作能立即更新；对字段加上volatile关键字，那么对该字段的任何请求（读/写）都会立刻得到执行。<br><br><br><br>9、位操作（Bit manipulation） <br>嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a，写两段代码，第一个设置a的bit 3，第二个清除a 的bit 3。在以上两个操作中，要保持其它位不变。 对这个问题有三种基本的反应 <br><br>1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。 <br><br>2) 用bit fields。Bit fields是被扔到C语言死角的东西，它保证你的代码在不同编译器之间是不可移植的，同时也保证了的你的代码是不可重用的。<br><br>3) 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法，是应该被用到的方法。最佳的解决方案如下： <br><br>#define BIT3 (0x1 &lt;&lt; 3) （采用宏将数字定义为有意义的BIT3，明确，不易出错，改起来方便）<br><br>static int a; <br><br>void set_bit3(void) <br><br>{ <br><br>a |= BIT3; <br><br>} <br><br>void clear_bit3(void) <br><br>{ <br><br>a &amp;= ~BIT3; <br><br>} <br><br><br><br>一
些人喜欢为设置和清除值而定义一个掩码（待操作位全1，其余位全0的数，对于某个意义靠多位同时表示的最好带上掩码，隔离其他位的影响）同时定义一些说明
常数，这也是可以接受的。我希望看到几个要点：说明常数、|=和&amp;=~操作，先取反再&amp;是对某位清0的最好操作。 <br><br><br><br>考点：<br><br>在嵌入式系统中，时刻要关注移植性，具体的程序中不要出现具体的数字，这些数字都应该define成某个有意义的符号，可读性可移植性都很强，比如<br><br>#define BIT(x) (0x1 &lt;&lt; (x))<br><br>X作为参数可以很方便的对任意位进行操作，意义明确，更改替换方便<br><br><br><br>10、访问固定的内存位置（Accessing fixed memory locations） <br>嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。 <br><br>在
某工程中，要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。这一问题测试你是
否知道为了访问一绝对地址把一个整型数强制转换（typecast）为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下：
<br><br>int *ptr; <br><br>ptr = (int *)0x67a9; <br><br>*ptr = 0xaa55; <br><br>A more obscure approach is: ( 一个较晦涩的方法是)： <br><br>*(int * const)(0x67a9) = 0xaa55; <br><br>即使你的品味更接近第二种方案，但我建议你在面试时使用第一种方案。 <br><br><br><br>在嵌入式系统中，对于大量此类型数据如硬件寄存器应该采用如下方式<br><br>typedef volatile unsigned int HARD_REG;<br><br>#define REG_NAME (*(HARD_REG *)ADDR)<br><br>即将ADDR强制转换为一个指向HARD_REG类型数据的指针，*HARD_REG为volatile的无符号整型数<br><br><br><br>11、中断（Interrupts） <br>中
断是嵌入式系统中重要的组成部分，这导致了很多编译开发商提供一种扩展-让标准C支持中断。其代表事实是，产生了一个新的关键字
__interrupt（51即如此）。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR)，请评论一下这段代码的。
<br><br>__interrupt double compute_area (double radius) <br><br>{ <br><br>double area = PI * radius * radius; <br><br>printf("\nArea = %f", area); <br><br>return area; <br><br>} <br><br>这个函数有太多的错误了，以至让人不知从何说起了（前提是非操作系统下的中断服务函数）： <br><br>1)ISR 不能返回一个值（都应该为void类型）。如果你不懂这个，那么你不会被雇用的。 <br><br>2)ISR 不能传递参数。如果你没有看到这一点，你被雇用的机会等同第一项。 <br><br>3)在许多的处理器/编译器中，浮点一般都是不可重入的。有些处理器/编译器需要让额外的寄存器入栈，有些处理器/编译器就是不允许在ISR中做浮点运算。此外，ISR应该是短而有效率的，在ISR中做浮点运算是不明智的。 <br><br>///////////////////////////////<br><br>另外中断服务程序是运行在内核态的（linux），内核通常是不支持浮点运算的。<br><br><a  href="http://access911.net/n/doc1.asp?mode=a&amp;aid=4750647" target="_blank"><font color="#0000ff">http://access911.net/n/doc1.asp?mode=a&amp;aid=4750647</font></a><br><br>内核中的printk和标准库的printf不一样，前者因为由内核直接实现，不能支持浮点。<br><br>在＜linux内核设计与实现＞的第一章中内核开发的特点一小节里就有比较了内核开发与应用开发的差异。其中一点就是内核编程时浮点数的问题，书中有一句话是：内核编程时浮点数很难使用<br><br>因为没有浮点单元,内核要支持浮点必须把内核以soft-float 方式重新编译,其连接所有的库也都要用soft-float 方式编译.<br><br>否则另外一种方式使用整数定义浮点类型加浮点预算库完成你的工作,<br><br><br><br><a  href="http://topic.csdn.net/u/20070417/16/a4b56569-228c-4b70-b5ab-30ee61c99a3d.html" target="_blank"><font color="#0000ff">http://topic.csdn.net/u/20070417/16/a4b56569-228c-4b70-b5ab-30ee61c99a3d.html</font></a><br><br>如果你的内核里编译进了浮点支持，那么是可以的。要不内核或是模块不能用float或是double内型的变量或函数<br><br><br><br>在配置内核的时候把浮点模拟器选上，应该是可以支持的，但是速度非常慢。 <br>我曾经遇到过，硬件明明支持浮点运算的FPU，但是编译内核的时候选上了浮点模拟器，结果所有的应用程序的浮点运算速度都非常慢。所以我怀疑要支持浮点只要编译内核的时候选上，对于应用程序不需要怎么关心。<br><br>///////////////////////////////<br><br><br><br>4) 与第三点一脉相承，printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点，我不会太为难你的。不用说，如果你能得到后两点，那么你的被雇用前景越来越光明了。 <br><br><br><br>12、符号扩展的代码例子（Code examples） <br>下面的代码输出是什么，为什么？ <br><br>void foo(void) <br><br>{ <br><br>unsigned int a = 6; <br><br>int b = -20; <br><br>(a+b &gt; 6) ? puts("&gt; 6") : puts("&lt;= 6"); <br><br>} <br><br>Vc6.0测试情况<br><br>void main(void)<br><br>{<br><br>unsigned int a = 6; <br><br>int b = -20; <br><br>printf("unsigned int a + int b = %x\n", (a + b));<br><br><br><br>}<br><br>/*unsigned int a + int b = fffffff2*/<br><br>这
个问题测试你是否懂得C语言中的整数自动转换原则，我发现有些开发者懂得极少这些东西。不管如何，这无符号整型问题的答案是输出是
"&gt;6"。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数，所以该表达式
计算出的结果大于6。这一点对于频繁用到无符号数据类型的嵌入式系统（硬件寄存器的值全部是无符号的）来说是丰常重要的。如果你答错了这个问题，你也就到
了得不到这份工作的边缘。 <br><br><br><br>13、处理器字长导致的数据扩展问题<br>评价下面的代码片断： <br><br>unsigned int zero = 0; <br><br>unsigned int compzero = 0xFFFF; <br><br>/*1's complement of zero */ 0的补码为全1的数<br><br>对于一个int型不是16位的处理器为说，上面的代码是不正确的。应编写如下： <br><br>unsigned int compzero = ~0; <br><br>这一问题真正能揭露出应试者是否懂得处理器字长的重要性（嵌入式平台可能是8、16、32的，移植的角度来说写出固定的0xFFFF是不对的）。在我的经验里，好的嵌入式程序员非常准确地明白硬件的细节和它的局限，然而PC机程序往往把硬件作为一个无法避免的烦恼。 <br><br><br><br>到
了这个阶段，应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好，那么这个测试就在这里结束了。但如果显然应试者做得不错，那么我就
扔出下面的追加问题，这些问题是比较难的，我想仅仅非常优秀的应试者能做得不错。提出这些问题，我希望更多看到应试者应付问题的方法（很重要哦，面试者关
注的是你思考问题解决问题的过程，当你不知道答案时千万千万不要猜一个答案给他，因为现在不是选择题，面试官要的是过程，你只需要将你考虑问题的过程说明
白就OK了），而不是答案。不管如何，你就当是这个娱乐吧... <br><br><br><br>14、动态内存分配（Dynamic memory allocation） <br>尽
管不像非嵌入式计算机那么常见，嵌入式系统还是有从堆（heap）中动态分配内存的过程的。那么嵌入式系统中，动态分配内存可能发生的问题是什么？这里，
我期望应试者能提到内存碎片，碎片收集的问题，变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了（主要是 P.J. Plauger,
他的解释远远超过我这里能提到的任何解释），所有回过头看一下这些杂志吧！让应试者进入一种虚假的安全感觉后，我拿出这么一个小节目：下面的代码片段的输
出是什么，为什么？ <br><br>char *ptr; <br><br>if ((ptr = (char *)malloc(0)) == NULL) <br><br>puts("Got a null pointer"); <br><br>else <br><br>puts("Got a valid pointer"); <br><br>这
是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc，得到了一个合法的指针之后，我才想到这个问题。这就是上面的代码，该代码的输
出是"Got a valid
pointer"。我用这个来开始讨论这样的一问题，看看被面试者是否想到库例程这样做是正确（因为如果申请失败，则程序处理认为内存不足了，一般会终止
程序，是很严重的问题？）。得到正确的答案固然重要，但解决问题的方法和你做决定的基本原理更重要些。 <br><br>返回一個控指針還是指向 0 字節的指針甚至指向一个可以操作的指针？<br><br>（取决于系统平台的实现，C99及其他标准规定可以不同的）<br><br>malloc(0)
in glibc returns a valid pointer to something(!?!?) while in uClibc
calling malloc(0) returns a NULL. The behavior of malloc(0) is listed
as implementation-defined by SuSv3, so both libraries are equally
correct. This difference also applies to realloc(NULL, 0). I personally
feel glibc's behavior is not particularly safe. To enable glibc
behavior, one has to explicitly enable the MALLOC_GLIBC_COMPAT option.<br><br><br><br>15、用Typedef构造复合类型 <br>在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如，思考一下下面的例子： <br><br>#define dPS struct s * <br><br>typedef struct s * tPS; <br><br>以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢？（如果有的话）为什么？ <br><br>这是一个非常微妙的问题，任何人答对这个问题（正当的原因哦，而不是猜，如果你没有原因，说不会比猜一个答案要好的多，记住啊，说话是要讲根据的）是应当被恭喜的。答案是：typedef更好。思考下面的例子： <br><br>dPS p1,p2; <br><br>tPS p3,p4; <br><br>第一个扩展为 <br><br>struct s * p1, p2; <br><br>上面的代码定义p1为一个指向结构的指，p2为一个实际的结构，这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。 <br><br><br><br>16、晦涩的语法及代码风格 <br>C语言同意一些令人震惊的结构,下面的结构是合法的吗，如果是它做些什么？ <br><br>int a = 5, b = 7, c; <br><br>c = a+++b; <br><br>这个问题将做为这个测验的一个愉快的结尾。不管你相不相信，上面的例子是完全合乎语法的。问题是编译器如何处理它？水平不高的编译作者实际上会争论这个问题，编译器应尽可能多的从左至右将若干个字符组成一个运算符。因此，上面的代码被处理成：c = a++ + b; <br><br>逗号表达式依次对每个表达式计算，最后的结果为最后一个表达式的值<br><br>因此, 这段代码执行后a = 6, b = 7, c = 12。 <br><br>如果你知道答案，或猜出正确答案，做得好。如果你不知道答案，我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格（要明确的加上括号，避免歧义或者编译器不同带来的差异），代码的可读性，代码的可修改性的好的话题。 <br><br><br><br>注：引出代码风格的问题正是作者问此问题的目的，这告诉我们要揣摩面试管每个问题背后隐藏的考查点，能够趁机发挥下就大功告成了！<br><br><br><br>好了，伙计们，你现在已经做完所有的测试了。这就是我出的C语言测试题，我怀着愉快的心情写完它，希望你以同样的心情读完它。如果是认为这是一个好的测试，那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年，我就不做现在的工作，也需要找一个。 <br><br><br><br>以下为上述16个问题的英文表述，熟悉下相关的专业词汇对于英文面试的简单表述很重要。<br><br><a  href="http://www.yuanma.org/data/2007/0509/article_2585.htm" target="_blank"><font color="#0000ff">http://www.yuanma.org/data/2007/0509/article_2585.htm</font></a><br><br>An
obligatory and significant part of the recruitment process for embedded
systems programmers seems to be the "C test." Over the years, I have
had to both take and prepare such tests and, in doing so, have realized
that these tests can be informative for both the interviewer and
interviewee. Furthermore, when given outside the pressure of an
interview situation, these tests can also be quite entertaining. <br><br>From
the interviewee's perspective, you can learn a lot about the person who
has written or administered the test. Is the test designed to show off
the writer's knowledge of the minutiae of the ANSI standard rather than
to test practical know-how? Does it test ludicrous knowledge, such as
the ASCII values of certain characters? Are the questions heavily
slanted towards your knowledge of system calls and memory allocation
strategies, indicating that the writer may spend his time programming
computers instead of embedded systems? If any of these are true, then I
know I would seriously doubt whether I want the job in question. <br><br>From
the interviewer's perspective, a test can reveal several things about
the candidate. Primarily, you can determine the level of the
candidate's knowledge of C. However, it's also interesting to see how
the person responds to questions to which they don't know the answers.
Do they make intelligent choices backed up with good intuition, or do
they just guess? Are they defensive when they are stumped, or do they
exhibit a real curiosity about the problem and see it as an opportunity
to learn something? I find this information as useful as their raw
performance on the test. <br><br>With these ideas in mind, I have
attempted to construct a test that is heavily slanted towards the
requirements of embedded systems. This is a lousy test to give to
someone seeking a job writing compilers! The questions are almost all
drawn from situations I have encountered over the years. Some of them
are tough; however, they should all be informative. <br><br>This test
may be given to a wide range of candidates. Most entry-level applicants
will do poorly on this test, while seasoned veterans should do very
well. Points are not assigned to each question, as this tends to
arbitrarily weight certain questions. However, if you choose to adapt
this test for your own uses, feel free to assign scores. <br><br><br><br>Preprocessor <br>1.
Using the #define statement, how would you declare a manifest constant
that returns the number of seconds in a year? Disregard leap years in
your answer. <br><br>#define SECONDS_PER_YEAR <br><br>(60 * 60 * 24 * 365)UL<br><br><br><br>I'm looking for several things here: <br><br>Basic knowledge of the #define syntax (for example, no semi-colon at the end, the need to parenthesize, and so on) <br><br>An
understanding that the pre-processor will evaluate constant expressions
for you. Thus, it is clearer, and penalty-free, to spell out how you
are calculating the number of seconds in a year, rather than actually
doing the calculation yourself <br><br>A realization that the
expression will overflow an integer argument on a 16-bit machine-hence
the need for the L, telling the compiler to treat the variable as a
Long <br><br>As a bonus, if you modified the expression with a UL
(indicating unsigned long), then you are off to a great start. And
remember, first impressions count! <br><br><br><br>2. Write the "standard" MIN macro-that is, a macro that takes two arguments and returns the smaller of the two arguments. <br><br>#define MIN(A,B) <br><br>((A) <br><br>&lt; <br><br>= (B) ? (A) : (B))<br><br><br><br>The purpose of this question is to test the following: <br><br>Basic
knowledge of the #define directive as used in macros. This is important
because until the inline operator becomes part of standard C, macros
are the only portable way of generating inline code. Inline code is
often necessary in embedded systems in order to achieve the required
performance level <br><br>Knowledge of the ternary conditional
operator. This operator exists in C because it allows the compiler to
produce more optimal code than an if-then-else sequence. Given that
performance is normally an issue in embedded systems, knowledge and use
of this construct is important <br><br>Understanding of the need to very carefully parenthesize arguments to macros <br><br>I
also use this question to start a discussion on the side effects of
macros, for example, what happens when you write code such as: <br><br>least = MIN(*p++, b);<br><br><br><br>3. What is the purpose of the preprocessor directive #error? <br><br>Either
you know the answer to this, or you don't. If you don't, see Reference
1. This question is useful for differentiating between normal folks and
the nerds. Only the nerds actually read the appendices of C textbooks
to find out about such things. Of course, if you aren't looking for a
nerd, the candidate better hope she doesn't know the answer. <br><br><br><br>Infinite loops <br>4. Infinite loops often arise in embedded systems. How does you code an infinite loop in C? <br><br>There are several solutions to this question. My preferred solution is: <br><br>while(1)<br><br>{<br><br>?<br><br>}<br><br><br><br>Many programmers seem to prefer: <br><br>for(;;)<br><br>{<br><br>?<br><br>}<br><br><br><br>This
construct puzzles me because the syntax doesn't exactly spell out
what's going on. Thus, if a candidate gives this as a solution, I'll
use it as an opportunity to explore their rationale for doing so. If
their answer is basically, "I was taught to do it this way and I
haven't thought about it since," it tells me something (bad) about
them. <br><br>A third solution is to use a goto : <br><br><br><br>Loop:<br><br>...<br><br>goto Loop;<br><br><br><br><br><br><br><br>Candidates
who propose this are either assembly language programmers (which is
probably good), or else they are closet BASIC/FORTRAN programmers
looking to get into a new field. <br><br><br><br>Data declarations <br>5. Using the variable a, give definitions for the following: <br>a) An integer <br>b) A pointer to an integer <br>c) A pointer to a pointer to an integer <br>d) An array of 10 integers <br>e) An array of 10 pointers to integers <br>f) A pointer to an array of 10 integers <br>g) A pointer to a function that takes an integer as an argument and returns an integer <br>h) An array of ten pointers to functions that take an integer argument and return an integer <br><br>The answers are: <br>a) int a; // An integer <br>b) int *a; // A pointer to an integer <br>c) int **a; // A pointer to a pointer to an integer <br>d) int a[10]; // An array of 10 integers <br>e) int *a[10]; // An array of 10 pointers to integers <br>f) int (*a)[10]; // A pointer to an array of 10 integers <br>g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer <br>h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer <br><br>People
often claim that a couple of these are the sorts of thing that one
looks up in textbooks-and I agree. While writing this article, I
consulted textbooks to ensure the syntax was correct. However, I expect
to be asked this question (or something close to it) when I'm being
interviewed. Consequently, I make sure I know the answers, at least for
the few hours of the interview. Candidates who don't know all the
answers (or at least most of them) are simply unprepared for the
interview. If they can't be prepared for the interview, what will they
be prepared for? <br><br><br><br>Static <br>6. What are the uses of the keyword static? <br><br>This simple question is rarely answered completely. Static has three distinct uses in C: <br><br>A variable declared static within the body of a function maintains its value between function invocations <br><br>A
variable declared static within a module, (but outside the body of a
function) is accessible by all functions within that module. It is not
accessible by functions within any other module. That is, it is a
localized global <br><br>Functions declared static within a module may
only be called by other functions within that module. That is, the
scope of the function is localized to the module within which it is
declared <br><br>Most candidates get the first part correct. A
reasonable number get the second part correct, while a pitiful number
understand the third answer. This is a serious weakness in a candidate,
since he obviously doesn't understand the importance and benefits of
localizing the scope of both data and code. <br><br>Const <br><br><br>7. What does the keyword const mean? <br><br>As
soon as the interviewee says "const means constant," I know I'm dealing
with an amateur. Dan Saks has exhaustively covered const in the last
year, such that every reader of ESP should be extremely familiar with
what const can and cannot do for you. If you haven't been reading that
column, suffice it to say that const means "read-only." Although this
answer doesn't really do the subject justice, I'd accept it as a
correct answer. (If you want the detailed answer, read Saks'
columns-carefully!) <br><br>If the candidate gets the answer correct, I'll ask him these supplemental questions: <br><br>What do the following declarations mean? <br><br><br><br>const int a;<br><br>int const a;<br><br>const int *a;<br><br>int * const a;<br><br>int const * a const;<br><br><br><br>The
first two mean the same thing, namely a is a const (read-only) integer.
The third means a is a pointer to a const integer (that is, the integer
isn't modifiable, but the pointer is). The fourth declares a to be a
const pointer to an integer (that is, the integer pointed to by a is
modifiable, but the pointer is not). The final declaration declares a
to be a const pointer to a const integer (that is, neither the integer
pointed to by a, nor the pointer itself may be modified). If the
candidate correctly answers these questions, I'll be impressed.
Incidentally, you might wonder why I put so much emphasis on const,
since it is easy to write a correctly functioning program without ever
using it. I have several reasons: <br><br>The use of const conveys
some very useful information to someone reading your code. In effect,
declaring a parameter const tells the user about its intended usage. If
you spend a lot of time cleaning up the mess left by other people,
you'll quickly learn to appreciate this extra piece of information. (Of
course, programmers who use const , rarely leave a mess for others to
clean up.) <br><br>const has the potential for generating tighter code by giving the optimizer some additional information <br><br>Code
that uses const liberally is inherently protected by the compiler
against inadvertent coding constructs that result in parameters being
changed that should not be. In short, they tend to have fewer bugs <br><br><br><br>Volatile <br>8. What does the keyword volatile mean? Give three different examples of its use. <br><br>A
volatile variable is one that can change unexpectedly. Consequently,
the compiler can make no assumptions about the value of the variable.
In particular, the optimizer must be careful to reload the variable
every time it is used instead of holding a copy in a register. Examples
of volatile variables are: <br><br>Hardware registers in peripherals (for example, status registers) <br><br>Non-automatic variables referenced within an interrupt service routine <br><br>Variables shared by multiple tasks in a multi-threaded application <br><br>Candidates
who don't know the answer to this question aren't hired. I consider
this the most fundamental question that distinguishes between a C
programmer and an embedded systems programmer. Embedded folks deal with
hardware, interrupts, RTOSes, and the like. All of these require
volatile variables. Failure to understand the concept of volatile will
lead to disaster. <br><br>On the (dubious) assumption that the
interviewee gets this question correct, I like to probe a little deeper
to see if they really understand the full significance of volatile . In
particular, I'll ask them the following additional questions: <br><br>Can a parameter be both const and volatile ? Explain. <br><br>Can a pointer be volatile ? Explain. <br><br>What's wrong with the following function?: <br><br><br><br>int square(volatile int *ptr)<br><br>{<br><br>return *ptr * *ptr;<br><br>}<br><br><br><br>The answers are as follows: <br><br>Yes.
An example is a read-only status register. It is volatile because it
can change unexpectedly. It is const because the program should not
attempt to modify it <br><br>Yes, although this is not very common. An example is when an interrupt service routine modifies a pointer to a buffer <br><br>This
one is wicked. The intent of the code is to return the square of the
value pointed to by *ptr . However, since *ptr points to a volatile
parameter, the compiler will generate code that looks something like
this: <br><br><br><br>int square(volatile int *ptr) <br><br>{<br><br>int a,b;<br><br>a = *ptr;<br><br>b = *ptr;<br><br>return a * b;<br><br>}<br><br><br><br>Because
it's possible for the value of *ptr to change unexpectedly, it is
possible for a and b to be different. Consequently, this code could
return a number that is not a square! The correct way to code this is: <br><br><br><br>long square(volatile int *ptr) <br><br>{<br><br>int a;<br><br>a = *ptr;<br><br>return a * a;<br><br>}<br><br><br><br>Bit manipulation <br>9.
Embedded systems always require the user to manipulate bits in
registers or variables. Given an integer variable a, write two code
fragments. The first should set bit 3 of a. The second should clear bit
3 of a. In both cases, the remaining bits should be unmodified. <br><br>These are the three basic responses to this question: <br><br>No idea. The interviewee cannot have done any embedded systems work <br><br>Use
bit fields. Bit fields are right up there with trigraphs as the most
brain-dead portion of C. Bit fields are inherently non-portable across
compilers, and as such guarantee that your code is not reusable. I
recently had the misfortune to look at a driver written by Infineon for
one of their more complex communications chips. It used bit fields and
was completely useless because my compiler implemented the bit fields
the other way around. The moral: never let a non-embedded person
anywhere near a real piece of hardware! <br><br>Use #defines and bit
masks. This is a highly portable method and is the one that should be
used. My optimal solution to this problem would be: <br><br><br><br>#define BIT3 (0x1 &lt;&lt; 3)<br><br>static int a;<br><br>void set_bit3(void) {<br><br>a |= BIT3;<br><br>}<br><br>void clear_bit3(void) {<br><br>a &amp;= ~BIT3;<br><br>}<br><br><br><br>Some
people prefer to define a mask together with manifest constants for the
set and clear values. This is also acceptable. The element that I'm
looking for is the use of manifest constants, together with the |= and
&amp;= ~ constructs <br><br><br><br>Accessing fixed memory locations <br>10.
Embedded systems are often characterized by requiring the programmer to
access a specific memory location. On a certain project it is required
to set an integer variable at the absolute address 0x67a9 to the value
0xaa55. The compiler is a pure ANSI compiler. Write code to accomplish
this task. <br><br>This problem tests whether you know that it is
legal to typecast an integer to a pointer in order to access an
absolute location. The exact syntax varies depending upon one's style.
However, I would typically be looking for something like this: <br><br><br><br>int *ptr;<br><br>ptr = (int *)0x67a9;<br><br>*ptr = 0xaa55;<br><br><br><br>A more obscure approach is: <br><br><br><br>*(int * const)(0x67a9) = 0xaa55;<br><br><br><br>Even if your taste runs more to the second solution, I suggest the first solution when you are in an interview situation. <br><br><br><br>Interrupts <br>11.
Interrupts are an important part of embedded systems. Consequently,
many compiler vendors offer an extension to standard C to support
interrupts. Typically, this new keyword is __interrupt. The following
code uses __interrupt to define an interrupt service routine (ISR).
Comment on the code. <br><br><br><br>__interrupt double compute_area<br><br>(double<br><br>radius) <br><br>{<br><br>double area = PI * radius * <br><br>radius;<br><br>printf("\nArea = %f", area);<br><br>return area;<br><br>}<br><br><br><br>This function has so much wrong with it, it's hard to know where to start: <br><br>ISRs cannot return a value. If you don't understand this, you aren't hired <br><br>ISRs cannot be passed parameters. See the first item for your employment prospects if you missed this <br><br>On
many processors/compilers, floating-point operations are not
necessarily re-entrant. In some cases one needs to stack additional
registers. In other cases, one simply cannot do floating point in an
ISR. Furthermore, given that a general rule of thumb is that ISRs
should be short and sweet, one wonders about the wisdom of doing
floating-point math here <br><br>In a vein similar to the third point,
printf() often has problems with reentrancy and performance. If you
missed points three and four, I wouldn't be too hard on you. Needless
to say, if you got these last two points, your employment prospects are
looking better and better <br><br><br><br>Code examples <br>12. What does the following code output and why? <br><br>void foo(void)<br><br>{<br><br>unsigned int a = 6;<br><br>int b = -20;<br><br>(a+b &gt; 6) ? puts("&gt; 6") : <br><br>puts("<br><br>&lt; <br><br>= 6");<br><br>}<br><br><br><br>This
question tests whether you understand the integer promotion rules in
C-an area that I find is very poorly understood by many developers.
Anyway, the answer is that this outputs "&gt; 6." The reason for this
is that expressions involving signed and unsigned types have all
operands promoted to unsigned types. Thus ?20 becomes a very large
positive integer and the expression evaluates to greater than 6. This
is a very important point in embedded systems where unsigned data types
should be used frequently (see Reference 2). If you get this one wrong,
you are perilously close to not getting the job. <br><br><br><br>13. Comment on the following code fragment. <br><br>unsigned int zero = 0;<br><br>unsigned int compzero = 0xFFFF; <br><br>/*1's complement of zero */<br><br><br><br>On machines where an int is not 16 bits, this will be incorrect. It should be coded: <br><br>unsigned int compzero = ~0;<br><br><br><br>This
question really gets to whether the candidate understands the
importance of word length on a computer. In my experience, good
embedded programmers are critically aware of the underlying hardware
and its limitations, whereas computer programmers tend to dismiss the
hardware as a necessary annoyance. <br><br>By this stage, candidates
are either completely demoralized-or they're on a roll and having a
good time. If it's obvious that the candidate isn't very good, then the
test is terminated at this point. However, if the candidate is doing
well, then I throw in these supplemental questions. These questions are
hard, and I expect that only the very best candidates will do well on
them. In posing these questions, I'm looking more at the way the
candidate tackles the problems, rather than the answers. Anyway, have
fun... <br><br><br><br>Dynamic memory allocation <br>14. Although not
as common as in non-embedded computers, embedded systems do still
dynamically allocate memory from the heap. What are the problems with
dynamic memory allocation in embedded systems? <br><br>Here, I expect
the user to mention memory fragmentation, problems with garbage
collection, variable execution time, and so on. This topic has been
covered extensively in ESP , mainly by P.J. Plauger. His explanations
are far more insightful than anything I could offer here, so go and
read those back issues! Having lulled the candidate into a sense of
false security, I then offer up this tidbit: <br><br>What does the following code fragment output and why? <br><br><br><br>char *ptr;<br><br>if ((ptr = (char *)malloc(0)) == <br><br>NULL) <br><br>else<br><br>puts("Got a null pointer");<br><br>puts("Got a valid pointer");<br><br><br><br>This
is a fun question. I stumbled across this only recently when a
colleague of mine inadvertently passed a value of 0 to malloc and got
back a valid pointer! That is, the above code will output "Got a valid
pointer." I use this to start a discussion on whether the interviewee
thinks this is the correct thing for the library routine to do. Getting
the right answer here is not nearly as important as the way you
approach the problem and the rationale for your decision. <br><br><br><br>Typedef <br>15.
Typedef is frequently used in C to declare synonyms for pre-existing
data types. It is also possible to use the preprocessor to do something
similar. For instance, consider the following code fragment: <br><br><br><br>#define dPS struct s *<br><br>typedef struct s * tPS;<br><br><br><br>The intent in both cases is to define dPS and tPS to be pointers to structure s. Which method, if any, is preferred and why? <br><br>This
is a very subtle question, and anyone who gets it right (for the right
reason) is to be congratulated or condemned ("get a life" springs to
mind). The answer is the typedef is preferred. Consider the
declarations: <br><br>dPS p1,p2;<br><br>tPS p3,p4;<br><br><br><br>The first expands to: <br><br>struct s * p1, p2;<br><br><br><br>which
defines p1 to be a pointer to the structure and p2 to be an actual
structure, which is probably not what you wanted. The second example
correctly defines p3 and p4 to be pointers. <br><br><br><br>Obscure syntax <br>16. C allows some appalling constructs. Is this construct legal, and if so what does this code do? <br><br><br><br>int a = 5, b = 7, c;<br><br>c = a+++b;<br><br><br><br>This
question is intended to be a lighthearted end to the quiz, as, believe
it or not, this is perfectly legal syntax. The question is how does the
compiler treat it? Those poor compiler writers actually debated this
issue, and came up with the "maximum munch" rule, which stipulates that
the compiler should bite off as big (and legal) a chunk as it can.
Hence, this code is treated as: <br><br>c = a++ + b;<br><br>Thus, after this code is executed, a = 6, b = 7, and c = 12. <br><br>If
you knew the answer, or guessed correctly, well done. If you didn't
know the answer then I wouldn't consider this to be a problem. I find
the greatest benefit of this question is that it is good for
stimulating questions on coding styles, the value of code reviews, and
the benefits of using lint. <br><br>Well folks, there you have it.
That was my version of the C test. I hope you had as much fun taking it
as I had writing it. If you think the test is a good test, then by all
means use it in your recruitment. Who knows, I may get lucky in a year
or two and end up being on the receiving end of my own work. <br><br>Nigel
Jones is a consultant living in Maryland. When not underwater, he can
be found slaving away on a diverse range of embedded projects. He
enjoys hearing from readers and can be reached at
NAJones@compuserve.com . <br><br>References <br><br>Jones, Nigel, "In Praise of the #error directive," Embedded Systems Programming, September 1999, p. 114. <br><br>Jones, Nigel, " Efficient C Code for Eight-bit MCUs ," Embedded Systems Programming, November 1998, p. 66. </div>
</div>
<p style="margin: 5px; line-height: 150%;">
</p>
&nbsp;<font color="#000099"><strong>原文地址</strong></font>
<a  href="http://blog.csdn.net/sailor_8318/archive/2008/03/25/2215041.aspx" target="_blank">http://blog.csdn.net/sailor_8318/archive/2008/03/25/2215041.aspx</a><img src ="http://www.cppblog.com/zzh/aggbug/68049.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zzh/" target="_blank">Xiao.Zhu</a> 2008-11-28 08:41 <a href="http://www.cppblog.com/zzh/archive/2008/11/28/68049.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Visual Studio 6.0 和　Visual Studio 2005 编辑器配色方案</title><link>http://www.cppblog.com/zzh/archive/2007/09/18/32402.html</link><dc:creator>Xiao.Zhu</dc:creator><author>Xiao.Zhu</author><pubDate>Tue, 18 Sep 2007 02:12:00 GMT</pubDate><guid>http://www.cppblog.com/zzh/archive/2007/09/18/32402.html</guid><wfw:comment>http://www.cppblog.com/zzh/comments/32402.html</wfw:comment><comments>http://www.cppblog.com/zzh/archive/2007/09/18/32402.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/zzh/comments/commentRss/32402.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zzh/services/trackbacks/32402.html</trackback:ping><description><![CDATA[<p>Visual Studio 6.0 编辑器配色方案<br><br>复制以下代码保存成.reg文件，然后导入注册表。</p>
<p>--------------------------------------------------------------------------------------------------------------------<br>Windows Registry Editor Version 5.00<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format]<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Calls Window]<br>"FontFace"="Fixedsys"<br>"FontSize"=dword:0000000c<br>"Text"=hex:00,00,00,00,ff,ff,ff,00,13,01,b3,00<br>"Text Selection"=hex:ff,ff,ff,00,00,00,00,00,19,00,19,00<br>"Calls Highlight"=hex:00,00,00,00,00,ff,00,00,10,00,10,00<br>"Superceded code"=hex:80,80,80,00,ff,ff,ff,00,10,00,10,00<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Disassembly Window]<br>"FontFace"="Fixedsys"<br>"FontSize"=dword:0000000c<br>"Text"=hex:00,00,00,00,ff,ff,ff,00,13,01,b3,00<br>"Text Selection"=hex:ff,ff,ff,00,00,00,00,00,19,00,19,00<br>"Assembly Code"=hex:80,80,80,00,ff,ff,ff,00,10,00,10,00<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Memory Window]<br>"FontFace"="Fixedsys"<br>"FontSize"=dword:0000000c<br>"Text"=hex:00,00,00,00,ff,ff,ff,00,13,01,b3,00<br>"Text Selection"=hex:ff,ff,ff,00,00,00,00,00,19,00,19,00<br>"Memory Highlight"=hex:ff,00,00,00,ff,ff,ff,00,10,00,10,00<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Output Window]<br>"FontFace"="FixedSys"<br>"FontSize"=dword:0000000c<br>"Text"=hex:c0,c0,c0,00,00,00,00,00,15,00,15,00<br>"Text Selection"=hex:00,00,00,00,c0,c0,c0,00,19,00,19,00<br>"Current Error/Tag"=hex:ff,ff,ff,00,00,00,80,00,55,00,55,00<br>"Bookmark"=hex:00,00,00,00,00,ff,ff,00,10,00,10,00<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Registers Window]<br>"FontFace"="Fixedsys"<br>"FontSize"=dword:0000000c<br>"Text"=hex:00,00,00,00,ff,ff,ff,00,13,01,b3,00<br>"Text Selection"=hex:ff,ff,ff,00,00,00,00,00,19,00,19,00<br>"Value Highlight"=hex:ff,00,00,00,ff,ff,ff,00,10,00,10,00<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Source Browser]<br>"FontFace"="DejaVu Sans"<br>"FontSize"=dword:0000000c<br>"Text"=hex:c0,c0,c0,00,00,00,00,00,15,00,15,00<br>"Text Selection"=hex:00,00,00,00,c0,c0,c0,00,19,00,19,00<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Source Window]<br>"FontFace"="DejaVu Sans Mono"<br>"FontSize"=dword:0000000d<br>"Text"=hex:c0,c0,c0,00,00,00,00,00,12,01,b2,00<br>"Text Selection"=hex:00,00,00,00,c0,c0,c0,00,19,00,19,00<br>"Current Error/Tag"=hex:ff,ff,ff,00,00,00,80,00,d2,01,b2,01<br>"Bookmark"=hex:00,00,00,00,00,ff,ff,00,10,00,10,00<br>"Breakpoint"=hex:ff,ff,ff,00,80,00,00,00,10,00,10,00<br>"Current Statement"=hex:00,00,00,00,ff,ff,00,00,70,00,70,00<br>"Selection Margin"=hex:d4,d0,c8,00,d4,d0,c8,00,13,00,13,00<br>"Keyword"=hex:ff,00,ff,00,00,00,00,00,10,00,11,00<br>"Comment"=hex:00,e6,00,00,00,00,00,00,10,00,11,00<br>"Number"=hex:ff,ff,00,00,00,00,00,00,10,00,11,00<br>"String"=hex:ff,80,00,00,00,00,00,00,10,00,11,00<br>"Operator"=hex:c0,c0,c0,00,00,00,00,00,11,00,11,00<br>"Wizard IDL/ODL Code"=hex:80,80,80,00,00,00,00,00,14,00,15,00<br>"HTML Element Name"=hex:80,00,80,00,00,00,00,00,14,00,15,00<br>"HTML Attribute Name"=hex:ff,00,00,00,00,00,00,00,14,00,15,00<br>"HTML Attribute Value"=hex:00,00,ff,00,00,00,00,00,14,00,15,00<br>"HTML Comment"=hex:00,80,00,00,00,00,00,00,14,00,15,00<br>"HTML Entity"=hex:ff,00,00,00,00,00,00,00,14,00,15,00<br>"HTML Tag Delimiter"=hex:00,00,ff,00,00,00,00,00,14,00,15,00<br>"HTML String"=hex:00,00,ff,00,00,00,00,00,14,00,15,00<br>"HTML Tag Text"=hex:ff,00,ff,00,00,00,00,00,14,00,15,00<br>"HTML Operator"=hex:00,00,ff,00,00,00,00,00,14,00,15,00<br>"HTML Server-Side Script"=hex:00,00,00,00,ff,ff,00,00,14,00,14,00<br>"User Defined Keywords"=hex:00,00,ff,00,00,00,00,00,14,00,15,00<br>"Wizard Code"=hex:80,80,80,00,00,00,00,00,14,00,15,00<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Variables Window]<br>"FontFace"="Fixedsys"<br>"FontSize"=dword:0000000c<br>"Text"=hex:00,00,00,00,ff,ff,ff,00,13,01,b3,00<br>"Text Selection"=hex:ff,ff,ff,00,00,00,00,00,19,00,19,00<br>"Variables Highlight"=hex:ff,00,00,00,ff,ff,ff,00,10,00,10,00<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Watch Window]<br>"FontFace"="Fixedsys"<br>"FontSize"=dword:0000000c<br>"Text"=hex:00,00,00,00,ff,ff,ff,00,13,01,b3,00<br>"Text Selection"=hex:ff,ff,ff,00,00,00,00,00,19,00,19,00<br>"Watch Highlight"=hex:ff,00,00,00,ff,ff,ff,00,10,00,10,00<br>[HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Format\Workspace Window]<br>"FontFace"="Segoe UI"<br>"FontSize"=dword:0000000c<br>-----------------------------------------------------------------------------------------------------------------------<br><br><br>Visual Studio 2005 <br>复制以下代码保存成.reg文件，然后导入注册表。<br>------------------------------------------------------------------------------------------------------------------------<br>Windows Registry Editor Version 5.00</p>
<p>[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\8.0\FontAndColors\{A27B4E24-A735-4D1D-B8E7-9716E1E3D8E0}]<br>"Colorable item format version"=dword:00000008<br>"FontName"="Courier"<br>"FontPointSize"=dword:0000000a<br>"FontCharSet"=dword:00000000<br>"Plain Text Foreground"=dword:00e5e5e5<br>"Plain Text Background"=dword:00000000<br>"Plain Text FontFlags"=dword:00000000<br>"Selected Text Foreground"=dword:00000000<br>"Selected Text Background"=dword:00ebe8e7<br>"Selected Text FontFlags"=dword:80000000<br>"Line Numbers Foreground"=dword:00804000<br>"Line Numbers Background"=dword:02000000<br>"Line Numbers FontFlags"=dword:80000000<br>"Visible White Space Foreground"=dword:0080ffff<br>"Visible White Space Background"=dword:02000000<br>"Visible White Space FontFlags"=dword:80000000<br>"Collapsible Text Foreground"=dword:00c9c9c9<br>"Collapsible Text Background"=dword:00000000<br>"Collapsible Text FontFlags"=dword:00000000<br>"Comment Foreground"=dword:00c4c4c4<br>"Comment Background"=dword:001f1f1f<br>"Comment FontFlags"=dword:00000000<br>"CSS Property Name Foreground"=dword:0000ffff<br>"CSS Property Name Background"=dword:02000000<br>"CSS Property Name FontFlags"=dword:00000000<br>"CSS Property Value Foreground"=dword:0000ff00<br>"CSS Property Value Background"=dword:02000000<br>"CSS Property Value FontFlags"=dword:00000000<br>"CSS Selector Foreground"=dword:00ffff00<br>"CSS Selector Background"=dword:02000000<br>"CSS Selector FontFlags"=dword:00000000<br>"HTML Attribute Value Foreground"=dword:0000ff00<br>"HTML Attribute Value Background"=dword:02000000<br>"HTML Attribute Value FontFlags"=dword:00000000<br>"HTML Comment Foreground"=dword:0080ff80<br>"HTML Comment Background"=dword:02000000<br>"HTML Comment FontFlags"=dword:00000000<br>"HTML Element Name Foreground"=dword:0000ffff<br>"HTML Element Name Background"=dword:02000000<br>"HTML Element Name FontFlags"=dword:00000000<br>"HTML Entity Foreground"=dword:00ffb66c<br>"HTML Entity Background"=dword:02000000<br>"HTML Entity FontFlags"=dword:00000000<br>"HTML Operator Foreground"=dword:00ffffff<br>"HTML Operator Background"=dword:02000000<br>"HTML Operator FontFlags"=dword:00000000<br>"HTML Server-Side Script Foreground"=dword:00000000<br>"HTML Server-Side Script Background"=dword:00007777<br>"HTML Server-Side Script FontFlags"=dword:00000000<br>"HTML Tag Delimiter Foreground"=dword:00ffff00<br>"HTML Tag Delimiter Background"=dword:02000000<br>"HTML Tag Delimiter FontFlags"=dword:00000000<br>"Identifier Foreground"=dword:00eaeaea<br>"Identifier Background"=dword:02000000<br>"Identifier FontFlags"=dword:00000000<br>"Keyword Foreground"=dword:00ffff00<br>"Keyword Background"=dword:02000000<br>"Keyword FontFlags"=dword:00000000<br>"Number Foreground"=dword:0000a8a8<br>"Number Background"=dword:02000000<br>"Number FontFlags"=dword:00000000<br>"Operator Foreground"=dword:00bf80ff<br>"Operator Background"=dword:02000000<br>"Operator FontFlags"=dword:00000000<br>"Preprocessor Keyword Foreground"=dword:00ff6f6f<br>"Preprocessor Keyword Background"=dword:02000000<br>"Preprocessor Keyword FontFlags"=dword:00000000<br>"Read-Only Region Foreground"=dword:02000000<br>"Read-Only Region Background"=dword:00c0c0c0<br>"Read-Only Region FontFlags"=dword:00000000<br>"Register Data Foreground"=dword:02000000<br>"Register Data Background"=dword:02000000<br>"Register Data FontFlags"=dword:00000000<br>"Register NAT Foreground"=dword:00ffffff<br>"Register NAT Background"=dword:02000000<br>"Register NAT FontFlags"=dword:00000000<br>"String Foreground"=dword:0081c2fe<br>"String Background"=dword:02000000<br>"String FontFlags"=dword:00000000<br>"XML Doc Comment Foreground"=dword:00b7ffb7<br>"XML Doc Comment Background"=dword:001f1f1f<br>"XML Doc Comment FontFlags"=dword:00000000<br>------------------------------------------------------------------------------------------------------------------------<br></p>
<img src ="http://www.cppblog.com/zzh/aggbug/32402.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zzh/" target="_blank">Xiao.Zhu</a> 2007-09-18 10:12 <a href="http://www.cppblog.com/zzh/archive/2007/09/18/32402.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>杂７杂8技术资料</title><link>http://www.cppblog.com/zzh/archive/2007/08/13/29864.html</link><dc:creator>Xiao.Zhu</dc:creator><author>Xiao.Zhu</author><pubDate>Mon, 13 Aug 2007 01:22:00 GMT</pubDate><guid>http://www.cppblog.com/zzh/archive/2007/08/13/29864.html</guid><wfw:comment>http://www.cppblog.com/zzh/comments/29864.html</wfw:comment><comments>http://www.cppblog.com/zzh/archive/2007/08/13/29864.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zzh/comments/commentRss/29864.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zzh/services/trackbacks/29864.html</trackback:ping><description><![CDATA[<p>一般全局变量应该用比较长，详细的名称，而本地变量则简单明了为宜。<br>GetSystemMetrics可以获得很多系统信息。</p>
<p>＃i nclude &lt;&gt;先搜索/I编译选项的路径，然后搜索环境变量INCLUDE，""先搜索父文件的路径，然后才是前面两个路径。</p>
<p>用C设计一个动态增长的数组，需要用到MALLOC和REALLOC，对与同一个指针，如果内存不够存储数据，就用REALLOC重新分配多一倍的内存。并且返回的指针不能直接给原来的指针，因为如果分配失败，那么原来的数据就会丢失。</p>
<p>传递函数地址作为参数</p>
<p>首先定义以函数地址作为参数的函数</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>fun1(返回类型(*fun2)(参数))<br>{<br>&nbsp;&nbsp; ...=(*fun2)(参数);</p>
<p>}<br>fun2正常定义<br>调用的时候　　fun1(函数名);<br>int fun2(int n)<br>{<br>&nbsp;return n+1;</p>
<p>}</p>
<p>int fun1(int n,int(*f)(int b))<br>{<br>&nbsp;return (*f)(n);</p>
<p>&nbsp;</p>
<p>}<br>int main(int argc, char* argv[])<br>{<br>&nbsp;int a(B),b;<br>&nbsp;&nbsp;&nbsp; b=fun1(a,fun2);<br>&nbsp;&nbsp;&nbsp; cout&lt;&lt;b;<br>&nbsp;return 0;<br>}</p>
<p>实现散列表：使用一个散列函数，将项散列到一个数组里面，每个数组元素是一个链表，记录这个散列值的所有项。</p>
<p>CMemoryState 类可用于检查内存泄露。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>构造函数和析构函数都没有返回值</p>
<p>类的成员缺省是私有的.<br>如果定义了构造函数和析构函数,必须将它们设置为PUBLIC,否则无法访问.<br>(事实上也可以是private,但只能访问类的静态成员,这时不能生成对象,如果要生成对象,需要一定的技巧)</p>
<p>重载依靠参数不同而不是返回值的不同,因为我们可以在调用函数的时候忽略返回值,这个时候编译器无法确定该调用那个函数.</p>
<p>构造函数可以根据参数类型不同区分,也可以根据参数个数不同区分,但如果一个构造函数的参数有缺省值而且前面的参数与其它构造函数相同,那么如果在调用的时候用到了缺省值,编译器无法分辨该调用那个函数,会报错.</p>
<p>可以在定义函数的时候,让某个参数只有类型名而没有标识符,这样做的目的是为了将来可能需要插入一个参数,在调用的时候随便给这个位置一个值就可以了.</p>
<p>int temp(int a,int,int b)<br>{}</p>
<p>~按位求反</p>
<p>const 存放在符号表里,不会被分配内存.如果使用&amp;符号,就会强迫编译器为常量分配地址,const数组也会被分配内存.</p>
<p>const int* p；int const* p;指向常量的指针，它的内容(*p)不能被改变，也不能将它的值赋int * const p,常指针，值不变。给非常量指针。<br>对临时对象要使用常值引用接收，因为临时对象是常量。</p>
<p>对于const成员变量，必须在构造函数前(构造函数初始化表)对它赋初值(莄onst也可以在这里赋值,但没有必要,可以转到构造函数体内,虽然在初始化表里效率更高)。任何类型的成员变量都不能在声明的时候赋值。</p>
<p>类中的常量const是对某个具体对象而言的，如果需要对整个类的常值，使用enum.</p>
<p>char *p="123456";<br>cout&lt;&lt;*p++&lt;&lt;*p++;<br>*p=3;<br>char *t;<br>t=p;<br>输出结果为:21,因为求值顺序是从右到左,第三个语句是错的,*p的内存是不可写的,似乎p应该为一个常量指针,但确实可以将p赋给一个非常指针,语句5不报错.</p>
<p>如果成员函数被声明为const,那么其中不能含有改变成员变量值的语句(但可以改变被mutable修饰的成员变量)),也不能调用非const成员函数,如果对象被声明为const,那么它只能调用const成员函数.</p>
<p>volatile的用法同const,甚至可以使用const volatile做修饰没,volatile标识数据可能被别的进程改变,因此有必要在每次使用的时候重读这个数据,这在优化期间特别重要,防止编译器做一些假设.</p>
<p>给宏的参数最好是简单变量，如果不是，如a++,那么变量在宏中出现几次，a就会被加多少次。最好不用宏做类似函数的事情，在类中，用内联函数代替宏，一样可以得到高效率。</p>
<p>在类中定义的函数自动成为内联函数，在类外，使用inline关键字。</p>
<p>不应该使用public成员变量，而是应该使用内联函数存取这些变量，使用重载可以只用一个函数名字完成存取。</p>
<p>一个程序的所有文件，所有的名字（不在函数或类中）缺省都是外部连接，这意味着不同文件中相同的名字（不在函数或类中）会引起冲突，如果对这些名字用static修饰，就会变成内部连接，名字仅在编译单元内可见。extern是static的反义词，表示外部连接，同缺省的意义相同。两种连接的名字都会存储在静态存储区。</p>
<p>一旦用于修饰局部变量，static就不再表示可见性，而只改变变量的存贮类型。</p>
<p>register表示希望这个变量被放在寄存器里，因为它经常被用到，应该避免使用这个关键字，因为这方面通常机器比人更擅长。</p>
<p>类中的static变量（包括用static修饰的const）必须在类和函数外的全局位置初始化，初始化的语法是：static 类型 类::变量名=值；</p>
<p>存在嵌套类和局部类:类中定义的类和函数中定义的类,后者不能有静态成员变量(显然，没有办法初始化这种static变量).</p>
<p>调用c库,在声明的时候要使用extern "C" 函数声明,指明这是一个c连接.因为c++同c的编译器不同,会为函数产生不同的内部名,按照c++的方式连接c函数,会找不到库中的函数体.当然,通常情况下库的开发上已经为我们做好了这些.</p>
<p>引用必须被初始化,且引用的对象不能改变.不能引用null.</p>
<p>int f(const int&amp;)<br>上面是一个常量引用,使用常量引用是为了保证外部变量不被修改,另外,如果传入的是常量或者临时对象,不使用常量引用的参数将出错.因为二者都是常量.</p>
<p>无返回值的函数是void类型.</p>
<p>void inf(int*&amp;i){i++;}&nbsp; 调用:int *i=0; inf(i); <br>上面的函数是以指针引用做参数,改变指针的值.还可以使用指向指针的指针,要麻烦一些,不过表达更明确:void inf(int **i){(*i)++}; 调用时:int *i=0; inf(&amp;i); </p>
<p>通过值传递给函数，或者函数返回一个对象,是使用位拷贝建立对象,这种情况下编译器会调用拷贝构造函数(如果没有编译器会建立一个缺省的),但对象销毁时会调用析构函数.</p>
<p>如果想禁止通过值传递某个对象,只要声明一个私有的拷贝构造函数,此时编译器认为用户接管了这项工作,不会建立缺省的拷贝构造函数,而用户建立的函数是私有的,没法调用,编译器就会报错.</p>
<p>可以定义指向类的成员变量和成员函数的指针，程序不必使用函数的名字就可以调用它，想起了高通的CDMA程序框架.c++编程思想第10章。</p>
<p>一个指向函数的指针:<br>void inf(int *&amp;i){i++;}<br>int main(int argc, char* argv[])<br>{<br>int *i=0;<br>cout&lt;&lt;i&lt;&lt;endl;<br>void (*pf)(int *&amp;);<br>pf=&amp;inf;<br>(*pf)(i);<br>cout&lt;&lt;i&lt;&lt;endl;<br>}</p>
<p><br>运算符重载<br>重载仅是对用户类型的数据来说的，对内置的数据类型是不可以重载运算符的。<br>.和.*都不能重载.可以将运算符重载看作另外一种形式的函数调用，函数的名字是operator@,@代表运算符，参数的个数取决于两个因素：<br>1 运算符是一元还是二元<br>2 运算符是全局函数(一元是一个参数,二元是两个参数),还是成员函数(一元没有参数,二元一个参数----对象变为左侧参数)</p>
<p>可以重载几乎所有的运算符,但对于现在c中没有意义的运算符是不能重载的,也不能改变运算符的参数个数和优先级.</p>
<p>重载运算符的返回值:如果需要返回对象本身,根据需要返回<br>对象的指针或者引用,如果是返回临时生成的对象,那么返回对象.</p>
<p>重载运算符的返回值是否常量:当返回的是一个临时值得时候,如:%,&amp;,&gt;&gt;,这些运算符得到的结果要赋给另外一个变量,这时返回值是const,如果返回值直接用于变量,如-=,+=,这是返回值不要加const.</p>
<p>函数返回对象的时候,返回一个临时对象比新建一个对象在返回效率要高很多,因为这时调用的是普通构造函数而不是拷贝构造函数,而且不需要调用析构函数,虽然新建一个对象再返回返回的也是一个临时对象.</p>
<p>智能指针(smart pointer):对象,包容器,迭代器.</p>
<p>自动类型转换:可以编程实现自动类型转换.如需要从对象one到two,那么只需要为two定义一个以one&amp;为参数的构造函数,当编译器发现需要进行从对象one到two的转换的时候,会自动检查two的定义,找到这个构造函数,构造一个two对象.如果需要显式类型转换,在构造函数前加一个:explicit</p>
<p>还有一种自动类型转换方法是:为需要转换的对象重载一个运算符,运算符以要转换到的对象的名字命名.无须声明返回值.<br>operator one() const{ return one(x);}</p>
<p><br>不过并不提倡隐式类型转换,这样容易隐藏错误,也会降低调用时的效率.<br>&nbsp;<br>使用全局重载运算符而不是成员运算符的好处是可以对左右操作书都自动作类型转换,而成员运算符的操作数左侧的必须是正确的对象</p>
<p>重载赋值操作符"=",返回可以是引用也可以是值,前者效率较高,但要记得此时返回的引用不能是属于局部对象的.通常返回*this.</p>
<p>return String(s1+s2); 与String temp(s1+s2);return temp;的效率是不同的，后者要进行对象拷贝，而前者直接将临时对象创建在函数的返回区。同时也更加简洁。</p>
<p>函数中少用static变量。让相同的输入产生相同的输出，这样的代码便于使用和维护。</p>
<p>对函数的参数和返回值的有效性进行检查。</p>
<p>积极使用断言(ASSERT),同时要加上注释，防止将来忘记ASSERT的目的。</p>
<p>之所以有了指针还要引入引用，是为了对功能加以限制，防止发生意外，就像对参数加上const限定的目的一样。</p>
<p>动态分配内存的原则:<br>1 分配后要检查是否分配成功,即if(p==NULL)<br>2 释放内存后要记得令p=NULL,防止产生野指针.野指针会让我们在使用指针前的if(p==NULL)检查形同虚设.</p>
<p>要申请一块内存复制数组char a[]的内容,应该申请的内存大小是sizeof(char)*(strlen(a)+1);</p>
<p>如果通过参数传递数组,数组名自动退化为一个指针.<br>main()<br>{<br>&nbsp;char a[100];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;sizeof(a);<br>&nbsp;fun(a);<br>}<br>void fun(char a[100])<br>{<br>&nbsp;&nbsp; cout&lt;&lt;sizeof(a);<br>}<br>输出100 4.</p>
<p>对内存分配失败进行处理有两种方法:<br>1 if(p==NULL) 适用于内存分配语句较少的情况<br>2 _set_new_handler&nbsp; _set_new_mode 适用于内存分配语句较多的情况</p>
<p>unsigned与没有unsigned 类型只是表示范围不同,大小相同.</p>
<p>&nbsp;</p>
<p>如果不给类定义拷贝构造函数和赋值函数，如果类中有指针变量，就会导致错误，如果指针指向动态内存区，那这块内存会丢失，而两个指针相同一个块内存，导致其值无法判定，而且两个函数的析构函数会将这块内存释放两次，导致出错。<br>String a("hello");<br>String b("world");<br>String c(a); //调用拷贝构造函数，还可以写成：String c=a;但风格较差。<br>c=a;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //调用赋值函数(operator =) 赋值函数中注意先检查自赋值。</p>
<p>在继承当中，构造函数，析构函数，赋值函数都不能被继承，在编写子类时要注以下几点：<br>1子类必须在构造函数的初始化表调用基类的构造函数。<br>2父类和子类的析构函数都必须是virtual.//用于多态。<br>3子类赋值函数要调用父类的赋值函数：Base::operater=(other);</p>
<p><br>对函数参数和返回值进行const限定仅对指针和引用有意义，对值传递没有意义，对输出参数一定不要用const,不然无法输出参数。</p>
<p>重载new和delete的原因有两个：需要反复分配内存，需要亲自做这个工作提高效率，还有就是减少内存碎片，比如可以首先使用静态成员指针保留很大一块内存(在静态存储区)，在其中完成内存的分配，并自己标记分配和释放，释放的时候，只是标记内存，而不free释放。<br>重载的new和delete只完成内存的分配和回收工作。new接受size_t函数，完成内存的分配，返回一个void*指针，delete接受一个void*指针，将它释放。<br>注意重载new和delete有两种不同的形式，一个用于每次创建一个对象，另一个用来创建一个对象数组，需要加上[]。如果重载了前者，那么在创建对象数组的时候，系统会调用全局的new和delete.</p>
<p>发现一个有趣的现象，可以使用值为NULL的指针调用任意对象的成员函数，只要先强制转换到这个对象，并且调用的是纯代码。</p>
<p>成员对象的初始化可以和父类构造函数的调用并排放在初始化表。</p>
<p>在进入构造函数的左括号前，所有的成员变量都必须被初始化。</p>
<p>构造函数，析构函数，赋值运算赋不被继承。</p>
<p>类的友元能够访问其private,protected成员，子类能访问类的protected成员。</p>
<p>不要在析构函数中抛出异常，因为异常处理函数在获得异常后要调用析构函数清理对象，此时再发生异常会导致程序无法再捕获异常，只能终止（只能在自定义的set_terminate()中作最后的处理。）。</p>
<p>拷贝字符串的方法<br>char dest[sz];<br>memset(dest,0,sz);<br>strncpy(dest,source,sz-1);<br>这样保证了不会超过缓冲区且结尾为'\0'.</p>
<p>异常处理函数会首先调用所有在try块中创建了的对象的析构函数，然后执行异常处理函数，然后继续运行后面的程序。但问题是，如果一个析构函数出现了异常，在析构函数中异常前创建的堆上的所有对象都无法调用其析构函数正常销毁。方法是使用模板，并自初始化表创建这些模板对象。</p>
<p>set_unexpceted可以截获没有被函数异常规格说明包括得异常，还可以简单的用一个throw;将这个异常作为已知异常再次抛出，如果有相应的catch语句，那么就可以捕获这个异常。</p>
<p>抛出异常的子类，会被能够捕获其父类异常的处理器捕获。这时会产生切片，即处理器收到的是一个父类，使用引用而不是传递值可以避免这个问题。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;throw(except("got it"));<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;catch(except &amp;t)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;t.what();<br>&nbsp;&nbsp;}</p>
<p><br>运行时类形识别对void指针无效。</p>
<p>dynamic_cast&lt;&gt;用于向下映射。<br>base* b=new derived;<br>derived* d=dynamic_cast&lt;derived*&gt;b; 如果dynamic_cast失败的话，将返回NULL,可以以此来试探着判断指针b的类型。</p>
<p>RTTI还可以使用typeinfo().name()的方法返回对象id。typeinfo()返回typeinfo对象，使用前要包含头文件typeinfo.h.</p>
<p>class B<br>class D:public B</p>
<p>B* p=new D;<br>B&amp; r=*p;</p>
<p>typeid(p)==typeid(B*)<br>typeid(r)!==typeid(D)<br>typeid(*p)==typeid(D)<br>typeid(&amp;r)==typeid(B*)</p>
<p>对引用的动态映射也要制定到一个引用上，如果失败不是返回NULL,因为应用不许为空，而是产生一个异常，因此对引用的动态映射必须使用异常处理。</p>
<p>对空指针使用typeid()也会产生异常，可以在使用之前检查指针是否为NULL来避免这个问题。</p>
<p>在对重继承的情况下，传统的强制类型转换可能无法正常工作，但动态映射和typeid工作的很好。</p>
<p>要是动态类型类型转换，需要基类包含virtual成员函数，并且vc编译器有/GR选项。经过动态类型转换，由父类转换而来的子类指针可以调用子类中新添加而父类中没有的方法。</p>
<p>static_cast 通常不是必需的，但它会让类型转换更加醒目。</p>
<p>const_cast用于将常量和volatile映射给普通指针。</p>
<p>reinterpret_cast是危险并且可移植性很差的转换，它将对象看作二进制数进行转换。最好不要使用。</p>
<p>C++中，将结构名直接作为类型名使用，而不需要象c中那样使用typedef&nbsp; struct 结构名{} 类型名;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WINDOWS核心编程<br>内核对象：每个内核对象都是一个内存块，由内核维护，进程在创建了一个内核对象后获得一个句柄，通常一个进程的句柄对另外一个进程是没有意义的，但可以通过一定措施在进程间共享内核对象。当进程终止后，它创建的内核对象不一定消失，内核维护每个内核对象的引用计数。</p>
<p><br>GDI对象不是内核对象，区分内核对象和GDI对象的方法是内核对象的创建函数的参数中有安全属性，而GDI对象没有。</p>
<p>内核对象的安全属性通常在创建服务器程序的时候用到，传递一个NULL可以获得缺省的安全属性。</p>
<p>当不再使用某个内核对象的时候，可以使用BOOL CloseHandle(HANDLE)关闭句柄，系统会自动将内核对象信息清除出进程的句柄表（此句柄表保存且仅保存该进程使用的所有内核对象信息。），并自动为内核对象的引用计数减一。如果忘记关闭句柄也不要紧，在进程推出后，系统会自动检查进程的句柄表，清理没有释放的句柄，因此忘记关闭句不一定会造成内存泄漏。</p>
<p>程序的进入点WinMain的第一个参数时进程的实例句柄，也是进程映射到虚拟地址空间的起始地址，vc++默认是0x00400000.可以用GetModuleHandle()得到这个值<br>PTSTR GetCommandLine()获得命令行<br>PWSTR CommandLineToArgvW()分解命令行</p>
<p>每个进程都有一个与他相关的环境块。<br>VarName1=VarValue1\0<br>VarName2=VarValue2\0<br>...............<br>\0</p>
<p>GetEnvironmentVariable()&nbsp; //获得环境变量值<br>ExpandEnvironmentStrings()//展开%包裹的环境变量值<br>SetEnvironmentVariable()&nbsp; //设定环境变量</p>
<p>进程的亲缘性：进程的线程被强迫再CPU的子集上运行。</p>
<p>子进程默认继承父进程的错误标志。<br>SetErrorMode(UINT)&nbsp; //设定错误模式</p>
<p>进程维护当前驱动器和目录信息<br>GetCurrentDirectory()<br>SetCurrentDirectory()</p>
<p>获得系统版本：<br>GetVersion()<br>GetVersionEx()<br>VeryfyVersionInfo()</p>
<p><br>GetExitCodeProcess(),对于还在运行的进程，可以得到0x103（STILL_ACTIVE),对于终止的进程，如果还没有CloseHandle(pi.hProcess),可以得到它的退出码,否则得到的是乱码。</p>
<p>windows2000支持作业管理， 通过将进程加入作业，可以对进程的运行权限，使用的资源进行限制。方法如下：<br>HANDLE hjob=CreateJobObject(NULL,NULL);//创建一个作业对象。<br>SetInformationJobObject();//设定作业对象的参数，包括对进程的各种限制。<br>CreateProcess(NULL,"CMD",NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&amp;si,&amp;pi);//创建新进程。<br>AssignProcessToJobObject(hjob,pi.hProcess); //将进程加入作业。可加入多个。<br>ResumeThread(pi.hThread);<br>CloseHandle(pi.hThread);<br>HANDLE h[2];<br>h[0]=pi.hProcess;<br>h[1]=hjob;<br>DWORD dw=WaitForMultipleObject(2,h,false,INFINITE);<br>switch(dw-WAIT_OBJECT_0）<br>&nbsp; case 0://the process has terminated..<br>&nbsp; case 1://all of the job's allotted cpu time was used.<br>}<br>CloseHandle(pi.hProcess);<br>CloseHandle(hjob);</p>
<p>终止作业中所有进程的运行<br>TerminateJobObject(hjob,UINT uExitCode)</p>
<p>查询作业统计信息<br>QueryInformationJobObject();</p>
<p>监视作业的运行：<br>JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp;//创建一个I/O完成端口对象<br>SetInformationJobObject(hjob.JobObjectAssociateCompletionPortInformation,&amp;joacp,sizeof(joacp)<br>//将作业同完成端口对相关联。<br>GetQueuedCompletionStatus()&nbsp; //监控I/O端口。</p>
<p>进程由两部分组成：进程内核对象， 地址空间。进程是不活泼的，它的执行依赖于线程。<br>线程由两部分组成：线程内核对象，线程堆栈。</p>
<p><br>创建新线程：<br>DWORD　WINAPI FUNC(PVOID pvParam)<br>int Param;<br>DWORD dwThreadID;<br>CreateThread(NULL,0,FUNC,(PVOID)&amp;Param,0,&amp;dwThreadID);</p>
<p>检查线程是否退出：<br>BOOL GetExitCodeThread(HANDLE hThread,PDWORD pdwExitCode);//如果还未终止，得到0x103.</p>
<p><br>获得伪句柄：<br>GetCurrentProcess()<br>GetCurrentThread()</p>
<p>获得运行时间：<br>GetProcessTimes()<br>GetThreadTimes()</p>
<p>线程或进程的伪句柄转化为实句柄：<br>DuplicatgeHandle();//此函数会增加内核对象的引用计数。</p>
<p>伪句柄用于本线程，获得这个句并不会影响内核对象的计数，而实句柄用于传递给子进程。</p>
<p>线程的暂停和运行：<br>ResumeThread(HANDLE)<br>SuspendThread(HANDLE)&nbsp; //使用此函数要小心死锁。</p>
<p>线程休眠：<br>Sleep(DWORD dwMilliseconds);<br>自动退出当前时间片：<br>SwitchtoThread();</p>
<p>可以获得和修改线程的上下文，使用之前要SuspendThread()<br>GetThreadContext()<br>SetThreadContext()</p>
<p>改变进程的优先级://记住进程是不可以调度的，调度的单位是线程。<br>BOOL　SetPriorityClass();<br>DWORD GetPriorityClass();</p>
<p>设定线程的相对优先级：<br>int GetThreadPriority(HANDLE hThread);<br>BOOL SetThreadPriority(Handle hThread,int nPriority);</p>
<p>Microsoft保留了随时修改调度算法的权利，因此使用相对优先级，可以保证程序在将来的系统上也可以正常运行。<br>结合进程优先级和线程的相对优先级，就可以得到线程的基本优先级。线程的当前优先级不可以低于基本优先级，<br>也就是说，系统会提高线程的优先级，并随着执行时间片的流逝降低优先级，但降到基本优先级后就不再降了。<br>优先级0-15成为动态优先级范围，高于15是实时范围，系统不会调度实时范围线程的优先级，也不会把动态优先级范围的<br>线程提高到15以上。</p>
<p>亲缘性是对多处理器系统来说的，为了能利用保留在cpu高速缓存和NUMA(非统一内存访问）结构计算机本插件板上内存中的数据，系统尽量线程上次运行使用的CPU来运行线程，包括软亲缘性（WIN2000默认）和硬亲缘性（用户可以选择CPU)<br>相关的函数有：<br>BOOL SetProcessAffinityMask(HANDLE hProcess,DWORD_PTR dwProcessAffinityMask);<br>BOOL GetProcessAffinityMask(Handle hProcess,PDWORD_PTR pdwProcessAffinityMask,PDWORD_PTR pdwSystemAffinityMask);<br>DWORD_PTR SetThreadAffinityMask(HANDLE hThread,DWORD_PTR dwThreadAffinityMask);<br>DWORD_PTR SetThreadIdealProcessor(HANDLE hThread,DWORD dwIdealProcessor);</p>
<p>临界区保证其中的资源（通常是各种共享变量）被原子的访问，当进入临界区后，其他访问这些资源的线程将不会被调度。</p>
<p>线程同步包括用户方式和内核方式，用户方式包括原子操作和临界区，它的特点是速度快，但功能有限。内核方式利用内核对象的通知状态来同步线程，由于需要由用户方式切换到内核方式（这种切换很废时间），且系统要进行很多操作，效率较低，但功能强大（能够设定超时值等,可以同步多个进程的线程）。</p>
<p>内核方式同步的原理：线程使自己进入休眠状态，等待内核对象由未通知状态变为已通知状态。</p>
<p>可处于未通知状态变和已通知状态的内核对象：进程，线程，作业，文件修改通知，时间，可等待定时器，文件，控制台输入，信号量，互斥体。</p>
<p>进程和线程在建立时处于未通知状态，在退出时变为已通知状态。</p>
<p>等待函数：<br>DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMilliseconds);<br>DWORD WaitForMultipleObject(DWORD dwCount,CONST HANDLE* phObjects,BOOL fWaitALL,DWORD dwMilliseconds);其中，<br>0&lt;dwCount&lt;WAIT_OBJECTS(windows头文件中定义为64），如果设定fWaitALl为TRUE,那么函数会知道左右对象变为已通知状态才会返回，如果传递FALSE，那么只要有一个对象变为已通知状态，函数就会返回。<br>返回值的含义：<br>HANDLE h[3];<br>h[0]=hProcess1;<br>h[1]=hProcess2;<br>h[2]=hProcess3;<br>DWORD dw=WaitForMultipleObject(3,h,FALSE,5000);<br>switch(dw)<br>{<br>&nbsp;&nbsp;&nbsp; case WAIT_FAILED://Bad call to function(invalid handle?)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp; case WAIT_TIMEOUT：//None of the object became signaled within 5000 milliseconds.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp; case WAIT_OBJECT_0+0:The process identified by h[0] terminated.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp; case WAIT_OBJECT_0+1:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp; case WAIT_OBJECT_0+2:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>}<br>//WaitForSingleObject()的返回值只有前三种情况。如果给WaitForMutipleObject()的fWaitAll参数传递TRUE,那么其返回值也只有前三种。</p>
<p>事件内核对象：有两种，人工事件对象：当它得到通知的时候，所有等待的线程都变为可调度线程；自动重置的事件：当事件得到通知的时候，只有一个等待线程变为可调度的线程。创建事件内核对象：<br>HANDLE CreateEvent(PSECURITY_ATTRIBUTES psa,BOOL fManualReset,BOOL fInitialState,PCTSTR pszName);<br>将事件改为通知状态：<br>BOOL SetEvent(HANDLE hEvent);<br>将事件改为未通知状态：<br>BOOL ResetEvent(HANDLE hEvent);<br>如果事件是自动重置事件，那么成功等待会产生副作用，即将事件自动置为未通知状态。如果是人工事件对象，则没有副作用。</p>
<p>等待定时器内核对象：是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象。<br>HANDLE CreateWaitableTimer(PSECURITY_ATTRIBUTES psa,BOOL fManualReset,PCSTR pszName);<br>初始总是未通知状态。<br>BOOL SetWaitableTimer(<br>HANDLE hTimer,<br>const LARGE_INTEGER *pDueTime,<br>LONG lPeriod,<br>PTIMERAPCROUTINE pfnCompletionRoutine,<br>PVOID pvArgToCompletionRoutine,<br>BOOL fResume);<br>取消定时器：<br>BOOL CancelWaitableTimer(HANDLE hTimer);<br>如果仅想改变报时条件，不用调用这个函数暂停报时器，直接调用SetWaitableTimer()就可以了。</p>
<p>信号量内核对象<br>如果当前资源的数量大于0，发出信号<br>如果当前资源数量等于0，不发出信号<br>决不允许资源数量为负值。<br>创建信号量：<br>HANDLE CreateSemaphore(PSECURITY_ATTRIBUTE psa,<br>LONG lInitialCount,<br>LONG lMaximumCount,<br>PCSTR pszName);<br>递增资源：<br>BOOL ReleaseSemaphore(HANDLE hsem,<br>LONG lReleaseCount,<br>PLONG plPreviousCount);</p>
<p>互斥体内核对象：互斥体确保对单个资源的互斥访问。它包含一个使用数量，一个线程ID,一个递归计数器<br>与临界区的区别：能够同步多个进程中的线程，可以设定超时值。<br>如果ID为0，那么表示没有线程占用互斥体，互斥体发出信号。<br>如果ID不为0，表示占用资源的线程ID，不发出信号。</p>
<p>HANDLE CreateMutex(PSECURITY_ATTRIBUTES psa,<br>BOOL fInitialOwner,<br>PCTSTR　pszName);</p>
<p>释放资源：<br>BOOL ReleaseMutex(HANDLE hMutex);</p>
<p>额外的函数：<br>DWORD SingalObjectAndWait(<br>HANDLE hObjectToSignal,<br>HANDLE hObjectToWaitOn,<br>DWORD dwMilliseconds,<br>BOOL fAlertable);<br>发出一个通知信号并等待另一个通知，效率比分别操作提高很多。</p>
<p>windows2000提供了如下几种线程池函数用于线程管理：<br>一、异步调用函数:<br>BOOL QueueUserWorkItem(<br>PTHREAD_START_ROUTINE pfnCallback,<br>PVOID pvContext，<br>ULONG dwFlags);<br>该函数将&#8220;工作项目&#8221;放入线程池并且立即返回。工作项目是指一个用pfnCallback参数标识的函数。它被调用并且传递单个参数pvContext.工作项目函数原型如下：<br>DWORD WINAPI WorkItemFunc(PVOID pvContext);<br>dwFlags参数：WT_EXECUTEDEFAULT&nbsp; 工作项目放入非I/O组件得线程中<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WT_EXECUTEINIOTHREAD 工作项目放入I/O组件的线程中，这样的线程在I/O请求没有完成之前不会被终止运行&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ，防止因为线程被终止导致I/O请求丢失。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WT_EXECUTEINPERSISTENTTHREAD 放入永久线程池，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WT_EXECUTELONGFUNCTION&nbsp; 工作项目需要长时间的工作，系统会据此安排更多的线程。</p>
<p>线程池不能设置线程个数的上限，否则排队个数超过线程个数上限的时候，会导致所有的线程都被中断。</p>
<p>工作项目函数如果访问了已经被卸载的DLL,会产生违规访问。</p>
<p><br>二、按规定的时间间隔调用函数<br>创建定时器队列：<br>HANDLE CreateTimerQueue();<br>在队列中创建定时器：<br>BOOL CreateTimerQueueTimer(<br>PHANDLE phNewTimer,<br>HANDLE hTimerQueue,<br>WAITORTIMERCALLBACK pfnCallback,<br>PVOID pvContext,<br>DWORD dwDueTime,<br>DWORD dwPeriod,<br>ULONG dwFlags);<br>工作回调函数原型如下：<br>VOID WINAPI WaitOrTimerCallback(<br>PVOID pvContext,<br>BOOL fTimerOrWaitFired);<br>dwFlags比前面的多了一个标志：WT_EXECUTEINTIMERTHREAD,表示由组件的定时器线程（定时器组件只有一个线程）运行这个<br>工作函数，此时的工作函数必须是很快返回的，否则定时器组件将无法处理其他的请求。</p>
<p>删除定时器：<br>BOOL DeleteTimerQueueTimer(<br>HANDLE hTimerQueue,<br>HANDLE hTimer,<br>HANDLE hCompletionEvent);<br>在定时器线程中删除定时器会造成死锁。设定hCompletionEvent为INVALID_HANDLE_VALUE，那么在定时器的所有排队工作项目没有完成之前，DeleteTimerQueueTimer不会返回，也就是说在工作项目中对定时器进行中断删除会死锁。可以给hCompletionEvent传递事件句柄，函数会立即返回，在排队工作完成之后，会设置该事件。</p>
<p>重新设定定时器：//不能修改已经触发的单步定时器。<br>BOOL ChangeTimerQueueTimer(<br>HANDLE hTimerQueue,<br>HANDLE hTimer,<br>ULONG dwDueTime,<br>ULONG dwPeriod;</p>
<p>删除定时器队列：<br>BOOL DeleteTimerQueueEx(<br>HANDLE hTimerQueue,<br>HANDLE hCompletionEvent);</p>
<p>三、当单个内核对象变为已通知状态时调用函数<br>BOOL RegisterWaitForSIngleObject(<br>PHANDLE phNewWaitObject,<br>HANDLE hObject,<br>WAITORTIMERCALLBACK pfnCallback,<br>PVOID pvContext,<br>ULONG dwMilliseconds,<br>ULONG dwFlags);<br>pfnCallBack原型：<br>VOID WINAPI WaitOrTimerCallbadkFunc(<br>PVOID pvContext,<br>BOOLEAN fTimerorWaitFired);<br>如果等待超时，fTimerorWaitFired==TRUE,如果是已通知状态，则为FALSE.</p>
<p>dwFlags可以传递参数：WT_EXECUTEINWAITTHREAD,它让等待组件得线程之一运行工作项目函数。注意项同前。</p>
<p>如果等待的内核对象是自动重置的，那么会导致工作函数被反复调用，传递WT_EXECUTEONLYONCE会避免这种情况。</p>
<p>取消等待组件的注册状态：<br>BOOL UnregisterWaitEx(<br>HANDLE hWaitHandle,<br>HANDLE hCompletionEvent);</p>
<p>四、当异步I/O请求完成时调用函数<br>将设备和线程池的非I/O组件关联<br>BOOL BindIoCompletionCallback(<br>HANDLE hDevice,<br>POVERLAPPED_COMPLETION_ROUTINE pfnCallback,<br>ULONG dwFlags//始终为0);</p>
<p>工作函数原型：<br>VOID WINAPI OverlappedCompletionRoutine(<br>DWORD dwErrorCode,<br>DWORD dwNumberOfBytesTransferred,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ,<br>POVERLAPPED pOverlapped);</p>
<p>Windows的内存结构</p>
<p>从98，2000，到64位的windows,内存管理方式都是不同的，32位的win2000用户内存是从0x10000到0x7fffffff（64kB-2G),2000 Advanced server可以达到（64kB-3G),其中最高64kB也是禁止进入的。再往上则由系统使用。98则是从0x400000-0x7fffffff(4M-2G),2G-3G是系统用来存放32位共享数据的地方，如很多系统动态连接库。0-4M是为了兼容16位程序保留的。3G-4G由系统自身使用。98的内核区是不受保护的，2000受保护。<br>对虚拟地址空间的分配称作保留，使用虚拟内存分配函数(VirtualAlloc),释放使用VirtualFree(),目前，所有cpu平台的分配粒度都是64kB,页面大小则不同，x86是4kB,Alpha是8kB,系统在保留内存的时候规定要从分配粒度边界开始，并且是页面的整数倍，用户使用VirtualAlloc都遵守这个规定，但系统不是，它是从页面边界开始分配的。</p>
<p>将物理存储器映射到保留的内存区域的过程称为提交物理存储器，提交是以页面为单位进行的，也使用VirtualAlloc函数。</p>
<p>物理存储器是由内存和（硬盘上的）页文件组成的，如果访问的数据是在页文件中，则称为页面失效，cpu会把访问通知操作系统，操作系统负责将数据调入内存，并指导cpu再次运行上次失效的指令。</p>
<p>当启动一个程序的时候，系统并不是将整个文件读入内存或者页文件，而是将这个文件直接映射到虚拟内存空间，并将需要的数据读入内存，即将硬盘上的文件本身当作页文件（虽然不是）。当硬盘上的一个程序的文件映像（这是个exe文件或者dll文件）用作地址空间的物理存储器，它称为内存映射文件。当一个.exe或者dll文件被加载时，系统将自动保留一个地址空间的区域，并将该文件映射到该区域中。但系统也提供了一组函数，用于将数据文件映射到一个地址空间的区域中。</p>
<p><br>物理存储器的页面具有不同的保护属性：<br>PAGE_NOACESS<br>PAGE_READONLY<br>PAGE_READWRITE<br>PAGE_EXECUTE<br>PAGE_EXECUTE_READ<br>PAGE_EXECUTE_READWRITE<br>PAGE_WRITECOPY<br>PAGE_EXECUTE_WRITECOPY<br>后两个属性是配合共享页面机制使用的。WINDOWS支持多个进程共享单个内存块，比如运行notepad的10个实例，可以让他们共享应用程序的代码和数据，这样可以大大提高性能，但要求该内存块是不可写的。于是系统在调入.exe或者dll的时候，会计算那些页面是可以写入的，为这些页面分配虚拟内存。然后同其他的页面一起映射到一块虚拟内存，但赋PAGE_WRITECOPY或者PAGE_EXECUTE_WRITECOPY属性(通常包含代码的块是PAGE_EXECUTE_READ，包含数据的块是PAGE_READWRITE)。当一个进程试图将数据写入共享内存块时，系统会进行如下操作：寻找预先分配的一个空闲页面，将试图修改的页面拷贝到这个空闲页面，赋予PAGE_READWRITE或者PAGE_EXECUTE_READWRITE属性，然后更新进程的页面表，使得用户可以对新的页面进行写入。</p>
<p>还有三个特殊的保护属性：PAGE_NOCACHE PAGE_WRITECOMBINE PAGE_GUARD,前两个用于驱动程序开发，最后一个可以让应用程序在页面被写入的时候获得一个异常。</p>
<p>块的意思是一组相邻的页面，它们具有相同的保护属性，并且受相同类型的物理存储器支持。<br>赋予虚拟内存页面保护属性的意义是为了提高效率，而且这个属性总会被物理存储器的保护属性取代。</p>
<p>如果数据在内存中没有对齐，那么cpu要多次访问才能得到数据，效率很低。</p>
<p>内存管理函数：</p>
<p>获得系统信息：<br>VOID GetSystemInfo(LPSYSTEM_INFO psinf);//可以得到页面大小，分配粒度，最大内存地址，最小内存地址。</p>
<p>获得内存状态：<br>VOID GlobalMemoryStatus(LPMEMORYSTATUS pmst);</p>
<p>获得内存地址的某些信息：<br>DWORD VirtualQuery(<br>LPVOID pvAddress,<br>PMEMORY_BASIC_INFORMATION pmbi,<br>DWORD dwLength);</p>
<p>DWORD VirtualQuery(<br>HANDLE hProcess,<br>LPVOID pvAddress,<br>PMEMORY_BASIC_INFORMATION pmbi,<br>DWORD dwLength);</p>
<p>内存映射文件的优点：<br>1 节省页面文件;<br>2 加快程序启动；<br>3 在多个进程间共享数据。</p>
<p>进程的启动过程：<br>系统首先将.exe文件映射到地址空间，缺省基地址是0x400000,然后查询.exe的输入表，将其使用的所有.dll也映射到地址空间（基地址在每个.dll文件中，如果不能满足，需要重定位），然后将执行.exe的启动代码。此时.exe文件还在硬盘上。每次代码跳到一个尚未加载到内存的指令地址，就会出现一个错误，系统会发现这个错误，并将代码加再到内存中。<br>如果再创建这个.exe文件的一个实例。那么直接将原来的地址空间中的内容映射到新的地址空间就可以了。这样多个实例就可以共享相同的代码和数据。如果某个实例要改变共享内容，系统就为要更改的页面申请一个新的页面，将内容拷贝一份，然后用新的页面代替地址空间中原来页面的映射就可以了。98同2000不同，它不待修改便立即为所有的实例分配新的页面。</p>
<p>使用内存映射文件：<br>1 创建或打开一个文件内核对象：<br>HANDLE CreateFile(<br>PCSTR pszFileName,<br>DWORD dwDesiredAccess,<br>DWORD dwShareMode,<br>PSECURITY_ATTRIBUTES psa,<br>DWORD dwCreationDisposition,<br>DWORD dwFlagsAndAttributes,<br>HANDLE hTemplateFile);<br>失败的返回值是INVALID_HANDLE_VALUE<br>2 创建一个文件映射内核对象：<br>HANDLE CreateFileMapping(<br>HANDLE hFile,<br>PSECURITY_ATTRIBUTES psa,<br>DWORD fdwProtect,<br>DWORD dwMaximumSizeHigh,<br>DWORD dwMaximumSizeLow,<br>PCTSTR pszName);<br>如果给函数的fdwProtect传递PAGE_READWRITE标志，那么磁盘上文件的大小会变为同映像文件相同大小。<br>失败的返回值是NULL。</p>
<p>3 将文件映射到进程的地址空间：<br>PVOID MapViewOfFile(<br>HANDLE hFileMappingObject,<br>DWORD dwDesiredAccess,<br>DWORD dwFileOffsetHigh,<br>DWORD dwFileOffsetLow,<br>SIZE_T dwNumberOfBytesToMap);<br>windows2000会根据要求将部分文件映射到地址空间，而win98总是把全部内容映射到地址空间，并且仅能映射到2G-3G空间，此空间为共享空间，所有的进程如果映射相同的文件，那么都会映射到相同的地址，一个进程甚至不必映射就可以访问这个空间里其他进程的映射文件，win2000多个进程映射同一个文件返回的地址通常是不同的。<br>4 从进程的地址空间中撤销文件数句的映像<br>BOOL UnmapViewOfFile(PVOID pvBaseAddress);</p>
<p>将文件映像写入磁盘：<br>BOOL FlushViewOfFile(<br>PVOID pvAddress,<br>SIZE_T dwNumberOfBytesToFlush);</p>
<p>windows保证单个文件映射对象的多个视图具有相关性。但不保证但个文件的多个映射对象有相关性。</p>
<p>使用MapViewOfFileEx代替MapViewOfFile可以设定文件映射的基地址：<br>PVOID MapViewOfFileEx(<br>HANDLE hFileMappingObject,<br>DWORD dwDesiredAccess,<br>DWORD dwFileOffsetHigh,<br>DWORD dwFileOffsetLow,<br>SIZE_T dwNumberOfBytesToMap,<br>PVOID pvBaseAddress);</p>
<p>使用内存映射文件在进程间共享数据<br>共享机制：RPC ,COM,OLE,DDE,窗口消息（WM_COPYDATA),剪贴板，邮箱，管道，套接字。<br>在单机上，它们的底层实现方法都是内存映射文件。</p>
<p>可以在页文件中直接创建文件映射对象，方法是给CreateFileMapping函数的hFile参数传递INVALID_HANDLE_VALUE.注意，<br>CreateFile()函数运行失败也会返回这个参数，因此一定要检查CreateFile（）的返回值。记住，文件函数运行失败的可能性太大了。</p>
<p>第三章：多个进程共享对象。</p>
<p>堆栈：优点：可以不考虑分配粒度和页面边界之类的问题，集中精力处理手头的任务，缺点是：分配和释放内存块的速度比其他机制慢，并且无法直接控制物理存储器的提交和回收。</p>
<p>进程的默认堆栈是1MB,可以使用/HEAP链接开关调整大小，DLL没有相关的堆栈。</p>
<p>堆栈的问题在于：很多windows函数要使用临时内存块，进程的多个线程要分配内存块，这些内存都是在默认堆栈上分配的，但规定时间内，每次只能由一个线程能够分配和释放默认堆栈的内存块，其他想要处理内存块的线程必须等待。这种方法对速度又影响。可以为进程的线程创建辅助堆栈，但windows函数只能使用默认堆栈。</p>
<p>获取进程默认堆栈句柄：<br>HANDLE GetProcessHeap();</p>
<p>创建辅助堆栈的理由<br>1 保护组件：<br>多个组件的数据混合交叉的存放在一块内存里，那么一个组件的错误操作很容易影响到另外一个组件。而要定位错误的来源将十分困难。<br>2 更有效的内存管理<br>通过在堆栈中分配同样大小的对象，可以更加有效的管理内存，减少内存碎片。<br>3 进行本地访问：<br>将同种数据集中到一定的内存块，可以在操作的时候访问较少的页面，这就减少了RAM和硬盘对换的可能.<br>4 减少线程同步的开销：<br>通过告诉系统只有一个线程使用堆栈(创建堆栈时使用HEAP_NO_SERIALIZE标志给fdwOptions)，可以避免堆栈函数执行额外的用于保证堆栈安全性的代码，提高效率，但此时用户必须自己维护线程的安全性，系统不再对此负责。<br>5 迅速释放堆栈。<br>因为数据单一，因此释放的时候只要释放堆栈即可，不必显示的释放每个内存块。</p>
<p>创建辅助堆栈：<br>HANDLE HeapCreate(<br>DWORD fdwOptions,<br>SIZE_T dwInitialSize,<br>SIZE_T dwMaximumSize);</p>
<p>从堆栈中分配内存：<br>PVOID HeapAlloc(<br>HANDLE hHeap,<br>DWORD fdwFlags,<br>SIZE_T dwBytes);注意：当分配超过（1MB)内存块的时候，最好使用VirtualAlloc（）；</p>
<p>改变内存块的大小：<br>PVOID HeapReAlloc(<br>HANDLE hHeap,<br>DWORD fdwFlags,<br>PVOID pvMem,<br>SIZE_T　dwBytes);</p>
<p>检索内存块的大小：<br>SIZE_T HeapSize(<br>HANDLE hHeap,<br>DWORD fdwFlags,<br>LPVOID pvMem);</p>
<p>释放内存块:<br>BOOL HeapFree(<br>HANDLE hHeap,<br>DWORD fdwFlags,<br>PVOID pvMem);</p>
<p>撤销堆栈：<br>BOOL HeapDestroy(HANDLE hHeap);</p>
<p>使用辅助堆栈的方法：重载对象的new操作符，在辅助堆上分配内存，并给对象添加一个静态变量用于保存堆句柄。</p>
<p>其它堆栈函数：<br>获取进程中所有堆栈得句柄：<br>DWORD GetProcessHeaps(DWORD dwNumHeaps,PHANDLE pHeaps);<br>验证堆栈完整性：<br>BOOL HeapValidate(<br>HANDLE hHeap,<br>DWORD fdwFlags,<br>LPCVOID pvMem);<br>合并地址中的空闲块<br>UINT HeapCompact(<br>HANDLE hHeap，<br>DWORD fdwFlags);</p>
<p>BOOL HeapLock(HANDLE hHeap);<br>BOOL HeapUnlock(HANDLE </p>
<p>遍历堆栈：<br>BOOL HeapWalk(<br>HANDLE hHeap,<br>PProcess_HEAP_ENTRY pHeapEntry);</p>
<p>各个dll也可以有自己的输入表。</p>
<p>如何编写DLL:<br>在DLL的头文件中，有如下代码：<br>#ifdef MYLIB<br>#else<br>&nbsp;&nbsp;&nbsp; #define MYLIB extern "C" __declspec(dllimport)&nbsp;&nbsp;&nbsp; <br>#endif<br>在每个输出变量和输出函数的声明前，用MYLIB修饰。<br>在DLL的实现文件中，有如下代码：<br>＃i nclude "windows.h"<br>#define MYLIB extern "C" __declspec(dllexport)<br>＃i nclude "Mylib.h"</p>
<p>其它的同编写普通C++程序完全相同。 "C" 表示按C方式链接和调用函数。C++编译器缺省按照__stdcall方式编译和调用，这种方式会改变函数的内部名字。此处如果把"C"都去掉也可以，但C程序将无法调用。另外，使用GetProcAddress函数时也会发生困难，因为<br>编译程序已经把函数名字改变了，无法用原来的名字得到函数地址。（核心编程说的不明白，没想到这本书错误这么多）<br>发行的时候，将头文件、.lib文件和DLL文件给用户就可以了。lib文件的作用是说明了头文件中函数所在的DLL文件，如果没有lib文件，编译器将在链接过程中提示错误：unresolved external symbol 函数名。<br>事实上，调用DLL有两种方式，第一种是比较常用，即包含DLL的头文件，并在链接的时候将动态链接库同exe文件像连接，建立输入表。这个时候需要.lib文件。第二种方法exe文件中没有输入表，程序使用LoadLibrary(Ex)和GetProcAddress()显式的加载DLL文件（卸载用FreeLibrary())，这个时候不需要.lib文件。<br>HINSTANCE LoadLibrary(PCTSTR pszDLLpathName);<br>HINSTANCE LoadLibraryEx(PCTSTR pszDLLpathName,NULL,0);</p>
<p>两次调用LoardLibrary并不会装载两次dll文件，只是将dll映射进进程的地址空间。系统会自动为每个进程维护一个dll的计数。FreeLiabray会使计数减一，如果计数为0，系统就会将dll从进程的地址空间卸载。</p>
<p><br>HINSTANCE GetModuleHandle(PCTSTR pszModuleName);//确定dll是否已经被映射进地址空间。<br>HINSTANCE hinstDll=GetModuleHandle("MyLib");<br>if(hinstDll==NULL)<br>{<br>&nbsp;&nbsp;&nbsp; hinstDll=LoadLibrary("MyLib");<br>}</p>
<p>DWORD GetModuleFileName(<br>&nbsp;&nbsp;&nbsp; HINSTANCE hinstModule,<br>&nbsp;&nbsp;&nbsp; PTSTR pszPathName,<br>&nbsp;&nbsp;&nbsp; DWORD cchPath<br>}<br>可以获得某个模块（.exe或者dll)的全路径名。</p>
<p>几个函数的用法：（注意GetProcAddress()函数的用法，如何定义和使用一个函数指针）<br>typedef int (*MYPROC)(int,int);</p>
<p>int main()<br>{<br>&nbsp;HINSTANCE t;<br>&nbsp;t=LoadLibraryEx(TEXT("tt.dll"),NULL,0);<br>&nbsp;if(t)<br>&nbsp;{<br>&nbsp;&nbsp;cout&lt;&lt;TEXT("load success")&lt;&lt;endl;</p>
<p>&nbsp;}<br>&nbsp;HINSTANCE hinstDll=GetModuleHandle("tt.dll");<br>&nbsp;if(hinstDll==NULL)<br>&nbsp;{<br>&nbsp;&nbsp;cout&lt;&lt;TEXT("first load failed")&lt;&lt;endl;<br>&nbsp;&nbsp;hinstDll=LoadLibrary("MyLib");<br>&nbsp;}<br>&nbsp;size_t sz=100;<br>&nbsp;PTCHAR str=new TCHAR[sz];<br>&nbsp;GetModuleFileName(t,str,sz);<br>&nbsp;cout&lt;&lt;str&lt;&lt;endl;<br>&nbsp;delete str;<br>&nbsp;MYPROC add=NULL;<br>&nbsp;add=(MYPROC)GetProcAddress(t,"add");<br>&nbsp;&nbsp;&nbsp; if(NULL!=add)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;cout&lt;&lt;(*add)(1,2)&lt;&lt;endl;<br>&nbsp;}<br>&nbsp;FreeLibrary(t);</p>
<p>&nbsp;return 0;<br>}</p>
<p><br>UNICODE<br>ANSI/UNICODE通用的定义方法（转换只需要在编译的时候使用_UNICODE和UNICODE）：<br>TCHAR _TEXT("success") PTSTR PCTSTR _tcscpy(),_tcscat();<br>使用BYTE PBYTE定义字节，字节指针和数据缓冲。<br>传递给函数的缓存大小：sizeof(szBuffer)/sizeof(TCHAR)<br>给字符串分配内存：malloc(nCharacters*sizeof(TCHAR));<br>其它的字符串函数：<br>PTSTR CharLower(PTSTR pszString);<br>PTSTR CharUpper(PTSTR pszString);<br>转换单个字符：<br>TCHAR c=CharLower((PTSTR)szString[0]);<br>转换缓存中的字符串（不必以0结尾）：<br>DWORD CharLowerBuff(<br>PTSTR pszString,<br>DWORD cchString);<br>DWORD CharUpperBuff(<br>PTSTR pszString,<br>DWORD cchString);</p>
<p>BOOL IsCharAlpha(TCHAR ch);<br>BOOL IsCharAlpahNumeric(TCHAR ch);<br>BOOL IsCharLower(TCHAR ch);<br>BOOL IsCharUpper(TCHAR ch);</p>
<p>线程本地存储(TLS):为进程的每个线程存储私有数据。用于那些一次传递参数后多次调用的函数（函数会保存上次调用的数据）。<br>实现方法：进程中有一个位标志树组（win2000的这个数组大小超过1000）。在每个线程中有一个对应的PVOID数组。通过设定位标志树组的某个位来分配每个线程中的PVOID数组得相应单元。函数需要每次检索线程的PVOID数组，获得该线程的相应数据。</p>
<p>DWORD TlsAlloc();&nbsp; //为每个线程分配一个空的PVOID数组单元。</p>
<p>BOOL TlsSetValue(&nbsp; //线程设定自己的PVOID数组单元。<br>DWORD dwTlsIndex,<br>PVOID pvTlsValue);</p>
<p>PVOID TlsGetValue(<br>DWORD dwTlsIndex);&nbsp; //检索PVOID数组。</p>
<p>BOOL TLSFree(<br>DWORD dwTlsIndex);&nbsp; //释放PVOID数组单元</p>
<p>静态TLS:__declspec(thread) DWORD gt_dwStartTime=0;//只能修饰全局或者静态变量。</p>
<p>DLL挂接（进程注入）：让自己的DLL插入到其他进程的地址空间。</p>
<p>1 使用注册表插入DLL<br>HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs<br>将你的DLL路径放入这个关键字下面。当User32.dll被映射到进程中的时候，它会加在这个关键字下的每个库。<br>注意：（1）对win98无效<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （2）由于加载事间比较早，你的DLL可能无法调用kernel32以外的dll.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3) 如果进程没有使用user32.dll，这个方法无效。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (4) 需要重新启动。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （5）user32不会检查每个库是否加载成功。<br>2 使用windows钩子<br>HOOK hHook=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hinstDll,0);<br>BOOL UnhookWindowsHookEx(HHOOK hhook);<br>具体过程如下：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （1）进程B的一个线程准备发送消息给一个窗口。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （2）系统察看线程上是否已经安装了WH_GETMESSAGE钩子。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （3）系统察看GetMsgProc的DLL是否已经映射到了进程B的地址空间，如果没有，系统将把DLL映射到B的地址空间，并自动增加引用计数。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （4）调用GetMsgProc函数，返回时，系统会自动为DLL的引用计数减一。</p>
<p>3 使用远程线程来插入DLL<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1)使用VirtualAllocEx,分配远程进程的地址空间的内存。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2)使用WriteProcessMemory,将Dll的路径名拷贝到第一个步骤中已经分配的内存中。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3)使用GetProcAddress,获得LoadLibrary的实际地址。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (4)使用CreateRemoteThread,在远程进程中创建一个线程。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 退出：&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (5)使用VirtualFreeEx，释放内存<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (6)使用GetProcAddress,获得FreeLiabary的地址。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (7)使用CreateRemoteThread,在远程进程中创建一个线程，调用FreeLiabary函数。<br>4 使用特洛伊DLL插入<br>&nbsp; 替换dll.<br>5 将DLL作为调试程序插入<br>6 win98内存映射文件，creatprocess </p>
<p>结构化异常处理：<br>结束处理程序：__try{} __finally{}<br>除非__try执行中进程或者线程结束，否则总会执行__finally，并且__finally中的return会替代__try中的return;好的习惯是将return ,continue,break,goto语句拿到结构化异常处理语句外面，可以节省开销。将__try中的return 换成__leave，可以节省开销。在__finally总确定时正常进入还是展开进入：<br>BOOL AbnormalTermination();//正常进入返回FALSE,局部展开或者全局展开返回TRUE;</p>
<p>异常处理程序：__try{}__exception(异常过滤表达式){}</p>
<p>EXCEPTION_EXECUTE_HANDLE<br>表示处理异常，处理后转到exception块后面的代码继续执行。<br>EXCEPTION_CONTINUE_EXECUTION<br>EXCEPTION_CONTINUE_SEARCH</p>
<p>可以对异常过滤表达式进行硬编码，也可以用一个调用一个函数来决定过滤表达式，函数的返回值是LONG.例如，可以进行一定处理，然后返回EXCEPTION_CONTINUE_EXECUTION，再次执行出错语句。但可能再次出错，因此这种方法必须小心，防止生成死循环。</p>
<p>DWORD GetExceptionCode() 可以获得异常种类。它只能在__except后的括号或者异常处理程序中调用。</p>
<p>发生异常后，操作系统会像引起异常的线程的栈里压入三个结构:EXCEPTION_RECORD CONTEXT EXCEPTION_POINTERS,其中第三个结构包含两个成员指针，分别指向前两个结构，使用函数可以获得第三个结构的指针：<br>PEXCEPTION_POINTERS GetExceptionInformation();//仅可以在异常过滤器中调用,既__exception后面的小括号。</p>
<p>逗号表达式：从左到右对所有的表达式求值，并返回最有面的表达式的值。</p>
<p>引发软件异常：<br>VOID RaiseException(<br>DWORD dwExceptionCode,<br>DWORD dwExceptionFlags,<br>DWORD nNumberOfArguments,<br>CONST ULONG_PTR *pArguments);</p>
<p>缺省调试器所在注册表：<br>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug&nbsp; Debugger<br>win98是存放在win.ini里</p>
<p>调试器挂接到被调试进程<br>BOOL DebugActiveProcess(DWORD dwProcessID);</p>
<p><br>while(*str++!='\0');<br>应该注意的是在不满足条件后，str仍然会自加1。</p>
<p>位操作符&gt;&gt;和&lt;&lt;不会做循环位移，即不会把移出的位放到另一头。</p>
<p>多态性和动态联编的实现过程分析</p>
<p>　　一、基础：</p>
<p>　　1、多态性：使用基础类的指针动态调用其派生类中函数的特性。</p>
<p>　　2、动态联编：在运行阶段，才将函数的调用与对应的函数体进行连接的方式，又叫运行时联编或晚捆绑。</p>
<p>　　二、过程描述：</p>
<p>　　1、编译器发现一个类中有虚函数，编译器会立即为此类生成虚拟函数表 vtable（后面有对vtable的分析）。虚拟函数表的各表项为指向对应虚拟函数的指针。</p>
<p>　　2、编译器在此类中隐含插入一个指针vptr（对vc编译器来说，它插在类的第一个位置上）。</p>
<p>　　有一个办法可以让你感知这个隐含指针的存在，虽然你不能在类中直接看到它，但你可以比较一下含有虚拟函数时的类的尺寸和没有虚拟函数时的类的尺寸，你能够发现，这个指针确实存在。 </p>
<p>　　3、在调用此类的构造函数时，在类的构造函数中，编译器会隐含执行vptr与vtable的关联代码，将vptr指向对应的vtable。这就将类与此类的vtable联系了起来。</p>
<p>　　4、在调用类的构造函数时，指向基础类的指针此时已经变成指向具体的类的this指针，这样依靠此this指针即可得到正确的vtable，从而实现了多态性。在此时才能真正与函数体进行连接，这就是动态联编。</p>
<p>定义纯虚函数方法：virtual returntype function()= 0;</p>
<p>所有的类型都可以用new动态创建，包括类，结构，内置数据类型。</p>
<p>exit()函数包含在"cstdlib"中</p>
<p>泛型编程，使用STL库<br>总共有近75个泛型算法</p>
<p>所有容器的共通操作：<br>== != = empty() size() clear(),begin(),end(),以及insert和erase,不过后者随容器的的不同而不同</p>
<p>序列式容器：<br>vector(数组):插入和删除的效率较低，但存取效率高。<br>list（双向链表）:与前者相反，插入和删除的效率较高，但存取效率低。每个元素包含三个字段：value back front.<br>deque(队列):在前端和末尾操作效率高。</p>
<p>生成序列式容器的五种方法：<br>1 产生空的容器：<br>list&lt;string&gt; slist;<br>vector&lt;int&gt; vtor;<br>2 产生特定大小的容器，容器中的每个元素都以其默认值为初值（发现VC中的int double等没有默认值）。<br>list&lt;int&gt; ilist(1024);<br>vector&lt;string&gt; svec(24);<br>3 产生特定大小的容器，并为每个元素指定初值：<br>list&lt;int ilist(1024,0);<br>vector&lt;string&gt; svec(24,"default");<br>4 通过一对迭代器产生容器，这对迭代器用来表示数组作为初值的区间：<br>int ia[10]={1,2,3,4,5,6,7,8,9,0};<br>vector&lt;int&gt;&nbsp; iv(ia+2,ia+8);<br>5 复制某个现有的容器的值：<br>vector&lt;int&gt; ivec1;<br>//填充ivec1;<br>vector&lt;int&gt; ivec2(ivec1);</p>
<p>有6个方法用于操作开始和末尾的元素：push_front() pop_front() push_back() pop_back(),由于pop操作仅删除元素而不返回元素，因此还需要front() back()方法取开始和末尾的元素，另外，vector不包括push_front() pop_front方法，很显然，无法实现。</p>
<p>intert的四种变形：<br>iterator insert(iterator position,elemType value):将value插到position前。返回值指向被插入的元素。<br>void insert(iterator position,int count,elemType value):在position前插入count个元素，每个元素都是value.<br>void insert(iterator1 position,iterator2 first,iterator2 last):将first,last之间的元素插到position前.<br>iterator insert( iterator position):在position前插入元素，初值为所属类型的默认值。</p>
<p>erase的两种变形：<br>1&nbsp; iterator erase(iterator posit):删除posit指向的元素。<br>list&lt;string&gt;::iterator it=find(slist.begin(),slist,end(),str);<br>slist.erase(it);<br>2&nbsp; iterator erase(iterator first,iterator last):删除first,last间的元素。</p>
<p>list不支持iterator的偏移运算</p>
<p>对于常值容器，使用常迭代器：<br>const vector&lt;string&gt; cs_vec;<br>vector&lt;string::const_iterator iter_cs_vec.begin();</p>
<p>iterator可以当作指针用，可以用*取内容，也可以用-&gt;调用对象的成员。</p>
<p>使用泛型算法<br>＃i nclude &lt;algorithm&gt;</p>
<p>find():线性搜索无序集合<br>binary_search():二分搜索有序集合。<br>count()：返回元素个数。<br>search():搜索序列，如果存在返回的iterator指向序列首部，否则指向容器末尾。<br>max_element(begin,end):返回区间内的最大值。<br>copy(begin,end,begin):元素复制。<br>sort(begin,end):排序。</p>
<p>function objects:＃i nclude &lt;functional&gt;<br>算术运算：<br>plus&lt;type&gt; minus&lt;type&gt; negate&lt;type&gt; multiplies&lt;type&gt; divides&lt;type&gt; modules&lt;type&gt;<br>关系运算：<br>less&lt;type&gt; less equal&lt;type&gt; greater&lt;type greater equal&lt;type&gt; equal_to&lt;type&gt; not_equal_to&lt;type&gt;<br>逻辑运算：<br>logical_and&lt;type&gt; logical_or&lt;type&gt; logical_not&lt;type&gt;</p>
<p>adapter:适配器。<br>bind1st:将数值绑定到function object的第一个参数。<br>bind2nd:将数值绑定到function object的第二个参数。</p>
<p>使用map:<br>＃i nclude &lt;map&gt;<br>＃i nclude &lt;string&gt;<br>map&lt;string,int&gt; words;</p>
<p>words["vermeer"]=1;</p>
<p>map&lt;string,int&gt;::iterator it=words.begin();<br>for(;it!=words.end();++it)<br>cout&lt;&lt;"key:"&lt;&lt;it-&gt;first&lt;&lt;"value:"&lt;&lt;it-&gt;second&lt;&lt;endl;</p>
<p>查找map元素的方法:<br>words.find("vermeer");//返回iterator,指向找到的元素，找不到返回end();<br>还可以：<br>if(words.count(search_word))<br>count=words[search_word];</p>
<p>使用set:<br>＃i nclude &lt;set&gt;<br>＃i nclude &lt;string&gt;<br>set&lt;string&gt; word_exclusion;<br>//判断是否存在某个元素<br>if(word_exclusion.count(tword))<br>//默认情况下，所有元素按less-than运算排列</p>
<p>//加入元素<br>iset.insert(ival);<br>iset.insert(vec.begin(),vec.end());</p>
<p>与set相关的算法<br>set_intersection() set_union() set_difference() set_symmetric_difference()</p>
<p>使用insertion adapters:<br>＃i nclude &lt;iterator&gt;<br>back_inserter()<br>inserter()<br>front_inserter()</p>
<p>使用STL通常会有很多警告，为了避免在调试模式（debug mode）出现恼人的警告，使用下面的编译器命令：</p>
<p>#pragma warning(disable: 4786) </p>
<p>strncpy(dest,source,count) if(count〈=strlen(source)),那么null结尾不会被加在dest的尾部，如果count&gt;strlen(source),那么不足的部分会用null填充。</p>
<p><br>windows内存是由高地址向底地址分配的，但变量的存储是从底地址到高地址的，如INT类型的四个字节，数组的每个元素。<br>&nbsp;<br>内存复制的时候不能用字符串拷贝函数，因为即使使用strncpy指定了复制的长度，拷贝函数也会遇到'\0'自动终止，要使用MEMSET。</p>
<p>由于对齐的关系，下面两个结构使用sizeof，前者是12，后者是16。<br>struct DNSAnswer<br>{<br>&nbsp;unsigned short name;<br>&nbsp;unsigned short type;<br>&nbsp;unsigned short cla; <br>&nbsp;unsigned short length;<br>&nbsp;unsigned int&nbsp;&nbsp; ttl;<br>};<br>struct DNSAnswer<br>{<br>&nbsp;unsigned short name;<br>&nbsp;unsigned short type;<br>&nbsp;unsigned short cla;<br>&nbsp;unsigned int&nbsp;&nbsp; ttl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsigned short length;<br>};</p>
<p>子类可以使用父类的保护成员，而友元比子类的权限还大，可以使用类的私有和保护成员。</p>
<p>在内存分配失败的情况下，系统只有在出错处理函数为空的情况下，才会抛出异常：std::bad_alloc(),否则会反复调用处理函数并再次尝试分配内存。</p>
<p>如果重载了NEW，那么在继承的时候要小心，如果子类没有覆盖NEW，那么它会去使用父类的NEW ，因此应该在new，delete中做检查<br>if (size != sizeof(base))&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 如果数量&#8220;错误&#8221;，让标准operator new，base为类名<br>&nbsp;&nbsp;&nbsp; return ::operator new(size);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 去处理这个请求</p>
<p>&nbsp; if (size != sizeof(base)) {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 如果size"错误"，<br>&nbsp;&nbsp;&nbsp; ::operator delete(rawmemory);&nbsp; // 让标准operator来处理请求<br>&nbsp;&nbsp;&nbsp; return;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp; }</p>
<p>c++标准规定，要支持0内存请求（分配一个字节），并且可以删除NULL指针（直接返回）。</p>
<p>在创建线程的时候，传递的变量一定要是全局或者静态的变量，因为传递的是变量的地址，如果是局部变量地址很快就会失效。</p>
<p>主线程退出后，其子线程自动结束。</p>
<p>智能指针：它可以避免内存泄露，因为智能指针是在栈上创建的；还可以避免堆上内存的重复释放错误，因为它保证只有一个指针拥有这块内存的所有权。</p>
<img src ="http://www.cppblog.com/zzh/aggbug/29864.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zzh/" target="_blank">Xiao.Zhu</a> 2007-08-13 09:22 <a href="http://www.cppblog.com/zzh/archive/2007/08/13/29864.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux下基于jrtplib库的实时传送实现 </title><link>http://www.cppblog.com/zzh/archive/2007/08/08/29578.html</link><dc:creator>Xiao.Zhu</dc:creator><author>Xiao.Zhu</author><pubDate>Wed, 08 Aug 2007 08:39:00 GMT</pubDate><guid>http://www.cppblog.com/zzh/archive/2007/08/08/29578.html</guid><wfw:comment>http://www.cppblog.com/zzh/comments/29578.html</wfw:comment><comments>http://www.cppblog.com/zzh/archive/2007/08/08/29578.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zzh/comments/commentRss/29578.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zzh/services/trackbacks/29578.html</trackback:ping><description><![CDATA[linux&nbsp;下基于jrtplib库的实时传送实现<br>一、RTP&nbsp;是进行实时流媒体传输的标准协议和关键技术<br>&nbsp;实时传输协议（Real-time&nbsp;Transport&nbsp;Protocol，PRT）是在&nbsp;Internet&nbsp;上处理多媒体数据流的一种网络协议，利用它能够在一对一（unicast，单播）或者一对多（multicast，多播）的网络环境中实现传流媒体数据的实时传输。RTP&nbsp;通常使用&nbsp;UDP&nbsp;来进行多媒体数据的传输，但如果需要的话可以使用&nbsp;TCP&nbsp;或者&nbsp;ATM&nbsp;等其它协议。<br>&nbsp;协议分析&nbsp;：每一个RTP数据报都由头部（Header）和负载（Payload）两个部分组成，其中头部前&nbsp;12&nbsp;个字节的含义是固定的，而负载则可以是音频或者视频数据。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RTP&nbsp;是目前解决流媒体实时传输问题的最好办法，要在&nbsp;Linux&nbsp;平台上进行实时传送编程，可以考虑使用一些开放源代码的&nbsp;RTP&nbsp;库，如&nbsp;LIBRTP、JRTPLIB&nbsp;等。JRTPLIB&nbsp;是一个面向对象的&nbsp;RTP&nbsp;库，它完全遵循&nbsp;RFC&nbsp;1889&nbsp;设计，在很多场合下是一个非常不错的选择。JRTPLIB&nbsp;是一个用&nbsp;C++&nbsp;语言实现的&nbsp;RTP&nbsp;库，这个库使用socket&nbsp;机制实现网络通讯&nbsp;因此可以运行在&nbsp;Windows、Linux、FreeBSD、Solaris、Unix和VxWorks&nbsp;等多种操作系统上。<br>二、JRTPLIB&nbsp;库的使用方法及程序实现<br>&nbsp;(1)JRTPLIB&nbsp;&nbsp;函数&nbsp;的使用<br>&nbsp;a、在使用&nbsp;JRTPLIB&nbsp;进行实时流媒体数据传输之前，首先应该生成&nbsp;RTPSession&nbsp;类的一个实例来表示此次&nbsp;RTP&nbsp;会话，然后调用&nbsp;Create()&nbsp;方法来对其进行初始化操作。RTPSession&nbsp;类的&nbsp;Create()&nbsp;方法只有一个参数，用来指明此次&nbsp;RTP&nbsp;会话所采用的端口号。<br>&nbsp;RTPSession&nbsp;sess;&nbsp;&nbsp;sess.Create(5000);&nbsp;<br><br>&nbsp;b、设置恰当的时戳单元，是&nbsp;RTP&nbsp;会话初始化过程所要进行的另外一项重要工作，这是通过调用&nbsp;RTPSession&nbsp;类的&nbsp;SetTimestampUnit()&nbsp;方法来实现的，该方法同样也只有一个参数，表示的是以秒为单元的时戳单元。<br>&nbsp;sess.SetTimestampUnit(1.0/8000.0);<br><br>&nbsp;c、当&nbsp;RTP&nbsp;会话成功建立起来之后，接下去就可以开始进行流媒体数据的实时传输了。首先需要设置好数据发送的目标地址，RTP&nbsp;协议允许同一会话存在多个目标地址，这可以通过调用&nbsp;RTPSession&nbsp;类的&nbsp;AddDestination()、DeleteDestination()&nbsp;和&nbsp;ClearDestinations()&nbsp;方法来完成。例如，下面的语句表示的是让&nbsp;RTP&nbsp;会话将数据发送到本地主机的&nbsp;6000&nbsp;端口：&nbsp;<br><br>&nbsp;unsigned&nbsp;long&nbsp;addr&nbsp;=&nbsp;ntohl(inet_addr("127.0.0.1"));&nbsp;<br>&nbsp;sess.AddDestination(addr,&nbsp;6000);<br>&nbsp;<br>&nbsp;d、目标地址全部指定之后，接着就可以调用&nbsp;RTPSession&nbsp;类的&nbsp;SendPacket()&nbsp;方法，向所有的目标地址发送流媒体数据。SendPacket()&nbsp;是&nbsp;RTPSession&nbsp;类提供的一个重载函数<br>对于同一个&nbsp;RTP&nbsp;会话来讲，负载类型、标识和时戳增量通常来讲都是相同的，JRTPLIB&nbsp;允许将它们设置为会话的默认参数，这是通过调用&nbsp;RTPSession&nbsp;类的&nbsp;SetDefaultPayloadType()、SetDefaultMark()&nbsp;和&nbsp;SetDefaultTimeStampIncrement()&nbsp;方法来完成的。为&nbsp;RTP&nbsp;会话设置这些默认参数的好处是可以简化数据的发送，例如，如果为&nbsp;RTP&nbsp;会话设置了默认参数：&nbsp;<br><br>&nbsp;sess.SetDefaultPayloadType(0);<br>&nbsp;&nbsp;sess.SetDefaultMark(false);&nbsp;&nbsp;<br>&nbsp;sess.SetDefaultTimeStampIncrement(10);<br>&nbsp;<br><br><br>之后在进行数据发送时只需指明要发送的数据及其长度就可以了：&nbsp;<br><br>&nbsp;sess.SendPacket(buffer,&nbsp;5);&nbsp;<br><br><br>&nbsp;e、对于流媒体数据的接收端，首先需要调用&nbsp;RTPSession&nbsp;类的&nbsp;PollData()&nbsp;方法来接收发送过来的&nbsp;RTP&nbsp;或者&nbsp;RTCP&nbsp;数据报。由于同一个&nbsp;RTP&nbsp;会话中允许有多个参与者（源），你既可以通过调用&nbsp;RTPSession&nbsp;类的&nbsp;GotoFirstSource()&nbsp;和&nbsp;GotoNextSource()&nbsp;方法来遍历所有的源，也可以通过调用&nbsp;RTPSession&nbsp;类的&nbsp;GotoFirstSourceWithData()&nbsp;和&nbsp;GotoNextSourceWithData()&nbsp;方法来遍历那些携带有数据的源。在从&nbsp;RTP&nbsp;会话中检测出有效的数据源之后，接下去就可以调用&nbsp;RTPSession&nbsp;类的&nbsp;GetNextPacket()&nbsp;方法从中抽取&nbsp;RTP&nbsp;数据报，当接收到的&nbsp;RTP&nbsp;数据报处理完之后，一定要记得及时释放。<br><br>JRTPLIB&nbsp;为&nbsp;RTP&nbsp;数据报定义了三种接收模式，其中每种接收模式都具体规定了哪些到达的&nbsp;RTP&nbsp;数据报将会被接受，而哪些到达的&nbsp;RTP&nbsp;数据报将会被拒绝。通过调用&nbsp;RTPSession&nbsp;类的&nbsp;SetReceiveMode()&nbsp;方法可以设置下列这些接收模式：&nbsp;<br>?&nbsp;RECEIVEMODE_ALL　　缺省的接收模式，所有到达的&nbsp;RTP&nbsp;数据报都将被接受；&nbsp;<br>?&nbsp;RECEIVEMODE_IGNORESOME　　除了某些特定的发送者之外，所有到达的&nbsp;RTP&nbsp;数据报都将被接受，而被拒绝的发送者列表可以通过调用&nbsp;AddToIgnoreList()、DeleteFromIgnoreList()&nbsp;和&nbsp;ClearIgnoreList()&nbsp;方法来进行设置；&nbsp;<br>?&nbsp;RECEIVEMODE_ACCEPTSOME　　除了某些特定的发送者之外，所有到达的&nbsp;RTP&nbsp;数据报都将被拒绝，而被接受的发送者列表可以通过调用&nbsp;AddToAcceptList&nbsp;()、DeleteFromAcceptList&nbsp;和&nbsp;ClearAcceptList&nbsp;()&nbsp;方法来进行设置。&nbsp;下面是采用第三种接收模式的程序示例。<br>&nbsp;if&nbsp;(sess.GotoFirstSourceWithData())&nbsp;{&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;do&nbsp;{&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;sess.AddToAcceptList(remoteIP,&nbsp;allports,portbase);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sess.SetReceiveMode(RECEIVEMODE_ACCEPTSOME);<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;RTPPacket&nbsp;*pack;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;pack&nbsp;=&nbsp;sess.GetNextPacket();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;处理接收到的数据&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;delete&nbsp;pack;&nbsp;&nbsp;&nbsp;}&nbsp;<br>&nbsp;&nbsp;while&nbsp;(sess.GotoNextSourceWithData());&nbsp;<br>&nbsp;&nbsp;}<br><br><br>&nbsp;&nbsp;（2）程序流程图<br>发送：获得接收端的&nbsp;IP&nbsp;地址和端口号&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;创建&nbsp;RTP&nbsp;会话&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;指定&nbsp;RTP&nbsp;数据接收端&nbsp;设置&nbsp;RTP&nbsp;会话默认参数&nbsp;&nbsp;&nbsp;发送流媒体数据<br>接收：获得用户指定的端口号&nbsp;&nbsp;创建RTP会话&nbsp;&nbsp;设置接收模式&nbsp;&nbsp;接受RTP数据&nbsp;&nbsp;检索RTP数据源&nbsp;&nbsp;获取RTP数据报&nbsp;&nbsp;删除RTP数据报<br><br><br>三、环境搭建及编译方法<br>（1）Toolchain的安装<br>&nbsp;首先找到xscale-arm-toolchain.tgz文件，假设该文件包放在/tmp/下<br>&nbsp;#cd&nbsp;/<br>&nbsp;#tar&nbsp;-zxvf&nbsp;/tmp/xscale-arm-toolchain.tgz<br>&nbsp;再设置环境变量<br>&nbsp;#export&nbsp;PATH=/usr/local/arm-linux/bin:$PATH<br>&nbsp;最后检查一下交叉编译工具是否安装成功<br>&nbsp;#arm-linux-g++&nbsp;--version<br>&nbsp;看是否显示arm-linux-g++的版本，如有则安装成功。<br>（2）JRTPLIB&nbsp;库的交叉编译及安装<br>&nbsp;首先从&nbsp;JRTPLIB&nbsp;的网站（<img alt="" src="http://www.21control.com/RTOS/UploadFiles_RTOS/200512/20051205014856542.gif" align=absMiddle><a href="http://lumumba.luc.ac.be/jori/jrtplib/jrtplib.htmll" target=_blank><u><font face=宋体 color=#0000ff size=3>http://lumumba.luc.ac.be/jori/jrtplib/jrtplib.htmll</font></u></a><font face=宋体 size=3>）&nbsp;下载最新的源码包，此处使用的是jrtplib-2.8.tar，假设下载后的源码包放在/tmp下，执&nbsp;行下面的命令对其解压缩：<br>&nbsp;#cd&nbsp;/tmp<br>&nbsp;#tar&nbsp;-zxvf&nbsp;jrtplib-2.8.tar<br>&nbsp;然后要对jrtplib进行配置和编译<br>&nbsp;#cd&nbsp;jrtplib-2.8<br>&nbsp;#./configure&nbsp;CC=arm-linux-g++&nbsp;cross-compile=yes<br>&nbsp;修改Makefile文件<br>&nbsp;将链接命令ld&nbsp;和ar改为arm-linux-ld和&nbsp;arm-linux-ar<br>&nbsp;#make<br>&nbsp;最后再执行如下命令就可以完成&nbsp;JRTPLIB&nbsp;的安装：<br>&nbsp;#make&nbsp;install<br>(3)程序编译<br>&nbsp;a、配置编译环境<br>&nbsp;可以用export来配置，也可以用编写Makefile的方法。这里采用Makefile。<br>&nbsp;编写Make<em>file&amp;:</em><br>INCL&nbsp;=&nbsp;-I/usr/local/include<br>CFLAGS&nbsp;=&nbsp;-pipe&nbsp;-O2&nbsp;-fno-strength-reduce<br>LFLAGS&nbsp;=&nbsp;/usr/local/lib/libjrtp.a&nbsp;-L/usr/X11R6/lib<br>LIBS&nbsp;=&nbsp;-LX11&nbsp;-LXext&nbsp;/usr/local/lib/libjrtp.a<br>CC&nbsp;=&nbsp;arm-linux-g++<br><br>main:main.o<br>&nbsp;$(CC)&nbsp;$(LFLAGS)&nbsp;$(INCL)&nbsp;-o&nbsp;main&nbsp;main.o&nbsp;$(LIBS)<br>main.o:main.cpp<br><br>clean:<br>&nbsp;rm&nbsp;-f&nbsp;main<br>&nbsp;rm&nbsp;-f&nbsp;*.o<br>&nbsp;<br>.SUFFIXES:.cpp<br>.cpp.o:<br>&nbsp;$(CC)&nbsp;-c&nbsp;$(CFLAGS)&nbsp;$(INCL)&nbsp;-o&nbsp;$@&nbsp;$&lt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;&nbsp;$@表示目标的完整名字&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;$&lt;表示第一个依赖文件的名字&nbsp;*/<br>&nbsp;b、编译<br>&nbsp;假设发送和接收程序分别放在/tmp/send和/tmp/receive目录下<br>&nbsp;#cd&nbsp;/tmp/send<br>&nbsp;#make<br>&nbsp;#cd&nbsp;/tmp/receive<br>&nbsp;#make<br><br>四、易出错误及注意问题<br>&nbsp;1、找不到一些标准的最&nbsp;基本的一些头文件。<br>&nbsp;&nbsp;主要是因为Toolchain路径没安装对，要&nbsp;严格按照步骤安装。<br>&nbsp;2、找不到使用的jrtplib库中的一些头文件。<br>&nbsp;&nbsp;在&nbsp;jrtplib的安装目录下，include路径下不能再有别的目录。<br>&nbsp;3、recieve函数接收数据包不能正确提出所要数据。<br>&nbsp;&nbsp;由于每一个RTP数据报都由头部（Header）和负载（Payload）两个部分组成，若使用getrawdata()是返回整个数据包的数据，包含传输媒体的类型、格式、序列号、时间戳以及是否有附加数据等信息。getpayload()函数是返回所发送的数据。两者一定要分清。<br>&nbsp;4、设置RECEIVEMODE_ACCEPTSOME　　接收模式后，运行程序接收端不能接包。<br>&nbsp;&nbsp;IP地址格式出了问题。iner_addr()与ntohl()函数要用对，否则参数传不进去，接受列表中无值，当然接收不了数据包。<br>&nbsp;5、编译通过，但测试时接收端不能接收到数据。<br>&nbsp;&nbsp;可能是接收机防火墙未关闭。运行：<br>&nbsp;&nbsp;#iptables&nbsp;-F<br>&nbsp;&nbsp;也可能是IP地址没有设置好。运行：<br>&nbsp;&nbsp;#ifocnfig&nbsp;eth0&nbsp;&nbsp;*.*.*.*&nbsp;&nbsp;netmask&nbsp;*.*.*.*<br>&nbsp;6、使用jrtolib库时，在程序中include&nbsp;后最好加上库所在的路径。<br>五、程序<br><br>send:<br><br>#include&nbsp;&lt;stdio.h&gt;<br>#include&nbsp;&lt;string.h&gt;<br>#include&nbsp;"rtpsession.h"<br><br>//&nbsp;错误处理函数<br>void&nbsp;checkerror(int&nbsp;err)<br>{<br>&nbsp;&nbsp;if&nbsp;(err&nbsp;&lt;&nbsp;0)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;char*&nbsp;errstr&nbsp;=&nbsp;RTPGetErrorString(err);<br>&nbsp;&nbsp;&nbsp;&nbsp;printf("Error:%s\\n",&nbsp;errstr);<br>&nbsp;&nbsp;&nbsp;&nbsp;exit(-1);<br>&nbsp;&nbsp;}<br>}<br><br>int&nbsp;main(int&nbsp;argc,&nbsp;char**&nbsp;argv)<br>{<br>&nbsp;&nbsp;RTPSession&nbsp;sess;<br>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;destip;<br>&nbsp;&nbsp;int&nbsp;destport;<br>&nbsp;&nbsp;int&nbsp;portbase&nbsp;=&nbsp;6000;<br>&nbsp;&nbsp;int&nbsp;status,&nbsp;index;<br>&nbsp;&nbsp;char&nbsp;buffer[128];<br><br>&nbsp;&nbsp;if&nbsp;(argc&nbsp;!=&nbsp;3)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;printf("Usage:&nbsp;./sender&nbsp;destip&nbsp;destport\\n");<br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;-1;<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;//&nbsp;获得接收端的IP地址和端口号<br>&nbsp;&nbsp;destip&nbsp;=&nbsp;inet_addr(argv[1]);<br>&nbsp;&nbsp;if&nbsp;(destip&nbsp;==&nbsp;INADDR_NONE)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;printf("Bad&nbsp;IP&nbsp;address&nbsp;specified.\\n");<br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;-1;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;destip&nbsp;=&nbsp;ntohl(destip);<br>&nbsp;&nbsp;destport&nbsp;=&nbsp;atoi(argv[2]);<br><br>&nbsp;&nbsp;//&nbsp;创建RTP会话<br>&nbsp;&nbsp;status&nbsp;=&nbsp;sess.Create(portbase);<br>&nbsp;&nbsp;checkerror(status);<br><br>&nbsp;&nbsp;//&nbsp;指定RTP数据接收端<br>&nbsp;&nbsp;status&nbsp;=&nbsp;sess.AddDestination(destip,&nbsp;destport);<br>&nbsp;&nbsp;checkerror(status);<br><br>&nbsp;&nbsp;//&nbsp;设置RTP会话默认参数<br>&nbsp;&nbsp;sess.SetDefaultPayloadType(0);<br>&nbsp;&nbsp;sess.SetDefaultMark(false);<br>&nbsp;&nbsp;sess.SetDefaultTimeStampIncrement(10);<br><br>&nbsp;&nbsp;//&nbsp;发送流媒体数据<br>&nbsp;&nbsp;index&nbsp;=&nbsp;1;<br>&nbsp;&nbsp;do&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;sprintf(buffer,&nbsp;"%d:&nbsp;RTP&nbsp;packet",&nbsp;index&nbsp;++);<br>&nbsp;&nbsp;&nbsp;&nbsp;sess.SendPacket(buffer,&nbsp;strlen(buffer));<br>&nbsp;&nbsp;&nbsp;&nbsp;printf("Send&nbsp;packet&nbsp;!\\n");<br>&nbsp;&nbsp;}&nbsp;while(1);<br><br>&nbsp;&nbsp;return&nbsp;0;<br>}<br><br><br><br><br><br><br>receive:<br><br>#include&nbsp;&lt;stdio.h&gt;<br>#include&nbsp;"rtpsession.h"<br>#include&nbsp;"rtppacket.h"<br><br>//&nbsp;错误处理函数<br>void&nbsp;checkerror(int&nbsp;err)<br>{<br>&nbsp;&nbsp;if&nbsp;(err&nbsp;&lt;&nbsp;0)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;char*&nbsp;errstr&nbsp;=&nbsp;RTPGetErrorString(err);<br>&nbsp;&nbsp;&nbsp;&nbsp;printf("Error:%s\\n",&nbsp;errstr);<br>&nbsp;&nbsp;&nbsp;&nbsp;exit(-1);<br>&nbsp;&nbsp;}<br>}<br><br>int&nbsp;main(int&nbsp;argc,&nbsp;char**&nbsp;argv)<br>{<br>&nbsp;&nbsp;RTPSession&nbsp;sess;<br>&nbsp;&nbsp;int&nbsp;localport,portbase;<br>&nbsp;&nbsp;int&nbsp;status;<br>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;remoteIP;<br>&nbsp;&nbsp;if&nbsp;(argc&nbsp;!=&nbsp;4)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;printf("Usage:&nbsp;./sender&nbsp;localport\\n");<br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;-1;<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;//&nbsp;获得用户指定的端口号<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;remoteIP&nbsp;=&nbsp;inet_addr(argv[1]);<br>&nbsp;&nbsp;localport&nbsp;=&nbsp;atoi(argv[2]);<br>&nbsp;&nbsp;portbase&nbsp;=&nbsp;atoi(argv[3]);<br>&nbsp;&nbsp;//&nbsp;创建RTP会话<br>&nbsp;&nbsp;status&nbsp;=&nbsp;sess.Create(localport);<br>&nbsp;&nbsp;checkerror(status);<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;//RTPHeader&nbsp;*rtphdr;<br>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;timestamp1;<br>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;*&nbsp;RawData;<br>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;temp[30];<br>&nbsp;&nbsp;int&nbsp;lengh&nbsp;,i;<br>&nbsp;&nbsp;bool&nbsp;allports&nbsp;=&nbsp;1;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;sess.AddToAcceptList(remoteIP,&nbsp;allports,portbase);<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;do&nbsp;{<br>&nbsp;//设置接收模式<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sess.SetReceiveMode(RECEIVEMODE_ACCEPTSOME);<br>&nbsp;&nbsp;&nbsp;sess.AddToAcceptList(remoteIP,&nbsp;allports,portbase);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;接受RTP数据<br>&nbsp;&nbsp;&nbsp;&nbsp;status&nbsp;=&nbsp;sess.PollData();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;//&nbsp;检索RTP数据源<br>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(sess.GotoFirstSourceWithData())&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;do&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RTPPacket*&nbsp;packet;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;获取RTP数据报<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;((packet&nbsp;=&nbsp;sess.GetNextPacket())&nbsp;!=&nbsp;NULL)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("Got&nbsp;packet&nbsp;!\n");<br><br>&nbsp;&nbsp;&nbsp;timestamp1&nbsp;=&nbsp;packet-&gt;GetTimeStamp();<br>&nbsp;&nbsp;&nbsp;lengh=packet-&gt;GetPayloadLength();<br>&nbsp;&nbsp;&nbsp;RawData=packet-&gt;GetPayload();<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;for(i=0;i&lt;lengh;i++){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;temp[i]=RawData[i];<br>&nbsp;&nbsp;printf("%c",temp[i]);<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;temp[i]='\0';<br>&nbsp;&nbsp;&nbsp;printf("&nbsp;&nbsp;timestamp:&nbsp;%d&nbsp;lengh=%d&nbsp;data:%s\n",timestamp1,lengh,&amp;temp);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;删除RTP数据报<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete&nbsp;packet;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;while&nbsp;(sess.GotoNextSourceWithData());<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}&nbsp;while(1);<br><br>&nbsp;&nbsp;return&nbsp;0;<br>}</font><br>
<img src ="http://www.cppblog.com/zzh/aggbug/29578.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zzh/" target="_blank">Xiao.Zhu</a> 2007-08-08 16:39 <a href="http://www.cppblog.com/zzh/archive/2007/08/08/29578.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>RTP</title><link>http://www.cppblog.com/zzh/archive/2007/08/08/29577.html</link><dc:creator>Xiao.Zhu</dc:creator><author>Xiao.Zhu</author><pubDate>Wed, 08 Aug 2007 08:38:00 GMT</pubDate><guid>http://www.cppblog.com/zzh/archive/2007/08/08/29577.html</guid><wfw:comment>http://www.cppblog.com/zzh/comments/29577.html</wfw:comment><comments>http://www.cppblog.com/zzh/archive/2007/08/08/29577.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/zzh/comments/commentRss/29577.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zzh/services/trackbacks/29577.html</trackback:ping><description><![CDATA[&nbsp;
<p align=left><span>流媒体指的是在网络中使用流技术传输的连续时基媒体，其特点是在播放前不需要下载整个文件，而是采用边下载边播放的方式，它是视频会议、<span>IP</span>电话等应用场合的技术基础。<span>RTP</span>是进行实时流媒体传输的标准协议和关键技术，本文介绍如何在<span>Linux</span>下利用<span>JRTPLIB</span>进行实时流媒体编程。&nbsp;</span></p>
<p align=left><span>&nbsp;&nbsp; </span></p>
<p align=left><a name=0><span>一、流媒体简介</span></a><span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>随着<span>Internet</span>的日益普及，在网络上传输的数据已经不再局限于文字和图形，而是逐渐向声音和视频等多媒体格式过渡。目前在网络上传输音频<span>/</span>视频（<span>Audio/Video</span>，简称<span>A/V</span>）等多媒体文件时，基本上只有下载和流式传输两种选择。通常说来，<span>A/V</span>文件占据的存储空间都比较大，在带宽受限的网络环境中下载可能要耗费数分钟甚至数小时，所以这种处理方法的延迟很大。如果换用流式传输的话，声音、影像、动画等多媒体文件将由专门的流媒体服务器负责向用户连续、实时地发送，这样用户可以不必等到整个文件全部下载完毕，而只需要经过几秒钟的启动延时就可以了，当这些多媒体数据在客户机上播放时，文件的剩余部分将继续从流媒体服务器下载。</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>流（<span>Streaming</span>）是近年在<span>Internet</span>上出现的新概念，其定义非常广泛，主要是指通过网络传输多媒体数据的技术总称。流媒体包含广义和狭义两种内涵：广义上的流媒体指的是使音频和视频形成稳定和连续的传输流和回放流的一系列技术、方法和协议的总称，即流媒体技术；狭义上的流媒体是相对于传统的下载<span>-</span>回放方式而言的，指的是一种从<span>Internet</span>上获取音频和视频等多媒体数据的新方法，它能够支持多媒体数据流的实时传输和实时播放。通过运用流媒体技术，服务器能够向客户机发送稳定和连续的多媒体数据流，客户机在接收数据的同时以一个稳定的速率回放，而不用等数据全部下载完之后再进行回放。</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>由于受网络带宽、计算机处理能力和协议规范等方面的限制，要想从<span>Internet</span>上下载大量的音频和视频数据，无论从下载时间和存储空间上来讲都是不太现实的，而流媒体技术的出现则很好地解决了这一难题。目前实现流媒体传输主要有两种方法：顺序流（<span>progressive&nbsp;streaming</span>）传输和实时流（<span>realtime&nbsp;streaming</span>）传输，它们分别适合于不同的应用场合。</span></p>
<p align=left>&nbsp;</p>
<p align=left><strong><span>顺序流传输</span></strong></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>顺序流传输采用顺序下载的方式进行传输，在下载的同时用户可以在线回放多媒体数据，但给定时刻只能观看已经下载的部分，不能跳到尚未下载的部分，也不能在传输期间根据网络状况对下载速度进行调整。由于标准的<span>HTTP</span>服务器就可以发送这种形式的流媒体，而不需要其他特殊协议的支持，因此也常常被称作<span>HTTP&nbsp;</span>流式传输。顺序流式传输比较适合于高质量的多媒体片段，如片头、片尾或者广告等。</span></p>
<p align=left>&nbsp;</p>
<p align=left><strong><span>实时流传输</span></strong></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>实时流式传输保证媒体信号带宽能够与当前网络状况相匹配，从而使得流媒体数据总是被实时地传送，因此特别适合于现场事件。实时流传输支持随机访问，即用户可以通过快进或者后退操作来观看前面或者后面的内容。从理论上讲，实时流媒体一经播放就不会停顿，但事实上仍有可能发生周期性的暂停现象，尤其是在网络状况恶化时更是如此。与顺序流传输不同的是，实时流传输需要用到特定的流媒体服务器，而且还需要特定网络协议的支持。</span></p>
<p align=left>&nbsp;</p>
<p align=left><a name=1><span>二、流媒体协议</span></a><span><br></span><span>实时传输协议（<span>Real-time&nbsp;Transport&nbsp;Protocol</span>，<span>PRT</span>）是在<span>Internet</span>上处理多媒体数据流的一种网络协议，利用它能够在一对一（<span>unicast</span>，单播）或者一对多（<span>multicast</span>，多播）的网络环境中实现传流媒体数据的实时传输。<span>RTP</span>通常使用<span>UDP</span>来进行多媒体数据的传输，但如果需要的话可以使用<span>TCP</span>或者<span>&nbsp;ATM</span>等其它协议，整个<span>RTP</span>协议由两个密切相关的部分组成：<span>RTP</span>数据协议和<span>RTP</span>控制协议。实时流协议（<span>Real&nbsp;Time&nbsp;Streaming&nbsp;Protocol</span>，<span>RTSP</span>）最早由<span>Real&nbsp;Networks</span>和<span>Netscape</span>公司共同提出，它位于<span>RTP</span>和<span>RTCP</span>之上，其目的是希望通过<span>IP</span>网络有效地传输多媒体数据。</span></p>
<p align=left>&nbsp;</p>
<p align=left><strong><span>2.1&nbsp;RTP</span></strong><strong><span>数据协议</span></strong></p>
<p align=left>&nbsp;</p>
<p align=left><span>RTP</span><span>数据协议负责对流媒体数据进行封包并实现媒体流的实时传输，每一个<span>RTP</span>数据报都由头部（<span>Header</span>）和负载（<span>Payload</span>）两个部分组成，其中头部前<span>12</span>个字节的含义是固定的，而负载则可以是音频或者视频数据。<span>RTP</span>数据报的头部格式如图<span>1</span>所示：</span></p>
<p align=left>&nbsp;<span lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA"><v:shapetype id=_x0000_t75 stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><strong> <img height=153 alt="" src="http://www.cppblog.com/images/cppblog_com/zzh/image001.jpg" width=487 border=0><v:stroke joinstyle="miter"></v:stroke></strong><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></v:path><o:lock aspectratio="t" v:ext="edit"></o:lock></v:shapetype></span></p>
<p align=left><span lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA"><v:shapetype id=_x0000_t75 stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><strong>&nbsp;</strong></v:shapetype></span><a name=N10082><strong><span><br></span></strong></a><span><strong><span>图<span>1&nbsp;RTP</span>头部格式<span><br><br></span></span></strong></span></p>
<p align=left>&nbsp;</p>
<p align=left><span>其中比较重要的几个域及其意义如下：&nbsp;</span></p>
<p align=left>&nbsp;</p>
<ul type=disc>
    <li><strong><span>CSRC</span></strong><strong><span>记数（<span>CC</span>）</span></strong><span>　　表示<span>CSRC</span>标识的数目。<span>CSRC</span>标识紧跟在<span>RTP</span>固定头部之后，用来表示<span>RTP</span>数据报的来源，<span>RTP</span>协议允许在同一个会话中存在多个数据源，它们可以通过<span>RTP</span>混合器合并为一个数据源。例如，可以产生一个<span>CSRC</span>列表来表示一个电话会议，该会议通过一个<span>&nbsp;RTP</span>混合器将所有讲话者的语音数据组合为一个<span>RTP</span>数据源。&nbsp;</span></li>
    <li><strong><span>负载类型（<span>PT</span>）</span></strong><span>　　标明<span>RTP</span>负载的格式，包括所采用的编码算法、采样频率、承载通道等。例如，类型<span>2</span>表明该<span>RTP</span>数据包中承载的是用<span>ITU&nbsp;G.721</span>算法编码的语音数据，采样频率为<span>8000Hz</span>，并且采用单声道。&nbsp;</span></li>
    <li><strong><span>序列号</span></strong><span>　　用来为接收方提供探测数据丢失的方法，但如何处理丢失的数据则是应用程序自己的事情，<span>RTP</span>协议本身并不负责数据的重传。&nbsp;</span></li>
    <li><strong><span>时间戳</span></strong><span>　　记录了负载中第一个字节的采样时间，接收方能够时间戳能够确定数据的到达是否受到了延迟抖动的影响，但具体如何来补偿延迟抖动则是应用程序自己的事情。&nbsp;</span></li>
</ul>
<p align=left><span>从<span>RTP&nbsp;</span>数据报的格式不难看出，它包含了传输媒体的类型、格式、序列号、时间戳以及是否有附加数据等信息，这些都为实时的流媒体传输提供了相应的基础。<span>RTP</span>协议的目的是提供实时数据（如交互式的音频和视频）的端到端传输服务，因此在<span>RTP</span>中没有连接的概念，它可以建立在底层的面向连接或面向非连接的传输协议之上；<span>RTP</span>也不依赖于特别的网络地址格式，而仅仅只需要底层传输协议支持组帧（<span>Framing</span>）和分段（<span>Segmentation</span>）就足够了；另外<span>RTP&nbsp;</span>本身还不提供任何可靠性机制，这些都要由传输协议或者应用程序自己来保证。在典型的应用场合下，<span>RTP&nbsp;</span>一般是在传输协议之上作为应用程序的一部分加以实现的，如图<span>2</span>所示：</span></p>
<p align=left>&nbsp;</p>
<p align=left><a name=N100B2><strong><span><img height=191 alt="" src="http://www.cppblog.com/images/cppblog_com/zzh/image002.jpg" width=191 border=0><br></span></strong></a><span><strong><span>图<span>2&nbsp;RTP</span>与各种网络协议的关系<span><br><br></span></span></strong></span></p>
<p align=left>&nbsp;</p>
<p align=left><strong><span>2.2&nbsp;RTCP</span></strong><strong><span>控制协议</span></strong></p>
<p align=left>&nbsp;</p>
<p align=left><span>RTCP&nbsp;</span><span>控制协议需要与<span>RTP</span>数据协议一起配合使用，当应用程序启动一个<span>RTP</span>会话时将同时占用两个端口，分别供<span>RTP&nbsp;</span>和<span>RTCP</span>使用。<span>RTP</span>本身并不能为按序传输数据包提供可靠的保证，也不提供流量控制和拥塞控制，这些都由<span>RTCP</span>来负责完成。通常<span>RTCP</span>会采用与<span>&nbsp;RTP</span>相同的分发机制，向会话中的所有成员周期性地发送控制信息，应用程序通过接收这些数据，从中获取会话参与者的相关资料，以及网络状况、分组丢失概率等反馈信息，从而能够对服务质量进行控制或者对网络状况进行诊断。</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>RTCP</span><span>协议的功能是通过不同的<span>RTCP</span>数据报来实现的，主要有如下几种类型：&nbsp;</span></p>
<p align=left>&nbsp;</p>
<ul type=disc>
    <li><strong><span>SR</span></strong><span>　　发送端报告，所谓发送端是指发出<span>RTP</span>数据报的应用程序或者终端，发送端同时也可以是接收端。&nbsp;</span></li>
    <li><strong><span>RR</span></strong><span>　　接收端报告，所谓接收端是指仅接收但不发送<span>RTP</span>数据报的应用程序或者终端。&nbsp;</span></li>
    <li><strong><span>SDES</span></strong><span>　　源描述，主要功能是作为会话成员有关标识信息的载体，如用户名、邮件地址、电话号码等，此外还具有向会话成员传达会话控制信息的功能。&nbsp;</span></li>
    <li><strong><span>BYE</span></strong><span>　　通知离开，主要功能是指示某一个或者几个源不再有效，即通知会话中的其他成员自己将退出会话。&nbsp;</span></li>
    <li><strong><span>APP</span></strong><span>　　由应用程序自己定义，解决了<span>RTCP</span>的扩展性问题，并且为协议的实现者提供了很大的灵活性。&nbsp;</span></li>
</ul>
<p align=left><span>RTCP</span><span>数据报携带有服务质量监控的必要信息，能够对服务质量进行动态的调整，并能够对网络拥塞进行有效的控制。由于<span>RTCP</span>数据报采用的是多播方式，因此会话中的所有成员都可以通过<span>RTCP</span>数据报返回的控制信息，来了解其他参与者的当前情况。</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>在一个典型的应用场合下，发送媒体流的应用程序将周期性地产生发送端报告<span>SR</span>，该<span>RTCP</span>数据报含有不同媒体流间的同步信息，以及已经发送的数据报和字节的计数，接收端根据这些信息可以估计出实际的数据传输速率。另一方面，接收端会向所有已知的发送端发送接收端报告<span>RR</span>，该<span>RTCP</span>数据报含有已接收数据报的最大序列号、丢失的数据报数目、延时抖动和时间戳等重要信息，发送端应用根据这些信息可以估计出往返时延，并且可以根据数据报丢失概率和时延抖动情况动态调整发送速率，以改善网络拥塞状况，或者根据网络状况平滑地调整应用程序的服务质量。</span></p>
<p align=left>&nbsp;</p>
<p align=left><strong><span>2.3&nbsp;RTSP</span></strong><strong><span>实时流协议</span></strong></p>
<p align=left>&nbsp;</p>
<p align=left><span>作为一个应用层协议，<span>RTSP</span>提供了一个可供扩展的框架，它的意义在于使得实时流媒体数据的受控和点播变得可能。总的说来，<span>RTSP</span>是一个流媒体表示协议，主要用来控制具有实时特性的数据发送，但它本身并不传输数据，而是必须依赖于下层传输协议所提供的某些服务。<span>RTSP&nbsp;</span>可以对流媒体提供诸如播放、暂停、快进等操作，它负责定义具体的控制消息、操作方法、状态码等，此外还描述了与<span>RTP</span>间的交互操作。</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>RTSP&nbsp;</span><span>在制定时较多地参考了<span>HTTP/1.1</span>协议，甚至许多描述与<span>HTTP/1.1</span>完全相同。<span>RTSP</span>之所以特意使用与<span>HTTP/1.1</span>类似的语法和操作，在很大程度上是为了兼容现有的<span>Web</span>基础结构，正因如此，<span>HTTP/1.1</span>的扩展机制大都可以直接引入到<span>RTSP&nbsp;</span>中。</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>由<span>RTSP&nbsp;</span>控制的媒体流集合可以用表示描述（<span>Presentation&nbsp;Description</span>）来定义，所谓表示是指流媒体服务器提供给客户机的一个或者多个媒体流的集合，而表示描述则包含了一个表示中各个媒体流的相关信息，如数据编码<span>/</span>解码算法、网络地址、媒体流的内容等。</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>虽然<span>RTSP</span>服务器同样也使用标识符来区别每一流连接会话（<span>Session</span>），但<span>RTSP</span>连接并没有被绑定到传输层连接（如<span>TCP</span>等），也就是说在整个<span>&nbsp;RTSP</span>连接期间，<span>RTSP</span>用户可打开或者关闭多个对<span>RTSP</span>服务器的可靠传输连接以发出<span>RTSP&nbsp;</span>请求。此外，<span>RTSP</span>连接也可以基于面向无连接的传输协议（如<span>UDP</span>等）。</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>RTSP</span><span>协议目前支持以下操作：&nbsp;</span></p>
<p align=left>&nbsp;</p>
<ul type=disc>
    <li><strong><span>检索媒体</span></strong><span>　　允许用户通过<span>HTTP</span>或者其它方法向媒体服务器提交一个表示描述。如表示是组播的，则表示描述就包含用于该媒体流的组播地址和端口号；如果表示是单播的，为了安全在表示描述中应该只提供目的地址。&nbsp;</span></li>
    <li><strong><span>邀请加入</span></strong><span>　　媒体服务器可以被邀请参加正在进行的会议，或者在表示中回放媒体，或者在表示中录制全部媒体或其子集，非常适合于分布式教学。&nbsp;</span></li>
    <li><strong><span>添加媒体</span></strong><span>　　通知用户新加入的可利用媒体流，这对现场讲座来讲显得尤其有用。与<span>HTTP/1.1</span>类似，<span>RTSP</span>请求也可以交由代理、通道或者缓存来进行处理。&nbsp;</span></li>
</ul>
<p align=left><a name=2><span>三、流媒体编程&nbsp;</span></a><span><br>RTP&nbsp;</span><span>是目前解决流媒体实时传输问题的最好办法，如果需要在<span>Linux</span>平台上进行实时流媒体编程，可以考虑使用一些开放源代码的<span>RTP</span>库，如<span>LIBRTP</span>、<span>&nbsp;JRTPLIB</span>等。<span>JRTPLIB</span>是一个面向对象的<span>RTP</span>库，它完全遵循<span>RFC&nbsp;1889</span>设计，在很多场合下是一个非常不错的选择，下面就以<span>JRTPLIB</span>为例，讲述如何在<span>Linux</span>平台上运用<span>RTP</span>协议进行实时流媒体编程。</span></p>
<p align=left>&nbsp;</p>
<p align=left><strong><span>3.1&nbsp;</span></strong><strong><span>环境搭建</span></strong></p>
<p align=left>&nbsp;</p>
<p align=left><span>JRTPLIB&nbsp;</span><span>是一个用<span>C++</span>语言实现的<span>RTP</span>库，目前已经可以运行在<span>Windows</span>、<span>Linux</span>、<span>FreeBSD</span>、<span>&nbsp;Solaris</span>、<span>Unix</span>和<span>VxWorks</span>等多种操作系统上。要为<span>Linux&nbsp;</span>系统安装<span>JRTPLIB</span>，首先从<span>JRTPLIB</span>的网站（<span>http:&nbsp;//lumumba.luc.ac.be/jori/jrtplib/jrtplib.html</span>）下载最新的源码包，此处使用的是<span>jrtplib-&nbsp;2.7b.tar.bz2</span>。假设下载后的源码包保存在<span>/usr/local/src</span>目录下，执行下面的命令可以对其进行解压缩：</span></p>
<p align=left>&nbsp;</p>
<table cellSpacing=0 cellPadding=0 width="100%" align=left border=1>
    <tbody>
        <tr>
            <td>
            <p align=left><span><br><br>[root@linuxgam&nbsp;src]#&nbsp;bzip2&nbsp;-dc&nbsp;jrtplib-2.7b.tar.bz2&nbsp;|&nbsp;tar&nbsp;xvf&nbsp;-</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/zzh/aggbug/29577.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zzh/" target="_blank">Xiao.Zhu</a> 2007-08-08 16:38 <a href="http://www.cppblog.com/zzh/archive/2007/08/08/29577.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>将进程变成一个线程执行代码</title><link>http://www.cppblog.com/zzh/archive/2007/08/08/29574.html</link><dc:creator>Xiao.Zhu</dc:creator><author>Xiao.Zhu</author><pubDate>Wed, 08 Aug 2007 08:23:00 GMT</pubDate><guid>http://www.cppblog.com/zzh/archive/2007/08/08/29574.html</guid><wfw:comment>http://www.cppblog.com/zzh/comments/29574.html</wfw:comment><comments>http://www.cppblog.com/zzh/archive/2007/08/08/29574.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/zzh/comments/commentRss/29574.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zzh/services/trackbacks/29574.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;2//*******************************************************************************************************&nbsp;&nbsp;&nbsp;3//&nbsp;loadEXE.cpp&nbsp;:&nbsp;Defines&nb...&nbsp;&nbsp;<a href='http://www.cppblog.com/zzh/archive/2007/08/08/29574.html'>阅读全文</a><img src ="http://www.cppblog.com/zzh/aggbug/29574.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zzh/" target="_blank">Xiao.Zhu</a> 2007-08-08 16:23 <a href="http://www.cppblog.com/zzh/archive/2007/08/08/29574.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>中国职员的九种劣根性</title><link>http://www.cppblog.com/zzh/archive/2007/07/12/27900.html</link><dc:creator>Xiao.Zhu</dc:creator><author>Xiao.Zhu</author><pubDate>Thu, 12 Jul 2007 04:46:00 GMT</pubDate><guid>http://www.cppblog.com/zzh/archive/2007/07/12/27900.html</guid><wfw:comment>http://www.cppblog.com/zzh/comments/27900.html</wfw:comment><comments>http://www.cppblog.com/zzh/archive/2007/07/12/27900.html#Feedback</comments><slash:comments>10</slash:comments><wfw:commentRss>http://www.cppblog.com/zzh/comments/commentRss/27900.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zzh/services/trackbacks/27900.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 中国人不是文人相轻，而是人人相轻，只要想轻视别人，总有相轻的理由。比如北京人轻视外地人，上海人轻视外地人，城里人轻视农村人，南方人轻视北方人，有钱人轻视穷人，开车的轻视走路的，走路的轻视扫路的，吃饭的轻视做饭的……就是不会相互尊重。&nbsp;&nbsp;<a href='http://www.cppblog.com/zzh/archive/2007/07/12/27900.html'>阅读全文</a><img src ="http://www.cppblog.com/zzh/aggbug/27900.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zzh/" target="_blank">Xiao.Zhu</a> 2007-07-12 12:46 <a href="http://www.cppblog.com/zzh/archive/2007/07/12/27900.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深圳</title><link>http://www.cppblog.com/zzh/archive/2007/06/20/26730.html</link><dc:creator>Xiao.Zhu</dc:creator><author>Xiao.Zhu</author><pubDate>Wed, 20 Jun 2007 15:12:00 GMT</pubDate><guid>http://www.cppblog.com/zzh/archive/2007/06/20/26730.html</guid><wfw:comment>http://www.cppblog.com/zzh/comments/26730.html</wfw:comment><comments>http://www.cppblog.com/zzh/archive/2007/06/20/26730.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zzh/comments/commentRss/26730.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zzh/services/trackbacks/26730.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;远看深圳像天堂，近看深圳像银行；到了深圳像牢房，不如回家放牛羊。&nbsp;<br>　　个个都说深圳好，个个都往深圳跑；深圳挣钱深圳花，哪有钞票寄回家。 <br>　　都说这里工资高，害我没钱买牙膏；都说这里伙食好，青菜里面加青草。 <br>　　都说这里环境好，蟑螂蚂蚁四处跑；都说这里领班帅，个个平头像锅盖。 <br>　　年年打工年年愁，天天加班像只猴；加班加点无报酬，天天挨骂无理由。 <br>　　碰见老板低着头，发了工资摇摇头；到了月尾就发愁，不知何年才出头。 <br>　　罗湖的美女，福田的汉，布吉街的痞子满街串，南山的花，西丽的草，仙湖里的和尚满街跑。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;华侨城的帅哥，沙头角的狼，皇岗到处是流氓，老成都的饭，彭年的床，岗厦的女生吓死郎。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;盐田的田，大鹏湾的湾，小梅沙的男女太疯颠。南头关的痴，梅林关的怨，罗湖关的情侣香港转。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;福永的夜色，松岗的乱，公明的女工没男伴。西乡的土，沙井的苦，宝安的男人心里堵。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;关内的偷,关外的抢深圳的治安没法讲<br>
<img src ="http://www.cppblog.com/zzh/aggbug/26730.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zzh/" target="_blank">Xiao.Zhu</a> 2007-06-20 23:12 <a href="http://www.cppblog.com/zzh/archive/2007/06/20/26730.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>串口</title><link>http://www.cppblog.com/zzh/archive/2007/06/16/26461.html</link><dc:creator>Xiao.Zhu</dc:creator><author>Xiao.Zhu</author><pubDate>Sat, 16 Jun 2007 15:25:00 GMT</pubDate><guid>http://www.cppblog.com/zzh/archive/2007/06/16/26461.html</guid><wfw:comment>http://www.cppblog.com/zzh/comments/26461.html</wfw:comment><comments>http://www.cppblog.com/zzh/archive/2007/06/16/26461.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zzh/comments/commentRss/26461.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zzh/services/trackbacks/26461.html</trackback:ping><description><![CDATA[<p>&nbsp; </p>
<p align=left><span>无论那种操作方式，一般都通过四个步骤来完成：<span><br></span>（<span>1</span>） <a name=打开串口>打开串口</a></span></p>
<p align=left><span>Win32</span><span>系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台，都是用<span>API</span>函数<span>CreateFile</span>来打开或创建的。该函数的原型为：</span></p>
<p align=left><span>HANDLE CreateFile( LPCTSTR lpFileName,<span>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>DWORD dwDesiredAccess,<span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>DWORD dwShareMode,<span>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>LPSECURITY_ATTRIBUTES lpSecurityAttributes,<span>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>DWORD dwCreationDistribution,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DWORD dwFlagsAndAttributes,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HANDLE hTemplateFile);</span></p>
<p align=left><span><span>&#183;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span>lpFileName</span><span>：将要打开的串口逻辑名，如<span>&#8220;COM<st1:chmetcnv w:st="on" TCSC="0" NumberType="1" Negative="False" HasSpace="False" SourceValue="1" UnitName="&#8221;">1&#8221;</st1:chmetcnv></span>；</span></p>
<p align=left><span><span>&#183;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span>dwDesiredAccess</span><span>：指定串口访问的类型，可以是读取、写入或二者并列；</span></p>
<p align=left><span><span>&#183;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span>dwShareMode</span><span>：指定共享属性，由于串口不能共享，该参数必须置为<span>0</span>；</span></p>
<p align=left><span><span>&#183;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span>lpSecurityAttributes</span><span>：引用安全性属性结构，缺省值为<span>NULL</span>；</span></p>
<p align=left><span><span>&#183;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span>dwCreationDistribution</span><span>：创建标志，对串口操作该参数必须置为<span>OPEN_EXISTING</span>；</span></p>
<p align=left><span><span>&#183;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span>dwFlagsAndAttributes</span><span>：属性描述，用于指定该串口是否进行异步操作，该值为<span>FILE_FLAG_OVERLAPPED</span>，表示使用异步的<span>I/O</span>；该值为<span>0</span>，表示同步<span>I/O</span>操作；</span></p>
<p align=left><span><span>&#183;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span>hTemplateFile</span><span>：对串口而言该参数必须置为<span>NULL</span>；</span></p>
<p align=left><span>同步<span>I/O</span>方式打开串口的示例代码：</span></p>
<p align=left><span>HANDLE hCom;&nbsp;//</span><span>全局变量，串口句柄<br><span>hCom=CreateFile("COM1",//COM1</span>口<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;<br>&nbsp;&nbsp; </span>GENERIC_READ|GENERIC_WRITE, //</span>允许读和写<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; </span>0, //</span>独占方式<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NULL,<span>&nbsp;&nbsp;&nbsp;</span><span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>OPEN_EXISTING, //</span>打开而不是创建<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;</span>&nbsp;&nbsp;&nbsp;0, //</span>同步方式<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp; </span>NULL);<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>if(hCom==(HANDLE)-1)<span>&nbsp;&nbsp;<br></span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AfxMessageBox("</span>打开<span>COM</span>失败<span>!");<span>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>return FALSE;&nbsp;<br>}<span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; </span>return TRUE;</span></span></p>
<p align=left><span>重叠<span>I/O</span>打开串口的示例代码：</span></p>
<p align=left><span>HANDLE hCom;&nbsp;//</span><span>全局变量，串口句柄<span>&nbsp;<br>hCom =CreateFile("COM1",&nbsp;//COM1</span>口<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp; </span>GENERIC_READ|GENERIC_WRITE, //</span>允许读和写<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>0,&nbsp;//</span>独占方式<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>NULL,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;</span>&nbsp;&nbsp;&nbsp;OPEN_EXISTING,&nbsp;//</span>打开而不是创建<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span>FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //</span>重叠方式<span><span>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>NULL);<span><br>&nbsp;</span>if(hCom ==INVALID_HANDLE_VALUE)<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>AfxMessageBox("</span>打开<span>COM</span>失败<span>!");<span>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>return FALSE;&nbsp;<br>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;<br>&nbsp; </span>return TRUE;</span></span></p>
<p align=left><span>（<span>2</span>）、<a name=配置串口>配置串口</a></span></p>
<p align=left><span>在打开通讯设备句柄后，常常需要对串口进行一些初始化配置工作。这需要通过一个<span>DCB</span>结构来进行。<span>DCB</span>结构包含了诸如波特率、数据位数、奇偶校验和停止位数等信息。在查询或配置串口的属性时，都要用<span>DCB</span>结构来作为缓冲区。<span><br></span>　　一般用<span>CreateFile</span>打开串口后，可以调用<span>GetCommState</span>函数来获取串口的初始配置。要修改串口的配置，应该先修改<span>DCB</span>结构，然后再调用<span>SetCommState</span>函数设置串口。<span><br></span>　　<span>DCB</span>结构包含了串口的各项参数设置，下面仅介绍几个该结构常用的变量：</span></p>
<p align=left><span>typedef struct _DCB{<span>&nbsp;<br>&nbsp; </span>&#8230;&#8230;&#8230;<span>&nbsp;&nbsp; </span>//</span><span>波特率，指定通信设备的传输速率。这个成员可以是实际波特率值或者下面的常量值之一：<br><span><span>&nbsp;&nbsp; </span>DWORD BaudRate; <br>CBR_110</span>，<br><span>CBR_300</span>，<br><span>CBR_600</span>，<br><span>CBR_1200</span>，<br><span>CBR_2400</span>，<span><br>CBR_4800</span>，<br><span>CBR_9600</span>，<br><span>CBR_19200</span>，<br><span>CBR_38400</span>，<br><span>CBR_56000</span>，<br><span>CBR_57600</span>，<span> <br>CBR_115200</span>，<span> <br>CBR_128000</span>，<span> <br>CBR_256000</span>，<span> <br>CBR_14400DWORD fParity; // </span>指定奇偶校验使能。若此成员为<span>1</span>，允许奇偶校验检查<span><span>&nbsp;&nbsp;&nbsp; <br></span>&#8230;BYTE ByteSize; // </span>通信字节位数，<span>4—8BYTE <br>Parity; //</span>指定奇偶校验方法。此成员可以有下列值：<span>EVENPARITY </span>偶校验<span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>NOPARITY </span>无校验<span>MARKPARITY </span>标记校验<span><span>&nbsp;&nbsp; </span>ODDPARITY </span>奇校验<span>BYTE <br>StopBits; //</span>指定停止位的位数。<br>此成员可以有下列值：<br><span>ONESTOPBIT 1</span>位停止位<span><span>&nbsp;&nbsp; </span><br>TWOSTOPBITS 2</span>位停止位<span><br>ONE5STOPBITS<span>&nbsp;&nbsp; </span>1.5</span>位停止位<span><span>&nbsp;&nbsp; </span>&#8230;&#8230;&#8230;&nbsp;} DCB;<br>winbase.h</span>文件中定义了以上用到的常量。<br>如下：<br><span>#define NOPARITY<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>0<br>#define ODDPARITY<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>1<br>#define EVENPARITY<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>2<br>#define ONESTOPBIT<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>0<br>#define ONE5STOPBITS<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>1<br>#define TWOSTOPBITS<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>2<br>#define CBR_110<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>110<br>#define CBR_300<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>300<br>#define CBR_600<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>600<br>#define CBR_1200<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>1200<br>#define CBR_2400<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>2400<br>#define CBR_4800<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>4800<br>#define CBR_9600<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>9600<br>#define CBR_14400<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>14400<br>#define CBR_19200<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>19200<br>#define CBR_38400<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>38400<br>#define CBR_56000<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>56000<br>#define CBR_57600<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>57600<br>#define CBR_115200<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>115200<br>#define CBR_128000<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>128000<br>#define CBR_256000<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>256000</span></span></p>
<p align=left><span>GetCommState</span><span>函数可以获得<span>COM</span>口的设备控制块，从而获得相关参数：</span></p>
<p align=left><span>BOOL GetCommState(<span>&nbsp;&nbsp; </span>HANDLE hFile, //</span><span>标识通讯端口的句柄<span><span>&nbsp;&nbsp; <br></span>LPDCB lpDCB //</span>指向一个设备控制块（<span>DCB</span>结构）的指针<span>&nbsp;);<br>SetCommState</span>函数设置<span>COM</span>口的设备控制块：<br><span>BOOL SetCommState(<span>&nbsp;&nbsp; </span>HANDLE hFile,<span>&nbsp;&nbsp;&nbsp; </span>LPDCB lpDCB<span>&nbsp;&nbsp; </span>);</span></span></p>
<p align=left><span>除了在<span>BCD</span>中的设置外，程序一般还需要设置<span>I/O</span>缓冲区的大小和超时。<span>Windows</span>用<span>I/O</span>缓冲区来暂存串口输入和输出的数据。如果通信的速率较高，则应该设置较大的缓冲区。调用<span>SetupComm</span>函数可以设置串行口的输入和输出缓冲区的大小。</span></p>
<p align=left><span>BOOL SetupComm(<span>&nbsp;&nbsp;&nbsp; </span>HANDLE hFile,<span>&nbsp;&nbsp;&nbsp; </span>// </span><span>通信设备的句柄<span><span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD dwInQueue,<span> </span>// </span>输入缓冲区的大小（字节数）<span><span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD dwOutQueue<span>&nbsp;&nbsp; </span>// </span>输出缓冲区的大小（字节数）<span><span>&nbsp;&nbsp; </span>);</span></span></p>
<p align=left><span>在用<span>ReadFile</span>和<span>WriteFile</span>读写串行口时，需要考虑超时问题。超时的作用是在指定的时间内没有读入或发送指定数量的字符，<span>ReadFile</span>或<span>WriteFile</span>的操作仍然会结束。<span><br></span>　　要查询当前的超时设置应调用<span>GetCommTimeouts</span>函数，该函数会填充一个<span>COMMTIMEOUTS</span>结构。调用<span>SetCommTimeouts</span>可以用某一个<span>COMMTIMEOUTS</span>结构的内容来设置超时。<span><br></span>　　读写串口的超时有两种：间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延。总超时是指读写操作总共花费的最大时间。写操作只支持总超时，而读操作两种超时均支持。用<span>COMMTIMEOUTS</span>结构可以规定读写操作的超时。<span><br>COMMTIMEOUTS</span>结构的定义为：</span></p>
<p align=left><span>typedef struct _COMMTIMEOUTS {<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD ReadIntervalTimeout; //</span><span>读间隔超时<span><span>&nbsp;&nbsp;&nbsp; <br></span>DWORD ReadTotalTimeoutMultiplier; //</span>读时间系数<span><span>&nbsp;&nbsp;&nbsp; <br></span>DWORD ReadTotalTimeoutConstant; //</span>读时间常量<span><span>&nbsp;&nbsp;&nbsp; </span><br>DWORD WriteTotalTimeoutMultiplier; // </span>写时间系数<span><span>&nbsp;&nbsp;&nbsp;<br></span>DWORD WriteTotalTimeoutConstant; //</span>写时间常量<span><br>} COMMTIMEOUTS,*LPCOMMTIMEOUTS;</span></span></p>
<p align=left><span>COMMTIMEOUTS</span><span>结构的成员都以毫秒为单位。总超时的计算公式是：<span><br></span>总超时＝时间系数<span>&#215;</span>要求读<span>/</span>写的字符数＋时间常量<span> <br></span>例如，要读入<span>10</span>个字符，那么读操作的总超时的计算公式为：<span><br></span>读总超时＝<span>ReadTotalTimeoutMultiplier&#215;10</span>＋<span>ReadTotalTimeoutConstant <br></span>可以看出：间隔超时和总超时的设置是不相关的，这可以方便通信程序灵活地设置各种超时。<span> <br><br></span>如果所有写超时参数均为<span>0</span>，那么就不使用写超时。如果<span>ReadIntervalTimeout</span>为<span>0</span>，那么就不使用读间隔超时。如果<span>ReadTotalTimeoutMultiplier </span>和<span> ReadTotalTimeoutConstant </span>都为<span>0</span>，则不使用读总超时。如果读间隔超时被设置成<span>MAXDWORD</span>并且读时间系数和读时间常量都为<span>0</span>，那么在读一次输入缓冲区的内容后读操作就立即返回，而不管是否读入了要求的字符。<span><br></span>　　在用重叠方式读写串口时，虽然<span>ReadFile</span>和<span>WriteFile</span>在完成操作以前就可能返回，但超时仍然是起作用的。在这种情况下，超时规定的是操作的完成时间，而不是<span>ReadFile</span>和<span>WriteFile</span>的返回时间。<span><br></span>配置串口的示例代码：</span></p>
<p align=left><span>SetupComm(hCom,1024,1024); //</span><span>输入缓冲区和输出缓冲区的大小都是<span>1024<span> <br></span>COMMTIMEOUTS TimeOuts;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>设定读超时<span><span>&nbsp;&nbsp; </span><br>TimeOuts.ReadIntervalTimeout=1000;<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>TimeOuts.ReadTotalTimeoutMultiplier=500;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>TimeOuts.ReadTotalTimeoutConstant=5000;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>设定写超时<span><span>&nbsp;&nbsp; </span>T<br>imeOuts.WriteTotalTimeoutMultiplier=500;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>TimeOuts.WriteTotalTimeoutConstant=2000;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>SetCommTimeouts(hCom,&amp;TimeOuts); //</span>设置超时<span><span> <br></span>DCB dcb;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>GetCommState(hCom,&amp;dcb);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>dcb.BaudRate=9600; //</span>波特率为<span>9600<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>dcb.ByteSize=8; //</span>每个字节有<span>8</span>位<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>dcb.Parity=NOPARITY; //</span>无奇偶校验位<span><span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>dcb.StopBits=TWOSTOPBITS; //</span>两个停止位<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>SetCommState(hCom,&amp;dcb);<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);</span></span></p>
<p align=left><span>在读写串口之前，还要用<span>PurgeComm()</span>函数清空缓冲区，该函数原型：</span></p>
<p align=left><span>BOOL PurgeComm(<span>&nbsp;&nbsp;&nbsp; </span>HANDLE hFile,<span>&nbsp;&nbsp;&nbsp; </span>//</span><span>串口句柄<span><span>&nbsp;&nbsp;&nbsp;</span>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DWORD dwFlags<span>&nbsp;&nbsp;&nbsp; </span>// </span>需要完成的操作<span><span>&nbsp;&nbsp; <br></span>);</span></span></p>
<p align=left><span>参数<span>dwFlags</span>指定要完成的操作，可以是下列值的组合：</span></p>
<p align=left><span>PURGE_TXABORT<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&nbsp;</span><span>中断所有写操作并立即返回，即使写操作还没有完成。<span>PURGE_RXABORT<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&nbsp;</span>中断所有读操作并立即返回，即使读操作还没有完成。<span>PURGE_TXCLEAR<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>&nbsp;</span>清除输出缓冲区<span>PURGE_RXCLEAR<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&nbsp;</span>清除输入缓冲区</span></p>
<p align=left><span>（<span>3</span>）、<a name=读写串口>读写串口</a></span></p>
<p align=left><span>我们使用<span>ReadFile</span>和<span>WriteFile</span>读写串口，下面是两个函数的声明：</span></p>
<p align=left><span>BOOL ReadFile(<span>&nbsp;&nbsp;&nbsp; </span>HANDLE hFile,<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>串口的句柄<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>读入的数据存储的地址，<span><span>&nbsp;&nbsp;&nbsp; </span>// </span>即读入的数据将存储在以该指针的值为首地址的一片内存区<span><span>&nbsp;&nbsp;&nbsp;<br></span>LPVOID lpBuffer,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp; <br></span>DWORD nNumberOfBytesToRead,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>要读入的数据的字节数<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>指向一个<span>DWORD</span>数值，该数值返回读操作实际读入的字节数<span><span>&nbsp;&nbsp;&nbsp; </span><br>LPDWORD lpNumberOfBytesRead,<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>重叠操作时，该参数指向一个<span>OVERLAPPED</span>结构，同步操作时，该参数为<span>NULL</span>。<span><span>&nbsp;&nbsp;&nbsp; </span><br>LPOVERLAPPED lpOverlapped <span>&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;</span>);<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>BOOL WriteFile(<span>&nbsp;&nbsp;&nbsp; </span>HANDLE hFile,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>串口的句柄<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span><span>&nbsp;&nbsp;&nbsp; </span>// </span>即以该指针的值为首地址的<span>nNumberOfBytesToWrite<span>&nbsp;&nbsp;&nbsp; </span>// </span>个字节的数据将要写入串口的发送数据缓冲区。<span><span>&nbsp;&nbsp;&nbsp; <br></span>LPCVOID lpBuffer,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 写入的数据存储的地址，</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>DWORD nNumberOfBytesToWrite,&nbsp;//</span>要写入的数据的字节数<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>指向指向一个<span>DWORD</span>数值，该数值返回实际写入的字节数<span><span>&nbsp;&nbsp;&nbsp; </span><br>LPDWORD lpNumberOfBytesWritten,<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>重叠操作时，该参数指向一个<span>OVERLAPPED</span>结构，<span><span>&nbsp;&nbsp;&nbsp; </span>// </span>同步操作时，该参数为<span>NULL</span>。<span><span>&nbsp;&nbsp;<br>&nbsp; </span>LPOVERLAPPED lpOverlapped <span>&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;</span>);</span></span></p>
<p align=left><span>在用<span>ReadFile</span>和<span>WriteFile</span>读写串口时，既可以同步执行，也可以重叠执行。在同步执行时，函数直到操作完成后才返回。这意味着同步执行时线程会被阻塞，从而导致效率下降。在重叠执行时，即使操作还未完成，这两个函数也会立即返回，费时的<span>I/O</span>操作在后台进行。<span><br></span>　　<span>ReadFile</span>和<span>WriteFile</span>函数是同步还是异步由<span>CreateFile</span>函数决定，如果在调用<span>CreateFile</span>创建句柄时指定了<span>FILE_FLAG_OVERLAPPED</span>标志，那么调用<span>ReadFile</span>和<span>WriteFile</span>对该句柄进行的操作就应该是重叠的；如果未指定重叠标志，则读写操作应该是同步的。<span>ReadFile</span>和<span>WriteFile</span>函数的同步或者异步应该和<span>CreateFile</span>函数相一致。<span><br></span>　　<span>ReadFile</span>函数只要在串口输入缓冲区中读入指定数量的字符，就算完成操作。而<span>WriteFile</span>函数不但要把指定数量的字符拷入到输出缓冲区，而且要等这些字符从串行口送出去后才算完成操作。<span><br></span>　　如果操作成功，这两个函数都返回<span>TRUE</span>。需要注意的是，当<span>ReadFile</span>和<span>WriteFile</span>返回<span>FALSE</span>时，不一定就是操作失败，线程应该调用<span>GetLastError</span>函数分析返回的结果。例如，在重叠操作时如果操作还未完成函数就返回，那么函数就返回<span>FALSE</span>，而且<span>GetLastError</span>函数返回<span>ERROR_IO_PENDING</span>。这说明重叠操作还未完成。<span><br><br></span>同步方式读写串口比较简单，下面先例举同步方式读写串口的代码：</span></p>
<p align=left><span>//</span><span>同步读串口<span>char str[100];<br>DWORD wCount;//</span>读取的字节数<br><span>BOOL bReadStat;<br>bReadStat=ReadFile(hCom,str,100,&amp;wCount,NULL);<br>if(!bReadStat){<span>&nbsp;&nbsp; </span><br>AfxMessageBox("</span>读串口失败<span>!");<span> </span><br>return FALSE;<br>}<br>return TRUE;<br>//</span>同步写串口<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>char lpOutBuffer[100];<span> <br></span>DWORD dwBytesWrite=100;<span>&nbsp;&nbsp; <br></span>COMSTAT ComStat;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>DWORD dwErrorFlags;<span>&nbsp;&nbsp;&nbsp; </span><br>BOOL bWriteStat;<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,&amp; dwBytesWrite,NULL);<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>if(!bWriteStat)<span> </span>{<span>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>AfxMessageBox("</span>写串口失败<span>!");<br>&nbsp;}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>PurgeComm(hCom, PURGE_TXABORT|<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);</span></span></p>
<p align=left><span>在重叠操作时<span>,</span>操作还未完成函数就返回。<span> <br><br></span>　　重叠<span>I/O</span>非常灵活，它也可以实现阻塞（例如我们可以设置一定要读取到一个数据才能进行到下一步操作）。有两种方法可以等待操作完成：一种方法是用象<span>WaitForSingleObject</span>这样的等待函数来等待<span>OVERLAPPED</span>结构的<span>hEvent</span>成员；另一种方法是调用<span>GetOverlappedResult</span>函数等待，后面将演示说明。<span><br></span>下面我们先简单说一下<span>OVERLAPPED</span>结构和<span>GetOverlappedResult</span>函数：<span><br>OVERLAPPED</span>结构<span><br>OVERLAPPED</span>结构包含了重叠<span>I/O</span>的一些信息，定义如下：</span></p>
<p align=left><span>typedef struct _OVERLAPPED { // o<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>DWORD&nbsp;Internal;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>DWORD&nbsp;InternalHigh;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>DWORD&nbsp;Offset;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>DWORD&nbsp;OffsetHigh;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>HANDLE hEvent; } OVERLAPPED;</span></p>
<p align=left><span>在使用<span>ReadFile</span>和<span>WriteFile</span>重叠操作时，线程需要创建<span>OVERLAPPED</span>结构以供这两个函数使用。线程通过<span>OVERLAPPED</span>结构获得当前的操作状态，该结构最重要的成员是<span>hEvent</span>。<span>hEvent</span>是读写事件。当串口使用异步通讯时，函数返回时操作可能还没有完成，程序可以通过检查该事件得知是否读写完毕。<span><br></span>　　当调用<span>ReadFile, WriteFile </span>函数的时候，该成员会自动被置为无信号状态；当重叠操作完成后，该成员变量会自动被置为有信号状态。</span></p>
<p align=left><span>GetOverlappedResult</span><span>函数<span>BOOL GetOverlappedResult(<span>&nbsp;&nbsp;&nbsp; </span>HANDLE hFile,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>串口的句柄<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>指向重叠操作开始时指定的<span>OVERLAPPED</span>结构<span><span>&nbsp;&nbsp;&nbsp; </span>LPOVERLAPPED lpOverlapped,&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>指向一个<span>32</span>位变量，该变量的值返回实际读写操作传输的字节数。<span><span>&nbsp;&nbsp;&nbsp; </span>LPDWORD lpNumberOfBytesTransferred,<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>该参数用于指定函数是否一直等到重叠操作结束。<span><span>&nbsp;&nbsp;&nbsp; </span>// </span>如果该参数为<span>TRUE</span>，函数直到操作结束才返回。<span><span>&nbsp;&nbsp;&nbsp; </span>// </span>如果该参数为<span>FALSE</span>，函数直接返回，这时如果操作没有完成，<span><span>&nbsp;&nbsp;&nbsp; </span>// </span>通过调用<span>GetLastError()</span>函数会返回<span>ERROR_IO_INCOMPLETE</span>。<span><span>&nbsp;&nbsp;&nbsp; </span>BOOL bWait <span>&nbsp;&nbsp;&nbsp;</span>);</span></span></p>
<p align=left><span>该函数返回重叠操作的结果，用来判断异步操作是否完成，它是通过判断<span>OVERLAPPED</span>结构中的<span>hEvent</span>是否被置位来实现的。<span><br><br></span>异步读串口的示例代码：</span></p>
<p align=left><span>char lpInBuffer[1024];<br>DWORD dwBytesRead=1024;<br>COMSTAT ComStat;<br>DWORD dwErrorFlags;<br>OVERLAPPED m_osRead;<br>memset(&amp;m_osRead,0,sizeof(OVERLAPPED));<br>m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);<br>ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);<br>dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue);<br>if(!dwBytesRead)<br>return FALSE;<br>BOOL bReadStatus;<br>bReadStatus=ReadFile(hCom,<br>lpInBuffer,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>dwBytesRead,<br>&amp;dwBytesRead,<br>&amp;m_osRead);<br>if(!bReadStatus) //</span><span>如果<span>ReadFile</span>函数返回<span>FALSE<br>{<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>if(GetLastError()==ERROR_IO_PENDING)&nbsp;//GetLastError()</span>函数返回<span>ERROR_IO_PENDING,</span>表明串口正在进行读操作<span><span>&nbsp;&nbsp;<br></span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>WaitForSingleObject(m_osRead.hEvent,2000);<span>&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>使用<span>WaitForSingleObject</span>函数等待，直到读操作完成或延时已达到<span>2</span>秒钟<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>当串口读操作进行完毕后，<span>m_osRead</span>的<span>hEvent</span>事件会变为有信号<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>PurgeComm(hCom, PURGE_TXABORT|<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);<span>&nbsp;&nbsp;<br>&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>return dwBytesRead;<span>&nbsp;&nbsp;&nbsp; <br></span>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>return 0;<br>}<br>PurgeComm(hCom, PURGE_TXABORT|<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&nbsp;PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);<br>return dwBytesRead;</span></span></p>
<p align=left><span>对以上代码再作简要说明：在使用<span>ReadFile </span>函数进行读操作前，应先使用<span>ClearCommError</span>函数清除错误。<span>ClearCommError</span>函数的原型如下：</span></p>
<p align=left><span>BOOL ClearCommError(<span>&nbsp;&nbsp;&nbsp; </span>HANDLE hFile,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span><span>串口句柄<span><span>&nbsp;&nbsp;&nbsp; <br></span>LPDWORD lpErrors,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// </span>指向接收错误码的变量<span><span>&nbsp;&nbsp;&nbsp;<br></span>LPCOMSTAT lpStat&nbsp;// </span>指向通讯状态缓冲区<span><span>&nbsp;&nbsp; <br></span>);</span></span></p>
<p align=left><span>该函数获得通信错误并报告串口的当前状态，同时，该函数清除串口的错误标志以便继续输入、输出操作。<span><br></span>参数<span>lpStat</span>指向一个<span>COMSTAT</span>结构，该结构返回串口状态信息。<span> COMSTAT</span>结构<span> COMSTAT</span>结构包含串口的信息，结构定义如下：</span></p>
<p align=left><span>typedef struct _COMSTAT { // cst<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>DWORD fCtsHold : 1;<span>&nbsp;</span>// Tx waiting for CTS signal<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>DWORD fDsrHold : 1;<span>&nbsp;&nbsp; </span>// Tx waiting for DSR signal<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>DWORD fRlsdHold : 1;&nbsp;// Tx waiting for RLSD signal<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD fXoffHold : 1;&nbsp;// Tx waiting, XOFF char rec''d<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD fXoffSent : 1;&nbsp;// Tx waiting, XOFF char sent<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>DWORD fEof : 1;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// EOF character sent<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD fTxim : 1;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// character waiting for Tx<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>DWORD fReserved : 25; // reserved<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>DWORD cbInQue;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// bytes in input buffer<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>DWORD cbOutQue;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// bytes in output buffer <br>} COMSTAT, *LPCOMSTAT;</span></p>
<p align=left><span>本文只用到了<span>cbInQue</span>成员变量，该成员变量的值代表输入缓冲区的字节数。<span><br><br></span>　　最后用<span>PurgeComm</span>函数清空串口的输入输出缓冲区。</span></p>
<p align=left><span>这段代码用<span>WaitForSingleObject</span>函数来等待<span>OVERLAPPED</span>结构的<span>hEvent</span>成员，下面我们再演示一段调用<span>GetOverlappedResult</span>函数等待的异步读串口示例代码：</span></p>
<p align=left><span>char lpInBuffer[1024];<br>DWORD dwBytesRead=1024;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>BOOL bReadStatus;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD dwErrorFlags;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>COMSTAT ComStat;<br>OVERLAPPED m_osRead;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);&nbsp;<br>if(!ComStat.cbInQue)<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return 0;<br>dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue);&nbsp;<br>bReadStatus=ReadFile(hCom, <br>lpInBuffer,<br>dwBytesRead,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>&amp;dwBytesRead,<br>&amp;m_osRead);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>if(!bReadStatus) //</span><span>如果<span>ReadFile</span>函数返回<span>FALSE<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>if(GetLastError()==ERROR_IO_PENDING)<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>{<span>&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>GetOverlappedResult(hCom,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>&amp;m_osRead,&amp;dwBytesRead,TRUE);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// GetOverlappedResult</span>函数的最后一个参数设为<span>TRUE</span>，<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>函数会一直等待，直到读操作完成或由于错误而返回。<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>return dwBytesRead;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>return 0;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>return dwBytesRead;</span></span></p>
<p align=left><span>异步写串口的示例代码：</span></p>
<p align=left><span>char buffer[1024];<br>DWORD dwBytesWritten=1024;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>DWORD dwErrorFlags;<span>&nbsp;&nbsp;&nbsp; </span><br>COMSTAT ComStat;<br>OVERLAPPED m_osWrite;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>BOOL bWriteStat;<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>bWriteStat=WriteFile(hCom,buffer,dwBytesWritten,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>&amp;dwBytesWritten,&amp;m_OsWrite);<span>&nbsp;&nbsp; <br></span>if(!bWriteStat)<span> </span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>if(GetLastError()==ERROR_IO_PENDING)<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>WaitForSingleObject(m_osWrite.hEvent,1000);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>return dwBytesWritten;&nbsp;<br>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>return 0;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>return dwBytesWritten;</span></p>
<p align=left><span>（<span>4</span>）、<a name=关闭串口>关闭串口</a></span></p>
<p align=left><span>利用<span>API</span>函数关闭串口非常简单，只需使用<span>CreateFile</span>函数返回的句柄作为参数调用<span>CloseHandle</span>即可：</span></p>
<p align=left><span>BOOL CloseHandle(<span>&nbsp;&nbsp;&nbsp; </span>HANDLE hObject; //handle to object to close );</span></p>
<p align=left><span>串口编程的一个实例</span></p>
<p align=left><span>为了让您更好地理解串口编程<span>,</span>下面我们分别编写两个例程（见附带的源码部分）<span>,</span>这两个例程都实现了工控机与百特显示仪表通过<span>RS485</span>接口进行的串口通信。其中第一个例程采用同步串口操作<span>,</span>第二个例程采用异步串口操作。<span><br></span>　　我们只介绍软件部分，<span>RS485</span>接口接线方法不作介绍，感兴趣的读者可以查阅相关资料。</span></p>
<p align=left><span>例程<span>1</span></span></p>
<p align=left><span>打开<span>VC++6.0</span>，新建基于对话框的工程<span>RS485Comm</span>，在主对话框窗口<span>IDD_RS485COMM_DIALOG</span>上添加两个按钮，<span>ID</span>分别为<span>IDC_SEND</span>和<span>IDC_RECEIVE</span>，标题分别为<span>&#8220;</span>发送<span>&#8221;</span>和<span>&#8220;</span>接收<span>&#8221;</span>；添加一个静态文本框<span>IDC_DISP</span>，用于显示串口接收到的内容。<span><br><br></span>在<span>RS485CommDlg.cpp</span>文件中添加全局变量：</span></p>
<p align=left><span>HANDLE hCom;&nbsp;//</span><span>全局变量，串口句柄</span></p>
<p align=left><span>在<span>RS485CommDlg.cpp</span>文件中的<span>OnInitDialog()</span>函数添加如下代码：</span></p>
<p align=left><span>// TODO: Add extra initialization here<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>hCom=CreateFile("COM1",//COM1</span><span>口<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>GENERIC_READ|GENERIC_WRITE, //</span>允许读和写<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>0, //</span>独占方式<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>NULL,<span>&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>OPEN_EXISTING, //</span>打开而不是创建<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>0, //</span>同步方式<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>NULL);<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>if(hCom==(HANDLE)-1)<span>&nbsp;&nbsp;<br></span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>AfxMessageBox("</span>打开<span>COM</span>失败<span>!");<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>return FALSE;&nbsp;<br>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>SetupComm(hCom,100,100); //</span>输入缓冲区和输出缓冲区的大小都是<span>1024<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>COMMTIMEOUTS TimeOuts;<span> </span>//</span>设定读超时<span><span>&nbsp;&nbsp; <br></span>TimeOuts.ReadIntervalTimeout=MAXDWORD;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>TimeOuts.ReadTotalTimeoutMultiplier=0;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>TimeOuts.ReadTotalTimeoutConstant=0;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>在读一次输入缓冲区的内容后读操作就立即返回，<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>而不管是否读入了要求的字符。<span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>设定写超时<span><span>&nbsp;&nbsp; </span>TimeOuts.WriteTotalTimeoutMultiplier=100;<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>TimeOuts.WriteTotalTimeoutConstant=500;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>SetCommTimeouts(hCom,&amp;TimeOuts); //</span>设置超时<span><span> </span><br>DCB dcb;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>GetCommState(hCom,&amp;dcb);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>dcb.BaudRate=9600; //</span>波特率为<span>9600<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>dcb.ByteSize=8; //</span>每个字节有<span>8</span>位<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>dcb.Parity=NOPARITY; //</span>无奇偶校验位<span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>dcb.StopBits=TWOSTOPBITS; //</span>两个停止位<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>SetCommState(hCom,&amp;dcb);<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);</span></span></p>
<p align=left><span>分别双击<span>IDC_SEND</span>按钮和<span>IDC_RECEIVE</span>按钮，添加两个按钮的响应函数：</span></p>
<p align=left><span>void CRS485CommDlg::OnSend() {<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>// TODO: Add your control notification handler code here<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>// </span><span>在此需要简单介绍百特公司<span>XMA5000</span>的通讯协议：<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>//</span>该仪表<span>RS485</span>通讯采用主机广播方式通讯。<span><span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>//</span>串行半双工，帧<span>11</span>位，<span>1</span>个起始位<span>(0)</span>，<span>8</span>个数据位，<span>2</span>个停止位<span>(1)<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>//</span>如：读仪表显示的瞬时值，主机发送：<span>DC1 AAA BB ETX<span>&nbsp;&nbsp; <br></span>//</span>其中：<span>DC1</span>是标准<span>ASCII</span>码的一个控制符号，码值为<span>11H(</span>十进制的<span>17)<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>//</span>在<span>XMA5000</span>的通讯协议中，<span>DC1</span>表示读瞬时值<span><span>&nbsp;&nbsp;&nbsp; <br></span>//AAA</span>是从机地址码，也就是<span>XMA5000</span>显示仪表的通讯地址<span>&nbsp;<br>//BB</span>为通道号，读瞬时值时该值为<span>01<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>//ETX</span>也是标准<span>ASCII</span>码的一个控制符号，码值为<span>03H<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>//</span>在<span>XMA5000</span>的通讯协议中，<span>ETX</span>表示主机结束符<span>&nbsp;<br>char lpOutBuffer[7];<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>memset(lpOutBuffer,''\0'',7); //</span>前<span>7</span>个字节先清零<span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>l<br>pOutBuffer[0]=''\x11'';&nbsp;//</span>发送缓冲区的第<span>1</span>个字节为<span>DC1<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>lpOutBuffer[1]=''0'';&nbsp;//</span>第<span>2</span>个字节为字符<span>0(30H)<span>&nbsp;&nbsp;<br></span>lpOutBuffer[2]=''0''; //</span>第<span>3</span>个字节为字符<span>0(30H)<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>lpOutBuffer[3]=''1''; // </span>第<span>4</span>个字节为字符<span>1(31H)<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>lpOutBuffer[4]=''0''; //</span>第<span>5</span>个字节为字符<span>0(30H)<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>lpOutBuffer[5]=''1''; //</span>第<span>6</span>个字节为字符<span>1(31H)<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>lpOutBuffer[6]=''\x03''; //</span>第<span>7</span>个字节为字符<span>ETX<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>从该段代码可以看出，仪表的通讯地址为<span>001<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD dwBytesWrite=7;&nbsp;<br>COMSTAT ComStat;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>DWORD dwErrorFlags;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>BOOL bWriteStat;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,&amp; dwBytesWrite,NULL);<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>if(!bWriteStat)<span> </span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>AfxMessageBox("</span>写串口失败<span>!");&nbsp;<br>}<br>}<br>void CRS485CommDlg::OnReceive() {<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>// TODO: Add your control notification handler code here<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>char str[100];<span> <br></span>memset(str,''\0'',100);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>DWORD wCount=100;//</span>读取的字节数<span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>BOOL bReadStat;<span> <br></span>bReadStat=ReadFile(hCom,str,wCount,&amp;wCount,NULL);<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>if(!bReadStat)<span>&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>AfxMessageBox("</span>读串口失败<span>!");&nbsp;<br>PurgeComm(hCom, PURGE_TXABORT|<span> </span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);<span>&nbsp;&nbsp; <br></span>m_disp=str;<span>&nbsp;&nbsp;&nbsp; <br></span>UpdateData(FALSE);<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>}</span></span></p>
<p align=left><span>您可以观察返回的字符串，其中有和仪表显示值相同的部分，您可以进行相应的字符串操作取出仪表的显示值。<span><br></span>打开<span>ClassWizard,</span>为静态文本框<span>IDC_DISP</span>添加<span>CString</span>类型变量<span>m_disp</span>，同时添加<span>WM_CLOSE</span>的相应函数：</span></p>
<p align=left><span>void CRS485CommDlg::OnClose() {<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>// TODO: Add your message handler code here and/or call default<span>&nbsp;&nbsp;&nbsp; </span><br>CloseHandle(hCom);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>程序退出时关闭串口<span><span>&nbsp;&nbsp; </span><br>CDialog::OnClose();<br>}</span></span></p>
<p align=left><span>程序的相应部分已经在代码内部作了详细介绍。连接好硬件部分，编译运行程序，细心体会串口同步操作部分。</span></p>
<p align=left><span>例程<span>2</span></span></p>
<p align=left><span>打开<span>VC++6.0</span>，新建基于对话框的工程<span>RS485Comm</span>，在主对话框窗口<span>IDD_RS485COMM_DIALOG</span>上添加两个按钮，<span>ID</span>分别为<span>IDC_SEND</span>和<span>IDC_RECEIVE</span>，标题分别为<span>&#8220;</span>发送<span>&#8221;</span>和<span>&#8220;</span>接收<span>&#8221;</span>；添加一个静态文本框<span>IDC_DISP</span>，用于显示串口接收到的内容。在<span>RS485CommDlg.cpp</span>文件中添加全局变量：</span></p>
<p align=left><span>HANDLE hCom; //</span><span>全局变量，</span></p>
<p align=left><span>串口句柄在<span>RS485CommDlg.cpp</span>文件中的<span>OnInitDialog()</span>函数添加如下代码：</span></p>
<p align=left><span>hCom=CreateFile("COM1",//COM1</span><span>口<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>GENERIC_READ|GENERIC_WRITE, //</span>允许读和写<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>0, //</span>独占方式<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>NULL,<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>OPEN_EXISTING, //</span>打开而不是创建<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //</span>重叠方式<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>NULL);<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>if(hCom==(HANDLE)-1)<span>&nbsp;&nbsp; </span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>AfxMessageBox("</span>打开<span>COM</span>失败<span>!");<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>return FALSE;&nbsp;<br>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>SetupComm(hCom,100,100); //</span>输入缓冲区和输出缓冲区的大小都是<span>100<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>COMMTIMEOUTS TimeOuts;<span> </span>//</span>设定读超时<span><span>&nbsp;&nbsp; </span><br>TimeOuts.ReadIntervalTimeout=MAXDWORD;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>TimeOuts.ReadTotalTimeoutMultiplier=0;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>TimeOuts.ReadTotalTimeoutConstant=0;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>在读一次输入缓冲区的内容后读操作就立即返回，<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>而不管是否读入了要求的字符。<span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>设定写超时<span><span>&nbsp;&nbsp; </span>TimeOuts.WriteTotalTimeoutMultiplier=100;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>TimeOuts.WriteTotalTimeoutConstant=500;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>SetCommTimeouts(hCom,&amp;TimeOuts); //</span>设置超时<span><span> <br></span>DCB dcb;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>GetCommState(hCom,&amp;dcb);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>dcb.BaudRate=9600; //</span>波特率为<span>9600<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>dcb.ByteSize=8; //</span>每个字节有<span>8</span>位<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>dcb.Parity=NOPARITY; //</span>无奇偶校验位<span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>dcb.StopBits=TWOSTOPBITS; //</span>两个停止位<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>SetCommState(hCom,&amp;dcb);<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);</span></span></p>
<p align=left><span>分别双击<span>IDC_SEND</span>按钮和<span>IDC_RECEIVE</span>按钮，添加两个按钮的响应函数：</span></p>
<p align=left><span>void CRS485CommDlg::OnSend() {<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></span>// TODO: Add your control notification handler code here<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>OVERLAPPED m_osWrite;<br>&nbsp;memset(&amp;m_osWrite,0,sizeof(OVERLAPPED));<span>&nbsp;<br>&nbsp;&nbsp;&nbsp; </span>m_osWrite.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);<span>&nbsp;&nbsp; <br></span>char lpOutBuffer[7];<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>memset(lpOutBuffer,''\0'',7);&nbsp;<br>lpOutBuffer[0]=''\x11'';<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>lpOutBuffer[1]=''0'';<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>lpOutBuffer[2]=''0'';&nbsp;<br>lpOutBuffer[3]=''1'';&nbsp;<br>lpOutBuffer[4]=''0'';<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>lpOutBuffer[5]=''1'';&nbsp;<br>lpOutBuffer[6]=''\x03'';<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD dwBytesWrite=7;<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>COMSTAT ComStat;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD dwErrorFlags;<span>&nbsp;&nbsp;&nbsp; <br></span>BOOL bWriteStat;<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);&nbsp;<br>bWriteStat=WriteFile(hCom,lpOutBuffer,<span> </span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>dwBytesWrite,&amp; dwBytesWrite,&amp;m_osWrite);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>if(!bWriteStat)<span> </span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>if(GetLastError()==ERROR_IO_PENDING)<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>WaitForSingleObject(m_osWrite.hEvent,1000);<br>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>}<br>}<br>void CRS485CommDlg::OnReceive() <br>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>// TODO: Add your control notification handler code here<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>OVERLAPPED m_osRead;<span>&nbsp;&nbsp; </span><br>memset(&amp;m_osRead,0,sizeof(OVERLAPPED));<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><br>m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);<span>&nbsp;&nbsp;&nbsp; </span><br>COMSTAT ComStat;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD dwErrorFlags;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>char str[100];<span> </span>memset(str,''\0'',100);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>DWORD dwBytesRead=100;//</span><span>读取的字节数<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>BOOL bReadStat;<span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>ClearCommError(hCom,&amp;dwErrorFlags,&amp;ComStat);&nbsp;<br>dwBytesRead=min(dwBytesRead, (DWORD)ComStat.cbInQue);<span>&nbsp;&nbsp;&nbsp; <br></span>bReadStat=ReadFile(hCom,str,<span>&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>dwBytesRead,&amp;dwBytesRead,&amp;m_osRead);&nbsp;<br>if(!bReadStat)<span> </span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp; <br></span>if(GetLastError()==ERROR_IO_PENDING)&nbsp;<span>&nbsp;&nbsp;&nbsp; </span>//GetLastError()</span>函数返回<span>ERROR_IO_PENDING,</span>表明串口正在进行读操作<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>WaitForSingleObject(m_osRead.hEvent,2000);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp; </span>//</span>使用<span>WaitForSingleObject</span>函数等待，直到读操作完成或延时已达到<span>2</span>秒钟<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp; </span>//</span>当串口读操作进行完毕后，<span>m_osRead</span>的<span>hEvent</span>事件会变为有信号<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br></span>PurgeComm(hCom, PURGE_TXABORT|<span> </span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);<span>&nbsp;&nbsp;<br></span>m_disp=str;<span>&nbsp;&nbsp;&nbsp; </span><br>UpdateData(FALSE);<br>}</span></span></p>
<p align=left><span>打开<span>ClassWizard,</span>为静态文本框<span>IDC_DISP</span>添加<span>CString</span>类型变量<span>m_disp</span>，同时添加<span>WM_CLOSE</span>的相应函数：</span></p>
<p align=left><span>void CRS485CommDlg::OnClose() {<span>&nbsp;&nbsp;&nbsp;&nbsp;<br></span>// TODO: Add your message handler code here and/or call default<span>&nbsp;&nbsp;&nbsp; </span><br>CloseHandle(hCom);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>程序退出时关闭串口<span><span>&nbsp;&nbsp; <br></span>CDialog::OnClose();<br>}</span></span></p>
<p align=left>&nbsp;</p>
<img src ="http://www.cppblog.com/zzh/aggbug/26461.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zzh/" target="_blank">Xiao.Zhu</a> 2007-06-16 23:25 <a href="http://www.cppblog.com/zzh/archive/2007/06/16/26461.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows Sockets 2.0:使用完成端口高性能，可扩展性Winsock服务程序 (转)</title><link>http://www.cppblog.com/zzh/archive/2007/05/18/24318.html</link><dc:creator>Xiao.Zhu</dc:creator><author>Xiao.Zhu</author><pubDate>Fri, 18 May 2007 01:52:00 GMT</pubDate><guid>http://www.cppblog.com/zzh/archive/2007/05/18/24318.html</guid><wfw:comment>http://www.cppblog.com/zzh/comments/24318.html</wfw:comment><comments>http://www.cppblog.com/zzh/archive/2007/05/18/24318.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zzh/comments/commentRss/24318.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zzh/services/trackbacks/24318.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Windows Sockets 2.0:使用完成端口高性能，可扩展性Winsock服务程序 &nbsp;&nbsp;<a href='http://www.cppblog.com/zzh/archive/2007/05/18/24318.html'>阅读全文</a><img src ="http://www.cppblog.com/zzh/aggbug/24318.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zzh/" target="_blank">Xiao.Zhu</a> 2007-05-18 09:52 <a href="http://www.cppblog.com/zzh/archive/2007/05/18/24318.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>