<?xml version="1.0" encoding="gb2312"?>

<!-- RSS generated by oioj.net on 4/16/2004 ; 感谢LeXRus提供 RSS 2.0 文档; 此文件可自由使用，但请保留此行信息 --> 
<!-- Source download URL: http://blogger.org.cn/blog/rss2.asp       -->
<rss version="2.0">

<channel>
<title>seanno13的博客</title>
<link>http://blogger.org.cn/blog/blog.asp?name=seanno13</link>
<description>seanno13的博客</description>
<copyright>blogger.org.cn</copyright>
<generator>W3CHINA Blog</generator>
<webMaster>webmaster@blogger.org.cn</webMaster>
<item>
<title><![CDATA[一个计算机专业学生几年的Java编程经验汇总]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=45810</link>
<author>seanno13</author>
<pubDate>2009/6/4 17:50:21</pubDate>
<description><![CDATA[<P>一个计算机专业学生几年的编程经验汇总 <BR>绝对好文,该系列一共11篇,斑竹可以考虑置顶,各位坛友看完之后，java基础绝对有不小的提升！<BR>======================================================================================================<BR>想来学习Java也有两个年头了，永远不敢说多么精通，但也想谈谈自己的感受，写给软件学院的同仁们，帮助大家在技术的道路上少一点弯路。说得伟大一点是希望大家为软件学院争气，其实最主要的还是大家自身的进步提升??</P>
<P>1． 关于动态加载机制??<BR>学习Java比C++更容易理解OOP的思想，毕竟C++还混合了不少面向过程的成分。很多人都能背出来Java语言的特点，所谓的动态加载机制等等。当然概念往往是先记住而后消化的，可有多少人真正去体会过动态加载的机制，试图去寻找过其中的细节呢? 提供大家一个方法：<BR>在命令行窗口运行Java程序的时候，加上这个很有用的参数：</P>
<P>java -verbose *.class</P>
<P>这样会清晰的打印出被加载的类文件，大部分是jdk自身运行需要的，最后几行会明显的看到自己用到的那几个类文件被加载进来的顺序。即使你声明了一个类对象，不实例化也不会加载，说明只有真正用到那个类的实例即对象的时候，才会执行加载。这样是不是大家稍微能明白一点动态加载了呢？^_^</P>
<P>2． 关于寻找class文件原理??<BR>建议大家在入门的时候在命令行窗口编译和运行，不要借助JCreator或者Eclipse等IDE去帮助做那些事情。尝试自己这样做：<BR>javac -classpath yourpath *.java<BR>java -classpath yourpath *.class<BR>也许很多人都能看懂，设置classpath的目的就是告诉编译器去哪里寻找你的class文件. 不过至少笔者今日才弄懂JVM去查询类的原理，编译器加载类要依靠classloader，而classloader有3个级别，从高到低分别是BootClassLoader(名字可能不准确) , ExtClassLoader, AppClassLoader.</P>
<P>这3个加载器分别对应着编译器去寻找类文件的优先级别和不同的路径：BootClassLoader对应jre/classes路径，是编译器最优先寻找class的地方<BR>ExtClassLoader对应jre/lib/ext路径，是编译器次优先寻找class的地方<BR>AppClassLoader对应当前路径，所以也是编译器默认找class的地方</P>
<P>其实大家可以自己写个程序简单的测试，对任何class，例如A, <BR>调用new A().getClass().getClassLoader().toString() 打印出来就可以看到，把class文件放在不同的路径下再次执行，就会看到区别。特别注意的是如果打印出来是null就表示到了最高级 BootClassLoader, 因为它是C++编写的，不存在Java对应的类加载器的名字。</P>
<P>寻找的顺序是一种向上迂回的思想，即如果本级别找不到，就只能去本级别之上的找，不会向下寻找。不过似乎从Jdk1.4到Jdk1.6这一特点又有改变，没有找到详细资料。所以就不举例子了。告诉大家设计这种体系的是Sun公司曾经的技术核心宫力先生，一个纯种华人哦！^_^</P>
<P>这样希望大家不至于迷惑为什么总报错找不到类文件，不管是自己写的还是导入的第三方的jar文件（J2ee中经常需要导入的）。</P>
<P>3． 关于jdk和jre??<BR>大家肯定在安装JDK的时候会有选择是否安装单独的jre，一般都会一起安装，我也建议大家这样做。因为这样更能帮助大家弄清楚它们的区别：</P>
<P>Jre 是java runtime environment, 是java程序的运行环境。既然是运行，当然要包含jvm，也就是大家熟悉的虚拟机啦，还有所有java类库的class文件，都在lib目录下打包成了jar。大家可以自己验证。至于在windows上的虚拟机是哪个文件呢？学过MFC的都知道什么是dll文件吧，那么大家看看jre/bin/client里面是不是有一个jvm.dll呢？那就是虚拟机。</P>
<P>Jdk 是java development kit，是java的开发工具包，里面包含了各种类库和工具。当然也包括了另外一个Jre. 那么为什么要包括另外一个Jre呢？而且jdk/jre/bin同时有client和server两个文件夹下都包含一个jvm.dll。说明是有两个虚拟机的。这一点不知道大家是否注意到了呢？</P>
<P>相信大家都知道jdk的bin下有各种java程序需要用到的命令，与jre的bin目录最明显的区别就是jdk下才有javac，这一点很好理解，因为 jre只是一个运行环境而已。与开发无关，正因为如此，具备开发功能的jdk自己的jre下才会同时有client性质的jvm和server性质的 jvm， 而仅仅作为运行环境的jre下只需要client性质的jvm.dll就够了。</P>
<P>记得在环境变量path中设置jdk/bin路径麽？这应该是大家学习Java的第一步吧，老师会告诉大家不设置的话javac和java是用不了的。确实jdk/bin目录下包含了所有的命令。可是有没有人想过我们用的java命令并不是 jdk/bin目录下的而是jre/bin目录下的呢？不信可以做一个实验，大家可以把jdk/bin目录下的java.exe剪切到别的地方再运行 java程序，发现了什么？一切OK！</P>
<P>那么有人会问了？我明明没有设置jre/bin目录到环境变量中啊？</P>
<P>试想一下如果java为了提供给大多数人使用，他们是不需要jdk做开发的，只需要jre能让java程序跑起来就可以了，那么每个客户还需要手动去设置环境变量多麻烦啊？所以安装jre的时候安装程序自动帮你把jre的java.exe添加到了系统变量中，验证的方法很简单，大家看到了系统环境变量的 path最前面有“%SystemRoot%\system32;%SystemRoot%;”这样的配置，那么再去Windows/system32下面去看看吧，发现了什么？有一个java.exe。</P>
<P>如果强行能够把jdk/bin挪到system32变量前面，当然也可以迫使使用jdk/jre里面的java，不过除非有必要，我不建议大家这么做。使用单独的jre跑java程序也算是客户环境下的一种测试。</P>
<P>这下大家应该更清楚jdk和jre内部的一些联系和区别了吧？</P>
<P>PS: 其实还有满多感想可以总结的，一次写多了怕大家扔砖头砸死我，怪我太罗唆。大家应该更加踏实更加务实的去做一些研究并互相分享心得，大方向和太前沿的技术讨论是必要的但最好不要太多，毕竟自己基础都还没打好，什么都讲最新版本其实是进步的一大障碍！</P>
<P>&nbsp;</P>
<P>&nbsp;</P>
<P>Java 学习杂谈（二）<BR>鉴于上回写的一点感想大家不嫌弃，都鼓励小弟继续写下去，好不容易等到国庆黄金周，实习总算有一个休息的阶段，于是这就开始写第二篇了。希望这次写的仍然对志同道合的朋友们有所帮助。上回讲了Java动态加载机制、classLoader原理和关于jdk和jre三个问题。这次延续着讲一些具体的类库??</P>
<P>1． 关于集合框架类<BR>相信学过Java的各位对这个名词并不陌生，对 java.util.*这个package肯定也不陌生。不知道大家查询API的时候怎么去审视或者分析其中的一个package，每个包最重要的两个部分就是interfaces和classes，接口代表了它能做什么，实现类则代表了它如何去做。关注实现类之前，我们应该先理解清楚它的来源接口，不管在j2se还是j2ee中，都应该是这样。那么我们先看这三个接口：List、Set、Map。<BR>也许有些人不太熟悉这三个名字，但相信大部分人都熟悉ArrayList，LinkedList，TreeSet，HashSet，HashMap， Hashtable等实现类的名字。它们的区别也是满容易理解的，List放可以重复的对象集合，Set放不可重复的对象组合，而Map则放 &lt;Key,Value &gt; 这样的名值对， Key不可重复，Value可以。这里有几个容易混淆的问题：</P>
<P>到底Vector和ArrayList，Hashtable和HashMap有什么区别？<BR>很多面试官喜欢问这个问题，其实更专业一点应该这样问：新集合框架和旧集合框架有哪些区别？新集合框架大家可以在这些包中找since jdk1.2的，之前的如vector和Hashtable都是旧的集合框架包括的类。那么区别是？<BR>a. 新集合框架的命名更加科学合理。例如List下的ArrayList和LinkedList<BR>b. 新集合框架下全部都是非线程安全的。建议去jdk里面包含的源代码里面自己去亲自看看vector和ArrayList的区别吧。当然如果是jdk5.0之后的会比较难看一点，因为又加入了泛型的语法，类似c++的template语法。</P>
<P>那么大家是否想过为什么要从旧集合框架默认全部加锁防止多线程访问更新到新集合框架全部取消锁，默认方式支持多线程？(当然需要的时候可以使用collections的静态方法加锁达到线程安全)<BR>笔者的观点是任何技术的发展都未必是遵循它们的初衷的，很多重大改变是受到客观环境的影响的。大家知道Java的初衷是为什么而开发的麽？是为嵌入式程序开发的。记得上一篇讲到classLoader机制麽？那正是为了节约嵌入式开发环境下内存而设计的。而走到今天，Java成了人们心中为互联网诞生的语言。互联网意味着什么？多线程是必然的趋势。客观环境在变，Java技术也随着飞速发展，导致越来越脱离它的初衷。据说Sun公司其实主打的是J2se，结果又是由于客观环境影响，J2se几乎遗忘，留在大家谈论焦点的一直是j2ee。</P>
<P>技术的细节这里就不多说了，只有用了才能真正理解。解释这些正是为了帮助大家理解正在学的和将要学的任何技术。之后讲j2ee的时候还会再讨论。<BR>多扯句题外话：几十年前的IT巨人是IBM，Mainframe市场无人可比。微软如何打败IBM？正是由于硬件飞速发展，对个人PC的需求这个客观环境，让微软通过OS称为了第二个巨人。下一个打败微软的呢？Google。如何做到的？如果微软并不和IBM争大型机，Google借着互联网飞速发展这个客观环境作为决定性因素，避开跟微软争OS，而是走搜索引擎这条路，称为第3个巨人。那么第4个巨人是谁呢？很多专家预言将在亚洲或者中国出现， Whatever，客观环境变化趋势才是决定大方向的关键。当然笔者也希望会出现在中国，^_^~~</P>
<P>2． 关于Java设计模式<BR>身边的很多在看GOF的23种设计模式，似乎学习它无论在学校还是在职场，都成了一种流行风气。我不想列举解释这23种Design Pattern， 我写这些的初衷一直都是谈自己的经历和看法，希望能帮助大家理解。<BR>首先我觉得设计模式只是对一类问题的一种通用解决办法，只要是面向对象的编程预言都可以用得上这23种。理解它们最好的方法就是亲自去写每一种，哪怕是一个简单的应用就足够了。如果代码实现也记不住的话，记忆它们对应的UML图会是一个比较好的办法，当然前提是必须了解UML。<BR>同时最好能利用Java自身的类库帮助记忆，例如比较常用的观察者模式，在java.util.*有现成的Observer接口和Observable这个实现类，看看源代码相信就足够理解观察者模式了。再比如装饰器模式，大家只要写几个关于java.io.*的程序就可以完全理解什么是装饰器模式了。有很多人觉得刚入门的时候不该接触设计模式，比如图灵设计丛书系列很出名的那本《Java设计模式》，作者: Steven John Metsker，大部分例子老实说令现在的我也很迷惑。但我仍然不同意入门跟学习设计模式有任何冲突，只是我们需要知道每种模式的概念的和典型的应用，这样我们在第一次编写 FileOutputStream、BufferedReader、PrintWriter的时候就能感觉到原来设计模式离我们如此之近，而且并不是多么神秘的东西。</P>
<P>另外，在学习某些模式的同时，反而更能帮助我们理解java类库的某些特点。例如当你编写原型(Prototype)模式的时候，你必须了解的是 java.lang.Cloneable这个接口和所有类的基类Object的clone()这个方法。即深copy和浅copy的区别：<BR>Object.clone()默认实现的是浅copy，也就是复制一份对象拷贝，但如果对象包含其他对象的引用，不会复制引用，所以原对象和拷贝共用那个引用的对象。<BR>深copy当然就是包括对象的引用都一起复制啦。这样原对象和拷贝对象，都分别拥有一份引用对象。如果要实现深copy就必须首先实现 java.lang.Cloneable接口，然后重写clone()方法。因为在Object中的clone()方法是protected签名的，而 Cloneable接口的作用就是把protected放大到public，这样clone()才能被重写。</P>
<P>那么又有个问题了？如果引用的对象又引用了其他对象呢？这样一直判断并复制下去，是不是显得很麻烦？曾经有位前辈告诉我的方法是重写clone方法的时候直接把原对象序列化到磁盘上再反序列化回来，这样不用判断就可以得到一个深copy的结果。如果大家不了解序列化的作法建议看一看 ObjectOutputStream和ObjectInputStream</P>
<P>归根结底，模式只是思想上的东西，把它当成前人总结的经验其实一点都不为过。鼓励大家动手自己去写，例如代理模式，可以简单的写一个Child类， Adult类。Child要买任何东西由Adult来代理实现。简单来说就是Adult里的buy()内部实际调用的是Child的buy()，可是暴露在main函数的却是Adult.buy()。这样一个简单的程序就足够理解代理模式的基本含义了。</P>
<P>&nbsp;</P>
<P>&nbsp;</P>
<P>Java 杂谈（三）<BR>这已经笔者写的第三篇Java杂记了，庆幸前两篇一直得到论坛朋友们的支持鼓励，还望大家继续指正不足之处。笔者也一直渴望通过这样方式清醒的自审，来寻找自己技术上的不足之处，希望和共同爱好Java的同仁们一起提高。<BR>前两次分别讲述了关于jvm、jdk、jre、collection、classLoader和一些Design Pattern的自我理解。这次仍然不准备开始过渡到j2ee中，因为觉得还有一些琐碎的j2se的问题没有总结完毕。</P>
<P>1． 关于Object类理解<BR>大家都知道Object是所有Java类的基类， 意味着所有的Java类都会继承了Object的11个方法。建议大家去看看Object的 11个成员函数的源代码，就会知道默认的实现方式。比如equals方法，默认实现就是用"=="来比较，即直接比较内存地址，返回true 或者 false。而toString()方法，返回的串组成方式是??<BR>"getClass().getName() + "@" + Integer.toHexString(hashCode())"<BR>其实不用我过多的解释，大家都能看懂这个串的组成。接下来再看看hashCode()：<BR>public native int hashCode()；</P>
<P>由于是native方法，跟OS的处理方式相关，源代码里仅仅有一个声明罢了。我们有兴趣的话完全可以去深究它的hashCode到底是由OS怎么样产生的呢？但笔者建议最重要的还是先记住使用它的几条原则吧！首先如果equals()方法相同的对象具有相通的hashCode，但equals ()对象不相通的时候并不保证hashCode()方法返回不同的整数。而且下一次运行同一个程序，同一个对象未必还是当初的那个hashCode() 哦。<BR>其余的方法呢？nofigy()、notifyAll()、clone()、wait()都是native方法的，说明依赖于操作系统的实现。最后一个有趣的方法是finalize()，类似C++的析构函数，签名是protected，证明只有继承扩展了才能使用，方法体是空的，默示什么也不做。它的作用据笔者的了解仅仅是通知JVM此对象不再使用，随时可以被销毁，而实际的销毁权还是在于虚拟机手上。那么它真的什么也不做麽？未必，实际上如果是线程对象它会导致在一定范围内该线程的优先级别提高，导致更快的被销毁来节约内存提高性能。其实从常理来说，我们也可以大概这样猜测出jvm做法的目的。</P>
<P>2． 关于重载hashCode()与Collection框架的关系<BR>笔者曾经听一位搞Java培训多年的前辈说在他看来hashCode方法没有任何意义，仅仅是为了配合证明具有同样的hashCode会导致equals 方法相等而存在的。连有的前辈都犯这样的错误，其实说明它还是满容易被忽略的。那么hashCode()方法到底做什么用？</P>
<P>学过数据结构的课程大家都会知道有一种结构叫hash table，目的是通过给每个对象分配一个唯一的索引来提高查询的效率。那么Java也不会肆意扭曲改变这个概念，所以hashCode唯一的作用就是为支持数据结构中的哈希表结构而存在的，换句话说，也就是只有用到集合框架的 Hashtable、HashMap、HashSet的时候，才需要重载hashCode()方法，<BR>这样才能使得我们能人为的去控制在哈希结构中索引是否相等。笔者举一个例子：<BR>曾经为了写一个求解类程序，需要随机列出1,2,3,4组成的不同排列组合，所以笔者写了一个数组类用int[]来存组合结果，然后把随机产生的组合加入一个HashSet中，就是想利用HashSet不包括重复元素的特点。可是HashSet怎么判断是不是重复的元素呢？当然是通过 hashCode()返回的结果是否相等来判断啦，可做一下这个实验：<BR>int[] A = {1,2,3,4};<BR>int[] B = {1,2,3,4};<BR>System.out.println(A.hashCode());<BR>System.out.println(B.hashCode());</P>
<P>这明明是同一种组合，却是不同的hashCode，加入Set的时候会被当成不同的对象。这个时候我们就需要自己来重写hashCode()方法了，如何写呢？其实也是基于原始的hashCode()，毕竟那是操作系统的实现， 找到相通对象唯一的标识，实现方式很多，笔者的实现方式是：<BR>首先重写了toString()方法:<BR>return A[0]“+” A[1]“+” A[2]“+” A[3]; //显示上比较直观<BR>然后利用toString()来计算hashCode()：<BR>return this.toString().hashCode()；<BR>这样上述A和B返回的就都是”1234”，在测试toString().hashCode()，由于String在内存中的副本是一样的，”1234”.hashCode()返回的一定是相同的结果。</P>
<P>说到这，相信大家能理解得比我更好，今后千万不要再误解hashCode()方法的作用。</P>
<P>3． 关于Class类的成员函数与Java反射机制<BR>很早刚接触Java就听很多老师说过Java的动态运行时机制、反射机制等。确实它们都是Java的显著特点，运行时加载笔者在第一篇介绍过了，现在想讲讲反射机制。在Java中，主要是通过java.lang包中的Class类和Method类来实现内存反射机制的。<BR>熟悉C++的人一定知道下面这样在C++中是做不到的：运行时以字符串参数传递一个类名，就可以得到这个类的所有信息，包括它所有的方法，和方法的详细信息。还可以实例化一个对象，并通过查到的方法名来调用该对象的任何方法。这是因为Java的类在内存中除了C++中也有的静态动态数据区之外，还包括一份对类自身的描述，也正是通过这描述中的信息，才能帮助我们才运行时读取里面的内容，得到需要加载目标类的所有信息，从而实现反射机制。大家有没有想过当我们需要得到一个JavaBean的实例的时候，怎么知道它有哪些属性呢？再明显简单不过的例子就是自己写一个JavaBean的解析器：</P>
<P>a. 通过Class.forName(“Bean的类名”)得到Class对象，例如叫ABeanClass<BR>b. 通过ABeanClass的getMethods()方法，得到Method[]对象<BR>c. 按照规范所有get方法名后的单词就代表着该Bean的一个属性<BR>d. 当已经知道一个方法名，可以调用newInstance()得到一个实例，然后通过invoke()方法将方法的名字和方法需要用的参数传递进去，就可以动态调用此方法。</P>
<P>当然还有更复杂的应用，这里就不赘述，大家可以参考Class类和Method类的方法。</P>
<P>4． 坦言Synchronize的本质<BR>Synchronize大家都知道是同步、加锁的意思，其实它的本质远没有大家想得那么复杂。声明Synchronize的方法被调用的时候，锁其实是加载对象上，当然如果是静态类则是加在类上的锁，调用结束锁被解除。它的实现原理很简单，仅仅是不让第二把锁再次被加在同一个对象或类上，仅此而已。一个简单的例子足以说明问题：<BR>class A{<BR>synchronized void f(){}<BR>void g(){}<BR>}</P>
<P>当A的一个对象a被第一个线程调用其f()方法的时候，第二个线程不能调用a的synchronized方法例如f()，因为那是在试图在对象上加第二把锁。但调用g()却是可以的，因为并没有在同一对象上加两把锁的行为产生。<BR>这样大家能理解了麽？明白它的原理能更好的帮助大家设计同步机制，不要滥用加锁。</P>
<P>PS：下篇笔者计划开始对J2ee接触到的各个方面来进行总结，谈谈自己的经验和想法。希望大家还能一如既往的支持笔者写下去，指正不足之处。</P>
<P>&nbsp;</P>
<P>Java杂谈（四）</P>
<P>不知不觉已经写到第四篇了，论坛里面不断的有朋友鼓励我写下去。坚持自己的作风，把一切迷惑不容易理清楚的知识讲出来，讲到大家都能听懂，那么自己就真的懂了。最近在公司实习的时候Trainer跟我讲了很多经典事迹，对还未毕业的我来说是笔不小的财富，我自己的信念是：人在逆境中成长的速度要远远快过顺境中，这样来看一切都能欣然接受了。<BR>好了，闲话不说了，第三篇讲的是反射机制集合框架之类的，这次打算讲讲自己对反序列化和多线程的理解。希望能对大家学习Java起到帮助??</P>
<P>1．关于序列化和反序列化<BR>应该大家都大概知道Java中序列化和反序列化的意思，序列化就是把一个Java对象转换成二进制进行磁盘上传输或者网络流的传输，反序列化的意思就是把这个接受到的二进制流重新组装成原来的对象逆过程。它们在Java中分别是通过ObjectInputStream和 ObjectInputStream这两个类来实现的（以下分别用ois和oos来简称）。</P>
<P>oos的writeObject()方法用来执行序列化的过程，ois的readObject()用来执行反序列化的过程，在传输二进制流之前，需要讲这两个高层流对象连接到同一个Channel上，这个Channel可以是磁盘文件，也可以是socket底层流。所以无论用哪种方式，底层流对象都是以构造函数参数的形式传递进oos和ois这两个高层流，连接完毕了才可以进行二进制数据传输的。例子：<BR>可以是文件流通道<BR>file = new File(“C:/data.dat”);<BR>oos = new ObjectOutputStream(new FileOutputStream(file));<BR>ois = new ObjectInputStream(new FileInputStream(file));</P>
<P>或者网络流通道<BR>oos = new ObjectOutputStream(socket.getOutputStream());<BR>ois = new ObjectInputStream(socket.getInputStream()); </P>
<P>不知道大家是否注意到oos总是在ois之前定义，这里不希望大家误解这个顺序是固定的么？回答是否定的，那么有顺序要求么？回答是肯定的。原则是什么呢？<BR>原则是互相对接的输入/输出流之间必须是output流先初始化然后再input流初始化，否则就会抛异常。大家肯定会问为什么？只要稍微看一看这两个类的源代码文件就大概知道了，output流的任务很简单，只要把对象转换成二进制往通道中写就可以了，但input流需要做很多准备工作来接受并最终重组这个Object，所以ObjectInputStream的构造函数中就需要用到output初始化发送过来的header信息，这个方法叫做 readStreamHeader()，它将会去读两个Short值用于决定用多大的缓存来存放通道发送过来的二进制流，这个缓存的size因jre的版本不同是不一样的。所以output如果不先初始化，input的构造函数首先就无法正确运行。</P>
<P>对于上面两个例子，第一个顺序是严格的，第二个因为oos和ois连接的已经不是对方了，而是socket另外一端的流，需要严格按照另外一方对接的output流先于对接的input流打开才能顺利运行。</P>
<P>这个writeObject和readObject本身就是线程安全的，传输过程中是不允许被并发访问的。所以对象能一个一个接连不断的传过来，有很多人在运行的时候会碰到EOFException, 然后百思不得其解，去各种论坛问解决方案。其实笔者这里想说，这个异常不是必须声明的，也就是说它虽然是异常，但其实是正常运行结束的标志。EOF表示读到了文件尾，发送结束自然连接也就断开了。如果这影响到了你程序的正确性的话，请各位静下心来看看自己程序的业务逻辑，而不要把注意力狭隘的聚集在发送和接受的方法上。因为笔者也被这样的bug困扰了1整天，被很多论坛的帖子误解了很多次最后得出的教训。如果在while循环中去readObject，本质上是没有问题的，有对象数据来就会读，没有就自动阻塞。那么抛出EOFException一定是因为连接断了还在继续read，什么原因导致连接断了呢？一定是业务逻辑哪里存在错误，比如NullPoint、 ClassCaseException、ArrayOutofBound，即使程序较大也没关系，最多只要单步调适一次就能很快发现bug并且解决它。</P>
<P>难怪一位程序大师说过：解决问题90％靠经验，5％靠技术，剩下5％靠运气！真是金玉良言，笔者大概查阅过不下30篇讨论在while循环中使用 readObject抛出EOFExceptionde 的帖子，大家都盲目的去关注解释这个名词、反序列化的行为或反对这样写而没有一个人认为EOF是正确的行为，它其实很老实的在做它的事情。为什么大家都忽略了真正出错误的地方呢？两个字，经验！</P>
<P>2．关于Java的多线程编程<BR>关于Java的线程，初学或者接触不深的大概也能知道一些基本概念，同时又会很迷惑线程到底是怎么回事？如果有人认为自己已经懂了不妨来回答下面的问题：<BR>a. A对象实现Runnable接口，A.start()运行后所谓的线程对象是谁？是A么？<BR>b. 线程的wait()、notify()方法到底是做什么时候用的，什么时候用？<BR>c. 为什么线程的suspend方法会被标注过时，不推荐再使用，线程还能挂起么？<BR>d. 为了同步我们会对线程方法声明Synchronized来加锁在对象上，那么如果父类的f()方法加了Synchronized，子类重写f()方法必须也加Synchronized么？如果子类的f()方法重写时声明Synchronized并调用super.f()，那么子类对象上到底有几把锁呢？会因为竞争产生死锁么？</P>
<P>呵呵，各位能回答上来几道呢？如果这些都能答上来，说明对线程的概念还是满清晰的，虽说还远远不能算精通。笔者这里一一做回答，碍于篇幅的原因，笔者尽量说得简介一点，如果大家有疑惑的欢迎一起讨论。</P>
<P>首先第一点，线程跟对象完全是两回事，虽然我们也常说线程对象。但当你用run()和start()来启动一个线程之后，线程其实跟这个继承了 Thread或实现了Runnable的对象已经没有关系了，对象只能算内存中可用资源而对象的方法只能算内存正文区可以执行的代码段而已。既然是资源和代码段，另外一个线程当然也可以去访问，main函数执行就至少会启动两个线程，一个我们称之为主线程，还一个是垃圾收集器的线程，主线程结束就意味着程序结束，可垃圾收集器线程很可能正在工作。</P>
<P>第二点，wait()和sleep()类似，都是让线程处于阻塞状态暂停一段时间，不同之处在于wait会释放当前线程占有的所有的锁，而 sleep不会。我们知道获得锁的唯一方法是进入了Synchronized保护代码段，所以大家会发现只有Synchronized方法中才会出现 wait，直接写会给警告没有获得当前对象的锁。所以notify跟wait配合使用，notify会重新把锁还给阻塞的线程重而使其继续执行，当有多个对象wait了，notify不能确定唤醒哪一个，必经锁只有一把，所以一般用notifyAll()来让它们自己根据优先级等竞争那唯一的一把锁，竞争到的线程执行，其他线程只要继续wait。</P>
<P>从前Java允许在一个线程之外把线程挂起，即调用suspend方法，这样的操作是极不安全的。根据面向对象的思想每个对象必须对自己的行为负责，而对自己的权力进行封装。如果任何外步对象都能使线程被挂起而阻塞的话，程序往往会出现混乱导致崩溃，所以这样的方法自然是被毙掉了啦。</P>
<P>最后一个问题比较有意思，首先回答的是子类重写f()方法可以加Synchronized也可以不加，如果加了而且还内部调用了super.f ()的话理论上是应该对同一对象加两把锁的，因为每次调用Synchronized方法都要加一把，调用子类的f首先就加了一把，进入方法内部调用父类的 f又要加一把，加两把不是互斥的么？那么调父类f加锁不就必须永远等待已经加的锁释放而造成死锁么？实际上是不会的，这个机制叫重进入，当父类的f方法试图在本对象上再加一把锁的时候，因为当前线程拥有这个对象的锁，也可以理解为开启它的钥匙，所以同一个线程在同一对象上还没释放之前加第二次锁是不会出问题的，这个锁其实根本就没有加，它有了钥匙，不管加几把还是可以进入锁保护的代码段，畅通无阻，所以叫重进入，我们可以简单认为第二把锁没有加上去。</P>
<P>总而言之，Synchronized的本质是不让其他线程在同一对象上再加一把锁。</P>
<P>&nbsp;</P>
<P>&nbsp;</P>
<P>Java杂谈（五）</P>
<P><BR>本来预计J2se只讲了第四篇就收尾了，可是版主厚爱把帖子置顶长期让大家浏览让小弟倍感责任重大，务必追求最到更好，所以关于J2se一些没有提到的部分，决定再写几篇把常用的部分经验全部写出来供大家讨论切磋。这一篇准备讲一讲Xml解析包和Java Swing，然后下一篇再讲java.security包关于Java沙箱安全机制和RMI机制，再进入J2ee的部分，暂时就做这样的计划了。如果由于实习繁忙更新稍微慢了一些，希望各位见谅！ </P>
<P>1． Java关于XML的解析 <BR>相信大家对XML都不陌生，含义是可扩展标记语言。本身它也就是一个数据的载体以树状表现形式出现。后来慢慢的数据变成了信息，区别是信息可以包括可变的状态从而针对程序硬编码的做法变革为针对统一接口硬编码而可变状态作为信息进入了XML中存储。这样改变状态实现扩展的唯一工作是在XML中添加一段文本信息就可以了，代码不需要改动也不需要重新编译。这个灵活性是XML诞生时候谁也没想到的。 </P>
<P>当然，如果接口要能提取XML中配置的信息就需要程序能解析规范的XML文件，Java中当然要提高包对这个行为进行有利支持。笔者打算讲到的两个包是 org.w3c.dom和javax.xml.parsers和。（大家可以浏览一下这些包中间的接口和类定义） </P>
<P>Javax.xml.parsers包很简单，没有接口，两个工厂配两个解析器。显然解析XML是有两种方式的：DOM解析和SAX解析。本质上并没有谁好谁不好，只是实现的思想不一样罢了。给一个XML文件的例子： <BR>&lt;?xml version=”1.0” encoding=”UTF-8” &gt; <BR>&lt;root &gt; <BR>&lt;child name=”Kitty” &gt; <BR>A Cat <BR>&lt;/child &gt; <BR>&lt;/root &gt; </P>
<P>所谓DOM解析的思路是把整个树状图存入内存中，需要那个节点只需要在树上搜索就可以读到节点的属性，内容等，这样的好处是所有节点皆在内存可以反复搜索重复使用，缺点是需要消耗相应的内存空间。 </P>
<P>自然SAX解析的思路就是为了克服DOM的缺点，以事件触发为基本思路，顺序的搜索下来，碰到了Element之前触发什么事件，碰到之后做什么动作。由于需要自己来写触发事件的处理方案，所以需要借助另外一个自定义的Handler，处于org.xml.sax.helpers包中。它的优点当然是不用整个包都读入内存，缺点也是只能顺序搜索，走完一遍就得重来。 </P>
<P>大家很容易就能猜到，接触到的J2ee框架用的是哪一种，显然是DOM。因为类似Struts，Hibernate框架配置文件毕竟是很小的一部分配置信息，而且需要频繁搜索来读取，当然会采用DOM方式（其实SAX内部也是用DOM采用的结构来存储节点信息的）。现在无论用什么框架，还真难发现使用 SAX来解析XML的技术了，如果哪位仁兄知道，请让笔者也学习学习。 </P>
<P>既然解析方式有了，那么就需要有解析的存储位置。不知道大家是否发现org.w3c.dom这个包是没有实现类全部都是接口的。这里笔者想说一下Java 如何对XML解析是Jdk应该考虑的事，是它的责任。而w3c组织是维护定义XML标准的组织，所以一个XML结构是怎么样的由w3c说了算，它不关心 Java如何去实现，于是乎规定了所有XML存储的结构应该遵循的规则，这就是org.w3c.dom里全部的接口目的所在。在笔者看来，简单理解接口的概念就是实现者必须遵守的原则。 </P>
<P>整个XML对应的结构叫Document、子元素对应的叫做Element、还有节点相关的Node、NodeList、Text、Entity、 CharacterData、CDATASection等接口，它们都可以在XML的语法中间找到相对应的含义。由于这里不是讲解XML基本语法，就不多介绍了。如果大家感兴趣，笔者也可以专门写一篇关于XML的语法规则帖与大家分享一下。 </P>
<P>2． Java Swing <BR>Swing是一个让人又爱又恨的东西，可爱之处在于上手很容易，较AWT比起来Swing提供的界面功能更加强大，可恨之处在于编复杂的界面工作量实在是巨大。笔者写过超过3000行的Swing界面，感觉用户体验还不是那么优秀。最近又写过超过6000行的，由于功能模块多了，整体效果还只是一般般。体会最深的就一个字：累! 所以大家现在都陆续不怎么用Swing在真正开发的项目上了，太多界面技术可以取代它了。笔者去写也是迫于无奈组里面大家都没写过，我不入地域谁入？ </P>
<P>尽管Swing慢慢的在被人忽略，特别是随着B/S慢慢的在淹没C/S，笔者倒是很愿意站出来为Swing正身。每一项技术的掌握绝不是为了流行时尚跟风。真正喜欢Java的朋友们还是应该好好体会一下Swing，相信在校的很多学生也很多在学习它。很可能从Jdk 1.1、1.2走过来的很多大学老师可能是最不熟悉它的。 <BR>Swing提供了一组轻组件统称为JComponent，它们与AWT组件的最大区别是JComponent全部都是Container，而 Container的特点是里面可以装载别的组件。在Swing组件中无论是JButton、JLabel、JPanel、JList等都可以再装入任何其他组件。好处是程序员可以对Swing组件实现“再开发”，针对特定需求构建自己的按钮、标签、画板、列表之类的特定组件。 </P>
<P>有轻自然就有重，那么轻组件和重组件区别是？重组件表现出来的形态因操作系统不同而异，轻组件是Swing自己提供GUI，在跨平台的时候最大程度的保持一致。 <BR>那么在编程的时候要注意一些什么呢？笔者谈谈自己的几点经验： </P>
<P>a. 明确一个概念，只有Frame组件才可以单独显示的，也许有人会说JOptionPane里面的静态方法就实现了单独窗口出现，但追寻源代码会发现其实现实出来的Dialog也需要依托一个Frame窗体，如果没有指定就会默认产生一个然后装载这个Dialog显示出来。 </P>
<P>b. JFrame是由这么几部分组成： <BR>最底下一层JRootPane，上面是glassPane (一个JPanel)和layeredPane (一个JLayeredPane)，而layeredPane又由contentPane(一个JPanel)和menuBar构成。我们的组件都是加在 contentPane上，而背景图片只能加在layeredPane上面。至于glassPane是一个透明的覆盖了contentPane的一层，在特定效果中将被利用到来记录鼠标坐标或掩饰组件。 </P>
<P>c. 为了增强用户体验，我们会在一些按钮上添加快捷键，但Swing里面通常只能识别键盘的Alt键，要加入其他的快捷键，必须自己实现一个ActionListener。 </P>
<P>d. 通过setLayout(null)可以使得所有组件以setBounds()的四个参数来精确定位各自的大小、位置，但不推荐使用，因为好的编程风格不应该在Swing代码中硬编码具体数字，所有的数字应该以常数的形式统一存在一个静态无实例资源类文件中。这个静态无实例类统一负责Swing界面的风格，包括字体和颜色都应该包括进去。 </P>
<P>e. 好的界面设计有一条Golden Rule: 用户不用任何手册通过少数尝试就能学会使用软件。所以尽量把按钮以菜单的形式（不管是右键菜单还是窗体自带顶部菜单）呈现给顾客，除非是频繁点击的按钮才有必要直接呈现在界面中。 </P>
<P>其实Swing的功能是相当强大的，只是现在应用不广泛，专门去研究大概是要花不少时间的。笔者在各网站论坛浏览关于Swing的技巧文章还是比较可信的，自己所学非常有限，各人体会对Swing各个组件的掌握就是一个实践积累的过程。笔者只用到过以上这些，所以只能谈谈部分想法，还望大家见谅！</P>
<P>&nbsp;</P>
<P>&nbsp;</P>
<P>Java杂谈（六）</P>
<P><BR>这篇是笔者打算写的J2se部分的最后一篇了，这篇结束之后，再写J2ee部分，不知道是否还合适写在这个版块？大家可以给点意见，谢谢大家对小弟这么鼓励一路写完前六篇Java杂谈的J2se部分。最后这篇打算谈一谈Java中的RMI机制和JVM沙箱安全框架。 </P>
<P>1． Java中的RMI机制 <BR>RMI的全称是远程方法调用，相信不少朋友都听说过，基本的思路可以用一个经典比方来解释：A计算机想要计算一个两个数的加法，但A自己做不了，于是叫另外一台计算机B帮忙，B有计算加法的功能，A调用它就像调用这个功能是自己的一样方便。这个就叫做远程方法调用了。 </P>
<P>远程方法调用是EJB实现的支柱，建立分布式应用的核心思想。这个很好理解，再拿上面的计算加法例子，A只知道去call计算机B的方法，自己并没有B的那些功能，所以A计算机端就无法看到B执行这段功能的过程和代码，因为看都看不到，所以既没有机会窃取也没有机会去改动方法代码。EJB正式基于这样的思想来完成它的任务的。当简单的加法变成复杂的数据库操作和电子商务交易应用的时候，这样的安全性和分布式应用的便利性就表现出来优势了。 </P>
<P>好了，回到细节上，要如何实现远程方法调用呢？我希望大家学习任何技术的时候可以试着依赖自己的下意识判断，只要你的想法是合理健壮的，那么很可能实际上它就是这么做的，毕竟真理都蕴藏在平凡的生活细节中。这样只要带着一些薄弱的Java基础来思考RMI，其实也可以想出个大概来。 </P>
<P>a) 需要有一个服务器角色，它拥有真正的功能代码方法。例如B，它提供加法服务 <BR>b) 如果想远程使用B的功能，需要知道B的IP地址 <BR>c) 如果想远程使用B的功能，还需要知道B中那个特定服务的名字 </P>
<P>我们很自然可以想到这些，虽然不完善，但已经很接近正确的做法了。实际上RMI要得以实现还得意于Java一个很重要的特性，就是Java反射机制。我们需要知道服务的名字，但又必须隐藏实现的代码，如何去做呢？答案就是：接口！ <BR>举个例子： <BR>public interface Person(){ <BR>public void sayHello(); <BR>} </P>
<P>Public class PersonImplA implements Person{ <BR>public PersonImplA(){} </P>
<P>public void sayHello(){ System.out.println(“Hello!”);} <BR>} </P>
<P>Public class PersonImplB implements Person{ <BR>public PersonImplB(){} </P>
<P>public void sayHello(){ System.out.println(“Nice to meet you!”);} <BR>} </P>
<P>客户端：Person p = Naming.lookup(“PersonService”); <BR>p.sayHello(); </P>
<P>就这几段代码就包含了几乎所有的实现技术，大家相信么？客户端请求一个say hello服务，服务器运行时接到这个请求，利用Java反射机制的Class.newInstance()返回一个对象，但客户端不知道服务器返回的是 ImplA还是ImplB，它接受用的参数签名是Person，它知道实现了Person接口的对象一定有sayHello()方法，这就意味着客户端并不知道服务器真正如何去实现的，但它通过了解Person接口明确了它要用的服务方法名字叫做sayHello()。 </P>
<P>如此类推，服务器只需要暴露自己的接口出来供客户端，所有客户端就可以自己选择需要的服务。这就像餐馆只要拿出自己的菜单出来让客户选择，就可以在后台厨房一道道的按需做出来，它怎么做的通常是不让客户知道的！（祖传菜谱吧，^_^） </P>
<P>最后一点是我调用lookup，查找一个叫PersonService名字的对象，服务器只要看到这个名字，在自己的目录（相当于电话簿）中找到对应的对象名字提供服务就可以了，这个目录就叫做JNDI (Java命名与目录接口），相信大家也听过的。 </P>
<P>有兴趣的朋友不妨自己做个RMI的应用，很多前辈的博客中有简单的例子。提示一下利用Jdk的bin目录中rmi.exe和 rmiregistry.exe两个命令就可以自己建起一个服务器，提供远程服务。因为例子很容易找，我就不自己举例子了！ </P>
<P>2． JVM沙箱&amp;框架 <BR>RMI罗唆得太多了，实在是尽力想把它说清楚，希望对大家有帮助。最后的最后，给大家简单讲一下JVM框架，我们叫做Java沙箱。Java沙箱的基本组件如下： <BR>a) 类装载器结构 <BR>b) class文件检验器 <BR>c) 内置于Java虚拟机的安全特性 <BR>d) 安全管理器及Java API </P>
<P>其中类装载器在3个方面对Java沙箱起作用： <BR>a. 它防止恶意代码去干涉善意的代码 <BR>b. 它守护了被信任的类库边界 <BR>c. 它将代码归入保护域，确定了代码可以进行哪些操作 </P>
<P>虚拟机为不同的类加载器载入的类提供不同的命名空间，命名空间由一系列唯一的名称组成，每一个被装载的类将有一个名字，这个命名空间是由Java虚拟机为每一个类装载器维护的，它们互相之间甚至不可见。 </P>
<P>我们常说的包（package）是在Java虚拟机第2版的规范第一次出现，正确定义是由同一个类装载器装载的、属于同一个包、多个类型的集合。类装载器采用的机制是双亲委派模式。具体的加载器框架我在Java杂谈（一）中已经解释过了，当时说最外层的加载器是AppClassLoader，其实算上网络层的话AppClassLoader也可以作为parent，还有更外层的加载器URLClassLoader。为了防止恶意攻击由URL加载进来的类文件我们当然需要分不同的访问命名空间，并且制定最安全的加载次序，简单来说就是两点： </P>
<P>a. 从最内层JVM自带类加载器开始加载，外层恶意同名类得不到先加载而无法使用 <BR>b. 由于严格通过包来区分了访问域，外层恶意的类通过内置代码也无法获得权限访问到内层类，破坏代码就自然无法生效。 </P>
<P>附：关于Java的平台无关性，有一个例子可以很明显的说明这个特性： <BR>一般来说，C或C++中的int占位宽度是根据目标平台的字长来决定的，这就意味着针对不同的平台编译同一个C++程序在运行时会有不同的行为。然而对于 Java中的int都是32位的二进制补码标识的有符号整数，而float都是遵守IEEE 754浮点标准的32位浮点数。 </P>
<P>PS: 这个小弟最近也没时间继续研究下去了，只是想抛砖引玉的提供给大家一个初步认识JVM的印象。有机会了解一下JVM的内部结构对今后做Java开发是很有好处的。</P>
<P>&nbsp;</P>
<P>&nbsp;</P>
<P>Java杂谈（七）－－接口&amp; 组件、容器</P>
<P>终于又静下来继续写这个主题的续篇，前六篇主要讲了一些J2se方面的经验和感受，眼下Java应用范围已经被J2ee占据了相当大的一块领域，有些人甚至声称Java被J2ee所取代了。不知道大家如何来理解所谓的J2ee (Java2 Enterprise Edition)，也就是Java企业级应用？ </P>
<P>笔者的观点是，技术的发展是顺应世界变化的趋势的，从C/S过渡到B/S模式，从客户端的角度考虑企业级应用或者说电子商务领域不在关心客户端维护问题，这个任务已经交给了任何一台PC都会有的浏览器去维护；从服务器端的角度考虑，以往C/S中的TCP/IP协议实现载体ServerSocket被Web Server Container所取代，例如大家都很熟悉的Tomcat、JBoss、WebLogic等等。总之一切的转变都是为了使得Java技术能更好的为人类生产生活所服务。 </P>
<P>有人会问，直接去学J2ee跳过J2se行否？笔者是肯定不赞成的，实际上确实有人走这条路，但笔者自身体会是正是由于J2se的基础很牢固，才会导致在J2ee学习的道路上顺风顺水，知识点上不会有什么迷惑的地方。举个简单的例子吧： </P>
<P>笔者曾经跟大学同学讨论下面这两种写法的区别： <BR>ArrayList list = new ArrayList(); //笔者不说反对，但至少不赞成 <BR>List list = new ArrayList(); //笔者支持 <BR>曾经笔者跟同学争论了几个小时，他非说第一种写法更科学，第二种完全没有必要。我无法完全说服他，但笔者认为良好的习惯和意识是任何时候都应该针对接口编程，以达到解耦合和可扩展性的目的。下面就以接口开始进入J2ee的世界吧： </P>
<P>1. J2ee与接口 </P>
<P>每一个版本的J2ee都对应着一个确定版本的JDK，J2ee1.4对应Jdk1.4，现在比较新的是JDK5.0，自然也会有J2EE 5.0。其实笔者一直在用的是J2EE1.4，不过没什么关系，大家可以下任何一个版本的J2ee api来稍微浏览一下。笔者想先声明一个概念，J2ee也是源自Java，所以底层的操作依然调用到很多J2se的库，所以才建议大家先牢牢掌握J2se 的主流技术。 </P>
<P>J2ee api有一个特点，大家比较熟悉的几个包java.jms、javax.servlet.http、javax.ejb等都以interface居多，实现类较少。其实大家真正在用的时候百分之六十以上都在反复的查着javax.servlet.http这个包下面几个实现类的api函数，其他的包很少问津。笔者建议在学习一种技术之前，对整体的框架有一个了解是很有必要的，J2ee旨在通过interface的声明来规范实现的行为，任何第三方的厂商想要提供自己品牌的实现前提也是遵循这些接口定义的规则。如果在从前J2se学习的道路上对接口的理解很好的话，这里的体会将是非常深刻的，举个简单的例子： </P>
<P>public interface Mp3{ <BR>public void play(); <BR>public void record(); <BR>public void stop(); <BR>} </P>
<P>如果我定义这个简单的接口，发布出去，规定任何第三方的公司想推出自己的名字为Mp3的产品都必须实现这个接口，也就是至少提供接口中方法的具体实现。这个意义已经远远不止是面向对象的多态了，只有厂商遵循J2ee的接口定义，世界上的J2ee程序员才能针对统一的接口进行程序设计，最终不用改变代码只是因为使用了不同厂商的实现类而有不同的特性罢了，本质上说，无论哪一种厂商实现都完成了职责范围内的工作。这个就是笔者想一直强调的，针对接口编程的思想。 </P>
<P>接口到底有什么好处呢？我们这样设想，现在有AppleMp3、SonyMp3、SamsungMp3都实现了这个Mp3的接口，于是都有了play、 record、stop这三个功能。我们将Mp3产品座位一个组件的时候就不需要知道它的具体实现，只要看到接口定义知道这个对象有3个功能就可以使用了。那么类似下面这样的业务就完全可以在任何时间从3个品牌扩展到任意个品牌，开个玩笑的说，项目经理高高在上的写完10个接口里的方法声明，然后就丢给手下的程序员去写里面的细节，由于接口已经统一（即每个方法传入和传出的格式已经统一），经理只需关注全局的业务就可以天天端杯咖啡走来走去了，^_^： <BR>public Mp3 create(); <BR>public void copy(Mp3 mp3); <BR>public Mp3 getMp3(); </P>
<P>最后用一个简单的例子说明接口：一个5号电池的手电筒，可以装入任何牌子的5号电池，只要它符合5号电池的规范，装入之后任何看不到是什么牌子，只能感受到手电筒在完成它的功能。那么生产手电筒的厂商和生产5号电池的厂商就可以完全解除依赖关系，可以各自自由开发自己的产品，因为它们都遵守5号电池应有的形状、正负极位置等约定。这下大家能对接口多一点体会了么？ </P>
<P>2. 组件和容器 <BR>针对接口是笔者特意强调的J2ee学习之路必备的思想，另外一个就是比较常规的组件和容器的概念了。很多教材和专业网站都说J2EE的核心是一组规范与指南，强调J2ee的核心概念就是组件+容器，这确实是无可厚非的。随着越来越多的J2ee框架出现，相应的每种框架都一般有与之对应的容器。 </P>
<P>容器，是用来管理组件行为的一个集合工具，组件的行为包括与外部环境的交互、组件的生命周期、组件之间的合作依赖关系等等。J2ee包含的容器种类大约有 Web容器、Application Client容器、EJB容器、Applet客户端容器等。但在笔者看来，现在容器的概念变得有点模糊了，大家耳熟能详是那些功能强大的开源框架，比如 Hibernate、Struts2、Spring、JSF等，其中Hibernate就基于JDBC的基础封装了对事务和会话的管理，大大方便了对数据库操作的繁琐代码，从这个意义上来说它已经接近容器的概念了，EJB的实体Bean也逐渐被以Hibernate为代表的持久化框架所取代。 </P>
<P>组件，本意是指可以重用的代码单元，一般代表着一个或者一组可以独立出来的功能模块，在J2ee中组件的种类有很多种，比较常见的是EJB组件、DAO组件、客户端组件或者应用程序组件等，它们有个共同特点是分别会打包成.war，.jar，.jar，.ear，每个组件由特定格式的xml描述符文件进行描述，而且服务器端的组件都需要被部署到应用服务器上面才能够被使用。 </P>
<P>稍微理解完组件和容器，还有一个重要的概念就是分层模型，最著名的当然是MVC三层模型。在一个大的工程或项目中，为了让前台和后台各个模块的编程人员能够同时进行工作提高开发效率，最重要的就是实现层与层之间的耦合关系，许多分层模型的宗旨和开源框架所追求的也就是这样的效果。在笔者看来，一个完整的 Web项目大概有以下几个层次： </P>
<P>a) 表示层（Jsp、Html、Javascript、Ajax、Flash等等技术对其支持） <BR>b) 控制层（Struts、JSF、WebWork等等框架在基于Servlet的基础上支持，负责把具体的请求数据（有时卸载重新装载）导向适合处理它的模型层对象） <BR>c) 模型层（笔者认为目前最好的框架是Spring，实质就是处理表示层经由控制层转发过来的数据，包含着大量的业务逻辑） <BR>d) 数据层（Hibernate、JDBC、EJB等，由模型层处理完了持久化到数据库中） </P>
<P>当然，这仅仅是笔者个人的观点，仅仅是供大家学习做一个参考，如果要实现这些层之间的完全分离，那么一个大的工程，可以仅仅通过增加人手就来完成任务。虽然《人月神话》中已经很明确的阐述了增加人手并不能是效率增加，很大程度上是因为彼此做的工作有顺序上的依赖关系或者说难度和工作量上的巨大差距。当然理想状态在真实世界中是不可能达到的，但我们永远应该朝着这个方向去不断努力。最开始所提倡的针对接口来编程，哪怕是小小的细节，写一条List list= = new ArrayList()语句也能体现着处处皆使用接口的思想在里面。Anyway，这只是个开篇，笔者会就自己用过的J2ee技术和框架再细化谈一些经验.</P>
<P>&nbsp;</P>
<P>Java杂谈（八）－－Servlet/Jsp</P>
<P><BR>终于正式进入J2ee的细节部分了，首当其冲的当然是Servlet和Jsp了，上篇曾经提到过J2ee只是一个规范和指南，定义了一组必须要遵循的接口，核心概念是组件和容器。曾经有的人问笔者Servlet的Class文件是哪里来的？他认为是J2ee官方提供的，我举了一个简单的反例：稍微检查了一下Tomcat5.0里面的Servlet.jar文件和JBoss里面的Servlet.jar文件大小，很明显是不一样的，至少已经说明了它们不是源自同根的吧。其实Servlet是由容器根据J2ee的接口定义自己来实现的，实现的方式当然可以不同，只要都遵守J2ee规范和指南。 </P>
<P>上述只是一个常见的误区罢了，告诉我们要编译运行Servlet，是要依赖于实现它的容器的，不然连jar文件都没有，编译都无法进行。那么Jsp呢？ Java Server Page的简称，是为了开发动态网页而诞生的技术，其本质也是Jsp，在编写完毕之后会在容器启动时经过编译成对应的Servlet。只是我们利用Jsp 的很多新特性，可以更加专注于前后台的分离，早期Jsp做前台是满流行的，毕竟里面支持Html代码，这让前台美工人员可以更有效率的去完成自己的工作。然后Jsp将请求转发到后台的Servlet，由Servlet处理业务逻辑，再转发回另外一个Jsp在前台显示出来。这似乎已经成为一种常用的模式，最初笔者学习J2ee的时候，大量时间也在编写这样的代码。 </P>
<P>尽管现在做前台的技术越来越多，例如Flash、Ajax等，已经有很多人不再认为Jsp重要了。笔者觉得Jsp带来的不仅仅是前后端分离的设计理念，它的另外一项技术成就了我们今天用的很多框架，那就是Tag标签技术。所以与其说是在学习Jsp，不如更清醒的告诉自己在不断的理解Tag标签的意义和本质。 </P>
<P>1． Servlet以及Jsp的生命周期 <BR>Servlet是Jsp的实质，尽管容器对它们的处理有所区别。Servlet有init()方法初始化，service()方法进行Web服务， destroy()方法进行销毁，从生到灭都由容器来掌握，所以这些方法除非你想自己来实现Servlet，否则是很少会接触到的。正是由于很少接触，才容易被广大初学者所忽略，希望大家至少记住Servlet生命周期方法都是回调方法。回调这个概念简单来说就是把自己注入另外一个类中，由它来调用你的方法，所谓的另外一个类就是Web容器，它只认识接口和接口的方法，注入进来的是怎样的对象不管，它只会根据所需调用这个对象在接口定义存在的那些方法。由容器来调用的Servlet对象的初始化、服务和销毁方法，所以叫做回调。这个概念对学习其他J2ee技术相当关键！ </P>
<P>那么Jsp呢？本事上是Servlet，还是有些区别的，它的生命周期是这样的： <BR>a) 一个客户端的Request到达服务器 -&gt; <BR>b) 判断是否第一次调用 -&gt; 是的话编译Jsp成Servlet <BR>c) 否的话再判断此Jsp是否有改变 -&gt; 是的话也重新编译Jsp成Servlet <BR>d) 已经编译最近版本的Servlet装载所需的其他Class <BR>e) 发布Servlet，即调用它的Service()方法 </P>
<P>所以Jsp号称的是第一次Load缓慢，以后都会很快的运行。从它的生命的周期确实不难看出来这个特点，客户端的操作很少会改变Jsp的源码，所以它不需要编译第二次就一直可以为客户端提供服务。这里稍微解释一下Http的无状态性，因为发现很多人误解，Http的无状态性是指每次一张页面显示出来了，与服务器的连接其实就已经断开了，当再次有提交动作的时候，才会再次与服务器进行连接请求提供服务。当然还有现在比较流行的是Ajax与服务器异步通过 xml交互的技术，在做前台的领域潜力巨大，笔者不是Ajax的高手，这里无法为大家解释。 </P>
<P>2． Tag标签的本质 <BR>笔者之前说了，Jsp本身初衷是使得Web应用前后台的开发可以脱离耦合分开有效的进行，可惜这个理念的贡献反倒不如它带来的Tag技术对J2ee的贡献要大。也许已经有很多人开始使用Tag技术了却并不了解它。所以才建议大家在学习J2ee开始的时候一定要认真学习Jsp，其实最重要的就是明白标签的本质。 </P>
<P>Html标签我们都很熟悉了，有 &lt;html&gt; 、 &lt;head&gt; 、 &lt;body&gt; 、 &lt;title&gt; ，Jsp带来的Tag标签遵循同样的格式，或者说更严格的Xml格式规范，例如 &lt;jsp:include&gt; 、 &lt;jsp:useBean&gt; 、 &lt;c:if&gt; 、 &lt;c:forEach&gt; 等等。它们没有什么神秘的地方，就其源头也还是Java Class而已，Tag标签的实质也就是一段Java代码，或者说一个Class文件。当配置文件设置好去哪里寻找这些Class的路径后，容器负责将页面中存在的标签对应到相应的Class上，执行那段特定的Java代码，如此而已。 <BR>说得明白一点的话还是举几个简单的例子说明一下吧： </P>
<P>&lt;jsp:include&gt; 去哪里找执行什么class呢?首先这是个jsp类库的标签，当然要去jsp类库寻找相应的class了，同样它也是由Web容器来提供，例如 Tomcat就应该去安装目录的lib文件夹下面的jsp-api.jar里面找，有兴趣的可以去找一找啊！ </P>
<P>&lt;c:forEach&gt; 又去哪里找呢？这个是由Jsp2.0版本推荐的和核心标记库的内容，例如 &lt;c:if&gt; 就对应在页面中做if判断的功能的一断Java代码。它的class文件在jstl.jar这个类库里面，往往还需要和一个standard.jar类库一起导入，放在具体Web项目的WEB-INF的lib目录下面就可以使用了。 </P>
<P>顺便罗唆一句，Web Project的目录结构是相对固定的，因为容器会按照固定的路径去寻找它需要的配置文件和资源，这个任何一本J2ee入门书上都有，这里就不介绍了。了解Tag的本质还要了解它的工作原理，所以大家去J2ee的API里找到并研究这个包：javax.servlet.jsp.tagext。它有一些接口，和一些实现类，专门用语开发Tag，只有自己亲自写出几个不同功能的标签，才算是真正理解了标签的原理。别忘记了自己开发的标签要自己去完成配置文件，容器只是集成了去哪里寻找jsp标签对应class的路径，自己写的标签库当然要告诉容器去哪里找啦。 </P>
<P>说了这么多，我们为什么要用标签呢？完全在Jsp里面来个 &lt;% %&gt; 就可以在里面任意写Java代码了，但是长期实践发现页面代码统一都是与html同风格的标记语言更加有助于美工人员进行开发前台，它不需要懂Java，只要Java程序员给个列表告诉美工什么标签可以完成什么逻辑功能，他就可以专注于美工，也算是进一步隔离了前后台的工作吧！ </P>
<P>3． 成就Web框架 <BR>框架是什么？曾经看过这样的定义：与模式类似，框架也是解决特定问题的可重用方法，框架是一个描述性的构建块和服务集合，开发人员可以用来达成某个目标。一般来说，框架提供了解决某类问题的基础设施，是用来创建解决方案的工具，而不是问题的解决方案。 </P>
<P>正是由于Tag的出现，成就了以后出现的那么多Web框架，它们都开发了自己成熟实用的一套标签，然后由特定的Xml文件来配置加载信息，力图使得Web 应用的开发变得更加高效。下面这些标签相应对很多人来说相当熟悉了： <BR>&lt;html:password&gt; <BR>&lt;logic:equal&gt; <BR>&lt;bean:write&gt; <BR>&lt;f:view&gt; <BR>&lt;h:form&gt; <BR>&lt;h:message&gt; </P>
<P>它们分别来自Struts和JSF框架，最强大的功能在于控制转发，就是MVC三层模型中间完成控制器的工作。Struts-1实际上并未做到真正的三层隔离，这一点在Struts-2上得到了很大的改进。而Jsf向来以比较完善合理的标签库受到人们推崇。 </P>
<P>今天就大概讲这么多吧，再次需要强调的是Servlet/Jsp是学习J2ee必经之路，也是最基础的知识，希望大家给与足够的重视！</P>
<P>&nbsp;</P>
<P><BR>Java杂谈（九）－－Struts</P>
<P>J2ee的开源框架很多，笔者只能介绍自己熟悉的几个，其他的目前在中国IT行业应用得不是很多。希望大家对新出的框架不要盲目的推崇，首先一定要熟悉它比旧的到底好在哪里，新的理念和特性是什么？然后再决定是否要使用它。 </P>
<P>这期的主题是Struts，直译过来是支架。Struts的第一个版本是在2001年5月发布的，它提供了一个Web应用的解决方案，如何让Jsp和 servlet共存去提供清晰的分离视图和业务应用逻辑的架构。在Struts之前，通常的做法是在Jsp中加入业务逻辑，或者在Servlet中生成视图转发到前台去。Struts带着MVC的新理念当时退出几乎成为业界公认的Web应用标准，于是当代IT市场上也出现了众多熟悉Struts的程序员。即使有新的框架再出来不用，而继续用Struts的理由也加上了一条低风险，因为中途如果开发人员变动，很容易的招进新的会Struts的IT民工啊， ^_^! </P>
<P>笔者之前说的都是Struts-1，因为新出了Struts-2，使得每次谈到Struts都必须注明它是Struts-1还是2。笔者先谈比较熟悉的 Struts-1，下次再介绍一下与Struts－2的区别： </P>
<P>1． Struts框架整体结构 <BR>Struts-1的核心功能是前端控制器，程序员需要关注的是后端控制器。前端控制器是是一个Servlet，在Web.xml中间配置所有 Request都必须经过前端控制器，它的名字是ActionServlet，由框架来实现和管理。所有的视图和业务逻辑隔离都是应为这个 ActionServlet，它就像一个交通警察，所有过往的车辆必须经过它的法眼，然后被送往特定的通道。所有，对它的理解就是分发器，我们也可以叫做Dispatcher，其实了解Servlet编程的人自己也可以写一个分发器，加上拦截request的Filter，其实自己实现一个struts框架并不是很困难。主要目的就是让编写视图的和后台逻辑的可以脱离紧耦合，各自同步的完成自己的工作。 </P>
<P>那么有了ActionServlet在中间负责转发，前端的视图比如说是Jsp，只需要把所有的数据Submit，这些数据就会到达适合处理它的后端控制器Action，然后在里面进行处理，处理完毕之后转发到前台的同一个或者不同的视图Jsp中间，返回前台利用的也是Servlet里面的forward 和redirect两种方式。所以到目前为止，一切都只是借用了Servlet的API搭建起了一个方便的框架而已。这也是Struts最显著的特性?? 控制器。 </P>
<P>那么另外一个特性，可以说也是Struts-1带来的一个比较成功的理念，就是以xml配置代替硬编码配置信息。以往决定Jsp往哪个servlet提交，是要写进Jsp代码中的，也就是说一旦这个提交路径要改，我们必须改写代码再重新编译。而Struts提出来的思路是，编码的只是一个逻辑名字，它对应哪个class文件写进了xml配置文件中，这个配置文件记录着所有的映射关系，一旦需要改变路径，改变xml文件比改变代码要容易得多。这个理念可以说相当成功，以致于后来的框架都延续着这个思路，xml所起的作用也越来越大。 </P>
<P>大致上来说Struts当初给我们带来的新鲜感就这么多了，其他的所有特性都是基于方便的控制转发和可扩展的xml配置的基础之上来完成它们的功能的。 <BR>下面将分别介绍Action和FormBean， 这两个是Struts中最核心的两个组件。 </P>
<P>2． 后端控制器Action <BR>Action就是我们说的后端控制器，它必须继承自一个Action父类，Struts设计了很多种Action，例如DispatchAction、 DynaValidationAction。它们都有一个处理业务逻辑的方法execute()，传入的request, response, formBean和actionMapping四个对象，返回actionForward对象。到达Action之前先会经过一个 RequestProcessor来初始化配置文件的映射关系，这里需要大家注意几点： </P>
<P>1) 为了确保线程安全，在一个应用的生命周期中，Struts框架只会为每个Action类创建一个Action实例，所有的客户请求共享同一个Action 实例，并且所有线程可以同时执行它的execute()方法。所以当你继承父类Action，并添加了private成员变量的时候，请记住这个变量可以被多个线程访问，它的同步必须由程序员负责。(所有我们不推荐这样做)。在使用Action的时候，保证线程安全的重要原则是在Action类中仅仅使用局部变量，谨慎的使用实例变量。局部变量是对每个线程来说私有的，execute方法结束就被销毁，而实例变量相当于被所有线程共享。 </P>
<P>2) 当ActionServlet实例接收到Http请求后，在doGet()或者doPost()方法中都会调用process()方法来处理请求。 RequestProcessor类包含一个HashMap，作为存放所有Action实例的缓存，每个Action实例在缓存中存放的属性key为 Action类名。在RequestProcessor类的processActionCreate()方法中，首先检查在HashMap中是否存在 Action实例。创建Action实例的代码位于同步代码块中，以保证只有一个线程创建Action实例。一旦线程创建了Action实例并把它存放到 HashMap中，以后所有的线程会直接使用这个缓存中的实例。 </P>
<P>3) &lt;action&gt; 元素的 &lt;roles&gt; 属性指定访问这个Action用户必须具备的安全角色，多个角色之间逗号隔开。RequestProcessor类在预处理请求时会调用自身的 processRoles()方法，检查配置文件中是否为Action配置了安全角色，如果有，就调用HttpServletRequest的 isUserInRole()方法来判断用户是否具备了必要的安全性角色，如果不具备，就直接向客户端返回错误。(返回的视图通过 &lt;input&gt; 属性来指定) </P>
<P>3． 数据传输对象FormBean <BR>Struts并没有把模型层的业务对象直接传递到视图层，而是采用DTO（Data Transfer Object）来传输数据，这样可以减少传输数据的冗余，提高传输效率；还有助于实现各层之间的独立，使每个层分工明确。Struts的DTO就是 ActionForm，即formBean。由于模型层应该和Web应用层保持独立。由于ActionForm类中使用了Servlet API，因此不提倡把ActionForm传递给模型层， 而应该在控制层把ActionForm Bean的数据重新组装到自定义的DTO中，再把它传递给模型层。它只有两个scope，分别是session和request。（默认是session）一个ActionForm标准的生命周期是： <BR>1) 控制器收到请求 -&gt; <BR>2) 从request或session中取出ActionForm实例，如不存在就创建一个 -&gt; <BR>3) 调用ActionForm的reset()方法 -&gt; <BR>4) 把实例放入session或者request中 -&gt; <BR>5) 将用户输入表达数据组装到ActionForm中 -&gt; <BR>6) 如眼张方法配置了就调用validate()方法 -&gt; <BR>7) 如验证错误就转发给 &lt;input&gt; 属性指定的地方，否则调用execute()方法 </P>
<P>validate()方法调用必须满足两个条件： <BR>1) ActionForm 配置了Action映射而且name属性匹配 <BR>2) &lt;aciton&gt; 元素的validate属性为true </P>
<P>如果ActionForm在request范围内，那么对于每个新的请求都会创建新的ActionForm实例，属性被初始化为默认值，那么reset ()方法就显得没有必要；但如果ActionForm在session范围内，同一个ActionForm实例会被多个请求共享，reset()方法在这种情况下极为有用。 </P>
<P>4． 验证框架和国际化 <BR>Struts有许多自己的特性，但是基本上大家还是不太常用，说白了它们也是基于JDK中间的很多Java基础包来完成工作。例如国际化、验证框架、插件自扩展功能、与其他框架的集成、因为各大框架基本都有提供这样的特性，Struts也并不是做得最好的一个，这里也不想多说。Struts的验证框架，是通过一个validator.xml的配置文件读入验证规则，然后在validation-rules.xml里面找到验证实现通过自动为Jsp插入 Javascript来实现，可以说做得相当简陋。弹出来的JavaScript框不但难看还很多冗余信息，笔者宁愿用formBean验证或者 Action的saveErrors()，验证逻辑虽然要自己写，但页面隐藏/浮现的警告提示更加人性化和美观一些。 </P>
<P>至于Struts的国际化，其实无论哪个框架的国际化，java.util.Locale类是最重要的Java I18N类。在Java语言中，几乎所有的对国际化和本地化的支持都依赖于这个类。如果Java类库中的某个类在运行的时候需要根据Locale对象来调整其功能，那么就称这个类是本地敏感的(Locale-Sensitive)，例如java.text.DateFormat类就是，依赖于特定Locale。 </P>
<P>创建Locale对象的时候，需要明确的指定其语言和国家的代码，语言代码遵从的是ISO-639规范，国家代码遵从ISO-3166规范，可以从 <BR><A href="http://www.unicode.org/unicode/onlinedat/languages.html">http://www.unicode.org/unicode/onlinedat/languages.html</A> <BR><A href="http://www.unicode.org/unicode/onlinedat/countries.htm">http://www.unicode.org/unicode/onlinedat/countries.htm</A> </P>
<P>Struts的国际化是基于properties的message/key对应来实现的，笔者曾写过一个程序，所有Jsp页面上没有任何Text文本串，全部都用的是 &lt;bean:message&gt; 去Properties文件里面读，这个时候其实只要指定不同的语言区域读不同的Properties文件就实现了国际化。需要注意的是不同语言的字符写进Properties文件的时候需要转化成Unicode码，JDK已经带有转换的功能。JDK的bin目录中有native2ascii这个命令，可以完成对*.txt和*.properties的Unicode码转换。 </P>
<P>OK，今天就说到这里，本文中的很多内容也不是笔者的手笔，是笔者一路学习过来自己抄下来的笔记，希望对大家有帮助！Java杂谈一路走来，感谢大家持续的关注，大概再有个2到3篇续篇就改完结了！笔者尽快整理完成后续的写作吧……^_^</P>
<P>&nbsp;</P>
<P><BR>Java杂谈（九）－－Struts2</P>
<P><BR>最近业余时间笔者一直Java Virtual Machine的研究，由于实习分配到项目组里面，不想从前那么闲了，好不容易才抽出时间来继续这个话题的帖子。我打算把J2ee的部分结束之后，再谈谈 JVM和JavaScript，只要笔者有最新的学习笔记总结出来，一定会拿来及时和大家分享的。衷心希望与热爱Java的关大同仁共同进步…… </P>
<P>这次准备继续上次的话题先讲讲Struts-2，手下简短回顾一段历史：随着时间的推移，Web应用框架经常变化的需求，产生了几个下一代 Struts的解决方案。其中的Struts Ti 继续坚持 MVC模式的基础上改进，继续Struts的成功经验。 WebWork项目是在2002年3月发布的，它对Struts式框架进行了革命性改进，引进了不少新的思想，概念和功能，但和原Struts代码并不兼容。WebWork是一个成熟的框架，经过了好几次重大的改进与发布。在2005年12月，WebWork与Struts Ti决定合拼， 再此同时， Struts Ti 改名为 Struts Action Framework 2.0,成为Struts真正的下一代。 </P>
<P>看看Struts-2的处理流程： <BR>1) Browser产生一个请求并提交框架来处理：根据配置决定使用哪些拦截器、action类和结果等。 <BR>2) 请求经过一系列拦截器：根据请求的级别不同拦截器做不同的处理。这和Struts-1的RequestProcessor类很相似。 <BR>3) 调用Action: 产生一个新的action实例，调用业务逻辑方法。 <BR>4) 调用产生结果：匹配result class并调用产生实例。 <BR>5) 请求再次经过一系列拦截器返回：过程也可配置减少拦截器数量 <BR>6) 请求返回用户：从control返回servlet，生成Html。 </P>
<P>这里很明显的一点是不存在FormBean的作用域封装，直接可以从Action中取得数据。 这里有一个Strut-2配置的web.xml文件： <BR>&lt;filter&gt; <BR>&lt;filter-name&gt; controller &lt;/filter-name&gt; <BR>&lt;filter-class&gt; org.apache.struts.action2.dispatcher.FilterDispatcher &lt;/filter-class&gt; <BR>&lt;/filter&gt; <BR>&lt;filter-mapping&gt; <BR>&lt;filter-name&gt; cotroller &lt;/filter-name&gt; <BR>&lt;url-pattern&gt; /* &lt;/url-pattern&gt; <BR>&lt;/filter-mapping&gt; </P>
<P>注意到以往的servlet变成了filter，ActionServlet变成了FilterDispatcher，*.do变成了/*。filter 配置定义了名称(供关联)和filter的类。filter mapping让URI匹配成功的的请求调用该filter。默认情况下，扩展名为 ".action "。这个是在default.properties文件里的 "struts.action.extension "属性定义的。 </P>
<P>default.properties是属性定义文件，通过在项目classpath路径中包含一个名为“struts.properties”的文件来设置不同的属性值。而Struts-2的默认配置文件名为struts.xml。由于1和2的action扩展名分别为.do和.action，所以很方便能共存。我们再来看一个Struts-2的action代码： <BR>public class MyAction { <BR>public String execute() throws Exception { <BR>//do the work <BR>return "success "; <BR>} <BR>} </P>
<P>很明显的区别是不用再继承任何类和接口，返回的只是一个String，无参数。实际上在Struts-2中任何返回String的无参数方法都可以通过配置来调用action。所有的参数从哪里来获得呢？答案就是Inversion of Control技术（控制反转）。笔者尽量以最通俗的方式来解释，我们先试图让这个Action获得reuqest对象，这样可以提取页面提交的任何参数。那么我们把request设为一个成员变量，然后需要一个对它的set方法。由于大部分的action都需要这么做，我们把这个set方法作为接口来实现。 <BR>public interface ServletRequestAware { <BR>public void setServletRequest(HttpServletRequest request); <BR>} </P>
<P>public class MyAction implements ServletRequestAware { <BR>private HttpServletRequest request; </P>
<P>public void setServletRequest(HttpServletRequest request) { <BR>this.request = request; <BR>} </P>
<P>public String execute() throws Exception { <BR>// do the work directly using the request <BR>return Action.SUCCESS; <BR>} <BR>} </P>
<P>那么谁来调用这个set方法呢？也就是说谁来控制这个action的行为，以往我们都是自己在适当的地方写上一句 action.setServletRequest(…)，也就是控制权在程序员这边。然而控制反转的思想是在哪里调用交给正在运行的容器来决定，只要利用Java反射机制来获得Method对象然后调用它的invoke方法传入参数就能做到，这样控制权就从程序员这边转移到了容器那边。程序员可以减轻很多繁琐的工作更多的关注业务逻辑。Request可以这样注入到action中，其他任何对象也都可以。为了保证action的成员变量线程安全， Struts-2的action不是单例的，每一个新的请求都会产生一个新的action实例。 </P>
<P>那么有人会问，到底谁来做这个对象的注入工作呢？答案就是拦截器。拦截器又是什么东西？笔者再来尽量通俗的解释拦截器的概念。大家要理解拦截器的话，首先一定要理解GOF23种设计模式中的Proxy模式。 </P>
<P>A对象要调用f()，它希望代理给B来做，那么B就要获得A对象的引用，然后在B的f()中通过A对象引用调用A对象的f()方法，最终达到A的f()被调用的目的。有没有人会觉得这样很麻烦，为什么明明只要A.f()就可以完成的一定要封装到B的f()方法中去？有哪些好处呢？ </P>
<P>1) 这里我们只有一个A，当我们有很多个A的时候，只需要监视B一个对象的f()方法就可以从全局上控制所有被调用的f()方法。 <BR>2) 另外，既然代理人B能获得A对象的引用，那么B可以决定在真正调A对象的f()方法之前可以做哪些前置工作，调完返回前可有做哪些后置工作。 </P>
<P>讲到这里，大家看出来一点拦截器的概念了么？它拦截下一调f()方法的请求，然后统一的做处理（处理每个的方式还可以不同，解析A对象就可以辨别），处理完毕再放行。这样像不像对流动的河水横切了一刀，对所有想通过的水分子进行搜身，然后再放行？这也就是AOP（Aspect of Programming面向切面编程）的思想。 </P>
<P>Anyway，Struts-2只是利用了AOP和IoC技术来减轻action和框架的耦合关系，力图到最大程度重用action的目的。在这样的技术促动下，Struts-2的action成了一个简单被框架使用的POJO（Plain Old Java Object）罢了。实事上AOP和IoC的思想已经遍布新出来的每一个框架上，他们并不是多么新的技术，利用的也都是JDK早已可以最到的事情，它们代表的是更加面向接口编程，提高重用，增加扩展性的一种思想。Struts-2只是部分的使用这两种思想来设计完成的，另外一个最近很火的框架 Spring，更大程度上代表了这两种设计思想，笔者将于下一篇来进一步探讨Spring的结构。 </P>
<P>PS: 关于Struts-2笔者也没真正怎么用过，这里是看了网上一些前辈的帖子之后写下自己的学习体验，不足之处请见谅！</P>
<P><BR>Java杂谈（十）－－Spring</P>
<P><BR>笔者最近比较忙，一边在实习一边在寻找明年毕业更好的工作，不过论坛里的朋友非常支持小弟继续写，今天是周末，泡上一杯咖啡，继续与大家分享J2ee部分的学习经验。今天的主题是目前很流行也很好的一个开源框架－Spring。 </P>
<P>引用《Spring2.0技术手册》上的一段话： <BR>Spring的核心是个轻量级容器，它是实现IoC容器和非侵入性的框架，并提供AOP概念的实现方式；提供对持久层、事务的支持；提供MVC Web框架的实现，并对于一些常用的企业服务API提供一致的模型封装，是一个全方位的应用程序框架，除此之外，对于现存的各种框架，Spring也提供了与它们相整合的方案。 <BR>接下来笔者先谈谈自己的一些理解吧，Spring框架的发起者之前一本很著名的书名字大概是《J2ee Development without EJB》，他提倡用轻量级的组件代替重量级的EJB。笔者还没有看完那本著作，只阅读了部分章节。其中有一点分析觉得是很有道理的： </P>
<P>EJB里在服务器端有Web Container和EJB Container，从前的观点是各层之间应该在物理上隔离，Web Container处理视图功能、在EJB Container中处理业务逻辑功能、然后也是EBJ Container控制数据库持久化。这样的层次是很清晰，但是一个很严重的问题是Web Container和EJB Container毕竟是两个不同的容器，它们之间要通信就得用的是RMI机制和JNDI服务，同样都在服务端，却物理上隔离，而且每次业务请求都要远程调用，有没有必要呢？看来并非隔离都是好的。 </P>
<P>再看看轻量级和重量级的区别，笔者看过很多种说法，觉得最有道理的是轻量级代表是POJO + IoC，重量级的代表是Container + Factory。（EJB2.0是典型的重量级组件的技术）我们尽量使用轻量级的Pojo很好理解，意义就在于兼容性和可适应性，移植不需要改变原来的代码。而Ioc与Factory比起来，Ioc的优点是更大的灵活性，通过配置可以控制很多注入的细节，而Factory模式，行为是相对比较封闭固定的，生产一个对象就必须接受它全部的特点，不管是否需要。其实轻量级和重量级都是相对的概念，使用资源更少、运行负载更小的自然就算轻量。 </P>
<P>话题扯远了，因为Spring框架带来了太多可以探讨的地方。比如它的非侵入性：指的是它提供的框架实现可以让程序员编程却感觉不到框架的存在，这样所写的代码并没有和框架绑定在一起，可以随时抽离出来，这也是Spring设计的目标。Spring是唯一可以做到真正的针对接口编程，处处都是接口，不依赖绑定任何实现类。同时，Spring还设计了自己的事务管理、对象管理和Model2 的MVC框架，还封装了其他J2ee的服务在里面，在实现上基本都在使用依赖注入和AOP的思想。由此我们大概可以看到Spring是一个什么概念上的框架，代表了很多优秀思想，值得深入学习。笔者强调，学习并不是框架，而是框架代表的思想，就像我们当初学Struts一样…… </P>
<P>1．Spring MVC <BR>关于IoC和AOP笔者在上篇已经稍微解释过了，这里先通过Spring的MVC框架来给大家探讨一下Spring的特点吧。（毕竟大部分人已经很熟悉Struts了，对比一下吧） <BR>众所周知MVC的核心是控制器。类似Struts中的ActionServlet，Spring里面前端控制器叫做DispatcherServlet。里面充当Action的组件叫做Controller，返回的视图层对象叫做ModelAndView，提交和返回都可能要经过过滤的组件叫做 Interceptor。 </P>
<P>让我们看看一个从请求到返回的流程吧： <BR>(1) 前台Jsp或Html通过点击submit，将数据装入了request域 <BR>(2) 请求被Interceptor拦截下来，执行preHandler()方法出前置判断 <BR>(3) 请求到达DispathcerServlet <BR>(4) DispathcerServlet通过Handler Mapping来决定每个reuqest应该转发给哪个后端控制器Controller</P>
<P><BR>Java杂谈（十一）ORM</P>
<P>这是最后一篇Java杂谈了，以ORM框架的谈论收尾，也算是把J2ee的最后一方面给涵盖到了，之所以这么晚才总结出ORM这方面，一是笔者这两周比较忙，另一方面也想善始善终，仔细的先自己好好研究一下ORM框架技术，不想草率的敷衍了事。 </P>
<P>其实J2ee的规范指南里面就已经包括了一些对象持久化技术，例如JDO（Java Data Object）就是Java对象持久化的新规范，一个用于存取某种数据仓库中的对象的标准化API，提供了透明的对象存储，对开发人员来说，存储数据对象完全不需要额外的代码（如JDBC API的使用）。这些繁琐的工作已经转移到JDO产品提供商身上，使开发人员解脱出来，从而集中时间和精力在业务逻辑上。另外，JDO很灵活，因为它可以在任何数据底层上运行。JDBC只是面向关系数据库（RDBMS）JDO更通用，提供到任何数据底层的存储功能，比如关系数据库、文件、XML以及对象数据库（ODBMS）等等，使得应用可移植性更强。我们如果要理解对象持久化技术，首先要问自己一个问题：为什么传统的JDBC来持久化不再能满足大家的需求了呢？ </P>
<P>笔者认为最好是能用JDBC真正编写过程序了才能真正体会ORM的好处，同样的道理，真正拿Servlet/Jsp做过项目了才能体会到Struts、 Spring等框架的方便之处。很幸运的是笔者这两者都曾经经历过，用混乱的内嵌Java代码的Jsp加Servlet转发写过完整的Web项目，也用 JDBC搭建过一个完整C/S项目的后台。所以现在接触到新框架才更能体会它们思想和实现的优越之处，回顾从前的代码，真是丑陋不堪啊。^_^ </P>
<P>回到正题，我们来研究一下为什么要从JDBC发展到ORM。简单来说，传统的JDBC要花大量的重复代码在初始化数据库连接上，每次增删改查都要获得 Connection对象，初始化Statement，执行得到ResultSet再封装成自己的List或者Object，这样造成了在每个数据访问方法中都含有大量冗余重复的代码，考虑到安全性的话，还要加上大量的事务控制和log记录。虽然我们学习了设计模式之后，可以自己定义Factory来帮助减少一部分重复的代码，但是仍然无法避免冗余的问题。其次，随着OO思想深入人心，连典型的过程化语言Perl等都冠冕堂皇的加上了OO的外壳，何况是 Java中繁杂的数据库访问持久化技术呢？强调面向对象编程的结果就是找到一个桥梁，使得关系型数据库存储的数据能准确的映射到Java的对象上，然后针对Java对象来设计对象和方法，如果我们把数据库的Table当作Class，Record当作Instance的话，就可以完全用面向对象的思想来编写数据层的代码。于是乎，Object Relationship Mapping的概念开始普遍受到重视，尽管很早很早就已经有人提出来了。 <BR>缺点我们已经大概清楚了，那么如何改进呢？对症下药，首先我们要解决的是如何从Data Schema准备完美的映射到Object Schema，另外要提供对数据库连接对象生命周期的管理，对事务不同粒度的控制和考虑到扩展性后提供对XML、Properties等可配置化的文件的支持。到目前为止，有很多框架和技术在尝试着这样做。例如似乎是封装管理得过了头的EJB、很早就出现目前已经不在开发和升级了的Apache OJB、首先支持Manual SQL的iBATIS，还有公认非常优秀的Hibernate等等。在分别介绍它们之前，我还想反复强调这些框架都在试图做什么： </P>
<P>毕竟Java Object和数据库的每一条Record还是有很大的区别，就是类型上来说，DB是没有Boolean类型的。而Java也不得不用封装类（Integer、Double等）为了能映射上数据库中为null的情况，毕竟Primitive类型是没有null值的。还有一个比较明显的问题是，数据库有主键和外键，而Java中仍然只能通过基本类型来对应字段值而已，无法规定Unique等特征，更别提外键约束、事务控制和级联操作了。另外，通过Java Object预设某Field值去取数据库记录，是否在这样的记录也是不能保证的。真的要设计到完全映射的话，Java的Static被所有对象共享的变量怎么办？在数据库中如何表现出来…… <BR>我们能看到大量的问题像一座座大山横在那些框架设计者们面前，他们并不是没有解决办法，而是从不同的角度去考虑，会得到很多不同的解决方案，问题是应该采取哪一种呢？甚至只有等到真正设计出来了投入生产使用了，才能印证出当初的设想是否真的能为项目开发带来更多的益处。笔者引用一份文档中提到一个健壮的持久化框架应该具有的特点： <BR>A robust persistence layer should support---- <BR>1. Several types of persistence mechanism <BR>2. Full encapsulation of the persistence mechanism. <BR>3. Multi-object actions <BR>4. Transactions Control <BR>5. Extensibility <BR>6. Object identifiers <BR>7. Cursors: logical connection to the persistence mechanism <BR>8. Proxies: commonly used when the results of a query are to be displayed in a list <BR>9. Records: avoid the overhead of converting database records to objects and then back to records <BR>10. Multi architecture <BR>11. Various database version and/or vendors <BR>12. Multiple connections <BR>13. Native and non-native drivers <BR>14. Structured query language queries(SQL) </P>
<P>&nbsp;</P>
<P><BR>Java杂谈（十一） ORM<BR>现在来简短的介绍一下笔者用过的一些持久化框架和技术，之所以前面强调那么多共通的知识，是希望大家不要盲从流行框架，一定要把握它的本质和卓越的思想好在哪里。 <BR>1． Apache OJB <BR>OJB代表Apache Object Relational Bridge，是Apache开发的一个数据库持久型框架。它是基于J2ee规范指南下的持久型框架技术而设计开发的，例如实现了ODMG 3.0规范的API，实现了JDO规范的API， 核心实现是Persistence Broker API。OJB使用XML文件来实现映射并动态的在Metadata layer听过一个Meta-Object-Protocol(MOP)来改变底层数据的行为。更高级的特点包括对象缓存机制、锁管理机制、 Virtual 代理、事务隔离性级别等等。举个OJB Mapping的简单例子ojb-repository.xml： </P>
<P>&lt;class-descriptor class=”com.ant.Employee” table=”EMPLOYEE”&gt; <BR>&lt;field-descriptor name=”id” column=”ID” <BR>jdbc-type=”INTEGER” primarykey=”true” autoincrement=”true”/&gt; </P>
<P>&lt;field-descriptor name=”name” column=”NAME” jdbc-type=”VARCHAR”/&gt; <BR>&lt;/class-descrptor&gt; </P>
<P>&lt;class-descriptor class=”com.ant.Executive” table=”EXECUTIVE”&gt; <BR>&lt;field-descriptor name=”id” column=”ID” <BR>jdbc-type=”INTEGER” primarykey=”true” autoincrement=”true”/&gt; </P>
<P>&lt;field-descriptor name=”department” column=”DEPARTMENT” jdbc-type=”VARCHAR”/&gt; </P>
<P>&lt;reference-descriptor name=”super” class-ref=”com.ant.Employee”&gt; <BR>&lt;foreignkey field-ref=”id”/&gt; <BR>&lt;/reference-descriptor&gt; <BR>&lt;/class-descrptor&gt; </P>
<P>2． iBATIS <BR>iBATIS最大的特点就是允许用户自己定义SQL来组配Bean的属性。因为它的SQL语句是直接写入XML文件中去的，所以可以最大程度上利用到 SQL语法本身能控制的全部特性，同时也能允许你使用特定数据库服务器的额外特性，并不局限于类似SQL92这样的标准，它最大的缺点是不支持枚举类型的持久化，即把枚举类型的几个对象属性拼成与数据库一个字段例如VARCHAR对应的行为。这里也举一个Mapping文件的例子sqlMap.xml： <BR>&lt;sqlMap&gt; <BR>&lt;typeAlias type=”com.ant.Test” alias=”test”/&gt; </P>
<P>&lt;resultMap class=”test” id=”result”&gt; <BR>&lt;result property=”testId” column=”TestId”/&gt; <BR>&lt;result property=”name” column=”Name”/&gt; <BR>&lt;result property=”date” column=”Date”/&gt; <BR>&lt;/resultMap&gt; </P>
<P>&lt;select id=”getTestById” resultMap=”result” parameterClass=”int”&gt; <BR>select * from Test where TestId=#value# <BR>&lt;/select&gt; </P>
<P>&lt;update id=”updateTest” parameterClass=”test”&gt; <BR>Update Tests set Name=#name#, Date=”date” where TestId=#testId# <BR>&lt;/update&gt; <BR>&lt;/sqlMap&gt; </P>
<P>3． Hibernate <BR>Hibernate无疑是应用最广泛最受欢迎的持久型框架，它生成的SQL语句是非常优秀。虽然一度因为不能支持手工SQL而性能受到局限，但随着新一代 Hibernate 3.x推出，很多缺点都被改进，Hibernate也因此变得更加通用而时尚。同样先看一个Mapping文件的例子customer.hbm.xml来有一个大概印象： </P>
<P>&lt;hibernate-mapping&gt; <BR>&lt;class name=”com.ant.Customer” table=”Customers”&gt; <BR>&lt;id name=”customerId” column=”CustomerId” type=”int” unsaved-value=”0”&gt; <BR>&lt;generator class=”sequence”&gt; <BR>&lt;param name=”sequence”&gt; Customers_CustomerId_Seq &lt;/param&gt; <BR>&lt;/generator&gt; <BR>&lt;/id&gt; </P>
<P>&lt;property name=”firstName” column=”FirstName”/&gt; <BR>&lt;property name=”lastName” column=”LastName”/&gt; </P>
<P>&lt;set name=”addresses” outer-join=”true”&gt; <BR>&lt;key column=”Customer”/&gt; <BR>&lt;one-to-many class=”com.ant.Address”/&gt; <BR>&lt;/set&gt; <BR>&lt;/class&gt;<BR>&lt;/hibernate-mapping&gt; </P>
<P>Hibernate有很多显著的特性，最突出的就是它有自己的查询语言叫做HQL，在HQL中select from的不是Table而是类名，一方面更加面向对象，另外一方面通过在hibernate.cfg.xml中配置Dialect为HQL可以使得整个后台与数据库脱离耦合，因为不管用那种数据库我都是基于HQL来查询，Hibernate框架负责帮我最终转换成特定数据库里的SQL语句。另外 Hibernate在Object-Caching这方面也做得相当出色，它同时管理两个级别的缓存，当数据被第一次取出后，真正使用的时候对象被放在一级缓存管理，这个时候任何改动都会影响到数据库；而空闲时候会把对象放在二级缓存管理，虽然这个时候与数据库字段能对应上但未绑定在一起，改动不会影响到数据库的记录，主要目的是为了在重复读取的时候更快的拿到数据而不用再次请求连接对象。其实关于这种缓存的设计建议大家研究一下Oracle的存储机制（原理是相通的），Oracle牺牲了空间换来时间依赖于很健壮的缓存算法来保证最优的企业级数据库访问速率。 </P>
<P>以上是一些Mapping的例子，真正在Java代码中使用多半是继承各个框架中默认的Dao实现类，然后可以通过Id来查找对象，或者通过 Example来查找，更流行的是更具Criteria查找对象。Criteria是完全封装了SQL条件查询语法的一个工具类，任何一个查询条件都可以在Criteria中找到方法与之对应，这样可以在Java代码级别实现SQL的完全控制。另外，现在许多ORM框架的最新版本随着JDk 5.0加入Annotation特性都开始支持用XDoclet来自动根据Annotation来生成XML配置文件了。 </P>
<P>笔者不可能详细的讲解每一个框架，也许更多的人在用Hibernate，笔者是从OJB开始接触ORM技术的，它很原始却更容易让人理解从JDBC到 ORM的过渡。更多的细节是可以从官方文档和书籍中学到的，但我们应该更加看中它们设计思想的来源和闪光点，不是盲从它们的使用方法。</P>
<P><BR>本文来自CSDN博客，转载请标明出处：<A href="http://blog.csdn.net/hchen2008/archive/2009/04/03/4001147.aspx">http://blog.csdn.net/hchen2008/archive/2009/04/03/4001147.aspx</A></P>]]></description>
</item><item>
<title><![CDATA[java 分包处理]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=44181</link>
<author>seanno13</author>
<pubDate>2009/3/26 9:16:52</pubDate>
<description><![CDATA[
<P><FONT size=2>一般在socket处理大数据量传输的时候会产生粘包和半包问题，有的时候tcp为了提高效率会缓冲N个包后再一起发出去，这个与缓存和网络有关系。接收端接收到正确的后都要给发送端一个应答。不给应答的算超时，发送端将重发。 出现粘包和半包现象,是因为TCP当中,只有流的概念,没有包的概念.。<BR>可以使用UDP协议.这样可以就可以区分每个包了.但是要确保包的丢失处理.为了提到效率,可以考虑写一个滑动窗口进行收发包. <BR>若采用TCP协议进行传输,就要将每个包区分开来.可以有三种方式.因为TCP是面向流的.流只有打开和关闭,你要用一个流传输多个包,那就要向办法区分出每个包. </FONT></P>
<P><FONT size=2>1.可以每次发送同样大小的包,过大的包不予发送,过小的包,后面部分用固定的字符'\0'进行填充。 </FONT></P>
<P><FONT size=2>2. 将流按字符处理,抽出一个字符做转义字符(通常Java用'\'来做转义字符,比如"\n"表示换行).假如就设'\'为转义字符,发送方如果流当中出现'\',就在后面在追加一个'\',如果包结束,则用'\'做包的结束符.这样,在接收方,若读取一个单独的'\'或者流结束,就标示前面的内容构成一个包,如果连续读取两个'\',就将两个'\'用一个'\'进行替换.这样,就可以保证原来包中的信息不变,同时也能区分出每个包了。 </FONT></P>
<P><FONT size=2>3.在发送方发送一个包的时候,先将这个包的长度发送给对方(一般是4个字节表示包长),然后再将包的内容发送过去.接收方先接收4个字节,看看包的长度,然后按照长度来接收包,最好是大端字节序。 </FONT></P>
<P><FONT size=2>以上三种方法,是网络传输中经常用到的方法.后两种很常见.最后一种,在TCP长连接传输中应用最多. <BR>综合以上的说法,就是要在TCP协议以上再封装一层协议,用来做分包的信息交换.<BR>当然,如果TCP不是非要长连接,或者,信息包不是批量传输的情况下.可以一次TCP连接只传输一个包.这种情况下一般,一次TCP完成一次交互,即发送方发送信息包,接收方接收信息包同时发送一个接收方的响应包给发送方,表明接收方收到信息包,还是收到了错误包,或者接收方系统异常没有处理这个包之类的信息. 其实HTTP的交互过程,就是这样的. </FONT></P>
<P><FONT size=2></FONT>&nbsp;</P>
<P><FONT size=2>读取数据使用DataInputStream和DataOutputStream，不要用BufferedReader和PrintStream。使用字节传送方式，使用readFully，<BR>readFully<BR>public final void readFully(byte[] b)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throws IOException参见 DataInput 的 readFully 方法的常规协定。 <BR>从所包含的输入流中读取此操作需要的字节。 </FONT></P>
<P><BR><FONT size=2>指定者：<BR>接口 DataInput 中的 readFully<BR>参数：<BR>b - 存储读取数据的缓冲区。 <BR>抛出： <BR>EOFException - 如果此输入流在读取所有字节之前到达末尾。 <BR>IOException - 如果发生 I/O 错误。<BR>另请参见：<BR>FilterInputStream.in</FONT></P>
<P><FONT size=2>--------------------------------------------------------------------------------</FONT></P>
<P><FONT size=2>readFully<BR>public final void readFully(byte[] b,<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; int off,<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; int len)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throws IOException参见 DataInput 的 readFully 方法的常规协定。 <BR>从所包含的输入流中读取此操作需要的字节。 </FONT></P>
<P><BR><FONT size=2>指定者：<BR>接口 DataInput 中的 readFully<BR>参数：<BR>b - 存储读取数据的缓冲区。<BR>off - 数据的起始偏移量。<BR>len - 要读取的字节数。 <BR>抛出： <BR>EOFException - 如果此输入流在读取所有字节之前到达末尾。 <BR>IOException - 如果发生 I/O 错误。<BR>另请参见：<BR>FilterInputStream.in<BR>&nbsp;<BR>&nbsp;<BR>Java code</FONT></P>
<P><FONT size=2>in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));<BR>out = new DataOutputStream(socket.getOutputStream());</FONT></P>]]></description>
</item><item>
<title><![CDATA[杂谈：我的25年嵌入式生涯]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42725</link>
<author>seanno13</author>
<pubDate>2009/2/3 14:23:00</pubDate>
<description><![CDATA[
<P align=center><FONT size=3><STRONG>杂谈：我的25年嵌入式生涯</STRONG></FONT><BR><EM></EM></P>
<P align=center><EM>文/周立功</EM></P>
<P>《程序员》杂志的编辑约我写一篇命题作文，想了几天都无从下手不知道写什么才好。在这篇文章里，我不打算将创业的艰辛与喜悦重新回忆一遍，我确实不想去误导大家，因为我所处的年代是一个物质缺乏的年代，成功相对来说要容易得多。每个人的成长经历都有其个性化的东西，每个人的成功创业经验虽然有一些可以遵循的普遍规律，但一般来说都很难复制，成功需要不断地创新。<BR>对于今天很多的年轻人来说，一味注重技术至上的观念刻骨铭心，其实很多时候努力并非一定有回报。事实上，有不少出类拔萃的人才往往做出来的产品就是卖不掉，因为设计者压根儿就不了解用户的需求和心理以及产品功能的恰当定义，而总想在同事及其老板面前卖弄自己的技术和产品功能。其实有很多思维性的东西恰恰是很多人所忽视的，因为从一开始的出发点就错了，怎么可能取得辉煌的成绩呢？所以有时拥有卓越的技术并不一定代表人生的成功，很多企业就是死在一些自命不凡的“卓越人才”手中。</P>
<P><STRONG>它山之石可以攻玉，减少“阶段0”的开发</STRONG><BR>有所“创造”确实是人人期待的，令人瞩目的发明虽然激动人心却谈何容易，人们时常将盖茨没有任何爆炸性的发明作为茶余饭后的笑料，我们不妨从另外一个角度去探索微软的成功奥妙从而为我们所用。<BR>确实不假，盖茨的DOS源代码是从帕特森手上“买”来的。这些年来，我收集了世界各地中英文版有关微软的专著来比较研究，大家仅仅注意到盖茨为建立整个行业架构标准的远见，并叹服盖茨深得市场运作经验的精髓，我认为这些研究成果都是后来者研究微软成功经验，是“牵强附会”不可复制的理论总结。<BR>当初微软公司还仅仅是一家很稚嫩的公司，可以说生存下去是盖茨作为老板唯一的使命。事实上，正当盖茨决定动手来写IBM所要的OS时，原计划在一年左右完成，但IBM公司只给了他几个月的时间。尽管帕特森的QDOS并不完善错误百出，但为了履行对IBM的承诺，盖茨购买了QDOS改贴标签后卖给了IBM公司。盖茨对此心知肚明，因为他知道如果用一年的时间来做OS的开发，他将失去与巨人IBM的合作机会，那是一种浪费。付一小部分的技术费用没有关系，只要能获得Know-how，获得更高的利润就好了。通过支付权利金引入技术，然后以模仿的方式学习他人的技术，再改造成符合IBM需要即可。<BR>“买”――只要有现成的就不需要自己开发，这就是盖茨的过人之处，而且恰恰是很多人忽视的地方，值得我们所有人学习和仿效。盖茨就是由于没有“阶段0”，从而大幅度地降低开发风险。<BR>当年，我是一个人单枪匹马借了2.15万元出来创业的，可以说是负资产，后来才开始有伙伴注资6万元。没有经验怎么办？我有一个非常好习惯，读书从不拘泥于他人的观点，也满足于人云亦云，否则那就是听别人讲故事，那不是您的东西，有入宝山空手而归。我认为，要想成功就必须“研究”成功者的轨迹，向成功者学习。<BR>下面我会把我“第一桶金”的故事告诉你，这也是尽量减少“阶段0”开发一个非常典型的例子。<BR>当年，《羊城晚报》几乎每天都有半版广告刊登信息台（听歌、悄悄话等）广告，一打听广告费每天几十万。当时相信不少人在利用公费电话在拨打这些信息台。后来我送货到客户那里发现电话机都外加了一个铁壳并上了一把锁，从看到那一幕开始，我决定做电话加密码锁，锁长途电话0字头，手机、BB机、信息台的9字头。通过朋友介绍，福州某公司有这个技术，于是我打了一个试探性的电话，使用5000元购买方案和源代码，对方非常爽快地同意了。我生怕有变立即坐飞机到福州去，很快就见到了郑新建工程师，他原来在福州某公司工作，我见到他的时候已经离开了福州某公司，但仍然自己写一些产品解决方案，通过福州某公司销售。买到方案与源代码之后，我立即带样机到各地去做测试，发现这台样机兼容性太差，而且市场已经开始在卖的产品也存在同样的问题，我想只要解决好兼容性的问题，如果在任何地区交换机局域网都能够使用，那就是最好的产品不愁没有市场。<BR>我当时既没有资金也没有生产和销售经验，到底怎么将技术变成市场上所需要的产品呢？那是因为我找到了生产电话分线器的广州市白云区百新电器厂的老板陈国亮，由他生产和销售，每销售一台产品给我提成。以这个项目为起点，我们使用PIC单片机的数量很快就达到了每月100K以上，一年以后Microchip香港公司经理Andi主动找上门来将当时的广州强力电子研究所发展成为了授权分销商。<BR>毫不否认盖茨的远见和判断力是微软至关重要的核心竞争力，但盖茨也绝非天才，否则盖茨就不会在浏览器上输给网景了，尽管微软使用的很多技术都不是微软所发明的，但毫不妨碍微软独步天下。</P>
<P><STRONG>注重核心技术，其余的外包</STRONG><BR>通过创业以来第一个项目的成功，我深深地体会到商业模式比技术本身更重要。<BR>通过十多年的努力，我们设计的“铁将军（Steel Mate）”品牌汽车防盗器、汽车倒车雷达不仅成为了全世界范围的“隐性冠军”，而且为GE等世界著名汽车厂商售前市场提供配套产品，与此同时我们为智能卡酒店门锁厂商设计与制造的各种智能卡门锁控制模块在国内也一直处于第一名的地位。<BR>他们为何能够取得与众不同的成功呢？其最大的长处就是产品创意与制造、简约时尚风格的工业设计、模具制造技术以及精心打造的销售渠道，这些厂商深刻明白“注重核心技术做自己擅长的，其余的外包分段取利”的基本道理，而对于我来说就是“智慧出租”，通过与强者合作获得双赢。<BR>通过这些成功经验的积累，我们开始全力以赴注重发展核心技术，将自己不擅长的技术全部外包。比如，我们长期投资清华大学计算机系，并建立以陈渝博士后为主Linux内核开发团队，为公司底层的技术提供良好支撑，而我们自己则将精力集中于Linux驱动开发。这种分工合作、协同开发的模式赢得市场上宝贵的时间，并快速取得了应用成果。<BR>也许有程序员会关心JTAG开发工具问题，那也是一个“外包”的项目。当时，我们在网上发现了一个由计算机爱好者业余时间设计的H-JTAG，这是一个比较稳定的调试器软件，正好适合我们使用。于是我就同设计者联系，并决定由周立功单片机资助他继续开发，同时，其开发成果还免费提供给网上的用户下载，保证它作为一个自由软件。其实，无论是Linux还是H-JTAG都是开源软件，如果担心其它的同行因此受益而超越自己的话，难免需要在公司负担很多开发人员，面面俱到地做许多事实上是重复的开发工作。其实，我们的合作伙伴都是所在领域的专家，如果自己从头到尾去学习和开发，不仅抢了合作伙伴的饭碗，而且结果未必理想。这种情况下不但会延误宝贵的商机，而且还会影响与合作伙伴之间的关系。其实只要引入我们企业长期积累和制定的嵌入式系统工程管理思想和规范，即可得到自己想要的结果：“做你最擅长的，其余的外包”，类似这样的案例在周立功单片机将会越来越多。</P>
<P><STRONG>专注与差异化生存</STRONG><BR>1999年1月23日我离开了原公司，先后分别创立了广州周立功单片机发展有限公司与广州致远电子有限公司，分别从事贸易业务与产品制造。<BR>作为代理商通过为客户提供解决方案从而达到大批量销售芯片是代理行业惯常使用的手段，而实际上通过设计能够真正达到增值获得丰厚回报的代理商却少之又少。要知道过去公司的人才数量和资源是非常宝贵的，如果没有正确的战略，机会可能稍纵即逝。事实上创业开始的前几年，我们一共为用户设计了超过100个品类的产品，产品型号超过500种，可以说几乎涉及到了各行各业，但能够带来稳定而丰厚回报的案例几乎没有。我经过一段时间的思考和报表分析发现，目前公司的业务做得太多、太杂，没有将任何一个行业做精。于是我下定决心减少用户数量集中精力在1－2个行业做强、做大，直至成为顶尖的行业专家，坚持十年如一日重点做好汽车配套产品与智能卡门锁控制模块的开发。通过多年的精耕细作，我们通过为直接用户设计产品增值年销售单片机的数量已经超过了1000万片，无论是对用户还是对半导体公司来说，我们无疑是一家非常有价值的代理商。<BR>1999年5月，PHILIPS半导体公司邀请我去上海洽谈是否有意向代理LPC700单片机，当我拿到用户手册时离去上海的时间只有5天。当时公司不像现在有250位大学生，总共只有8个人，怎么办？我一边写商业计划书，一边与大家翻译用户手册、打字和贴图，面对机会就像抓住救命稻草一样加班加点，每天只休息3－5个小时。第5天我赶到上海东亚富豪酒店时，我将整整齐齐的材料放在了来自美国PHILIPS半导体公司2位经理的面前，包括吃饭在内仅仅只洽谈了2个小时，代理权就正式谈下来了。事后他们告诉我，就是因为我与众不同，专注且非常有个性。<BR>风雨变换诱惑莫测，人怕出名猪怕壮，这是常理！随后找上门来要求我们代理芯片的著名半导体公司不下10家。这时候，对于很多人来说不做什么就是一个艰难的选择。此时，我认为不管对方开出的条件多好，有冲突的就是不做，性能不突出的也不做，如果能够形成价值链互补，就一定要通过自己的努力和业绩表现主动去“求”半导体公司授权给我们，我就是这样取得了Keil、Catalyst、Sipex的代理权和信用额度。2004年SHARP半导体公司北京、日本、美国一共五位经理来到我的办公室要求我代理SHARP的ARM，帮助SHARP在中国推广以及制造用于全球销售的开发套件，我根本没有思考就一口回绝了，可以说SHARP给我的条件非常之好，特别是美国人觉得是非常不可思议的事情，作为商人这么好的赚钱机会都不要。为什么？我们之所以暂时“强大”，就是因为我们长期以来专注于发展PHILIPS半导体，集中精力对准焦点成为了小河里的大鱼，进而才引起了众人的关注。如果我们分散了精力，最后的结果一定会什么都不是！<BR>当决定专注的目标之后，接下来的就是想办法如何做得与众不同，这是一件不容易的事情。最重要的就是穷举用户最大的需求和竞争者最容易忽略之处，然后将其做到最好。<BR>为了推广好PHILIPS的单片机和ARM，我们定位于帮助初学者快速入门，至今一共编写出版了20多本专著，并将大部分版权全部捐献给了出版社用于降低成本，同时我也还会继续坚持将这项工作做下去、争取做得更好。邓小平有一句名言：“计算机要从娃娃抓起！”我认为，任何一个有远见的厂商，要想成功地推广嵌入式系统，一定要从在校大学生抓起，这必定是一件一举多得的好事。既能够提高企业的美誉度，又能获得市场占有率，还能为学生提供实践的理论基础。为何很少有厂商或者代理商心甘情愿去做？这不仅可帮助企业建立差异化，而且还能够给企业带来长远的利益，何乐而不为？<BR>比如说，至今在国内任何一家代理商的网站上几乎都找不到完整的芯片中文数据手册、用户手册与应用手册，不是我们的同行不知道其中的重要性。首先大家普遍认为这是半导体公司的份内之事，其次大家或许感到自己做好之后放到网站任由用户免费下载，会给其它代理商占了便宜。但很多人却始终不明白，这是满足用户需求制造公司服务差异化的最佳机会。<BR>于是我们决心从网站入手坚持长期投资，做中国最好的嵌入式系统专业技术网站，建立专业的技术支持与开发团队。特别是对于优质用户的服务，网站还专门开通了“快速绿色通道”，如果这样的用户遇到问题，都是由我、分公司经理及其相关服务部门的经理牵头负责实施“保姆式”的服务，我们实施的服务战略就是：“用有限的资源为有效的用户提供高质量的服务”。<BR>“你若亲近神，神就亲近你！” 2001年8月一篇来自PHILIPS内部刊物的报道《风险意味着机会》，我是被推上了“本地英雄”封底人物栏目的第一位亚洲代理商；2004年PHILIPS半导体公司又给我授予了“优秀卓越贡献奖”，来自全国各地的用户连续多年通过《亚洲资源》媒体将我们公司评选为“本地十大最受客户欢迎的分销商”。没有用户崇高的忠诚度和长期的大力支持，我们不可能从1999年以来连续多年销售单片机取得PHILIPS亚太区第一名的业绩。<BR>一个“人弃我取”的机会成就了我，尽管个人与团队的努力至关重要，但可以毫不掩饰地说，如果没有PHILIPS半导体经理的慧眼相马，一定是巧妇难为无米之炊，业界不会再有我的一席之地。<BR>在嵌入式系统应用技术方面与我们不相伯仲或者说比我们聪明能干的人才何止万千，为何只有少数人能够脱颖而出呢？就是因为想法太多、不专注，不能坚持在一个行业里面十年如一日地下苦功夫。但面对众多的竞争者和后来者，我每天都如履薄冰，不敢有丝毫的懈怠，每天坚持学习到深夜，因为唯有不断地学习和更新思维才有可能具备远见并及时规避可能出现的失误。</P>
<P><STRONG>关注用户的需求</STRONG><BR>其实维系企业生命的关键在于产品是否能够获得市场的青睐，我们要做的并非是成为技术的领先者，而是要将技术成果转化为迎合消费者需要的商品。<BR>八十年代是一个物质缺乏的年代，而今天的人们更加注重消费享受和情感的分享，这是任何人都无法否认的现实。我们公司刚开始做产品时，由于我个人偏爱蓝色，所以我凭着老板的权威将所有产品的外观全部都做成蓝色，其实当时也有不少的人向我提出改进意见，但我仍然还是无动于衷，为什么？就是因为我是做技术出身的，而且从创业初期开始一直都是依靠技术一路拼杀过来的，可以说过于迷信技术的力量，因此在很长的一段时间内，公司完全走的是研究的路子。而我却恰恰忽视了用户读产品的心理需求，以至于在产品推向市场之后一直都无法打开局面。<BR>当时，EasyPRO系列编程器面市的时候，其销售数量一年下来都不到500套，同时有用户表示对于产品的设计很不满意，这可以说是一次深刻的教训。于是我下定决心组建工业设计事业部，首先从EasyPRO系列通用编程器开始，亲自管理、研究和参与产品的外观设计，将原来的模具全部废掉重新设计。尽管是第一次设计，在产品的外观上没有做出非常杰出的效果，但无论如何，新产品外观比原有的产品出众得多。2005年10个月的时间里，EasyPRO系列通用编程器的销售数量达到了6000套。2006年我们又推出了SmartPRO系列智能型通用编程器，预计本年度销售编程器的总数量将超过10000套，而这些产品将带着我和我们公司跻身于国内编程器市场主流品牌之列。<BR>从那次深刻的教训以后，我明白好的外观设计可以凸现产品的魅力，让产品与环境融为一体，刺激消费者产生强烈的购买欲望。即使是普通产品，经过精心设计，也会成为一件时尚产品，甚至让人们觉得它就是一件艺术品，从这一点来说，时尚、新潮、艺术化的外观设计是塑造产品品牌形象的第一要素。<BR>从那以后，我开始改变产品简介、广告以及展示会的形象设计，为了配合新的形象，我们展开了大规模、全方位的宣传：一方面尽量多地为用户提供产品简介手册，同时通过大量的媒体广告，让用户认识并初步了解公司，从而迅速有效地提高了品牌知名度；另一方面组织各种全国巡回演讲、免费ARM技术培训活动和产品展示会，而且我们每次参加展示会都是投入4个摊位并进行特装展位设计，目的就是为了获得与用户积极沟通的机会。<BR>2003年暑假期间，周立功单片机投资80万元邀请了行业3位著名的嵌入式系统专家组成讲师团在全国10个城市主办了免费的ARM巡回演讲，提供了免费的午餐，可以说规模空前。这次巡讲中，共有8000多人参加了这次活动，随后公司趁热打铁推出了售价400元的EasyARM2104开发学习套件，第一个月生产的1000套在15天之内抢购一空，直到销售了15000套之后才因新产品的推出而停产。这次活动不仅收回了80万元的宣传费用，而且还直接或间接地影响了超过50万嵌入式相关人员，强有力地达到了品牌宣传的目的。在赢得丰厚利润回报的同时，也打造了嵌入式行业一次非常成功的“事件营销”经典案例，我深深地体会到“俘虏”用户的心是提升品牌的唯一途径。</P>
<P><STRONG>全球化市场意识</STRONG><BR>过去，一个公司可能需要等到10年之后才开始考虑是否全球化，而今天由于网络技术的发展，无论我们身处何地，世界各地的新信息仿佛就在眼前，谁也不会被边缘化了，所以今天成立公司明天就要准备全球销售。<BR>这几年ARM已经到了热得不得了的程度，很多人不以为然，其实这是一种必然趋势。事实上，我在美国PHILIPS半导体公司看到，美国1997年出版的《嵌入式系统》杂志就已经大量刊登OS与32位嵌入式系统开发工具等文章和广告。在美国硅谷主办的嵌入式系统展示会上，来自欧美的上千展位展现在我眼前的是各种各样嵌入式系统产品，中国厂商普遍展示的是蜂鸣器、继电器等低档元器件。我第一次身临其境地感受到了中美之间的差距究竟有多大。中国太需要懂得国际市场需求和游戏规则的人才和企业家了。<BR>同样是做USB分析仪，全球只有5家，我们自己的USB分析仪升级版在中国目前定价为3800元，这款产品原来的售价只有1800元，用户还认为太贵了。其实这样的产品在国内一年的销售数量也就200台，但是同样的情况在美国，一个小公司一年的销售额就能够到2000万美元，他们的单台最低售价不低于10000美元，以至于美国同行直接给我来电话，希望我在售价的基础上至少加一个0。<BR>中国企业不做全球化销售能行吗？不行！而且中国企业必须向价值链顶端攀升，否则我们只能做欧美消费品市场的廉价加工基地。</P>
<P><STRONG>个性化的企业文化，帮助他人走向成功</STRONG><BR>通过与用户合作所经历的共同成功过程中，用户将我教育成为了一个善良、上进、诚实、优秀的人才。我不仅懂得如何做自己最擅长事情的方式方法，而且也懂得了如何与用户、与员工分享成功的道理。一旦芯片销售数量增大成本下降就主动给用户降价，赢得了用户更长远的支持；公司效益提高了，年终就主动给员工涨工资、加奖金并对优秀员工提供购房津贴。由于公司的规模不大，我们一直以招聘二类本科应届生为主，在内部采取导师制的培养模式，为他们提供至少一年的严格训练，帮助他们规划职业生涯，想尽办法提供机会将他们培养成优秀的人才。这些年来不少公司打电话向我们公司挖人，关键人才从未出现过流失，至今跳槽的员工不超过12人，我深深地感到这就是我人生最大的快乐和财富，离开了支持我的用户和追随我的人才，我将变得一钱不值。<BR>为了保证企业的可持续发展所带来的人才困境，我与江西理工大学合作以五年为期开办了“3＋1”嵌入式系统应用开发特训班，与此同时还设立了每人3000元共10人的“周立功奖学金”资助那些动手能力极强而成绩中等的学生，开展校企无缝联接，培养嵌入式系统创新性开发人才的有益尝试，即就是从7个班中选拔30位爱好者用3年的时间修完所有课程，我们为每一位学生提供一台计算机、全套开发工具及其设计中所发生的一切费用，由我们组织著名专家开设专题讲座和课程，学校组织1位专职辅导员和2位指导教师，并由我们提供必要的津贴，然后用一年的时间专门做毕业设计，为此我们制定了严谨、细致的培训计划。<BR>这些年我们还不断赞助各省电子大赛、毕业设计大赛、创新设计大赛，捐赠了50所大学单片机创新实验室，以求扩大企业的知名度和广泛寻求人才的来源。</P>
<P><STRONG>最后的话</STRONG><BR>很多开发工程师以为这些都是老板或者经理们应该考虑的问题，与己无关，其实是大错而特错。作为一位优秀而卓越的开发人员，如果希望获得人生的完全成功和快乐，我们时刻需要明白，任何一个企业的可持续发展一定是群策群力的结果。<BR>1981年我经历了高考失败之后上了技校，1999年我作为劳动模范被保送上了大学，创业至今十多年来，往事依然历历在目，用户给予我的回报很多，很难在此一一完全道来，谨以此文与同行、用户分享和交流。<BR>这些年来，我陆续捐资资助教育、设立奖学金以及帮助家乡修建乡村水泥公路、祠堂等公益事业回馈社会。1990年的中秋节，我只身一人南下广东打工，不曾想到会有今天这样的成绩，我一直非常珍惜这来之不易的机会。<BR>作为一个嵌入式技术爱好者，我的愿望就是：“生命不息、奋斗不止！”力争为发展中国的嵌入式系统应用技术贡献自己的一份力量。</P>]]></description>
</item><item>
<title><![CDATA[showModalDialog模式窗口提交打开新页面的解决办法]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42551</link>
<author>seanno13</author>
<pubDate>2009/1/20 16:49:29</pubDate>
<description><![CDATA[
<H3 class=type_reprint title=转载><A name=baidusnap0></A><A name=baidusnap1></A><A href="http://cache.baidu.com/blog/225034"><B style="COLOR: black; BACKGROUND-COLOR: #ffff66">showModalDialog</B>模式窗口<B style="COLOR: black; BACKGROUND-COLOR: #a0ffff">提交</B>打开新页面的解决办法</A></H3><STRONG>关键字: <B style="COLOR: black; BACKGROUND-COLOR: #ffff66">showmodaldialog</B>模式窗口<B style="COLOR: black; BACKGROUND-COLOR: #a0ffff">提交</B>打开新页面的解决办法</STRONG> 
<DIV class=blog_content>
<P>1.在head区加入&lt;base target="_self"/&gt; <BR>2.</P>
<P>window.<B style="COLOR: black; BACKGROUND-COLOR: #ffff66">showModalDialog</B>("xxx.htm") &nbsp; <BR>&nbsp; &nbsp; <BR>&nbsp; xx.htm &nbsp; <BR>&nbsp; ====================&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp; &lt;frameset &nbsp; rows="0,*"&gt; &nbsp; <BR>&nbsp; &lt;frame &nbsp; src="about:blank"&gt; &nbsp; <BR>&nbsp; &lt;frame &nbsp; src="xxx.asp"&gt; &nbsp; <BR>&nbsp; &lt;/frameset&gt; &nbsp; <BR>&nbsp; &nbsp; <BR>&nbsp; xxx.asp &nbsp; <BR>&nbsp; ========= &nbsp; <BR>&nbsp; &lt;form &nbsp; target="_self" &nbsp; method=post&gt;</P>
<P>&nbsp;</P>
<P>或者 </P>
<P>&lt;script language="javascript"&gt;&nbsp;&nbsp;<BR><BR>window.name="win_test" <BR>&lt;/script&gt; <BR>&lt;form action=""&nbsp;&nbsp;target="win_test"&gt; <BR>&lt;/form&gt; </P>
<P>&nbsp;</P>
<P>&lt;head&gt;之间加入<BR>&lt;BASE target=_self&gt;</P></DIV>]]></description>
</item><item>
<title><![CDATA[Windows CE下驱动开发基础]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42447</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:38:09</pubDate>
<description><![CDATA[
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>Windows CE下驱动开发基础</B><BR><BR>作者：<A href="mailto:windowsce@tom.com">付林林</A></P>
<P><SPAN lang=zh-cn>　　</SPAN>这是我从1月6日开始主持天极网论坛嵌入式开发版以来第一次发表文章，加上以前琐碎的文章共计30篇。研究的越多就越感觉自己懂的太少，其实在驱动开发方面我还是个菜鸟，我是想再次抛砖引玉，让做驱动有N年经验的人奉献一点出来，让大家减少一些研究驱动源码而又缺少注释所带来的痛苦。<BR><SPAN lang=zh-cn>　　</SPAN>我想即使读者看过微软的关于驱动开发的培训教材和CE帮助文档中的驱动部分，头脑中仍然一片茫然。要想真正了解驱动程序必须结合一些驱动程序源码，在此我以串口驱动程序（COM16550）中初始化过程为线索简单讲一讲驱动开发的基础知识。<BR><SPAN lang=zh-cn>　　</SPAN>Windows CE下的串口驱动程序能够处理所有I/O行为类似串口的设备，包括基于16450、16550 UART（通用异步收发芯片）的设备和一些采用DMA的设备，常见的有9针串口、红外I/O口、Modem等。在%_WINCEROOT%\Public\Common\OAK\Drivers\Serial目录下，COM_MDD2子目录包含新的串口驱动MDD层函数代码。COM16550子目录包含串口驱动PDD层代码。SER16550子目录包含的一系列函数专用于控制与16550兼容的UART，这样PDD层的主要工作就是调用SER16550中的函数。还有一个ISR16550子目录包含的是串口驱动程序专用的可安装ISR（中断服务例程），而很多硬件设备驱动程序采用CE默认的可安装ISR giisr.dll。一般串口设备相应的注册表设置例子及意义如下：</P>
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="90%" border=1>
<TBODY>
<TR>
<TD align=middle width="40%"><B>键</B></TD>
<TD align=middle width="60%"><B>意义</B></TD></TR>
<TR>
<TD width="40%">"SysIntr"=dword:13</TD>
<TD width="60%">串口1的中断ID为十进制13</TD></TR>
<TR>
<TD width="40%">"IoBase"=dword:02F8</TD>
<TD width="60%">串口1的IO空间首地址为十六进制2F8</TD></TR>
<TR>
<TD width="40%">"IoLen"=dword:8</TD>
<TD width="60%">串口1的IO空间长度为8个字节</TD></TR>
<TR>
<TD width="40%">"DeviceArrayIndex"=dword:0</TD>
<TD width="60%">串口1的索引，是1的由来</TD></TR>
<TR>
<TD width="40%">"Order"=dword:0</TD>
<TD width="60%">串口1驱动的加载顺序</TD></TR>
<TR>
<TD width="40%">"DeviceType"=dword:0</TD>
<TD width="60%">串口1的设备类型</TD></TR>
<TR>
<TD width="40%">"DevConfig"=hex: 10,00 ....</TD>
<TD width="60%">串口1在与Modem设备通讯时的配置，如波特率、奇偶校检等</TD></TR>
<TR>
<TD width="40%">"FriendlyName"="COM1:"</TD>
<TD width="60%">串口1在拨号程序中显示的名字</TD></TR>
<TR>
<TD width="40%">"Tsp"="Unimodem.dll"</TD>
<TD width="60%">串口1 被用于与Modem设备通讯的时候要加载的TSP（TAPI Service provider）DLL</TD></TR>
<TR>
<TD width="40%">"Prefix"="COM"</TD>
<TD width="60%">串口1的流接口的前缀</TD></TR>
<TR>
<TD width="40%">"Dll"="com16550.Dll"</TD>
<TD width="60%">串口1的驱动程序DLL</TD></TR></TBODY></TABLE>
<P><SPAN lang=zh-cn>　　</SPAN>SysIntr由CE在文件Nkintr.h中预定义，用于唯一标识中断设备。OEM可以在文件Oalintr.h中定义自己的SysIntr。常见的预定义SysIntr有SYSINTR_NOP（中断只由ISR处理，IST不再处理），SYSINTR_RESCHED（重新调度线程），SYSINTR_DEVICES（由CE预定义的设备中断ID的基值），SYSINTR_PROFILE、SYSINTR_TIMING、SYSINTR_FIRMWARE等都是基于SYSINTR_DEVICES定义的。IoBase是串口1的IO地址空间的首地址，IoLen是IO空间的大小。IO地址空间只存在于x86平台，如果在其它平台硬件寄存器必须映射到物理地址空间，那子键的名称为MemBase和MemLen。在x86平台更多硬件的寄存器由于IO空间的局限也映射到物理地址空间。DeviceArrayIndex是设备的索引，用于区分同类型的设备。Prefix是流驱动程序的前缀，当应用程序调用CreateFile函数传递COM1:参数时，文件系统负责与串口驱动程序通信，串口驱动程序是在CE启动时由device.exe加载的。<BR><BR><SPAN lang=zh-cn>　　</SPAN>下面从MDD层函数COM_Init开始探索串口驱动的初始化过程。COM_Init是在串口设备被检测后由设备管理器device.exe调用的，主要的作用是初始化设备，它的唯一参数Identifier是由device.exe传递的，其类型是一个字符串指针，字符串的内容是HLM\Drivers\Active\xx，xx是一个十进制数（device.exe会跟踪系统中每个驱动程序，把加载的驱动程序记录在Active键下）。COM_Init先分配一个HW_INDEP_INFO结构体，这个结构体是独立于串口硬件的头信息（MDD、PDD、SER16550都包含自己独特的结构体，具体的结构体定义请参见串口驱动源码），分配之后再初始化结构体中每个成员，初始化结构体后调用 OpenDeviceKey((LPCTSTR)Identifier)打开HLM\Drivers\Active\xx\Key包含的注册表路径，在这里路径一般为HLM\Drivers\BuiltIn\Serial，即串口的驱动程序信息在注册表中所处的位置。COM_Init接着在HLM\Drivers\BuiltIn\Serial下查询DeviceArrayIndex、Priority256的值，Priority256指定了驱动程序的优先级，如果没有就用默认的优先级。接下来调用GetSerialObject(DeviceArrayIndex)，这个函数由PDD层定义，返回HWOBJ结构体，这个结构体主要包含PDD层和SER16550定义的函数的指针。也就是说MDD通过调用这个函数才能调用底层实现的函数。接下来的大多数工作都是调用底层函数实现初始化。第一个调用的底层函数SerInit主要设置由用户设置的硬件配置，例如线路控制、波特率。它调用Ser_GetRegistryData函数得到保存在注册表中的硬件信息，Ser_GetRegistryData在内部调用系统提供的DDKReg_GetIsrInfoDDK和DDKReg_GetWindowInfo函数得到在HLM\Drivers\BuiltIn\Serial下保存的IRQ、SysIntr、IsrDll、IsrHandler、IoBase、IoLen。IRQ是逻辑中断号，IsrDll表示当前驱动程序的可安装ISR所在的DLL名称，IsrHandler 表示可安装ISR的函数名称。在这里顺便提一下可安装ISR，读者在我以前发表的关于OAL的文章中可以了解到OEM在OEMInit函数中关联IRQ和SysIntr，当硬件设备发生中断时，ISR会禁止同级和低级中断，然后根据IRQ返回关联的SysIntr，内核根据ISR返回的SysIntr唤醒相应的IST（SysIntr与IST创建的Event关联），IST处理中断之后调用InterruptDone解除中断禁止。在OEMInit中关联的缺点是一旦编译了CE内核后就无法添加这种关联了，而一些硬件设备会随时插拔或者共享中断，要关联这样的硬件设备解决方法就是可安装ISR，可安装ISR专用于处理指定的硬件设备发出的中断，所以如果硬件设备需要可安装ISR必须在注册表中添加IsrDll、IsrHandler。多数硬件设备采用CE默认的可安装ISR giisr.dll，格式如下：</P><PRE> "IsrDll"="giisr.dll"
"IsrHandler"="ISRHandler"</PRE>
<P><SPAN lang=zh-cn>　　</SPAN>如果一个硬件驱动程序需要可安装ISR而开发者又不想自己写一个，那么可以利用giisr.dll来实现。除了在注册表中添加如上所示外，还要在驱动程序中调用相关函数注册可安装ISR。伪代码如下：</P><PRE>g_IsrHandle = LoadIntChainHandler(IsrDll, IsrHandler, (BYTE)Irq);
GIISR_INFO Info;
PHYSICAL_ADDRESS PortAddress = {PhysAddr, 0};
TransBusAddrToStatic(BusType, dwBusNumber, PortAddress, dwAddrLen, &amp;dwIOSpace, &amp;(PVOID)PhysAddr)
Info.SysIntr = dwSysIntr;
Info.CheckPort = TRUE;
Info.PortIsIO = (dwIOSpace) ? TRUE : FALSE;
Info.UseMaskReg = TRUE;
Info.PortAddr = PhysAddr + 0x0C;
Info.PortSize = sizeof(DWORD);
Info.MaskAddr = PhysAddr + 0x10;
KernelLibIoControl(g_IsrHandle, IOCTL_GIISR_INFO, &amp;Info, sizeof(Info), NULL, 0, NULL);</PRE>
<P><SPAN lang=zh-cn>　　</SPAN>LoadIntChainHandler函数负责注册可安装ISR，参数1为DLL名称，参数2为ISR函数名称，参数3为IRQ。TransBusAddrToStatic函数在后面讲。如果要利用giisr.dll作为可安装ISR，必须先填充GIISR_INFO结构体，CheckPort=TRUE表示giisr要检测指定的寄存器来确定当前发出中断的是否是这个设备。PortIsIO表示寄存器地址属于哪个地址空间，FALSE表示是内定空间，TRUE表示IO空间。UseMaskReg=TRUE表示设备有一个掩码寄存器，专用于指定当前设备是否是中断源，也就是发出中断，而MaskAddr表示掩码寄存器的地址。如果对Info.Mask赋值，那么PortAddr表示一个特殊的寄存器地址，这个寄存器的值与Mask的值&amp;运算的结果如果为真，则证明当前设备是中断源，否则返回SYSINTR_CHAIN（表示当前ISR没有处理中断，内核将调用ISR链中下一个ISR），如果UseMaskReg=TRUE，那么MaskReg寄存器的值与PortAddr指定的寄存器的值&amp;运算的结果如果为真，则证明当前设备是中断源。<BR><SPAN lang=zh-cn>　　</SPAN>函数SerInit接着调用函数Ser_InternalMapRegisterAddresses转换IO地址并且映射地址，Ser_InternalMapRegisterAddresses在内部调用系统提供的HalTranslateBusAddress(Isa, 0, ioPhysicalBase, &amp;inIoSpace, &amp;ioPhysicalBase)函数将与总线相关的地址转换为系统地址，参数1为总线类型，参数2为总线号，参数3为要转换的地址（PHYSICAL_ADDRESS类型，实际是LARGE_INTEGER型），参数4指定寄存器地址属于IO地址空间还是物理地址空间，参数5返回转换后的物理地址。观察HalTranslateBusAddress的源码得知如果是在x86平台，这个函数除了把参数3赋给了参数5其余什么都没有做，而非x86平台将inIoSpace的值置为0，表示一定是物理地址。在调用HalTranslateBusAddress前要确定从注册表中得到的寄存器地址到底是属于哪个地址空间的，例如：</P><PRE>ULONG inIoSpace = 1; ///1表示是IO空间
PHYSICAL_ADDRESS ioPhysicalBase = {iobase, 0}; ///相当于ioPhysicalBase.LowPart = iobase</PRE>
<P><SPAN lang=zh-cn>　　</SPAN>在地址转换后就要将转换后的地址映射到驱动程序（一般IST和应用程序一样运行在用户模式）能够访问的虚拟地址空间（0x80000000以下）和ISR能够访问的静态虚拟地址空间中（0x80000000以上）。例如：</P><PRE>////如果地址属于物理地址空间
ioPortBase = (PUCHAR)MmMapIoSpace(ioPhysicalBase, Size, FALSE);
TransBusAddrToStatic(Isa, 0, ioPhysicalBase, Size, &amp;inIoSpace, ppStaticAddress);</PRE>
<P><SPAN lang=zh-cn>　　</SPAN>MmMapIoSpace函数负责将物理地址映射到驱动程序能够访问的虚拟地址空间中，通过源码分析MmMapIoSpace在内部分别调用：</P><PRE>pVirtualAddress =VirtualAlloc(0, SourceSize, MEM_RESERVE, PAGE_NOACCESS);
VirtualCopy(pVirtualAddress, (PVOID)(SourcePhys &gt;&gt; 8), SourceSize, PAGE_PHYSICAL | 
PAGE_READWRITE | (CacheEnable ? 0 : PAGE_NOCACHE));</PRE>
<P><SPAN lang=zh-cn>　　</SPAN>VirtualAlloc分配一块和MemLen一样大小的虚拟地址空间，因为参数1为0，所以内核自动分配。一般MemLen小于2MB，所以会在应用程序的地址空间中分配。VirtualCopy负责将硬件设备寄存器的物理地址与VirtualAlloc分配的虚拟地址做一个映射关系，这样驱动程序访问PvirtualAddress实际上就是访问第一个寄存器。因为硬件设备寄存器的物理地址一定是在512MB（CE支持RAM的最大值）以上，所以除了最后的参数要加PAGE_PHYSICAL外，第二个参数物理地址也要右移8位（或者除以256）。映射硬件寄存器当然PAGE_NOCACHE是必须加的。TransBusAddrToStatic函数负责将物理地址映射到ISR能够访问的静态虚拟地址空间中，当出现中断共享时，ISR要负责访问硬件设备的某一个寄存器来判断中断源，所以将寄存器的物理地址映射到静态虚拟地址空间中是必要的（ISR只能访问静态的虚拟地址空间）。所谓静态虚拟地址空间是指在OEMAddressTable中定义的虚拟地址空间（当然是0x80000000以上）。在x86平台一般这个表只定义RAM的物理地址与虚拟地址对应关系，而硬件设备的寄存器地址并不在该表中定义，所以如果要创建一块静态的虚拟地址空间供ISR访问，必须在此之前调用CreateStaticMapping函数在0xC4000000到0xE0000000虚拟地址空间中分配。TransBusAddrToStatic函数在内部就是调用了CreateStaticMapping函数。注：硬件设备的寄存器地址也可以在OEMAddressTable中定义。</P><PRE>////如果地址属于IO空间
ioPortBase = (PUCHAR)ioPhysicalBase.LowPart;
*ppStaticAddress=ioPortBase</PRE>
<P>这种情况只属于x86平台，是IO空间就可以直接访问，即使是用户模式。<BR><SPAN lang=zh-cn>　　</SPAN>SerInit函数接着初始化SER_INFO结构体成员，之后调用SL_Init函数，这个函数在ser16550中定义，负责初始化SER16550_INFO结构体，在这个结构体中保存串口8个寄存器的地址。SerInit函数执行完毕后COM_Init函数创建接收缓冲区，然后调用StartDispatchThread函数初始化中断并且创建IST。StartDispatchThread函数在内部调用InterruptInitialize函数关联SysIntr和Event，然后调用InterruptDone函数告诉内核当前串口可以中断处理，接着调用CreateThread函数创建IST线程。（over吧，再往下说就和串口硬件有关了，看多了没注释的代码我也烦！！）<BR><BR>写作时间：2005-03-25<BR><BR>如果您有技术问题向我咨询请登录<A href="http://club.yesky.com/bbs/jsp/list.jsp?forumID=378" target=_blank>天极网论坛嵌入式开发版</A><BR><BR>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至windowsce@tom.com或fllsoft@sina.com</P></TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231900670468&amp;lmt=1231900670&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231900670468&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1427&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Ffllsoft%2F&amp;frm=0&amp;ga_vid=628809780.1231900670&amp;ga_sid=1231900670&amp;ga_hid=769383552&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=16" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1427" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1427" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1427&amp;title=Windows+CE%cf%c2%c7%fd%b6%af%bf%aa%b7%a2%bb%f9%b4%a1" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 写的太好了<BR>看完你的这篇文章，再去看串口代码，一目了然 ( daemon2005 发表于 2005-5-30 13:15:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 写得非常精彩，能画张图表示一下就好了，俺也真正琢磨COM口驱动，真的是很经典。 ( liu_sir 发表于 2005-5-7 10:53:00)<BR>&nbsp;<BR>.......................................................<BR><A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1427" target=_blank><SPAN class=small>More...</SPAN></A> </TD></TR></TBODY></TABLE></DIV><BR>
<DIV align=right><BR><SPAN class=small>版权所有 &copy; 1999 - 2008 VC知识库&nbsp; <BR></SPAN></DIV>]]></description>
</item><item>
<title><![CDATA[在 Windows CE 下申请大容量物理内存]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42446</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:37:03</pubDate>
<description><![CDATA[
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>在 Windows CE 下申请大容量物理内存<BR></B><BR>作者：<A href="mailto:windowsce@tom.com">付林林</A></P>
<P><BR>　　申请大容量的物理内存看起来不是难事。这里的大容量是指几十MB甚至更多的物理内存。对于C++程序员来说可能平时习惯了使用"new"操作符来实现。我也是这样。使用"new"非常简单，申请之后只需判断返回的指针是否是空即可。在其它的Windows操作系统上的确不需要在申请大容量物理内存上过多考虑。但是在Windows CE上就不同了。如果只用"new"就能搞定，那就太省事了。<BR>　　不知道Windows CE下软件开发者是否遇到过这种情况，如果使用"new"申请超过30MB的物理内存，那么返回的一定是空（NULL），甚至程序会死锁无法响应。这其实不奇怪。在《Windows CE下进程、线程和内存管理》的系列文章中我早有所言，Windows CE下每个进程占有32MB的地址空间，虽然Slot 1槽存放所有的非XIP DLL，但是我们不可能占用Slot 1槽。32MB地址空间减去必要的代码段、静态数据段、默认堆和默认栈之后，所剩的地址空间少于32MB。即使程序什么都不做也无法满足超过30MB的地址空间的申请需求。所以返回为空非常正常。好在Windows CE下运行的大多数软件不需要那么多的物理内存。<BR>　　感觉微软的技术不是支持到很远的将来，而是得过且过，只要满足目前的和不远的将来的需求就行。拿Platform Builder来说，IMGRAM64环境变量用于支持64MB物理内存。可是没有IMGRAM128或者IMGRAM256甚至IMGRAM512。可能是当时绝大多数基于Windows CE的产品都没有超过64MB物理内存。现在要支持超过64MB物理内存就必须做一些修改操作。再如现在说的用"new"分配物理内存，也只是限制在32MB以内。如果想new多少就new多少，那多爽!<BR>　　"new"不行是因为地址空间不够，那我们可以采用虚拟内存分配，然后提交物理内存这种办法。理论上是这样，但是实际上还是不行。举例如下： </P><PRE>LPVOID  g_Address1, g_Address2;
g_Address1 = VirtualAlloc(0, 32 * 1024 * 1024, MEM_RESERVE, PAGE_NOACCESS);
g_Address2 = VirtualAlloc(g_Address1, 32 * 1024 * 1024, MEM_COMMIT, PAGE_READWRITE);
</PRE>　　上面这段代码中第一个语句是申请32MB的虚拟地址空间，函数返回一个地址说明申请是成功的。注意这个地址一定处于0x4200 0000以上（具体参见我的专栏中“<A href="http://www.vckbase.com/document/viewdoc/?id=1156" target=_blank>Windows CE下进程、线程和内存管理（三）</A>”）。第二个语句是提交物理内存，容量为32MB。这个函数返回NULL，说明申请物理内存不成功。如果申请10MB、20MB的还可以。<BR>　　希望再一次破灭。最后的办法就是内存映射文件了。在Windows CE的帮助文档中只提到了内存映射文件可以用来申请虚拟地址空间。可以试一试。结果证明用内存映射文件来申请大容量物理内存是可行的。内存映射文件用于多个进程共享数据时，创建内存映射的函数的第一个参数必须设置为INVALID_HANDLE_VALUE，表示在物理内存中创建。利用这个特点我们可以申请超过32MB的物理内存。具体能够申请的大小由剩余的物理内存决定。例子如下： <PRE>#define  MAXLEN  (64*1024*1024)
HANDLE  hFile;
hFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MAXLEN, NULL);
if(hFile == NULL)
{
	//创建文件映射对象失败
	return;
}

LPVOID  lpAddress;
lpAddress = MapViewOfFile(hFile, FILE_MAP_WRITE|FILE_MAP_READ, 0, 0, MAXLEN);

if(lpAddress == NULL)
{
	//创建文件视图失败
	return;
}
</PRE>　　上述的函数如果都成功了，你就可以使用物理内存了。物理内存的首地址是lpAddress。使用完了别忘了调用函数UnmapViewOfFile(lpAddress); 和CloseHandle(hFile);<BR><BR>写作时间：2004-08-25 <BR><BR>未经本文作者同意，不准擅自转载本篇文章。<BR>联系作者请邮至：fllsoft@sina.com 或 windowsce@tom.com<BR></TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231900607062&amp;lmt=1231900607&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231900607062&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1232&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Ffllsoft%2F&amp;frm=0&amp;ga_vid=1277683107.1231900607&amp;ga_sid=1231900607&amp;ga_hid=1877927620&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=16" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1232" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1232" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1232&amp;title=%d4%da+Windows+CE+%cf%c2%c9%ea%c7%eb%b4%f3%c8%dd%c1%bf%ce%ef%c0%ed%c4%da%b4%e6" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 欢迎大家到天极网嵌入式开发论坛http://club.yesky.com/bbs/jsp/list.jsp?forumID=378 ( fllsoft 发表于 2005-1-21 9:26:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 这篇文章争议很大啊！等我把驱动部分研究透了再回头客观评价这篇文章。如果是错的我会纠正的。 ( fllsoft 发表于 2004-12-23 8:11:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 回fllsoft&nbsp;"2、你以为你能轻易的得到物理内存的地址吗？你还好意思说你做了很久，我早就说过，只有自定义CE内核，你才真正懂得CE。当CE内核启动时，会将物理地址0映射到0x80000000。"<BR><BR>你说的这点，我表示怀疑&nbsp;ce内核启动时，我这块arm板子，是把sdram物理内存映射到0x8000&nbsp;0000,而不是把用作启动的rom&nbsp;0000&nbsp;0000地址开头的空间映射&nbsp;到0x8000&nbsp;0000<BR>希望和你进行更多的交流 ( yiiang 发表于 2004-12-14 15:14:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> “Windows&nbsp;CE&nbsp;supports&nbsp;both&nbsp;named&nbsp;and&nbsp;unnamed&nbsp;file-mapping&nbsp;objects.&nbsp;Unnamed&nbsp;files&nbsp;provide&nbsp;a&nbsp;method&nbsp;both&nbsp;for&nbsp;interprocess&nbsp;communication&nbsp;and&nbsp;as&nbsp;a&nbsp;way&nbsp;to&nbsp;allocate&nbsp;virtual&nbsp;memory&nbsp;regions&nbsp;larger&nbsp;than&nbsp;the&nbsp;32-MB&nbsp;slot&nbsp;size&nbsp;limit.&nbsp;”CE帮助文档的原话。充分说明了在CE中用内存映射技术来申请超过32MB的虚拟内存空间是合理的。 ( fllsoft 发表于 2004-9-15 14:48:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 申请物理内存首先要申请虚拟空间，这是不争的事实。所以本篇文章讲的就是如何申请到足够的虚拟空间。你如果知道还说废话，实在是没必要。另外你的言语实在刻薄，原谅你没有收到良好的道德教育。 ( fllsoft 发表于 2004-9-15 8:23:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 我不再做任何争辩了，如果你喜欢就自己接着说吧。 ( nbcool 发表于 2004-9-14 22:02:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> "在&nbsp;Windows&nbsp;CE&nbsp;下申请大容量物理内存"<BR>“物理内存的首地址是lpAddress”<BR>这是你文章的标题和内容，这完全说明不懂得区分物理内存和虚拟内存的人是你,要不然你就不会犯这种低级错误！<BR>简直让人贻笑大方。我也看到了你作为“井底之蛙”的本性，别人提意见，你就这种态度。<BR>我是设备制造商，当然知道如何写虚拟内存和物理内存的映射算法和CE内核工作原理，不过你既然这么要面子，犯了错还遮遮掩掩，我就没必要再多说了，就此打住，争辩也没必要了。 ( nbcool 发表于 2004-9-14 21:52:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> nbcool，你如果对我所有文章还有疑意，可以通过Email或者MSN交流。不要连虚拟地址和物理地址关系都不懂还要乱讲话。OK？ ( fllsoft 发表于 2004-9-14 9:09:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 回应你的话：<BR>1、你还不懂得虚拟地址和物理地址的关系。从程序员的角度讲，所有接触的地址都是进程的虚拟地址，你不会接触到实际的物理地址，而且也没必要得到实际的物理地址。举个最简单的例子，你用new得到的地址是虚拟地址，而不是实际的物理地址。每当申请物理内存时，操作系统内核会在进程的地址空间申请足够的空间，然后提交一部分物理内存，映射虚拟地址和物理地址。你不懂可以看看《windows核心编程》。所以整个文章中提到的都是虚拟地址。<BR>2、你以为你能轻易的得到物理内存的地址吗？你还好意思说你做了很久，我早就说过，只有自定义CE内核，你才真正懂得CE。当CE内核启动时，会将物理地址0映射到0x80000000。就连CE的内核工作时都是使用虚拟地址。你还以为你申请的内存返回的地址是实际物理地址。蠢！<BR>3、我的《windows&nbsp;ce进程、线程、内存管理》非常明确的提出进程的地址空间应该是64MB，slot1专用于加载XIP&nbsp;DLL。你可以看看，我早就告诉你要仔细看我的文章。你没有仔细看！ ( fllsoft 发表于 2004-9-14 8:58:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 前面我的话是有些过分了。如果要说到试验，我从PalmSize-PC开始就做过开发。相信我对WinCE以及WinCE.NET理解还是有一定程度的。<BR><BR>1.CreateFileMapping本来就是在虚拟内存中操作，然后通过映射回写到文件。你却把它叫做申请“物理内存”，简直让人十分奇怪，而且0x4200&nbsp;0000以上是[Large&nbsp;Memory&nbsp;Area]，是在2G虚拟地址空间之内，又说明了你想说的是虚拟内存地址。<BR><BR>2.你这篇文章有点过时了，因为在WinCE.NET中slot1是可以被占用的，一个活动的应用程序理论上可以有64MB可用虚拟地址空间。<BR><BR>3.VirtualAlloc&nbsp;在分配2M以上空间的时候，才会在[Large&nbsp;Memory&nbsp;Area]中分配。提交时没必要一次提交32M，一般是64K的倍数，否则一次提交32M，建立物理内存到虚拟内存的映射时，命中率会大大下降，这时，完全不能说明VirtualAlloc&nbsp;不能一次提交32M的结论，因为系统可能这时根本无法找到32M可用物理内存了。<BR><BR>4.MapViewOfFile返回的指针是虚拟地址空间的，而不是物理内存首地址。<BR><BR><BR>不否认你写的有正确的部分，但还有不少问题，我以前也浏览过你的前几篇文章，确实也存在不少问题。我也不想对你写的文章评论太多，不然你又要不高兴了。<BR><BR>既然你没看过Douglas&nbsp;Boling&nbsp;的著作，那么推荐你去看看，MSDN&nbsp;中的部分内容也是由他编写的，他被誉为是对&nbsp;Windows&nbsp;CE&nbsp;理解最完美的人之一。你所通过实验得到的一些方法，在他最新的第三版著作中，都有系统的总结和分析。<BR><BR>再给你看一篇文章，<BR>http://www.microsoft.com/china/MSDN/library/windev/WindowsCE/WCEdncenetadvmemmgmt.mspx<BR>对你改进本文有所帮助。 ( nbcool 发表于 2004-9-13 23:00:00)<BR>&nbsp;<BR>.......................................................<BR><A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1232" target=_blank><SPAN class=small>More...</SPAN></A> </TD></TR></TBODY></TABLE></DIV><BR>
<DIV align=right><BR></DIV>]]></description>
</item><item>
<title><![CDATA[Windows CE下拨号上网]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42445</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:36:40</pubDate>
<description><![CDATA[
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>Windows CE下拨号上网<BR></B><BR>作者：<A href="mailto:windowsce@tom.com">付林林</A></P>
<P>　　目前基于Windows CE的产品大多数具有上网功能，能够连接到Internet实现浏览网页、收发邮件等基本功能。要实现更多的功能取决于连接Internet的速度，而速度取决于连接Internet的方式。连接Internet的方式分为两种：有线和无线。事实上大多数产品还是采用无线上网方式。<BR>　　基于Windows mobile的Pocket PC和Smart phone能够轻松实现上网功能。这两种产品的软件开发人员可以不必过多考虑如何实现拨号上网。而对于采用定制Windows CE内核的产品而言，相关的软件开发人员必须自己实现拨号上网。幸运的是Windows CE的SDK中包含了拨号上网的支持，使软件开发人员的工作大大减轻。<BR>　　拨号上网的硬件设备可以是独立的带内置调制解调器和具有GPRS功能的手机，也可以是嵌入到主板中带有同样功能的手机芯片。具体是什么并不重要，因为Windows CE包含的拨号上网模块采用标准的AT命令集与硬件设备通信。软件开发人员可以直接利用CE提供的接口函数来控制整个拨号过程，直到断开连接。<BR>　　Windows CE.NET 提供了一个被称为RAS（Remote Access Service）的服务，通过RAS基于Windows CE的产品可以通过WAN（广域网）或VPN（虚拟个人网络）连接到远程服务器上。调用RAS的应用程序通常在客户机上执行，通过电话网连接到远程服务器，这种连接使用两种标准的远程访问协议：PPP点对点协议和SLIP串行线路Internet协议。RAS结构如图1所示。从图中可以看出RAS处于ISO/OSI模型的上层，下层的TAPI（电话API）调用Unimodem，Unimodem是通用的调制解调器驱动程序，它调用串口API实现与调制解调设备的通信。<BR><BR><IMG height=349 src="http://www.vckbase.com/document/journal/vckbase34/images/wincerasimg1.jpg" width=236 border=0><BR>图1 RAS服务结构图<BR><BR>　　Windows CE.NET下RAS所具备的所有功能同PC下的相似，同样支持电话本、拨号控制脚本。不同的是CE下的RAS的电话本是存放在注册表中。这些都是次要的功能，在此不给予讲述。实现拨号必须调用的函数如下：</P>
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 height=186 cellSpacing=0 cellPadding=0 width="77%" border=1>
<TBODY>
<TR>
<TD align=middle width="33%" height=20><B>函数</B></TD>
<TD align=middle width="67%" height=20><B>描述</B></TD></TR>
<TR>
<TD width="33%" height=18>RasDial</TD>
<TD width="67%" height=18>建立RAS连接</TD></TR>
<TR>
<TD width="33%" height=20>RasEnumEntries</TD>
<TD width="67%" height=20>列出注册表中所有拨号记录</TD></TR>
<TR>
<TD width="33%" height=20>RasGetConnectStatus</TD>
<TD width="67%" height=20>获得指定远程访问连接的当前状态。</TD></TR>
<TR>
<TD width="33%" height=20>RasGetEntryDialParams</TD>
<TD width="67%" height=20>获得指定的上次成功拨号记录的连接数据（参数）</TD></TR>
<TR>
<TD width="33%" height=20>RasSetEntryDialParams</TD>
<TD width="67%" height=20>设置指定的上次成功拨号记录的连接数据（参数）</TD></TR>
<TR>
<TD width="33%" height=20>RasGetEntryProperties</TD>
<TD width="67%" height=20>获得拨号记录属性</TD></TR>
<TR>
<TD width="33%" height=20>RasSetEntryProperties</TD>
<TD width="67%" height=20>设置拨号记录属性</TD></TR>
<TR>
<TD width="33%" height=20>RasHangUp</TD>
<TD width="67%" height=20>终止RAS连接</TD></TR></TBODY></TABLE>
<P>　　基于Windows CE的产品在设计上有的能够在断电前保存注册表数据，有的不能够。对于不能够保存注册表数据的产品，采用电话本来拨号是行不通的。只能让用户每次启动后重新输入拨号数据，或者将数据保存到永久存储器上，在下一次拨号程序启动时先读取拨号数据。<BR>　　整个拨号状态分为同步和异步两种。同步状态下RasDial函数直到建立连接或者发生错误不能继续工作时才返回。同步模式的缺点是客户不能收到连接操作的当前状态和进度信息。但是仍然有解决方法，办法是创建一个新的线程来进行拨号（调用RasDial），另外创建一个新的线程每隔一段时间查询当前拨号状态（调用RasGetConnectStatus）。相比较异步模式在得到当前拨号状态方面要容易的多。异步模式下RasDial函数会立刻返回，接着可以调用RasGetConnectStatus得到当前拨号状态。<BR>　　RasGetEntryDialParams和RasGetEntryProperties两个函数得到的是不同方面的数据。在此做一说明：CE为DialPrarms拨号参数定义了结构体RASDIALPARAMS。它包括拨号记录名、电话号码、用户名、密码、域名等。而对于Properties属性数据，CE定义了结构体RASENTRY。它包括连接选项、国家码、区域码、本地电话号，可以指定本地IP地址、远程DNS服务器IP、远程WINS服务器IP等，还可以指定连接控制脚本文件路径、采用的协议、调制解调器类型及名称等。在拨号前一定要设置要这些参数，不过不必每个参数都设置。最简单的办法是得到系统默认的所有参数。调用代码如下： </P><PRE>DWORD dwReturn;
dwReturn = RasGetEntryProperties(NULL, L"", &amp;RasEntry, &amp;dwSize, NULL, NULL); 
if(dwReturn != 0)    
{	
	////不能得到系统拨号参数
	return;	
}
</PRE>　　得到系统默认的参数后再加以修改，这样避免了设置错误的参数，而且大多数参数不用我们设置。关于拨号上网就讲这么多，如果读者有疑问或者遇到困难可以发邮件给我，如果有时间我将回复。<BR>　　另外关于PlatformBuilder，很多人发邮件给我希望我能回答他们关于PB的问题。在这里我向读者们宣布，关于PB和EVC的书我和我的同事正在写，预计9月份就能够完成。这本书详细介绍了PB（包括驱动）和EVC的使用，还有一些开发实例。PB和驱动部分都是我的同事写的，他们很有经验。<BR><BR>写作时间：2004-08-11<BR><BR>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至　<A href="mailto:fllsoft@sina.com">fllsoft@sina.com</A>　<A href="mailto:或windowsce@tom.com">或windowsce@tom.com</A><BR><BR>
<P></P></TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231900582531&amp;lmt=1231900582&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231900582531&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1206&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Ffllsoft%2F&amp;frm=0&amp;ga_vid=990703224.1231900583&amp;ga_sid=1231900583&amp;ga_hid=400587082&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=15" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1206" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1206" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1206&amp;title=Windows+CE%cf%c2%b2%a6%ba%c5%c9%cf%cd%f8" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 欢迎大家到天极网嵌入式开发论坛http://club.yesky.com/bbs/jsp/list.jsp?forumID=378 ( fllsoft 发表于 2005-1-21 9:26:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 我写的都是开发过的。没有研究或者开发过的不会随意写。另外如果有错误我会及时的在评论中指出错误和改正的结果。谢谢大家关注。 ( fllsoft 发表于 2004-8-24 16:30:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 不错,希望大侠能写一些关于通讯方面的文章:<BR>比如蓝牙接口,USb接口(可能这类的主控PDA还没出) ( liu_sir 发表于 2004-8-20 9:23:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 我的MSN：windowsce@tom.com&nbsp;。<BR>大家可以用MSN与我联系。 ( fllsoft 发表于 2004-8-19 12:17:00)<BR>&nbsp;<BR>.......................................................<BR><A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1206" target=_blank><SPAN class=small>More...</SPAN></A> </TD></TR></TBODY></TABLE></DIV>]]></description>
</item><item>
<title><![CDATA[Windows CE下串口通信]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42444</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:36:16</pubDate>
<description><![CDATA[<A>　
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>Windows CE下串口通信<BR></B><BR>作者：<A href="mailto:windowsce@tom.com">付林林</A></P>
<P><BR>　　网上已经有CE下串口通信的文章了。我之所以发表同样内容的文章是因为我的文章是一系列的，不会因为别人写过我就不写了。另外我对串口通信有着自己的观点。<BR>　　现在大多数的笔记本电脑都没有外置串口，这不奇怪，因为有更快更稳定的接口代替了串口。不过基于 Windows CE 的设备仍然保留着串口，而且目前看来串口的地位暂时不会动摇。目前流行的基于CE的设备很多都具有像导航、打电话等功能，而GPS、GSM/GPRS模块都是外置串口的终端设备，你想不用串口都不行。<BR>　　上面我说了我有着自己的观点，我的观点就是不要把串口通信封装成类。我不明白为什么有些人总要把串口封装成类呢。把一个事物封装成类，那这个事物就一定是不易改变的，如果每次编写都要修改，那封装成类就一点意义都没有了。设想如果MFC类总要改变的话，那我们用MFC编的程序也要修改同样次数了。如果编写超级终端一类的程序倒是可以将串口封装成类，因为超级终端只管输入命令和显示输出数据，不对输出数据进行处理，那读串口的函数就可以一直使用而不必更改。但事实上串口通信大多数用来与终端设备进行通信，需要对终端设备返回的数据进行处理。而返回的数据在什么时间返回、数据量的大小不是确定的，非要封装成类难度很大。<BR>　　正如CE的帮助文档所说，串口通信是最简单的通信之一。稍麻烦的是在读数据方面。<BR><BR>一、打开串口 </P><PRE>       hSerial = CreateFile(L"COM1:", 
　　　　　　　　　　　　　　　GENERIC_READ | GENERIC_WRITE,
　　　　　　　　　　　　　　　0, 
　　　　　　　　　　　　　　　NULL, 
　　　　　　　　　　　　　　　OPEN_EXISTING, 
　　　　　　　　　　　　　　　0, 
　　　　　　　　　　　　　　　NULL);

       if(m_hSerial == NULL)
       {
              ///L"串口打开失败";
              return;
       }
       ///配置串口
       DCB  PortDCB;    
       PortDCB.DCBlength = sizeof(DCB); 
       // 默认串口参数
       GetCommState(hSerial, &amp;PortDCB);
       PortDCB.BaudRate = 115200; // baud
       PortDCB.ByteSize = 8;     // Number of bits/byte, 4-8 
       PortDCB.Parity = NOPARITY; 
       PortDCB.StopBits = ONESTOPBIT;  
       if (! SetCommState(hSerial, &amp;PortDCB))
       {
              ///L"配置串口失败";
              return;
       }
       ////配置超时值
       COMMTIMEOUTS  CommTimeouts;
       GetCommTimeouts(m_hSerial, &amp;CommTimeouts);
       CommTimeouts.ReadIntervalTimeout = MAXDWORD;  
       CommTimeouts.ReadTotalTimeoutMultiplier = 10;  
       CommTimeouts.ReadTotalTimeoutConstant = 10;    
       CommTimeouts.WriteTotalTimeoutMultiplier = 50;  
       CommTimeouts.WriteTotalTimeoutConstant = 100;    
       if (!SetCommTimeouts(hSerial, &amp;CommTimeouts))
       {
              ///L"不能设置超时参数";
              return;
       }                  </PRE>　　CE的串口驱动不支持重叠，这个大家都知道的。这样的话收和发就要分开。要接收串口数据就必须创建一个线程专门用于接收数据。串口的配置不需要设置很多参数，默认的配置大部分是不需要修改的。一般改动就是波特率、位数、奇偶校检等几项。超时值是需要改动的。ReadIntervalTimeout是指两个字符传送之间的超时时间。一次写操作的超时时间等于WriteTotalTimeoutMultiplier 乘以 要发送的字符数 加上WriteTotalTimeoutConstant。 单位是毫秒。读操作的超时和写类似。所以设置超时是一个关键。设置太小可能丢失数据。<BR><BR>二、关闭串口<BR><BR>关闭串口用关闭句柄函数。 <PRE>if(hSerial != NULL)  
{
    CloseHandle(hSerial);
    hSerial = NULL;
}
</PRE>三、向串口发送数据 <PRE>WriteFile (hSerial,    　　　　// 句柄
           &amp;Byte,             // 数据缓冲区地址
           nByte,             // 数据大小
           &amp;dwNumBytes,       // 返回发送出去的字节数
           NULL               // 不支持重叠
);
</PRE>　　向串口发送数据一般都会成功。需要注意的是如果终端设备需要一定处理时间或者称反应时间的话，那么两个写操作之间一定要注意时间间隔不能太小。具体的时间由终端设备的反应时间和缓冲区大小有关。<BR><BR>四、读取串口数据<BR><BR>　　串口麻烦就麻烦在读取数据上。除了考虑及时的读取数据外，还要解决接收到的数据的处理工作。如果在读取串口数据的线程中安置数据处理工作，那么可能会丢失数据（终端设备发送数据但是没收到），也有可能不会丢失（终端设备发送的数据的时间、大小都是确定的）。如果肯定接收的数据在处理工作结束后终端设备才发送数据，那么完全可以将数据处理工作放在读取串口的线程中。对于及时的读取数据，下面提供了一种解决办法： <PRE>*** 假设接收的都是字符 ***
UINT  ReadThread(LPVOID pParam) ?////接收串口数据线程
{     
       HANDLE   hPort = *(HANDLE*)pParam;
       BYTE   Byte;
       int    iCounter = 0;
       DWORD   dwBytes;
       char    ReceiveBuf[1000];  ///缓冲区的大小     
              
       SetCommMask (hPort, EV_RXCHAR);   ///只接收字符
       while (hPort != INVALID_HANDLE_VALUE) 
       {
              DWORD  dwCommStatus;
              WaitCommEvent(hPort, &amp;dwCommStatus, 0);
              SetCommMask (hPort, EV_RXCHAR); ///重新设置要等待的信号
              //// 接收数据
              do 
              {
                     ReadFile(hPort, &amp;Byte, 1, &amp;dwBytes, 0);                         
                     if(dwBytes == 1)
                     {
                            ReceiveBuf[iCounter++] = Byte;
                            if(iCounter == 1000)
                            {
                                   ///L"接收缓冲区已满";
                                   return -1;
                            }
                     }                          
              } while (dwBytes == 1);
              if(iCounter == 0)  ?////没接到数据
              {
                     continue;
              }
              //////保存数据
              char* pTmp = new char[iCounter + 1];
              if(pTmp == NULL)
              {
                     ///L"内存不足，接收串口数据线程关闭";
                     return -1;
              }
              memcpy(pTmp, ReceiveBuf, iCounter);
              pTmp[iCounter] = NULL;   ////字符串结尾
              ////////创建新线程处理数据          
              
              ////在ProcessData函数中处理数据。别忘了delete[] pTmp;
              AfxBeginThread(ProcessData, pTmp);   
              iCounter = 0;         ////清空计数器
       } ///////end while
       return 0;
}
</PRE>写作时间：2004-08-22 <BR><BR>　　未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至　fllsoft@sina.com　或windowsce@tom.com<BR><BR>MSN Messenger：windowsce@tom.com</TD></TR></TBODY></TABLE><PRE></PRE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231900554296&amp;lmt=1231900554&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231900554296&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1213&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Ffllsoft%2F&amp;frm=0&amp;ga_vid=858916944.1231900554&amp;ga_sid=1231900554&amp;ga_hid=1137549144&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=16" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1213" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1213" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1213&amp;title=Windows+CE%cf%c2%b4%ae%bf%da%cd%a8%d0%c5" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 我现在是天极网嵌入式开发论坛的版主。大家有问题可以到这个论坛看看。 ( fllsoft 发表于 2005-1-6 14:38:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 多谢fllsoft,终于明白了! ( liu_sir 发表于 2004-8-31 8:55:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 如果终止读线程后程序就退出，那么可以用TerminateThead。如果终止读线程后程序不退出，那么就要破坏WaitCommEvent。可以按下列方法：设置等待事件掩码时加一项，如SetCommMask(hPort,EV_RXCHAR|EV_TXEMPTY)，表示当输出字符都发送出去作为一个事件。那么我们可以发送任意一个字符，这个字符发送出去后WaitCommEvent就返回。还有一种方法，如果WaitCommEvent执行时，这时调用SetCommMask，那么WaitCommEvent会立即返回。大家可以试一试。 ( fllsoft 发表于 2004-8-27 20:26:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> SetCommMask&nbsp;(hPort,&nbsp;EV_RXCHAR);&nbsp;&nbsp;&nbsp;///只接收字符<BR>WaitCommEvent(hPort,&nbsp;&amp;dwCommStatus,&nbsp;0);<BR>SetCommMask&nbsp;(hPort,&nbsp;EV_RXCHAR);&nbsp;///重新设置要等待的信号<BR>--------------------------------------------------<BR>感觉这样用WaitCommEvent监听com口事件是对的,可是有一点不明白,WaitCommEvent会同步等待,如果关闭串口的时候,如果解除等待,使线程返回? ( liu_sir 发表于 2004-8-27 17:27:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 另外这个网友所说的很有道理。他说用一个线程C专门处理接收的数据。而接收线程B在接收数据后把数据交由C处理。我强调我的方法不是最好的，只是一种方法。 ( fllsoft 发表于 2004-8-25 11:53:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 可能是我的错！在此声明：我写的这个例子不是通用的。也不存在通用的。具体问题具体分析。关于SetCommMask请去询问微软。微软提供的文档中的例子中重新设置了SetCommMask。 ( fllsoft 发表于 2004-8-25 11:48:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 作者的观点：<BR>我的观点就是不要把串口通信封装成类。我不明白为什么有些人总要把串口封装成类呢<BR>我是坚决不同意的。<BR>数据的处理根本是不在这一层的。<BR><BR>另外我不知到为什么要每次<BR>SetCommMask&nbsp;(hPort,&nbsp;EV_RXCHAR);&nbsp;///重新设置要等待的信号<BR>请告之。 ( sander 发表于 2004-8-25 10:03:00)<BR>&nbsp;<BR>.......................................................<BR><A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1213" target=_blank><SPAN class=small>More...</SPAN></A> </TD></TR></TBODY></TABLE></DIV><BR>
<DIV align=right><BR><SPAN class=small>版权所有 &copy; 1999 - 2008 VC知识库&nbsp; <BR></SPAN></DIV></A>]]></description>
</item><item>
<title><![CDATA[Windows CE 进程、线程和内存管理（三）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42443</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:35:45</pubDate>
<description><![CDATA[
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>Windows CE 进程、线程和内存管理（三）<BR></B><BR><BR>作者：<A href="mailto:windowsce@tom.com">付林林</A></P>
<P><BR><B>三、内存管理</B><BR><BR>　　同其它Windows操作系统一样，Windows CE.NET也支持32位虚拟内存机制、按需分配内存和内存映射文件等。但是与其它Windows操作系统又有明显的不同。毕竟Windows CE是一种嵌入式实时性的操作系统，在内存管理方面必须要比其它Windows操作系统更节约物理内存和虚拟地址空间。在内存管理API方面，为了便于移植程序，Windows CE和其它Windows操作系统函数声明基本一致，这使一个在其它Windows下开发的程序员可以直接使用早就熟悉的API函数， 但是CE下内存管理的原理开发者还是应该熟悉的。<BR><BR>1、ROM和RAM <BR>　　最早的基于Windows CE的民用产品，采用的存储设备都是ROM ＋ RAM ，ROM保存CE内核文件、应用程序，而RAM用于内核、所有应用程序运行时使用，关闭电源时必须给RAM提供电力来保存系统配置信息、用户产生的文件等。为了适应这样的存储硬件，CE采用了ROM文件系统和RAM文件系统。在ROM中存放的模块可以是压缩的，也可以是不压缩的，这取决于OEM。OEM在定制内核时可以设置是否压缩模块。如果是压缩的，模块在运行前先解压并全部存放到RAM中。如果是不压缩的，就本地执行（XIP，executed in place）。本地执行和其它Windows操作系统下执行应用程序、DLL方式一致，也就是应用了内存映射文件技术。在这里我顺便讲一下。在启动时应用程序或DLL的代码段不加载到物理内存中，内核只是分配虚拟地址空间给代码段，当执行代码时内核会到实际存放在硬盘上的文件中寻找代码并执行。采用这样的技术既可以节省可用内存又可以减少加载的时间。请注意，操作系统首先会到为硬盘准备的缓冲区里读取代码数据，如果没有就命令硬盘读取应用程序文件数据到缓冲区。所以缓冲区设置大点是有好处的。Windows CE的本地执行就是采用这样的技术来加载ROM内的应用程序和DLL的。所以Windows CE的DLL分为XIP DLL和非XIP DLL。这种加载方式的缺点就是执行相对较慢一点，如果用PB创建一个具有实时性特点的内核，一定不能选用XIP技术。<BR>　　到后来基于Windows CE的产品开始采用FLASH、IDE等永久存储设备时，文件系统又加了个FAT。内核文件和其它应用程序也可以存放到永久存储设备中，内核由加载程序解压并加载到RAM的对象存储区域（object store），包含在内核中的所有系统应用程序文件和DLL文件都存放到这个区域。当执行一个应用程序时，内核将这个应用程序调用的系统DLL加载到Slot 1（0x0200 0000－0x03FF FFFF）。在Windows CE.NET中Slot 1专用于XIP DLL使用。<BR>　　RAM文件系统专用于对象存储。在以前的文章中曾经讲过，它和ROM文件系统是Windows CE默认的文件系统。Windows CE启动后把RAM分为对象存储区域（object store）和应用程序内存区域（program memory）。对象存储区域采用RAM文件系统来保存文件，一般用于保存内核解开的所有文件。应用程序内存区域留给所有应用程序运行时使用。在Windows CE下"控制面板"－"系统"－"内存"中，可以调节这两个存储区域的比例，滑块向左，则释放对象存储区域的一些内存并将这些内存划到应用程序内存区域中。滑块向右则相反。<BR><BR>2、内存结构<BR>　　Windows CE.NET只能管理512MB的物理内存和4GB大小的虚拟地址空间。不同的CPU内存管理方法也不同。对于MIPS和SHX系列CPU来说，物理地址映射是由CPU完成的，CE内核可以直接访问512MB的物理内存。对于x86系列和ARM系列的CPU来说，在内核启动过程中它会将现有物理内存地址全部映射到0x8000 0000以上的虚拟地址空间中供内核以后使用。OEM可以通过OEMAddressTable来详细定义虚拟地址和物理地址的映射关系。OEMAddressTable本身并不是一个文件，它只是存在于其它文件中描述虚拟地址和实际物理地址的映射关系的数据。比如文件oem init.asm中包含一段代码：dd 80000000h, 0, 04000000h 。它表示将整个物理地址（0x0400 0000＝64MB）共64MB映射到虚拟地址从0x8000 0000到0x8400 0000中。关于OEMAddressTable我将在以后关于PB的文章中讲述。<BR>　　整个4GB虚拟地址空间主要划分为两部分，从0x8000 0000以上为内核使用部分，0x8000 0000以下为应用程序使用部分。详细见下表：<BR><BR>　 
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=4 cellPadding=4 width="98%" bgColor=#ffffff border=1>
<TBODY>
<TR>
<TD align=middle width="35%"><B>地址范围</B></TD>
<TD align=middle width="65%"><B>用途</B></TD></TR>
<TR>
<TD width="35%">0x0000 0000到0x41FF FFFF</TD>
<TD width="65%">　　由所有应用程序使用。共33个槽，每个槽占32MB。槽0（Slot 0）由当前占有CPU的进程使用。槽1由XIP DLL使用。其它槽用于进程使用，每个进程占用一个槽。</TD></TR>
<TR>
<TD width="35%">0x4200 0000到0x7FFF FFFF</TD>
<TD width="65%">　　由所有应用程序共享的区域。32MB地址空间有时不能够满足一些进程的需求。那么进程可以使用这个范围的地址空间。在这个区域里应用程序可以建堆、创建内存映射文件、分配大的地址空间等。</TD></TR>
<TR>
<TD width="35%">0xA000 0000到0xBFFF FFFF</TD>
<TD width="65%">　　在这个范围内核重复定义0x8000 0000到0x9FFF FFFF之间定义的物理地址映射空间。区别是在这范围映射的虚拟地址空间不能够用于缓冲。<BR>　　我举例来说明：假设一个产品有64MB物理内存。如上文所述定义好OEMAddressTable后。内核启动后一个物理地址映射空间范围在0x8000 0000到0x8400 0000，那么内核会从0xA000 0000到0xA400 0000定义一个同样范围的地址空间，这个地址空间和0x8000 0000到0x8400 0000映射到相同的物理地址。但这个虚拟地址空间不能够用于缓冲。</TD></TR>
<TR>
<TD width="35%">0xC000 0000到0xC1FF FFFF</TD>
<TD width="65%">系统保留空间</TD></TR>
<TR>
<TD width="35%">0xC200 0000到0xC3FF FFFF </TD>
<TD width="65%">内核程序nk.exe使用的地址空间。</TD></TR>
<TR>
<TD width="35%">0xC400 0000到0xDFFF FFFF</TD>
<TD width="65%">　　这个范围为用户定义的静态虚拟地址空间，但这个地址空间只能用于非缓冲使用。<BR>　　利用OEMAddressTable定义物理地址映射空间后，每次内核启动时这个范围都不改变了，除非产品包含的物理内存容量发生变化。假如增加到128MB物理内存，那么物理地址映射空间也向后扩大了一倍。Windows CE.NET也允许用户创建静态的物理地址映射空间。用户可以调用CreateStaticMapping函数或者NKCreateStaticMapping函数来映射某一段物理地址到0xC400 0000和0xE000 0000之间的某一个范围。需要注意的是用这个函数创建的静态虚拟地址只能够由内核访问，而且不能用于缓冲。</TD></TR>
<TR>
<TD width="35%">0xE000 0000到0xFFFF FFFF</TD>
<TD width="65%">　　内核使用的虚拟地址。当内核需要大的虚拟地址空间时，会在这个范围内分配。</TD></TR></TBODY></TABLE>
<P><BR><IMG src="http://www.vckbase.com/document/journal/vckbase32/images/fllprothr3img1.JPG" border=0><BR>图1 Windows CE.NET内存结构<BR><BR>3、进程地址空间结构<BR>　　进程地址空间结构如图2所示。这个图源至MSDN。Windows CE.NET同以前版本的Windows CE操作系统在进程地址空间上有所不同，以前的Windows CE把XIP DLL也加载到进程的32MB地址空间中，而Windows CE.NET把XIP DLL单独加载到Slot 1中，这样对于每个进程来说，它总的地址空间就大了一倍，也就是64MB。这个问题我在讲解进程的时候提到过。<BR>　　当一个应用程序启动时，内核为这个程序选择一个空闲的槽（Slot），并且加载所有的代码、资源，并分配堆栈，加载DLL等。当这个进程得到CPU使用权时，它的整个地址空间被内核映射到Slot 0，也就是当前进程使用的地址空间，然后开始运行。图中给出的地址实际上是经过映射到Slot 0之后的结构。从图中可以看出，进程首先加载代码段，因为每个进程最低部64KB作为保留区域，所以代码段从0x0001 0000开始，内核为代码段分配足够的虚拟地址空间后，接着分配空间为只读数据和可读/可写数据，接着分配空间为资源数据，之后分配空间为默认堆和栈。非XIP DLL从进程最高地址向下开始加载。非XIP DLL的加载按如下规则：内核先检查要加载的DLL是否被其它进程加载过，如果加载过，就做一个地址的重定位。这样就避免了整个系统内多次加载相同DLL。如果没有加载过，就按照从槽的高地址到槽的低地址的顺序查找空闲的地址空间。然后分配足够的地址空间用于加载DLL。因为每个进程在执行前都要映射到Slot 0，而且进程使用的所有DLL可能来自不同的槽（Slot），为避免所有使用的DLL在映射到Slot 0中出现地址空间冲突的现象，内核的加载器（Loader）在加载DLL时会查找所有槽中加载的DLL的地址，保证在映射到Slot 0时不会发生地址冲突现象。假如系统内有两个进程，进程A只加载了DLL A，进程B需要加载DLL A和DLL B，那么进程B会留出DLL A的地址空间，然后加载DLL B，也就是说进程B映射到Slot 0时，DLL A的地址空间和DLL B的地址空间是相邻的，不会发生冲突。好在Windows CE下DLL都很小，而且一个应用程序使用的DLL多数是系统的DLL（存在于Slot 1）。所以目前来看进程的地址空间还够用。<BR><BR><IMG src="http://www.vckbase.com/document/journal/vckbase32/images/fllprothr3img2.JPG" border=0><BR>图2 进程地址空间结构<BR><BR>4、堆和栈<BR>　　堆是一段连续的较大的虚拟地址空间。应用程序在堆中可以动态地分配、释放所需大小的内存块。利用堆的优点是在一定范围内减小了内存碎块。而且开发者分配内存块前不必去了解CPU的类型。因为不同的CPU分页大小不相同，每个内存页可能是1KB、4KB或更多。在堆内分配内存块可以是任意大小的，而直接分配内存就必须以内存页为单位。当一个应用程序启动时，内核在进程所在的地址空间中为进程分配一个默认192KB大小的虚拟地址空间，但是并不立刻提交物理内存。如果在运行当中192KB不能满足需求，那么内核会在进程地址空间中重新查找一个足够大小的空闲的地址空间，然后复制原来堆的数据，最后释放原来的堆所占的地址空间。这是因为默认的堆的高地址处还有栈，所以必须重新分配一个。Windows CE.NET的堆有明显的缺点，不同于其它Windows操作系统下的堆管理，在Windows CE.NET创建的堆中创建的内存块不能够移动，多次创建内存块、释放内存块会产生内存碎块，这样的话当需要分配一个大一点的连续的内存块时，本来空闲的内存块加起来足够用，但是这些内存块是分隔的，不符合要求。像Windows 2000或98的内核会频繁的移动分散的正使用的内存块，使它们聚集在一起。这也是为什么有时需要句柄而不用指针的原因。由于Windows CE.NET的堆的缺点，开发者如果要频繁的在堆中创建、释放内存块的话，最好自己创建一个单独的堆，而不用默认的堆。而且我还建议最好直接在全局地址空间中（0x4200 0000到0x7FFF FFFF）分配所需地址空间。因为进程地址空间可用的实在太小了。关于堆函数我在这就不多说了，和其它Windows操作系统堆API基本一致。请参考帮助文档。<BR>　　栈也是一段连续的虚拟地址空间，和堆相比空间要小的多，它是专为函数使用的。当调用一个函数时（包括线程），内核会产生一个默认的栈，并且内核会立刻提交少量的物理内存（也可以禁止内核立刻提交物理内存）。栈的大小和CPU有关，一般为64KB，并且保留顶部2KB为了防止溢出。可以修改栈的大小，具体修改方法在讲解线程的时候已经说过了，这里就不再重复了。修改栈的大小一般时候不会发生，如果采用在编译链接时修改大小，那么所有栈的大小都会改变，这不太合理。实际开发中最好不要在栈中分配很大、很多的内存块，如果分配的内存块超过了默认栈的限制，那么会引起访问非法并且内核会立刻终止进程。最好在进程的堆中分配大的内存块并且在函数返回前释放，或者在创建线程时指定栈的大小。<BR><BR>5、内存映射文件<BR>　　与虚拟内存一样，内存映射文件用来保留一个地址空间，并提交物理存储器。早期的内存映射文件并不是提交物理内存供调用者使用，而是提交永久存储器上的文件数据。当然操作系统会为永久存储器保留一个读缓冲区，这样读取文件数据就快多了。内存映射文件的特点使它很适合于加载EXE或DLL文件。这样可以节省内存又减少了加载所需时间。还可以使用它来映射大容量的文件，这样就不必在读取文件数据前设置很大的缓冲区。另外内存映射文件常用于进程间通信，也是进程间通信的主要手段，其它进程之间通信机制都是基于内存映射文件来实现。为了更快的在进程之间通信，现在的内存映射文件也可以提交物理内存，这样内存映射文件既可以提交物理内存又可以提交文件。<BR>　　Windows CE.NET同样支持无名和有名的内存映射文件。我建议在开发软件的过程中，如果需要读写大容量的文件，或者需要在不同进程内的线程之间通信，最好采用内存映射文件，而且最好在全局地址空间内（0x4200 0000到0x7FFF FFFF）分配。这会使我们事半功倍。<BR><BR>5.1 映射数据文件<BR>　　第一步：调用CreateFileForMapping函数。在Windows CE.NET中推荐使用这个函数替代CreateFile函数。CreateFileForMapping函数由内核执行并创建文件，它也可以打开由CreateFile函数创建的文件。其参数同CreateFile相似。参数1指定文件路径，注意文件路径的格式是没有盘符的，参数2指定访问方式（读或写），参数3指定共享模式，参数4指定安全属性（必须设置为NULL），参数5指定是创建还是打开文件，参数6指定文件属性，参数7忽略。具体参数细节参见Windows CE.NET帮助。函数返回创建或者打开的文件的句柄。<BR>　　第二步：调用CreateFileMapping函数。这个函数创建一个无名的或者有名的内存映射文件对象。参数1为文件句柄。这个值由CreateFileForMapping函数返回。参数2为安全属性（必须设置为NULL），参数3指定要映射的文件的保护属性（只读或者读写），参数4和参数5共同用于指定要映射的文件的大小。文件的容量过大将导致32位整数也不能表示，所以这里用64位变量表示，其中参数4为高32位数，参数5为低32位数。最后一个参数指定内存映射文件的名称。这里可以设置为NULL，表示不需要名字。<BR>　　第三步：调用MapViewOfFile函数。这个函数用于保留一段足够的地址空间，并且将永久存储器上的文件数据映射到这个地址空间。映射后这段地址空间又叫做文件视图，映射范围可以是全部文件，也可以是部分文件。这里需要注意的是如果文件很大，那这个函数将在全局地址空间内分配地址空间。参数1指定内存映射文件对象的句柄，这个值由CreateFileMapping函数返回。参数2和CreateFileMapping函数中参数3很相似，都是用于限定访问权限。参数3和参数4共同用于指定映射区域的开始位置。其中参数3为高32位数，参数4为低32位数。参数5指定映射区域的大小。需要注意的是参数3和参数4指定的64位数开始位置可以不是64KB的倍数。而其它Windows操作系统就必须限制以64KB为单位。另外还要注意的是帮助文档中说不能保证一个文件的映射视图是连续的，并建议为了防止访问非法，应该加入结构化异常处理机制。这个可能性我认为很小，一般对于大于2MB的虚拟地址空间的申请，内核都会在全局地址空间中分配。全局地址空间（0x4200 0000到0x7FFF FFFF）近1GB的空间应该足够用了。毕竟Windows CE下的文件都很小。不过在代码中加入结构化异常处理也不是坏事。我们应该养成凡是读写文件数据时都加入结构化异常处理的习惯。<BR>　　第四步：进行读/写操作。MapViewOfFile函数如果成功执行，那么返回映射视图的首地址。这时就可以把视图当成是一个缓冲区，开始读或写操作了。<BR>　　第五步：执行结束工作。先调用UnmapViewOfFile函数撤销文件映射视图。参数只有一个，指定视图首地址。然后调用CloseHandle函数关闭内存映射文件对象，参数为句柄。最后再次调用CloseHandle函数，关闭打开的文件的句柄。<BR><BR>5.2 进程之间通信<BR>　　进程之间有时需要通信。系统提供的进程之间的通信机制比如COM、剪贴板等，在底层实现上都是利用内存映射文件技术。其实进程之间通信的思路很简单，在这里我顺便讲一下。在其它Windows操作系统中，每个进程独自占有4GB的地址空间，高2GB是内核的地址空间，而低2GB是进程的地址空间。一个进程所能访问的所有低2GB地址都是自己的地址空间，当访问内核地址空间时就会受到内核的限制。这样一个进程当然无法访问其它进程了。为解决进程间通信的问题，内存映射文件技术被利用作为解决方案。原来内存映射文件只映射类似磁盘一类的存储器上的文件。而为了更快速地在进程之间通信，内存映射文件还可以提交物理内存。实现方法是通过访问同一个内存映射文件对象（映射到物理内存），两个进程或多个进程就能够访问到同一块物理内存，这样一个进程写到物理内存的数据，其它进程就能够看到了。而Windows CE虽然每个进程只占有32MB的地址空间，而且所有进程全部处于4GB的地址空间中，但是彼此还是不能够随意访问的。在Windows CE下除了使用内存映射文件技术外，还有一种方法也很适合使用，就是利用对象存储。对象存储本身使用RAM文件系统，用普通的操作文件的API就可以创建、读取存在于对象存储区域内的文件。\Windows 目录就存在于对象存储区域内。我们可以利用在\Windows目录下创建文件来实现进程间通信。这种方法既实现简单，只需调用几个文件API函数，又可以减少通信时间，因为\Windows目录存在于物理内存中，数据I/O当然很快了。利用对象存储来实现进程之间的通信是我自己想出来的，MSDN或其它文档并没有这方面的说明。需要注意的就是对象存储区域的大小。另外从实现的代码量上看也不如内存映射文件技术。<BR>　　下面讲解如何利用内存映射文件实现进程之间的通信。假设进程A和进程B需要通信，那么进程A需要先创建一个内存映射文件（之前不必调用CreateFileForMapping函数来创建文件，因为不需要创建文件）。这个内存映射文件可以是在永久存储器中，也可以是在内存中。为了减小通信时间，最好提交物理内存。进程A在调用CreateFileMapping函数时，参数1指定为INVALID_HANDLE_VALUE，这表示这个内存映射文件对象将要把物理内存提交到地址空间中。最后一个参数一定要指定一个名字。进程B也同样调用CreateFileMapping函数，而且参数相同。内核会根据名字来判断是否已经存在一个内存映射文件对象，如果创建了就返回原来的对象的句柄。接下去就不用细说了。参照5.1去执行就可以了。要注意的是进程B调用CreateFileMapping函数后要按如下代码检验函数执行结果： </P><PRE>HANDLE  hMap;
hMap = CreateFileMapping(INVALID_HANDLE_VALUE, 
			NULL,
			PAGE_READWRITE,
			0,
			1000,
			L"abc");
if (hMap == NULL || GetLastError() != ERROR_ALREADY_EXISTS)
{
	MessageBox(L"create file mapping fail");
	return;
}      </PRE>6、分配大的虚拟地址空间<BR>　　可以用内存映射文件来分配大的虚拟地址空间。也可以直接调用VirtualAlloc函数来分配。VirtualAlloc函数是最底层的分配虚拟地址空间的函数。它会在调用进程内分配符合条件的地址空间并且自动用0初始化提交的存储器。传递一个你希望的虚拟地址空间的首地址给参数1（如果为0，那么内核自动查找一个符合条件的空间），参数2为大小（单位：字节），参数3为分配类型（提交还是保留），参数4为保护标志（只读、读写、执行等）。函数返回分配的地址空间的首地址。在进程地址空间中每个分配的块有三种状态：可用、保留、提交。参数3就是指明块的状态。我在做实验时发现，给参数1传递非0值均不成功，即使传递0给参数1让内核自动查找，得到的返回值再次用于参数1也不成功。释放这个虚拟地址空间调用VirtualFree函数。VirtualFree函数参数1指定首地址，参数2指定大小，参数3指定释放类型（撤销提交、释放）。函数成功返回真，失败返回假。参数3有两个标志，并且不能复合。当指定撤销提交标志（MEM_DECOMMIT）时，函数将取消这个虚拟地址空间的物理内存的映射，但是保留这块虚拟地址空间。如果这个虚拟地址空间没有提交函数也不会失败返回。当指定释放标志（MEM_RELEASE）时，如果这块虚拟地址空间含有同样的标志（保留或者提交）。函数将释放这块虚拟地址空间。如果这个虚拟地址空间有一部分提交了，其它部分没有提交，那么必须先调用此函数，并传递撤销提交标志，先将提交的这部分取消物理内存映射。然后再次调用此函数，传递释放标志。这样整个虚拟地址空间就都能够释放了。关于虚拟地址空间还有其它函数，比如VirtualQuery、VirtualProtect。在这里就不介绍了，请参见Windows CE.NET帮助。<BR><BR><B>作者注：<BR>　　</B>《进程、线程和内存管理》讲解的内容是我根据以前在PC机Windows操作系统中掌握的相关知识，又查看了Windows CE.NET的帮助文档和MSDN中Technical Articles和knowledge Base而得出的结论。遗憾的是Windows CE.NET的帮助文档介绍的太简单，我只能把掌握的知识和查看到的知识相结合，另外我还做了一些实验。我感谢浏览此文章的各位Windows CE下开发者，如果你们认为有哪些地方说的不正确的，希望指出来让我改正错误。让更多的人看到的是准确无误的文章。<BR><BR>写作时间：2004-06-09 <BR><BR>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至 fllsoft@sina.com 或windowsce@tom.com</TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231900526968&amp;lmt=1231900526&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231900526968&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1156&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Ffllsoft%2F&amp;frm=0&amp;ga_vid=969578425.1231900527&amp;ga_sid=1231900527&amp;ga_hid=1770968270&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=110" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1156" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1156" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1156&amp;title=Windows+CE+%bd%f8%b3%cc%a1%a2%cf%df%b3%cc%ba%cd%c4%da%b4%e6%b9%dc%c0%ed%a3%a8%c8%fd%a3%a9" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 本人刚开始接触WinCE,主要在CEPC下进行开发&nbsp;<BR>有两个问题想请教一下：<BR>1）WinCE被加载到内存以后，物理内存的布局是怎样的？<BR>2）WinCE支持虚拟内存吗？他的虚拟内存是否与WinXp一样，<BR>&nbsp;&nbsp;&nbsp;是以硬盘为交换的？<BR><BR>&nbsp;多谢！！ ( spin 发表于 2007-9-28 15:03:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> sorry，應該是我搞錯了。MSDN&nbsp;裏面已經有很明確的説明：If&nbsp;your&nbsp;process&nbsp;exits&nbsp;without&nbsp;having&nbsp;called&nbsp;CreateFileMapping&nbsp;as&nbsp;well&nbsp;as&nbsp;CloseHandle,&nbsp;the&nbsp;handle&nbsp;returned&nbsp;by&nbsp;CreateFileForMapping&nbsp;is&nbsp;automatically&nbsp;closed.<BR><BR>The&nbsp;CreateFileForMapping&nbsp;function&nbsp;opens&nbsp;a&nbsp;file&nbsp;and&nbsp;returns&nbsp;a&nbsp;file&nbsp;handle&nbsp;that&nbsp;is&nbsp;passed&nbsp;to&nbsp;the&nbsp;CreateFileMapping&nbsp;function.&nbsp;The&nbsp;CreateFileMapping&nbsp;function&nbsp;then&nbsp;returns&nbsp;a&nbsp;handle&nbsp;to&nbsp;the&nbsp;file&nbsp;mapping.&nbsp;For&nbsp;Windows&nbsp;CE,&nbsp;it&nbsp;is&nbsp;only&nbsp;necessary&nbsp;to&nbsp;close&nbsp;the&nbsp;mapping&nbsp;handle&nbsp;as&nbsp;the&nbsp;file&nbsp;handle&nbsp;is&nbsp;automatically&nbsp;closed&nbsp;when&nbsp;you&nbsp;close&nbsp;the&nbsp;mapping&nbsp;handle.<BR>( JiaxWang 发表于 2006-6-4 20:57:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 这样写&nbsp;在关闭文件句柄时是正确的<BR>h&nbsp;=&nbsp;CreateFileForMapping(...);<BR>CloseHandle(h);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;正确<BR><BR>但是在其中加入一些代码就会使得关闭文件句柄出错<BR>h&nbsp;=&nbsp;CreateFileForMapping(...);<BR>h2&nbsp;=&nbsp;CreateFileMapping(h,...);&nbsp;&nbsp;//&nbsp;新插入语句<BR>CloseHandle(h2);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;新插入语句&nbsp;运行正确<BR>CloseHandle(h);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;出错！！！<BR><BR>调用&nbsp;GetLastError&nbsp;后得到的错误码指示说<BR>&nbsp;6&nbsp;The&nbsp;handle&nbsp;is&nbsp;invalid.&nbsp;&nbsp;ERROR_INVALID_HANDLE&nbsp;<BR>是不是不应该用&nbsp;CreateFileForMapping&nbsp;而应该换成&nbsp;CreateFile&nbsp;？<BR>还有付兄说&nbsp;Windows&nbsp;CE&nbsp;推荐用&nbsp;CreateFileForMapping<BR>我不以为然，MSDN&nbsp;里面有说明：<BR>This&nbsp;function&nbsp;is&nbsp;deprecated&nbsp;for&nbsp;Windows&nbsp;Mobile-based&nbsp;devices.&nbsp;To&nbsp;create&nbsp;or&nbsp;open&nbsp;a&nbsp;file&nbsp;that&nbsp;can&nbsp;be&nbsp;used&nbsp;for&nbsp;memory&nbsp;mapping,&nbsp;call&nbsp;CreateFile.<BR><BR>( JiaxWang 发表于 2006-6-2 13:37:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 《Windows&nbsp;CE进程、线程、内存管理》这三篇文章中的确有一些错误。要拿出正确的结果还需实验。最后我会把正确结果总结成一篇文章。 ( fllsoft 发表于 2004-9-17 10:13:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 这几天公司网坏了。郁闷！！<BR>当然可以交朋友了。关于PB我有时间再讲。现在的确很忙啊。 ( fllsoft 发表于 2004-6-29 11:18:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 能否讲讲Platform&nbsp;builder的知识。<BR>可否交个朋友。我做WinCE也有一年多了。<BR>My&nbsp;qq:36477574 ( sander 发表于 2004-6-23 10:32:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 如果有两个processor共享内存，我们通常用到CreateFileMapping，<BR>现在有个问题？<BR>如果我一个用&nbsp;&nbsp;&nbsp;PAGE_READONLY，一个processor用PAGE_READWRITE，<BR>会是怎样的效果？<BR>( sander 发表于 2004-6-23 10:30:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 我忘说明一点，在此补充：<BR>分配大块地址空间（0x4200&nbsp;0000&nbsp;－&nbsp;0x8000&nbsp;0000）<BR>例子：<BR>g_Address&nbsp;=&nbsp;VirtualAlloc(0,&nbsp;32&nbsp;*&nbsp;1024&nbsp;*&nbsp;1024,&nbsp;MEM_RESERVE,&nbsp;PAGE_NOACCESS);<BR>g_Address&nbsp;=&nbsp;VirtualAlloc(g_Address,&nbsp;32&nbsp;*&nbsp;1024,&nbsp;MEM_COMMIT,&nbsp;PAGE_READWRITE); ( fllsoft 发表于 2004-6-21 11:53:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 麻烦你在你的标题前面加上&nbsp;Windows&nbsp;CE，以免误导别人 ( wei20020225 发表于 2004-6-18 9:33:00)<BR>&nbsp;<BR></TD></TR></TBODY></TABLE></DIV>]]></description>
</item><item>
<title><![CDATA[Windows CE 进程、线程和内存管理（二）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42442</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:35:21</pubDate>
<description><![CDATA[
<P align=center><B>Windows CE 进程、线程和内存管理（二）<BR></B><BR><BR>作者：<A href="mailto:windowsce@tom.com">付林林</A></P>
<P><BR><B>二、同步</B><BR><BR>　　在多数情况下，线程之间难免要相互通信、相互协调才能完成任务。比如，当有多个线程共同访问同一个资源时，就必须保证一个线程正读取这个资源数据的时候，其它线程不能够修改它。这就需要线程之间相互通信，了解对方的行为。再有当一个线程要准备执行下一个任务之前，它必须等待另一个线程终止才能运行，这也需要彼此相互通信。实际开发过程中，线程间需要同步的情况非常多。Windows CE.NET给我们提供了很多的同步机制，熟练的掌握这些机制并合理运用会使线程之间的同步更合理、更高效。进程间的通信机制在下一篇文章中讲解。<BR>　　Windows CE.NET具有两种运行模式：用户模式和内核模式。并且允许一个运行于用户模式的应用程序随时切换为内核模式，或切换回来。线程同步的有些解决办法运行在用户模式，有些运行在内核模式。《Windows核心编程》上说从用户模式切换到内核模式再切换回来至少要1000个CPU周期。我查看过CE下API函数SetKMode的源码，这个函数用于在两种模式间切换，改变模式只需修改一些标志，至于需要多少个CPU周期很难确定。但至少可以肯定来回切换是需要一定时间的。所以在选择同步机制上应该优先考虑运行在用户模式的同步解决办法。<BR><BR>1、互锁函数<BR>　　互锁函数运行在用户模式。它能保证当一个线程访问一个变量时，其它线程无法访问此变量，以确保变量值的唯一性。这种访问方式被称为原子访问。互锁函数及其功能见如下列表：<BR>　 
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=4 cellPadding=4 width="95%" bgColor=#ffffff border=1>
<TBODY>
<TR>
<TD align=middle width="42%"><B>函数</B></TD>
<TD align=middle width="58%"><B>参数和功能</B></TD></TR>
<TR>
<TD width="42%">InterlockedIncrement</TD>
<TD width="58%">参数为PLONG类型。此函数使一个LONG变量增1</TD></TR>
<TR>
<TD width="42%">InterlockedDecrement</TD>
<TD width="58%">参数为PLONG类型。此函数使一个LONG变量减1</TD></TR>
<TR>
<TD width="42%">InterlockedExchangeAdd</TD>
<TD width="58%">参数1为PLONG类型，参数2为LONG类型。此函数将参数2赋给参数1指向的值</TD></TR>
<TR>
<TD width="42%">InterlockedExchange</TD>
<TD width="58%">参数1为PLONG类型，参数2为LONG类型。此函数将参数2的值赋给参数1指向的值</TD></TR>
<TR>
<TD width="42%">InterlockedExchangePointer</TD>
<TD width="58%">参数为PVOID* 类型，参数2为PVOID类型。此函数功能同上。具体参见帮助</TD></TR>
<TR>
<TD width="42%">InterlockedCompareExchange</TD>
<TD width="58%">参数1为PLONG类型，参数2为LONG类型，参数3为LONG类型。此函数将参数1指向的值与参数3比较，相同则把参数2的值赋给参数1指向的值。不相同则不变</TD></TR>
<TR>
<TD width="42%">InterlockedCompareExchangePointer</TD>
<TD width="58%">参数1为PVOID* 类型，参数2为PVOID类型，参数3为PVOID。此函数功能同上。具体参见帮助</TD></TR></TBODY></TABLE>
<P>2、临界区<BR>　　临界区对象运行在用户模式。它能保证在临界区内所有被访问的资源不被其它线程访问，直到当前线程执行完临界区代码。除了API外，MFC也对临界区函数进行了封装。临界区相关函数： </P><PRE>void  InitializeCriticalSection ( LPCRITICAL_SECTION );
void  EnterCriticalSection ( LPCRITICAL_SECTION );
void  LeaveCriticalSection ( LPCRITICAL_SECTION );
void  DeleteCriticalSection ( LPCRITICAL_SECTION );      </PRE>举例如下： <PRE>void CriticalSectionExample (void)
{
    CRITICAL_SECTION  csMyCriticalSection;
    InitializeCriticalSection (&amp;csMyCriticalSection);  ///初始化临界区变量

    __try 
    {
       EnterCriticalSection (&amp;csMyCriticalSection);   ///开始保护机制
       ///此处编写代码
    }

    __finally   ///异常处理，无论是否异常都执行此段代码
    {
       LeaveCriticalSection (&amp;csMyCriticalSection);  ///撤销保护机制
    }
} 
      </PRE>MFC类使用更简单： <PRE>CCriticalSection  cs;

cs.Lock();

///编写代码

cs.Unlock();      </PRE>　　使用临界区要注意的是避免死锁。当有两个线程，每个线程都有临界区，而且临界区保护的资源有相同的时候，这时就要在编写代码时多加考虑。 <BR><BR>3、事件对象 <BR>　　事件对象运行在内核模式。与用户模式不同，内核模式下线程利用等待函数来等待所需要的事件、信号，这个等待过程由操作系统内核来完成，而线程处于睡眠状态，当接收到信号后，内核恢复线程的运行。内核模式的优点是线程在等待过程中并不浪费CPU时间，缺点是从用户模式切换到内核模式需要一定的时间，而且还要切换回来。在讲解事件对象前应该先谈谈等待函数。等待函数有四个。具体参数和功能见下表：<BR><BR>　 
<TABLE id=AutoNumber2 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=4 cellPadding=4 width="86%" bgColor=#ffffff border=1>
<TBODY>
<TR>
<TD align=middle width="38%"><B>函数</B></TD>
<TD align=middle width="62%"><B>参数和功能</B></TD></TR>
<TR>
<TD width="38%">WaitForSingleObject</TD>
<TD width="62%">参数1为HANDLE类型，参数2为DWORD类型。此函数等待参数1标识的事件，等待时间为参数2的值，单位ms。如果不超时，当事件成为有信号状态时，线程唤醒继续运行。</TD></TR>
<TR>
<TD width="38%">WaitForMultipleObjects</TD>
<TD width="62%">参数1为DWORD类型，参数2为HANDLE * 类型，参数3为BOOL类型，参数4为DWORD类型。此函数等待参数2指向的数组中包含的所有事件。如果不超时，当参数3为FALSE时，只要有一个事件处于有信号状态，函数就返回这个事件的索引。参数3为TRUE时，等待所有事件都处于有信号状态时才返回。</TD></TR>
<TR>
<TD width="38%">MsgWaitForMultipleObjects</TD>
<TD width="62%">参数1为DWORD类型，参数2为LPHANDLE类型，参数3为BOOL类型，参数4为DWORD类型，参数5为DWORD类型。此函数功能上同WaitForMultipleObjects函数相似，只是多了一个唤醒掩码。唤醒掩码都是和消息有关的。此函数不但能够为事件等待，还能为特定的消息等待。其实这个函数就是专为等待消息而定义的。</TD></TR>
<TR>
<TD width="38%">MsgWaitForMultipleObjectsEx</TD>
<TD width="62%">参数1为DWORD类型，参数2为LPHANDLE类型，参数3为DWORD类型，参数4为DWORD类型，参数5为DWORD类型。此函数是MsgWaitForMultipleObjects函数的扩展。将原来函数的参数3除掉，添加参数5为标志。标志有两个值：0或MWMO_INPUTAVAILABLE。</TD></TR></TBODY></TABLE>
<P>　　如果一个线程既要执行大量任务同时又要响应用户的按键消息，这两个专用于等待消息的函数将非常有用。<BR><BR>和事件有关的函数有： </P><PRE>HANDLE  CreateEvent(LPSECURITY_ATTRIBUTES  lpEventAttributes, 
					BOOL bManualReset, 
					BOOL  bInitialState, 
					LPTSTR  lpName);

BOOL  SetEvent(HANDLE  hEvent );
BOOL  PulseEvent(HANDLE  hEvent);
BOOL  ResetEvent(HANDLE  hEvent);

HANDLE  OpenEvent(DWORD  dwDesiredAccess, 
				  BOOL  bInheritHandle, 
				  LPCTSTR  lpName );
</PRE>　　事件对象是最常用的内核模式同步方法。它包含一个使用计数和两个BOOL变量。其中一个BOOL变量指定这个事件对象是自动重置还是手工重置。另一个BOOL变量指定当前事件对象处于有信号状态还是无信号状态。<BR>　　函数CreateEvent创建一个事件对象，参数1必须为NULL，参数2指定是否手工重新设置事件对象的状态。如果为FALSE，当等待函数接到信号并返回后此事件对象被自动置为无信号状态。这时等待此事件对象的其它线程就不会被唤醒，因为事件对象已经被置为无信号状态。如果参数2设置为TRUE，当等待函数接到信号并返回后事件对象不会被自动置于无信号状态，其它等待此事件对象的线程都能够被唤醒。用ResetEvent函数可以手工将事件对象置为无信号状态。相反SetEvent函数将事件对象置为有信号状态。PulseEvent函数将事件对象置为有信号状态，然后立即置为无信号状态，在实际开发中这个函数很少使用。OpenEvent函数打开已经创建的事件对象，一般用于不同进程内的线程同步。在调用CreateEvent创建一个事件对象时，传递一个名字给参数4，这样在其它进程中的线程就可以调用OpenEvent函数并指定事件对象的名字，来访问这个事件对象。<BR><BR>4、互斥对象<BR>　　互斥对象运行在内核模式。它的行为特性同临界区非常相似，在一个线程访问某个共享资源时，它能够保证其它线程不能访问这个资源。不同的是，互斥对象运行在内核模式，从时间上比临界区要慢。由于内核对象具有全局性，不同的进程都能够访问，这样利用互斥对象就可以让不同的进程中的线程互斥访问一个共享资源。而临界区只能在一个进程内有效。<BR><BR>和互斥相关的函数有： <PRE>HANDLE CreateMutex(LPSECURITY_ATTRIBUTES  lpMutexAttributes, 
				   BOOL  bInitialOwner, 
				   LPCTSTR  lpName);

BOOL  ReleaseMutex(HANDLE  hMutex);      </PRE>　　互斥对象包含一个引用计数，一个线程ID和一个递归计数。引用计数是所有内核对象都含有的。线程ID表示哪个线程正在使用互斥资源，当ID为0时，互斥对象发出信号。递归计数用于一个线程多次等待同一个互斥对象。函数CreateMutex创建一个互斥对象，参数1必须设置为NULL，参数2如果设置为FALSE，表示当前线程并不占有互斥资源，互斥对象的线程ID和递归计数都被设置为0，互斥对象处于有信号状态。如果设置为TRUE，表示当前线程将占有互斥资源，互斥对象的线程ID被设置为当前线程ID，递归计数被设置为1，互斥对象处于无信号状态。当调用等待函数时，等待函数检验互斥对象的线程ID是否为0，如果为0，说明当前没有线程访问互斥资源，内核将线程唤醒，并且将互斥对象的递归计数加1。当一个线程被唤醒后，必须调用函数ReleaseMutex将互斥对象的递归计数减1。如果一个线程多次调用等待函数，就必须以同样的次数调用ReleaseMutex函数。与其它Windows不同的是，和互斥相关的函数中没有OpenMutex函数。要在不同进程中访问同一互斥对象，调用CreateMutex函数，参数传递互斥对象的名称，返回这个互斥对象的句柄。<BR><BR>5、信标对象<BR>　　信标对象，也叫信号灯，用于限制资源访问数量，他包含一个引用计数，一个当前可用资源数，一个最大可用资源数。如果当前可用资源数大于0，信标对象处于有信号状态。当可用资源数等于0，信标对象处于无信号状态。<BR><BR>和信标对象相关的函数： <PRE>HANDLE  CreateSemaphore(LPSECURITY_ATTRIBUTES  lpSemaphoreAttributes, 
		LONG  lInitialCount, 
		LONG  lMaximumCount, 
		LPCTSTR  lpName);

BOOL  ReleaseSemaphore(HANDLE  hSemaphore, 
		LONG  lReleaseCount, 
		LPLONG  lpPreviousCount);</PRE>　　函数CreateSemaphore的参数1为NULL，参数2为当前可用资源初始值，参数3为最大可用资源数，参数4为名字。当参数2的值等于0时，信标对象处于无信号状态，这时内核将调用等待函数的线程置于睡眠状态，如果参数2的值大于0，信标对象处于有信号状态，这时内核将调用等待函数的线程置于运行状态，并将信标对象的当前可用资源数减1。函数ReleaseSemaphore的参数1为信标对象的句柄，参数2为要释放的资源数，参数3返回原来可用资源数，调用此函数将当前可用资源数加上参数2的值。当一个线程访问完可用资源后，应该调用ReleaseSemaphore函数使当前可用资源数递增。要在不同进程中访问同一信标对象，调用CreateSemaphore函数并传递信标对象的名称，得到已经在其它进程创建的信标对象的句柄。CE下没有OpenSemaphore函数。另外我还要说明一点，等待函数默认将信标对象的当前可用资源数减1，但线程可能一次使用多个资源，这就可能出现问题了。为避免问题出现，应该遵守一个线程只使用一个资源的原则。<BR><BR>6、消息队列<BR>　　Windows CE.NET允许一个应用程序或驱动程序创建自己的消息队列。消息队列既可以作为在线程之间传递数据的工具，也可以作为线程之间同步的工具。它的优点是需要很小的内存，一般只用于点到点的通信。<BR><BR>和消息队列相关的函数： <PRE>HANDLE  WINAPI  CreateMsgQueue(LPCWSTR  lpszName, 
			   LPMSGQUEUEOPTIONS  lpOptions);

BOOL  WINAPI  CloseMsgQueue(HANDLE  hMsgQ);
BOOL  GetMsgQueueInfo(HANDLE  hMsgQ, 
		  LPMSGQUEUEINFO  lpInfo);

HANDLE  WINAPI  OpenMsgQueue(HANDLE  hSrcProc, 
			 HANDLE  hMsgQ, 
			 LPMSGQUEUEOPTIONS  lpOptions);

BOOL  ReadMsgQueue(HANDLE  hMsgQ, 
	   LPVOID  lpBuffer, 
	   DWORD  cbBufferSize, 
	   LPDWORD  lpNumberOfBytesRead, 
	   DWORD  dwTimeout,  
	   DWORD  *pdwFlags);

BOOL  WINAPI  WriteMsgQueue(HANDLE  hMsgQ, 
			LPVOID  lpBuffer, 
			DWORD  cbDataSize,  
			DWORD  dwTimeout, 
			DWORD  dwFlags);      </PRE>　　使用CreateMsgQueue函数创建一个消息队列，传递一个MSGQUEUEOPTIONS结构指针。在这个结构中设置标志（允许队列缓冲区动态改变大小，允许直接读或者写操作而不管之前是否有过写操作或读操作）、队列允许的最大消息数、队列属性（只读或者只写）。使用WriteMsgQueue函数把一个消息写入到消息队列中。传递一个消息队列的缓冲区、消息数据的大小、写入缓冲区的超时值、标志。使用ReadMsgQueue函数把一个消息从消息队列中读出。使用CloseMsgQueue函数关闭消息队列缓冲区。使用OpenMsgQueue函数能够打开其它进程中创建的消息队列。另外可以用等待函数等待消息队列的变化。当消息队列由没有消息到有消息时，或由满消息到不满消息时唤醒调用等待函数的线程。关于消息队列我并没有实验过，MSDN上有几个简单的例子。<BR><BR>写作时间：2004-06-04 <BR><BR>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至 fllsoft@sina.com 或windowsce@tom.com]]></description>
</item><item>
<title><![CDATA[Windows CE 进程、线程和内存管理（一）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42441</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:34:57</pubDate>
<description><![CDATA[
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>Windows CE 进程、线程和内存管理（一）<BR></B><BR><BR>作者：<A href="mailto:windowsce@tom.com">付林林</A></P>
<P><BR>　　进程、线程、内存管理是一个内核最基本的服务，也是一个内核最主要的组成部分。这几方面的知识是一个软件开发人员必须掌握的基础知识。虽然一个人不懂这些知识也能编写简单的程序，但这样的程序只能算是皮毛。掌握了进程、线程和内存管理方面的知识，就能够充分利用操作系统内核提供的服务，提高你编写的软件的执行效率、更节省资源、更健壮。顺便说一下，在Windows CE.net下可以运行用Visual Studio.net开发的.net平台上运行的软件，但这样的软件是最上层的软件，离操作系统内核太远了。不但执行效率相对较低，而且还要把.net 框架加到内核中。所以在大多数情况下，EVC仍然是第一选择。<BR><BR><IMG src="http://www.vckbase.com/document/journal/vckbase32/images/fllprothr1img1.JPG" border=0><BR>图一 CE内核结构<BR><BR><B>一、进程和线程</B><BR><BR>1、概念：<BR>　　Windows CE.NET是一个抢占多任务操作系统，抢占多任务又被称为调度。在调度过程中，内核的调度系统包含一个当前所有进程中线程的优先级列表，并对所有的线程按优先级排列顺序。当中断发生时，调度系统重新安排所有线程的排列顺序。<BR>　　一个进程是一个正运行的应用程序的实例。它由两个部分组成：一个是操作系统用来管理这个进程的内核对象。另一个是这个进程拥有的地址空间。这个地址空间包含应用程序的代码段、静态数据段、堆、栈，非XIP（Execute In Place）DLL。从执行角度方面看，一个进程由一个或多个线程组成。一个线程是一个执行单元，它控制CPU执行进程中某一段代码段。一个线程可以访问这个进程中所有的地址空间和资源。一个进程最少包括一个线程来执行代码，这个线程又叫做主线程。<BR><BR>2、进程：<BR>　　Windows CE.NET最多支持32个进程同时运行。这是由整个系统分配给所有进程的总地址空间决定的。低于Windows CE 4.0版本（也就是低于.NET的版本）的CE操作系统，总进程空间从0x0000 0000到0x4200 0000 ，每32MB地址空间为一个槽（Slot），共33个槽。当一个进程启动时，内核选择一个没有被占用的槽作为这个进程的地址空间。其中0x0000 0000到0x01FF FFFF这个槽称为Slot 0。每个进程在即将得到CPU控制权时，将整个地址映射到Slot 0。这个进程在帮助文档中称为当前运行进程（currently running process）。分配一个槽后，内核在这个槽内按由低地址到高地址顺序为代码段、静态数据段分配足够的地址空间，然后是堆、栈，栈之后的空间为所有 DLL保留，包括XIP和非XIP DLL。注意Slot 0最底部64KB是永远保留的。从Slot 1 到 Slot32 为进程使用。前几个槽一般为系统程序使用。如filesys.exe、device.exe、gwes.exe等。<BR>　　Windows CE.NET与低版本操作系统略有不同。这一点是从MSDN 的"Technical Articles"和"Knowledge Base"的文章中找到的，这的确让我费了一番功夫。在Windows CE.NET的帮助文件中只能找到和早期版本相同的说法，而"Technical Articles"和"Knowledge Base"中有几篇文章清楚的说明了Windows CE.NET 和低版本操作系统的不同。在低版本操作系统中，的确如上所说分为33个槽，Slot 0用于当前运行进程，共支持32个进程同时运行。而且所有DLL都加载到进程的地址空间。但Windows CE.NET下 Slot 1也用于当前进程（Slot 1只用于加载所有XIP DLL）。那么一个进程就不是占有32MB地址空间了，而是64MB。在讲解内存管理时我会具体讲解。<BR>创建一个进程的API函数如下： </P><PRE>BOOL  CreateProcess(LPCTSTR  lpApplicationName, LPTSTR  lpCommandLine,
		LPSECURITY_ATTRIBUTES  lpProcessAttributes, 
		LPSECURITY_ATTRIBUTES  lpThreadAttributes, 
		BOOL  bInheritHandles, 
		DWORD  dwCreationFlags, 
		LPVOID  lpEnvironment,
		LPCTSTR  lpCurrentDirectory, 
		LPSTARTUPINFO  lpStartupInfo, 
		LPPROCESS_INFORMATION  lpProcessInformation );
</PRE>　　Windows CE.NET不支持安全性、当前目录、继承性，所以这个函数很多参数都必须设为0或FALSE。具体第3、4、7、8、9设为0，第5设为FALSE。第1参数为应用程序名称，这个参数不能为NULL。如果只传递应用程序名称而没有指定路径，那么系统将先搜索\Windows目录，接着搜索OEM指定的搜索路径。第2参数用于传递启动参数，必须为UNICODE码。第6参数为创建标志。可以为0（创建一个常规进程）、CREATE_SUSPENDED（启动后挂起）、DEBUG_PROCESS（用于创建这个进程的父进程调试用）、DEBUG_ONLY_THIS_PROCESS（不调试子进程）、CREATE_NEW_CONSOLE（控制台进程）。第10参数传递给它一个PROCESS_INFORMATION结构变量的地址。返回进程和主线程的句柄和ID。<BR>　　终止一个进程最好是由WinMain函数返回。在主线程中调用ExitThread函数也可以。在当前进程终止另一个进程使用TerminateProcess函数。CE下的TerminateProcess函数要比其他Windows下TerminateProcess函数功能强大。CE下的TerminateProcess函数在使进程退出时，会通知每个加载的DLL并做出进程退出时该做的所有处理工作。<BR><BR>3、线程：<BR>　　线程除了能够访问进程的资源外，每个线程还拥有自己的栈。栈的大小是可以调整的，最小为1KB或4KB（也就是一个内存页。内存页的大小取决于CPU），一般默认为64KB，但栈顶端永远保留2KB为防止溢出。如果要改变栈初始时大小，在EVC"Project"－"Settings"－"Link"链接选项"/STACK"后的参数中指定大小。其中参数1为默认大小，参数2为一个内存页大小，都用十六进制表示。如果将栈的初始值设置太小，很容易导致系统访问非法并立即终止进程。<BR>　　线程有五中状态，分别为运行、挂起、睡眠、阻塞、终止。当所有线程全部处于阻塞状态时，内核处于空闲模式（Idle mode），这时对CPU的电力供应将减小。<BR>创建一个线程的API函数如下： <PRE>HANDLE  CreateThread(LPSECURITY_ATTRIBUTES  lpThreadAttributes,
		 DWORD  dwStackSize, 
		 LPTHREAD_START_ROUTINE  lpStartAddress,
		 LPVOID  lpParameter, 
		 DWORD  dwCreationFlags,  
		 LPDWORD  lpThreadId );      </PRE>Windows CE.NET 不支持安全性所以参数1必须设置为0。如果参数5为STACK_SIZE_PARAM_IS_A_RESERVATION，那么参数2可以指定栈的大小，内核将按照参数2的数值来为此线程拥有的栈保留地址空间。如果参数5不为STACK_SIZE_PARAM_IS_A_RESERVATION，那么参数2必须设置为0。参数3为执行路径的首地址，也就是函数的地址。参数4用来向线程中传递一个参数。参数5除了上面说明外，还可以为0、CREATE_SUSPENDED。CREATE_SUSPENDED表示这个线程在创建后一直处于挂起状态，直到用ResumeThread函数来恢复。最后一个参数保存函数返回的创建的线程ID。<BR>　　退出一个线程同退出一个进程有类似的方法。最好是由函数返回，在线程中调用ExitThead函数也可以。在当前线程中终止另一个线程使用TerminateThread函数。此函数在使一个线程退出时，会通知这个线程加载的所有DLL。这样DLL就可以做结束工作了。<BR>　　Windows CE.NET不像其他Windows操作系统将进程分为不同的优先级类，Windows CE.NET只将线程分为256个优先级。0优先级最高，255最低，0到248优先级属于实时性优先级。0到247优先级一般分配给实时性应用程序、驱动程序、系统程序。249到255优先级中，251优先级（THREAD_PRIORITY_NORMAL）是正常优先级。255优先级（THREAD_PRIORITY_IDLE）为空闲优先级。249优先级（THREAD_PRIORITY_HIGHEST）是高优先级。248到255优先级一般分配给普通应用程序线程使用。具体分段见下表：<BR>　 
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 height=105 cellSpacing=4 cellPadding=4 width="59%" bgColor=#ffffff border=1>
<TBODY>
<TR>
<TD align=middle width="50%" height=20><B>优先级范围</B></TD>
<TD align=middle width="50%" height=20><B>分配对象</B></TD></TR>
<TR>
<TD align=middle width="50%" height=21>0-96</TD>
<TD width="50%" height=21>高于驱动程序的程序</TD></TR>
<TR>
<TD align=middle width="50%" height=20>97-152</TD>
<TD width="50%" height=20>基于Windows CE的驱动程序</TD></TR>
<TR>
<TD align=middle width="50%" height=20>153-247</TD>
<TD width="50%" height=20>低于驱动程序的程序</TD></TR>
<TR>
<TD align=middle width="50%" height=20>248-255</TD>
<TD width="50%" height=20>普通的应用程序</TD></TR></TBODY></TABLE>
<P>　　Windows CE.NET操作系统具有实时性，所以调度系统必须保证高优先级线程先运行，低优先级线程在高优先级线程终止后或者阻塞时才能得到CPU时间片。而且一旦发生中断，内核会暂停低优先级线程的运行，让高优先级线程继续运行，直到终止或者阻塞。具有相同优先级的线程平均占有CPU时间片，当一个线程使用完了CPU时间片或在时间片内阻塞、睡眠，那么其他相同优先级的线程会占有时间片。这里提到的CPU时间片是指内核限制线程占有CPU的时间，默认为100ms。OEM可以更改这个值，甚至设置为0。如果为0，当前线程将一直占有CPU，直到更高优先级线程要求占有CPU。这个调度算法好像是很有效、很完美，但却存在着一种情况，当这种情况发生时程序会死锁。举例来说：一个应用程序包含两个线程，线程1是高优先级，线程2是低优先级，当线程1运行过程中处于阻塞时，线程2得到时间片，线程2这次进入了一个临界区，我们都知道临界区内的资源是不会被其它线程访问的，当线程2正运行时，线程1已经从阻塞状态转变为运行状态，而这次线程1却要访问线程2的资源，这个资源却被临界区锁定，那么线程1只能等待，等待线程2从临界区中运行结束并释放资源的独占权。但是线程2却永远不会得到时间片，因为CE保证高优先级线程会先运行。这时程序就会处于死锁状态。当然系统不会死锁，因为还有更高优先级的线程、驱动程序在运行。对于这种情况，CE采取优先级转换的办法来解决。就是当发生这种情况时，内核将线程2的优先级提高到线程1的优先级水平。这样线程2就可以执行完临界区代码了，线程1也就能够访问资源了。然后内核再恢复线程2原来的优先级。<BR>　　挂起一个线程使用SuspendThread函数。参数只有一个――线程的句柄。要说明的是如果要挂起的线程正调用一个内核功能，这时执行此函数可能会失败。需要多次调用此函数直到函数返回值不为0xFFFFFFFF，说明挂起成功。恢复线程使用ResumeThread函数。参数也只有一个――线程的句柄。<BR>　　关于线程本地存储器和纤程，实际用到的时候非常少，这部分知识可以参考《Windows核心编程》。<BR><BR>写作时间：2004-06-01 <BR><BR>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至 fllsoft@sina.com 或windowsce@tom.com</P></TD></TR></TBODY></TABLE>]]></description>
</item><item>
<title><![CDATA[模拟器和远程调试工具（二）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42440</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:34:11</pubDate>
<description><![CDATA[<A>　
<P align=center><B>模拟器和远程调试工具（二）<BR></B><BR><BR>作者：</A><A href="mailto:windowsce@tom.com">付林林</A><A></P>
<P></A><A href="http://www.vckbase.com/document/viewdoc/?id=1139" target=_blank>模拟器和远程调试工具（一）</A><A><BR><BR>3、远程堆查看程序（Remote Heap Walker）<BR><BR>　　显然“Walker”不能翻译成“查看”。你愿意怎么翻译就怎么翻译吧。在这里我就叫查看了，这样容易理解。从名字就可以看出它是用来查看操作系统中每个进程使用的堆的情况。什么是堆就不用我说了吧！堆大家都了解，但是CE下关于堆的知识也值得一说。以后我会有专门讲述CE下进程、线程、和内存管理方面的文章发表，那时会说的非常详细。利用“远程堆查看程序”能够查看到：</P>
<DIR>
<LI>内核中正运行的进程的名字、ID、及使用的所有的堆的ID。如图1所示 
<LI>进程的一个堆中的首地址、结束地址、及标志（Fixed、Free） 
<LI>堆中每块（Block）的实际内容 </LI></DIR>
<P><IMG height=228 src="http://www.vckbase.com/document/journal/vckbase32/images/emulator2img1.jpg" width=382 border=0><BR>图 1 进程列表<BR><BR>　　查看和使用这个工具是很简单的事情。不过前提是你对堆有所了解。下面我说说和这个工具有关的涉及到堆的知识。CE下每个进程启动时，系统会自动分配192KB的局部堆给这个进程。要是不够你就通过调用堆管理函数自己再分配自定义大小的堆。自己分配的堆，包括系统给你分配的堆，都由ID来标识。如图中所示的Heap ID。进程ID和进程名就不用说了。后面的“Flag”只有一个值“HF32_DEFAULT”，表示系统默认分配的堆。 图中的“device.exe”，有四个都一样。但从进程ID就可以看出这是一个进程，而不是一个应用程序的四个实例。因为列表是以堆为单位，“device.exe”分配了四个堆，就显示了四个。<BR>　　双击列表中任何一项，弹出这个堆的包含的块的列表。列出块的首地址、大小、标志。我们分配了一个堆，就可以在堆中分配数组、结构等。这个块就指每个分配的元素。标志中“Fixed”表示不能释放这个块占据的内存空间。而“Free”则相反，表示能够释放这个块占据的内存空间。看到这，也许你会有疑问，标志给谁看的？由谁释放啊？这涉及到内存的管理，内核的OOM组件。在以后的文章中我会做详细的解释。<BR>　　再单击每个块，在弹出的列表中就可以看到这个块的具体数据了（用ASCII表示）。<BR><BR>4、远程内核跟踪程序（Remote Kernel Tracker）<BR><BR>　　远程内核跟踪程序用于跟踪实际平台内核的执行情况，一般使用它跟踪内核的启动情况，搜集相关数据并在开发平台上以图表形式显示出来。如图2所示。这个程序能够跟踪所有的进程和进程中的所有线程。从开始运行起跟踪，跟踪结束的时间由数据接收缓冲区的大小决定。缓冲区的大小从1MB到100MB，由用户自己设定。跟踪内容为进程的运行、不运行，线程的运行、阻塞、睡眠。还有在线程中所有发生的同步事件。在此程序的右边有所有能够跟踪的事件对应的图标。<BR>　　要跟踪一个内核的启动情况（启动后也能使用这个程序跟踪所有进程、线程），先要使你要跟踪的内核具有几个特性。在PB菜单“Platform”－“Settings”－“Build Options”中复选“Enable Event Tracking During Boot”和“Enable Profiling”。然后重新编译整个内核。（注意PB在编译时可能提示产生错误，如果出现这种情况，那就重建内核）<BR>　　编译成功后，先打开远程内核跟踪程序，在弹出的平台选择对话框中（上篇文章中包含此图）按“OK”，这时立刻单击PB的“Download/Initialize”调出模拟器。等待一会远程内核跟踪程序就将整个启动过程中发生的所有情况记录并显示出来。哪个时间段线程运行或睡眠或阻塞，包括发生了哪个中断都记录下来。缺点就是数据量太大。这个程序提供了查找功能和过滤功能。查找功能可以查找你想要找的事件，而过滤功能可以让此程序只记录你想要的事件。最后还可以保存这些记录数据。保存到扩展名为“.clg”的文件中。<BR>　　这个工具我只能讲这么多了，平时很少用到。<BR><BR><IMG height=252 src="http://www.vckbase.com/document/journal/vckbase32/images/emulator2img2.JPG" width=847 border=0><BR>图 2 跟踪情况截图<BR><BR>5、远程性能监视程序（Remote Performance Monitor）<BR><BR>　　这个程序的界面大家一定很熟悉了。和其它Windows操作系统中附带的性能监视器界面非常相似。如图3所示。操作也几乎一样。<BR><BR><IMG height=268 src="http://www.vckbase.com/document/journal/vckbase32/images/emulator2img3.JPG" width=331 border=0><BR>图3 性能监视程序截图<BR><BR>　　它能够监视Remote Access Server (RAS)、Internet Control Message Protocol (ICMP)、TCP/IP、User Datagram Protocol (UDP)、Memory、Battery、System、Process、Thread。<BR><BR>6、远程进程浏览程序（Remote Process Viewer）<BR><BR>　　此程序共三个窗口，分别显示当前内核中所有进程、进程中的线程、及进程中所有加载的DLL。在显示进程的窗口中，分别显示进程名、进程ID、基本优先级级别、拥有的线程总数、基地址、访问键值、主窗口名。在显示线程的窗口中，分别显示线程ID、当前进程ID、线程优先级、访问键。在显示DLL模块的窗口中，分别显示模块名、模块ID、当前进程使用计数、全局使用计数、基地址、大小、模块句柄、路径。这里要说明的是线程窗口中的“当前进程ID”。当前进程ID会有不相同的时候，奇怪！线程窗口应该显示当前指定进程下的所有线程，怎么会有不同的进程ID呢？因为CE帮助中什么都没说，所以请允许我设想一下：比如“explorer.exe”，它包含的线程其中就有两个在“当前进程ID”中显示为进程“gwes.exe”，“gwes.exe”为图形、窗口、事件子系统。它负责图形和窗口以及窗口消息。“explorer.exe”的线程在显示窗口、收发消息时调用“Coredll.dll”（以后会具体讲解此DLL），这个DLL调用图形、窗口、事件模块。这些模块实际上是驱动程序（DLL），由gwes.exe来加载、管理这些DLL。所以显示当前进程为“gwes.exe”。目前只能这么解释了。<BR><BR><IMG height=323 src="http://www.vckbase.com/document/journal/vckbase32/images/emulator2img4.JPG" width=469 border=0><BR>图4 进程浏览程序截图<BR><BR>7、远程注册表编辑程序（Remote Registry Editor）<BR><BR>此程序和其它Windows 操作系统下的注册表编辑器非常相似。但它能够显示、编译开发平台下的注册表和实际平台下的注册表。如图5所示。具体操作我就不多说了。CE下注册表的限制我也曾说过了。<BR><BR><IMG height=238 src="http://www.vckbase.com/document/journal/vckbase32/images/emulator2img5.JPG" width=375 border=0><BR>图5 注册表编辑器截图<BR><BR>8、远程消息监视程序（Remote Spy）<BR><BR>　　这个程序和VC下附带的工具spy非常相似。能够列出所有实际平台下的窗口和窗口消息。我想这个程序也不用我多说了吧。熟悉VC下的工具，就能操作这个工具。界面如图6所示。<BR><BR><IMG height=356 src="http://www.vckbase.com/document/journal/vckbase32/images/emulator2img6.JPG" width=456 border=0><BR>图6 消息监视界面截图<BR><BR>9、远程系统信息（Remote System Information）<BR><BR>这个工具能够查看实际平台的系统信息，包括硬件和软件的信息。<BR><BR><IMG height=289 src="http://www.vckbase.com/document/journal/vckbase32/images/emulator2img7.JPG" width=329 border=0><BR>图7 系统信息截图<BR><BR>10、远程屏幕截图程序（Remote Zoom-in）<BR><BR>　　此工具能够截取实际平台屏幕图像。这个工具最适合写说明书了。假如一个产品要推向市场，那说明书或演示程序必须准备好。用这个程序截图放到说明书或演示程序中。截图单击“File”－“New Bitmap”。这个程序的界面我就不给出了。<BR><BR>总结<BR><BR>　　10个远程工具为我们开发内核和应用程序提供了很大的帮助。显然有些工具很少用，有些工具常用。对于注册表编辑器，在一些嵌入式网站有源码和程序可下载。把注册表编辑器带到内核中运行会更有效，更节省时间。这10个工具中，最帅的就是内核跟踪程序了，通过内核跟踪程序，整个内核启动过程清晰可见。还可以用它监视你的应用程序。有一点还要说明：这10个远程调试工具我是在模拟器上试验的。如果要调试实际平台，必须先通过串口、网卡把开发平台和实际平台连接起来。在“Target”－“Configure Remote Connection”中设置。实际设备最好有网卡，串口的速度太慢了。这方面请参考帮助文件，帮助文件中说的非常详细了。<BR><BR>写作时间：2004-5-24 <BR><BR>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至：fllsoft@sina.com或windowsce@tom.com<BR><BR></P></A>]]></description>
</item><item>
<title><![CDATA[模拟器和远程调试工具（一）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42439</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:33:46</pubDate>
<description><![CDATA[
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>模拟器和远程调试工具（一）<BR></B><BR>作者：<A href="mailto:windowsce@tom.com">付林林</A></P>
<P><BR>本篇文章主要讲解远程调试工具的使用方法，并采用PB下的模拟器来试验。<BR><BR><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> <B>一、模拟器</B><BR><BR>　　模拟器（emulator）是PB的一部分。专用于调试在PB下创建的内核及应用程序。模拟器能够在PC下模拟Windows CE的物理平台。但实际上它只不过是一个运行在CPU Ring 3级别的用户程序。所以它的运行速度要比正常的物理平台要慢。模拟器仅支持x86平台，它只能调试基于"Emulator：x86"的内核和所有用x86指令集编译的程序。模拟器有很大的限制，这些限制使它只能运行简单的应用程序。但是有一点是好的，操作系统的大多数API对于任何CPU而言都是相同的。考虑到不受CPU类型影响的简单程序，还是可以在模拟器上调试的。在实际开发中我只用模拟器调试软件的界面。如果你想学习PB，用它来创建内核而又没有实际的物理平台来调试，那么模拟器非常适合。但模拟器的限制也非常多，所以即使你在模拟器下调试过所有能够调试的内核特征（feature，或者称组件），你掌握的也不过是冰山一角。在这里顺便说一句闲话：PB包含的知识量和Windows CE本身的知识量加起来好比是浩瀚的海洋，比用EVC开发软件的知识量大的多。所以如果你只会用EVC开发几个软件，那你仍然是Windows CE专业的一年级学生。<BR><BR><IMG height=358 src="http://www.vckbase.com/document/journal/vckbase32/images/simulatorimg1.gif" width=502 border=0><BR>图一 模拟器<BR><BR>关于模拟器的硬件限制：（摘自Windows CE帮助）</P>
<DIR>
<LI>仅能模拟DEC 2114x 网卡驱动来提供网络支持，其它网卡驱动不支持。 
<LI>仅能模拟FLAT显示驱动，其它显示驱动不支持。 
<LI>不支持USB设备 
<LI>不支持PC卡、硬盘、CF卡。 
<LI>不支持FAT、CDFS、DVD文件格式。 
<LI>不支持IEEE1394设备。 </LI></DIR>
<P>微软推荐的使用模拟器的开发平台的配置：</P>
<DIR>
<LI>1 GHZ CPU 
<LI>384 MB 系统内存 
<LI>500 MB 可用硬盘空间 </LI></DIR>
<P><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> <B>二、创建内核并下载到模拟器上</B><BR><BR>为了测试远程调试工具，我们必须先使用PB v4.1创建一个内核：</P>
<OL>
<LI>使用新内核向导创建内核：单击PB的"New Platform"，在"step 2"的"Available BSPs"中选择"EMULATOR：X86"，接着在"step 3"的"Available configurations"中选择一个预定义的内核配置，这些预定义的内核配置具有针对性。在这里我们选择"Internet Appliance"。注意：有些预定义的内核配置附带了外壳程序，有些没带。选择之后，填写工程名（例如Internet）和工程的保存路径。默认"step 4"的选项，在"step 5"中去掉所有复选框。因为我们只要内核和外壳就行了。同样去掉"step 6"中所有复选框。最后一步单击"Done"按钮。 
<LI>编译这个新内核：在菜单"Platform"-"Settings"－"Environment"中，单击"New"，在弹出对话框中添加变量名"IMGRAM64"和值"1"。然后单击"Build"－"Build Platform"开始编译。具体编译的时间和你的机器配置有很大关系。 
<LI>配置模拟器：编译成功后，单击"Target"－"Configure Remote Connection"，在弹出的对话框中找到"Download"下拉框，选择"Emulator"，单击后面的"Configure"，在弹出的对话框中设置分辨率为800 X 600或640 X 480。系统内存设置为64MB。单击"OK"后，在"Kernel"下拉框中选择"Emulator"。单击"OK"。 
<LI>启动模拟器：单击"Target"－"Download Initialize"。等待一会，模拟器就出来了。可以看到标准外壳的界面，也就是桌面。 </LI></OL>
<P>在PB中编写一个应用程序并下载到模拟器：</P>
<OL>
<LI>在打开了内核工程的前提下（如果刚打开PB，先打开一个内核工程）。单击PB菜单"File"－"New Project or FIle"。余下的步骤我就不用多说了。和EVC一样，添个工程名就可以编码了。只是没有MFC、ATL的支持。在这里选择一个显示"hello world"的SDK程序。然后单击"Build"－"Build All"编译程序。我一般不在PB下开发软件。麻烦！ 
<LI>在模拟器已经打开的前提下，单击"Target"－"Run Programs"，找到你的程序名，再单击"Run"。在模拟器上就可以看到你的程序了。 
<LI>关闭操作：先关闭模拟器。再单击"Target"－"Disconnect"。 </LI></OL>
<P><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> <B>三、远程调试工具</B><BR><BR>　　远程调试工具用于在开发平台与实际平台间执行一些调试工作。利用远程调试工具可以在开发平台运行，得到实际平台上的文件、监视实际平台上进程或线程的状况、测试实际平台上应用程序的性能等。一部分远程调试工具是我们在开发应用程序时常用的，还有一部分是开发内核时常用的。对于不常用的远程调试工具，我在这里只给予简单的介绍。</P>
<OL>
<LI>远程调用评测程序（Remote Call Profiler）<BR>远程调用评测程序包含评测和分析工具。利用图形图像显示出你的代码中算法的效率或者占用资源情况。远程调用评测程序包含一系列接口函数。在你想要评测的代码中调用这些接口函数，然后运行你编写的程序。远程调用评测程序就能够接收你编写的程序的数据并记录。最后利用图形显示出来。显然这个工具我们并不常使用。尤其是开发非实时性内核和软件，加上目前的嵌入式微处理器处理速度已经很快了，对于一个软件性能的评测的需求就淡化了。当然，你要是愿意，觉得有必要也可以研究一下它的接口函数，然后测一下。我目前可没这个时间。微软提供了例子程序"hello"。如果你把Windows CE安装在C盘，那么路径为：<BR>"C:\Program Files\Common Files\Microsoft Shared\Windows CE Tools\Platman\sdk\wce410\samples\hello"。<BR>我编译了此程序并测试了一下。远程调用评测程序显示的图形如图2所示。<BR><BR><IMG height=322 src="http://www.vckbase.com/document/journal/vckbase32/images/simulatorimg2.gif" width=670 border=0><BR>图二 评测图像截图<BR>　 
<LI>远程文件浏览程序（Remote File Viewer）<BR>远程文件浏览程序是最常用、最易操作的工具之一。它的作用就是向实际平台导入或导出文件。假如我在调试一个显示位图的程序。位图允许用户自行选择。那么要在模拟器下调试，就必须将位图文件先导入到模拟器中。<BR>模拟器打开后，运行远程文件浏览程序，先弹出一个对话框，要你选择访问哪个平台。如图3所示。<BR><BR><IMG height=321 src="http://www.vckbase.com/document/journal/vckbase32/images/simulatorimg3.gif" width=288 border=0><BR>图三 平台选择对话框<BR><BR>选择"Default Device"后。主窗口就显示出来了。如图4所示。左边目录数列出的是模拟器运行的内核的所有目录。右边是指定目录下的文件。工具栏最右边是导入和导出按钮。这里的导入和导出是相对开发平台而言的。接上个例子所说，要将一个位图文件传到模拟器中。先单击导出按钮（上箭头），在弹出的对话框中选择一个位图文件，确定后，这个文件就出现在远程文件浏览程序的右边文件窗口中了。导入也简单，先选择一个文件，再单击导入按钮，再选择路径，保存即可。<BR><BR><IMG height=233 src="http://www.vckbase.com/document/journal/vckbase32/images/simulatorimg4.gif" width=375 border=0><BR>图四 远程文件浏览程序截图 </LI></OL>
<P><BR>写作时间：2004-5-21 <BR><BR><B>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至：<BR><A href="mailto:fllsoft@sina.com">fllsoft@sina.com</A> 或 windowsce@tom.com</B><BR><BR></P></TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231900409875&amp;lmt=1231900409&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231900409875&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1139&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Ffllsoft%2F&amp;frm=0&amp;ga_vid=1107318838.1231900410&amp;ga_sid=1231900410&amp;ga_hid=256852652&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=15" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1139" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1139" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1139&amp;title=%c4%a3%c4%e2%c6%f7%ba%cd%d4%b6%b3%cc%b5%f7%ca%d4%b9%a4%be%df%a3%a8%d2%bb%a3%a9" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 有是肯定的！但不能给。公司花几万买的。我会随便给你吗。另外就算给，只有x86的就要1个GB。网上有下。自己找。 ( fllsoft 发表于 2004-5-24 14:41:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> boyren1979@hotmail.com ( boyren 发表于 2004-5-24 10:21:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 楼主有platform&nbsp;builder&nbsp;？能否给俺一份啊 ( boyren 发表于 2004-5-24 10:20:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 谢谢！ ( xiaojin 发表于 2004-5-21 22:35:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 楼主的pb是指platform&nbsp;builder? ( cool_ice 发表于 2004-5-21 19:38:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 图片下载后再看就能看到清晰的了。 ( fllsoft 发表于 2004-5-21 19:05:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 我传的都是清晰的图片。怎么在这显示的却被缩小了。失真太多了。 ( fllsoft 发表于 2004-5-21 19:02:00)<BR>&nbsp;<BR>.......................................................<BR><A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1139" target=_blank><SPAN class=small>More...</SPAN></A> </TD></TR></TBODY></TABLE></DIV>]]></description>
</item><item>
<title><![CDATA[EVC开发实例三：浏览器窗口]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42438</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:33:23</pubDate>
<description><![CDATA[<A>　
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>EVC开发实例三：浏览器窗口<BR></B><BR><BR>作者：<A href="mailto:windowsce@tom.com">付林林</A></P>
<P><BR>　　曾有人问过我关于显示GIF图片的问题。我的回答是要么使用国外做的专用于显示GIF图片的类，要么利用IWebBrowser COM组件。我还说过我用它做过自定义的IE。其实微软提供了CE下IE的源程序，就是利用这个组件来实现IE的各个功能的。我做IE是因为Windows CE自带的IE在各个方面都不符合我们的要求，于是我在IE源程序的基础上编写了符合我们要求的浏览器。这篇文章并不是要讲解如何做浏览器，而是用IWebBrowser组件来显示GIF、JPG图片，当然也能够显示网页。做浏览器要比只显示图片、网页要复杂的多。<BR>　　在Pocket PC下开发的人多数会感到奇怪，不是有HtmlView吗？怎么还要自己写啊。因为微软为Pocket PC定制的内核包含的SDK中，含有支持网页浏览的控件，而在Windows CE.NET中没有。这就需要自己实现了。在这里我采用模拟器来试验。模拟器能通过实际平台就更能通过了。操作基本一样。<BR><BR>一、制作带有IE的内核<BR>　　在模拟器下定制一个内核大家应该不会陌生了，以前的文章中我已经讲过不止一次了。所以在这里我会简略的说明如何定制内核。第一步，选择"EMULATOR：X86"，之后在"Available configuration"中选择"Internet Appliance"。接着只选择"Internet Browser"，第五步只选择"Internet Browser"，第六步去掉所有网络选项，最后"Done"。编译前加入环境变量"IMGRAM64"，然后选择"EMULATOR：x86 Win32{WCE emulator} Release"，开始编译。编译后单击"Download/Initialize"<BR>　　下载内核到模拟器上运行。单击桌面上的IE快捷图标，如果IE显示出来那第一步就成功了。<BR><BR>二、编写程序<BR>　　这个程序我用EVC单独编写编译。而不在PB下编写。PB附带的应用程序向导没有MFC的选项，不如用EVC编写方便，编译后再将程序复制到内核目录下，通过PB来运行。<BR>　　编写程序我也不会过于详细的讲解，只讲解相关的代码。调出EVC，利用应用程序向导产生一个MFC程序，假设工程名为IEGIF，第一步选择单文档程序，语言下拉列表随便选。第二步复选"Windows Sockets"和"ActiveX Controls"，其它步骤默认。<BR>　　利用IWebBrowser组件前当然要先包含它需要的头文件了。在"stdafx . h"中添加下列文件名： <BR></P><PRE>/////////////////////
#undef __urlmon_h__
#include &lt;urlmon.h&gt;

#undef _SHLOBJ_H_
#include &lt;shlobj.h&gt;

#undef _WINNETWK_
#include &lt;winnetwk.h&gt;

#undef __AFXHTML_H__
#include "afxhtml.h"
</PRE>在IEGIFView . h文件里CIEGIFView类的声明中添加下列代码： <PRE>public:
	IWebBrowser2  *m_pBrowser;
	CWnd         m_wndBrowser;   ////IWebBrowser2 的容器
	void           Navigate(CString); ///调用IWebBrowser2的Navigate接口
</PRE>在CIEGIFView类的构造函数中初始化指针： <PRE>CIEGIFView:: CIEGIFView ()
{
	m_pBrowser = NULL;
}
</PRE>在IEGIFView . cpp文件里定义下列函数： <PRE>void  CIEGIFView::Navigate(CString  strURL)
{
	COleVariant     empty;
	BSTR bstrURL = strURL.AllocSysString();
	m_pBrowser-&gt;Navigate (bstrURL, COleVariant((long) 0, VT_I4), empty, empty, empty);
}
</PRE>在IEGIFView . cpp文件里定义消息处理函数： <PRE>///响应WM_CREATE
int CIEGIFView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	////////////////////////////////////////////////////
	RECT rectClient;
	GetClientRect(&amp;rectClient);  ///在客户端整个区域显示GIF
	///创建控件
	if (! m_wndBrowser.CreateControl(CLSID_WebBrowser, NULL, WS_VISIBLE | WS_CHILD, 
	rectClient, this, AFX_IDW_PANE_FIRST))
	{
		DestroyWindow();
		return FALSE;
	}
	///得到指针
	LPUNKNOWN lpUnk = m_wndBrowser.GetControlUnknown();
	HRESULT hr = lpUnk-&gt;QueryInterface(IID_IWebBrowser2, (void**) &amp;m_pBrowser);
	if (! SUCCEEDED(hr))
	{
		m_pBrowser = NULL;
		m_wndBrowser.DestroyWindow();
		DestroyWindow();
		return FALSE;
	}
	
	return 0;
}

///响应命令ON_COMMAND(ID_OPENGIF, OnOpenGIF) 
///随便用一个按钮，或菜单项。ID为ID_OPENGIF
void CIEGIFView::OnOpenGIF() 
{
	CFileDialog  dlg(TRUE, NULL, NULL);	
	if(dlg.DoModal() == IDCANCEL)
	{
		return;
	}
	Navigate(dlg.GetPathName());
}
</PRE><IMG src="http://www.vckbase.com/document/journal/vckbase32/images/evcemulator3img1.JPG" border=0><BR>图1 显示GIF截图 <BR><BR>注：代码一部分源至 MSDN<BR><BR>写作时间：2004-5-25 <BR><BR>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至：fllsoft@sina.com或windowsce@tom.com <BR><BR>
<P></P></TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231900381812&amp;lmt=1231900381&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231900381812&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1145&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Ffllsoft%2F&amp;frm=0&amp;ga_vid=1432303634.1231900382&amp;ga_sid=1231900382&amp;ga_hid=805360014&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=16" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1145" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1145" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1145&amp;title=EVC%bf%aa%b7%a2%ca%b5%c0%fd%c8%fd%a3%ba%e4%af%c0%c0%c6%f7%b4%b0%bf%da" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 包含的头文件不要那么复杂，只要包含Exdisp.h就可以了<BR>( yulincat 发表于 2004-7-7 17:33:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> ok ( tyong_obt 发表于 2004-6-3 13:30:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 用IWebBrowser控件，凡是IE能实现的，开发者都能够实现。而且还能够自定义浏览器。 ( fllsoft 发表于 2004-5-29 14:57:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> ppc或sp2002和2003下用<BR>SHLoadImageFile(_T("\\temp\\xxx.jpg"));<BR>就可以显示静态gif,jpg,bmp,png图像<BR>( nbcool 发表于 2004-5-28 19:31:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 已经修改，对不起！ ( hangwire 发表于 2004-5-28 17:41:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 题目标错了！！！！ ( fllsoft 发表于 2004-5-28 16:20:00)<BR>&nbsp;<BR>.......................................................<BR><A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1145" target=_blank><SPAN class=small>More...</SPAN></A> </TD></TR></TBODY></TABLE></DIV><BR></A>]]></description>
</item><item>
<title><![CDATA[EVC开发实例二：保存信息（二）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42437</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:32:50</pubDate>
<description><![CDATA[<A>　
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>EVC开发实例二：保存信息（二）<BR></B><BR>作者：<A href="mailto:fllsoft@sina.com">付林林</A></P>
<P><B><BR>保存到注册表中</B><BR><BR>这一章主要讲解Windows CE.NET下注册表相关技术。<BR><BR>1、 对象存储（object store）<BR>　　对象存储是Windows CE默认的数据存储机制。任何新创建的内核中都默认包含对象存储器。对象存储的实质是在RAM中创建一个文件系统，将文件保存在RAM中，这些文件来源于ROM。当设备启动时，引导程序将ROM中的内核文件解压并存放在RAM中。"\windows"目录就是基于对象存储的。对象存储的特点是文件可以压缩、支持事务机制（和数据库中的事务机制相似）、数据I/O相对较快。</P>
<DIR>
<LI>A、对象存储中的对象类型包括文件、目录、数据库、记录、数据库卷。CE为每个对象分配一个对象ID（CEOID）。访问或者操作任何对象的前提是得到该对象ID。 
<LI>B、CE能够自动压缩所有对象存储中的文件（CE提供了一个选项供OEM设置是否能够自动压缩文件）。所以文件没有压缩或不压缩的标志，但是有一个标志，标明此文件存在于ROM还是RAM中。一个文件最大长度可达到4 GB。 
<LI>C、CE提供了三种文件系统：基于ROM的文件系统、基于RAM的文件系统、FAT文件系统。 </LI></DIR>
<P>2、 注册表（registry）<BR>　　CE下注册表和其它windows操作系统中注册表概念和结构基本相同。</P>
<DIR>
<LI>A、CE下注册表限制：键名最大长度255个字符； 数据最大 4KB；子键深度最大值 16层。 
<LI>B、根键有HKEY_CLASSES_ROOT、HKEY_CURRENT_USER、HKEY_LOCAL_MACHINE、HKEY_USERS。 
<LI>C、操作注册表函数：<BR>
<UL>
<LI>i. 打开RegOpenKeyEx 和创建RegCreateKeyEx　 
<LI>ii. 读RegQueryValueEx写RegSetValueEx　 
<LI>iii. 枚举入口或子键RegEnumValue、RegEnumKeyEx　 
<LI>iv. 删除入口或子键RegDeleteValue、RegDeleteKey 　 
<LI>v. 关闭RegCloseKey </LI></UL></LI></DIR>
<P>3、CE下注册表类型<BR>　　分为基于RAM的注册表和基于HIVE的注册表。 
<P>　 
<DIR>
<LI>A、基于RAM的注册表，也叫基于对象存储（oject storage）的注册表。用于将注册表数据全部保存在RAM中。 
<UL>
<LI>i. 从CE v1.0开始到CE .NET之前，仅采用此技术来保存注册表。每个新创建的内核都默认采用此技术来保存注册表。 
<LI>ii. 适合频繁热启动而不冷启动的设备。系统关闭时提供低电源给RAM。如果断电，重新启动设备后，系统将从内核中重新读取注册表数据到RAM。当然以前保存的用户数据已经丢失。 
<LI>iii. 基于RAM的注册表也能够永久保存注册标数据。CE提供了两个机制。 <BR>
<UL>
<LI>1) 第一种机制的设计思路是在设备关闭前调用RegCopyFile函数将整个注册表数据以文件形式保存到永久存储器上。重新启动设备时，调用RegRestoreFile函数将文件全部读出到RAM中。但是这时必须一次热启动才能使恢复的注册表数据有效。所以每次启动就多出一次热启动。好在热启动非常快，几秒钟的时间。 
<P></P>
<LI>2) 另一种机制可以避免前一种机制的需要两次启动的缺点。但也有它的缺点。OEM（原始设备制造商）可以在OAL层编写WriteRegistryToOEM and ReadRegistryFromOEM两个函数，内核在启动时会自动调用ReadRegistryFromOEM函数来读注册表数据。而应用程序调用RegFlushKey函数时，这个函数用调用WriteRegistryToOEM函数写注册表数据到永久存储器上。这个机制避免了两次启动的缺陷。但问题出现在内核启动时，调用ReadRegistryFromOEM之前文件系统驱动程序还没加载，那就无法打开、读取文件。CE帮助文件中说解决办法是将从永久存储器中读取数据的代码加到ReadRegistryFromOEM中。帮助中说的意思可不是调用ReadFile这么简单的，因为文件系统驱动程 序还没加载。 <BR>
<LI>3) 个人建议：如果要采用基于RAM的注册表保存机制，而且要求永久保存注册表数据，使用第一种机制比较容易。</LI></UL></LI></UL>
<LI>B、 基于HIVE的注册表。用于将注册表数据全部或部分保存到永久存储器上。 
<UL>
<LI>i.它是从CE.NET开始采用的新技术。适合经常冷启动而不热启动的设备。 
<LI>ii.支持多用户信息分别保存。当一个用户登录时，加载这个用户的注册表数据，注销时卸载这个用户的注册表数据。 
<LI>iii. HIVE是指一组键，包括子键、键值、数据。是保存或者加载注册表数据的单位。分为系统HIVE（system hive）、用户HIVE（uer hive）、引导HIVE（boot hive）。 
<P></P>
<UL>
<LI>1) 系统HIVE包含了关于系统的设置信息。具体保存注册表中HKEY_LOCAL_MACHINE、HKEY_CLASSES_ROOT、HKEY_USERS键下所有数据。保存系统HIVE的文件的路径在【HKEY_LOCAL_MACHINE\init\BootVars】下，键名为"systemhive"，键值为文件的路径。默认为"\Documents and Settings\system.hv"。<BR>
<LI>2) 用户HIVE包含了一个用户的信息。具体保存注册表中HKEY_CURRENT_USER键下所有数据。保存用户HIVE的文件的路径同样为【HKEY_LOCAL_MACHINE\init\BootVars】下，键名为"profiledir"，键值为所有用户HIVE的共同目录。默认为"\Documents and Settings"，在这个目录下包含了以每个用户名命名的子目录。子目录里含有一个文件，默认文件名为user.hv。<BR>
<LI>3) 引导HIVE保存在ROM（内核）中。具体保存内容同系统HIVE一样。当解压内核并加载注册表时，系统先将引导HIVE数据读出，引导HIVE包含了永久存储器的驱动程序和文件系统的驱动程序，这些驱动加载后，系统HIVE被加载，然后引导HIVE被释放。因为引导HIVE被包含在内核中，所以存在一种情况：如果重新做了一个新内核，引导HIVE中的数据同系统HIVE可能不相同。那么系统该加载哪个版本好呢？为此，CE在生成每个内核时都做了一个标志。而系统HIVE也存在这样一个标志，当加载引导HIVE时，如果引导HIVE和系统HIVE的标志不相同，系统会删除系统HIVE文件，然后重新创建一个文件并从引导HIVE复制数据。 </LI></UL>
<LI>iv. 永久保存注册表数据<BR>Windows CE.NET采用新的注册表保存技术――基于HIVE的注册表，的确让人很兴奋，在这之前基于Windows CE的设备，大多数采用给RAM供电方式来保存注册表数据，虽然也可以通过RegCopyFile函数永久保存，但毕竟启动时还要再热启动一次，有了基于HIVE的技术，启动时系统会自动加载数据，免去了热启动的麻烦，而且当内核更新升级时，你不用担心保存在永久存储器上的系统HIVE文件影响你新的内核，系统会自动判断并删除过时的系统HIVE文件。只有拥有了这样的技术，基于CE的产品才算是一个真正的电脑。<BR>注：关于基于HIVE的注册表的实现，暂不讲解。</LI></UL></LI></DIR>
<P>下面简单说明 RegCopyFile和RegRestoreFile的用法。<BR>///使用基于RAM的注册表，利用RegCopyFile和RegRestoreFile<BR>///实现永久保存注册表数据。 </P><PRE>#include &lt;Pkfuncs.h&gt;
#include &lt;winbase.h&gt;

CString  strRegBackup = L"\\hard disk\\RegBackup.reg";
CString  strTmp      = L"\\windows\\temp.reg";

////////导出。在系统关闭前。  
if(! RegCopyFile(strTmp))     ///导出注册表，用temp.reg做缓冲用。
{
	return FALSE;  
}

if(! CopyFile(strTmp, strRegBackup, FALSE))  ///把temp.reg再复制到RegBackup.reg
{
	return  FALSE;
}

/////////导入。在系统启动时。
if(! CopyFile(strRegBackup, strTmp, FALSE))
{
	return  FALSE;
}

if(! RegRestoreFile(strTmp))  ///恢复注册表
{
	return  FALSE;
}

if(! KernelIoControl(IOCTL_HAL_REBOOT, NULL, 0, NULL, 0, NULL)) ///重新启动
{
	return  FALSE;
}
</PRE>写作时间：2004-5-17 <BR><BR>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至 fllsoft@sina.com或windowsce@tom.com<BR><BR></TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231900348203&amp;lmt=1231900348&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231900348203&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1136&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Ffllsoft%2F&amp;frm=0&amp;ga_vid=1302593603.1231900348&amp;ga_sid=1231900348&amp;ga_hid=1361015819&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=15" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1136" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1136" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1136&amp;title=EVC%bf%aa%b7%a2%ca%b5%c0%fd%b6%fe%a3%ba%b1%a3%b4%e6%d0%c5%cf%a2%a3%a8%b6%fe%a3%a9" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 同你一样，也很少。抓紧时间学习，就一定有好的未来。 ( fllsoft 发表于 2004-5-26 13:19:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 请问朋友你的薪水超过了10k没有？我有一年的PB及EVC经验，薪水可是超低，希望托你的福，来日能涨个几倍！！！ ( onion53_sh 发表于 2004-5-26 8:54:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 从阅读次数就可以看出。大部分人还只是涉足于使用EVC开发程序。很少有人懂PB，更少有人需要PB去定制内核。现在招聘网络游戏开发人员年薪几十万！就是因为懂的太少。定制CE内核也会有这么一天。 ( fllsoft 发表于 2004-5-20 16:26:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 已经修正，对不起！ ( hangwire 发表于 2004-5-19 14:03:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 管理员把#include&nbsp;后面的文字弄丢了。<BR>我添上：<BR>#include&nbsp;&lt;Pkfuncs.h&gt;<BR>#include&nbsp;&lt;winbase.h&gt;<BR><BR>( fllsoft 发表于 2004-5-19 9:08:00)<BR>&nbsp;<BR>.......................................................<BR><A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1136" target=_blank><SPAN class=small>More...</SPAN></A> </TD></TR></TBODY></TABLE></DIV></A>]]></description>
</item><item>
<title><![CDATA[EVC开发实例二：保存信息（一）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42436</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:32:21</pubDate>
<description><![CDATA[<A>　
<P align=center><B>EVC开发实例二：保存信息（一）</B><B><BR></B><BR><BR>作者：</A><A href="mailto:fllsoft@sina.com">付林林</A><A></P>
<P></A><A href="http://www.vckbase.com/code/downcode.asp?id=2364">下载源代码</A><A><BR><BR>　　Windows CE下开发的软件大多和硬件紧密相关。如果写和硬件相关的技术，那只有极少数甚至没有人受益。相反写出来的文章，应该让更多的人受益，都能学而用之。任何一篇文章我都会遵循此原则。当然，本人资质甚浅。只能尽力而为。<BR>　　题目中的信息是指很少量的数据。比如配置信息、注册表数据等。在其它Windows 操作系统中，这些信息一般都被保存在*.ini文件或注册表中。<BR>　　嵌入式设备没有统一的要求。有的不需要保存任何数据，有的将数据保存到RAM中，在关闭设备时仍然给RAM提供电源。有的将数据保存到永久存储设备中。不同的情况有不同的解决方案，这就造成了在保存信息方面，相关的技术很繁杂，要弄明白需要时间和实践。 <BR>　　 在这篇文章中我主要讲解在Windows CE下如何将数据保存到永久存储器中。将数据保存到永久存储器中应用广泛，不易丢失数据，可以保存大量的数据，也不必在系统关闭后还要提供电源。相比较将数据保存到RAM中要不间断地提供电源，存储空间也大大受限制。<BR><BR><B>保存到*.ini文件中</B><BR><BR>　　要想让你定制的内核支持永久存储设备，需要加入对永久存储设备的支持（就是驱动程序），再加入一种文件系统。现在假如我们的存储硬件采用IDE接口，使用一种简单实用的文件系统，FAT是合适的选择。那么对应的操作步骤如下：先使用新内核生成向导，生成一个内核工程后（采用哪个平台模板都行），在PB右边“catalog”窗口中加入对存储设备的支持。在PB v4.1下具体位置为“device drivers”－“storage devices”－“ATAPI PCI/IDE Storage Block Driver”。单击右键，在弹出菜单中单击“add platform”，PB立刻将相关驱动程序源码或者编译后的文件，还有相关环境变量加到你的内核工程中。之后再加入FAT文件系统驱动。FAT组件的具体位置为：<BR>“Core OS”－“display based devices”－“file system and data store”－“storage manager”－“FAT File System”。<BR>　　也用同样方法加入到左边内核工程中。之后设置环境变量。方法是单击PB菜单“platform”－“settings”－“environment”－“new”，输入环境变量名“IMGRAM64”，再输入值“1”。准备工作完成后，编译整个内核。你定制的内核就应该支持永久存储了。 <BR>　　 先不考虑注册表问题。假如我们的产品不需要保存注册表数据，但是需要将一些软件使用的配置信息保存到永久存储器中，那么采用ini文件方式比较适合。不采用注册表保存数据的原因是在定制内核时需要进行复杂的设置，来保证内核能在每次启动时加载保存到永久存储设备上的注册表数据。如果只是几个软件需要使用配置信息，那就没必要为了能够保存注册表数据而设置内核。把话说简单一点：除非必要，能不用注册表保存最好。<BR>　　虽然在EVC帮助文件中提到了MFC库中有CWinApp::WriteProfileInt和CWinApp::WriteProfileString 。而且在EVC下编译也通过了。但实际执行起来函数返回值为0，说明没有任何数据被写到硬盘上。在硬盘或者\windows目录下也没找到ini文件。这看起来很蹊跷，如果你使用EVC开发过，你就见怪不怪了。下面顺便说说我遇到过的“蹊跷”： <BR>　　 A、有的函数在帮助文件中提到过，说CE支持此函数。但在编译时EVC不识别，或者链接时不识别。一种原因可能是你在安装了CE和EVC后，没有调整EVC的编译和链接目录，也就是包含头文件和库文件的目录。EVC在安装后，默认包含头文件和库文件的目录为Standard SDK目录，Standard SDK是专为模拟器准备的，自然内容很少，找不到某个函数也在情理中。解决办法是修改EVC的包含头文件和库文件的目录。位置在菜单“tools”－“options”－“directories”。在“CPUs”栏里选择你的CPU类型，然后分别添加头文件和库文件的目录，添加的头文件目录和库文件目录是安装Windows CE时SDK的目录。举例来说，假如开发平台使用的是x86 CPU。那么在“CPUs”中选择“Win32 (WCE x86)”，然后在“Show directories”中选择“include files”，接着在下面“Directories”栏里添加几个目录。如图1所示（SDK指软件开发包，OAK指OAL开发包，DDK指驱动开发包）。接着在“Show directories”中选择“library files”，把库文件目录也添加进去。如图2所示。另一种原因就是真的没有此函数。CE的帮助文件做的并不好。很多函数的说明、例子代码都是照抄于VC下的帮助文件。只有编译或者运行程序时你才知道这个函数是否存在。<BR>注：如果你是在PB下创建工程编写程序，就不必设置了。<BR><BR><IMG src="http://www.vckbase.com/document/journal/vckbase32/images/evcsaveinfoimg1.gif"><BR>图一<BR><BR><IMG src="http://www.vckbase.com/document/journal/vckbase32/images/evcsaveinfoimg2.gif"><BR>图二 <BR><BR>　　 回到正题。CWinApp::WriteProfileInt和CWinApp::WriteProfileString函数既然不能写数据到永久存储设备。那就必须自己写这两个函数和对应的读取数据函数GetProfileString和GetProfileInt。因为这四个函数使用频率很高，故我将此函数写出来，当你需要的时候就不用再写了。<BR>注：1、函数中所有字符串均为UNICODE。2、采用了MFC类库。3、具体问题见代码。</P>
<P>写作时间：2004-5-14</P>
<P>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至 fllsoft@sina.com或windowsce@tom.com<BR></P></A>]]></description>
</item><item>
<title><![CDATA[EVC开发实例一：窗口切换]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42435</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:31:55</pubDate>
<description><![CDATA[<A>　
<TABLE cellSpacing=0 cellPadding=0 width=900 border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>EVC开发实例一：窗口切换</B><B><BR></B><BR>作者：<A href="mailto:fllsoft@sina.com">付林林</A></P><BR><BR>　　嵌入式平台下应用于民用领域的软件有它的特点：占用资源低、操作界面简单、反应迅速。而且传统观念上嵌入式平台同一时刻只运行一两个程序。这不同于PC下软件，除了实现基本功能外，还要求华丽的界面，甚至换肤，启动N个进程也无所谓。所以在Windows CE下构思一个应用于民用领域的软件时，必须考虑软件的实用性、易操作性等。太复杂的软件就不要想了，也不要妄想把PC下所有软件都移植到Windows CE下。<BR>　　不过随着嵌入式CPU的快速发展和RAM的降价，在Windows CE下实现多个程序同时运行也是可行的。而且运行速度也可以接受。我们公司的产品就能够一边上网，一边听mp3。使用起来真的很爽。 <BR>　　假如我们要做一个产品，需要同时运行多个程序，这就涉及到窗口切换的问题。Windows CE 自带了资源管理器explorer.exe作为外壳程序。它有窗口切换的功能。但是绝大多数基于Windows CE的产品都不可能用这个程序做主界面。原因之一，它的界面和PC下资源管理器一样（也就是桌面），不符合我们这个产品的整体界面风格。可能我的所有应用程序的界面风格都很好，很漂亮，但主界面，既资源管理器的界面和其它所有程序界面风格不相符，那样太糟糕了。而且总用同一个界面，用户迟晚会烦的。原因之二，它将所有资源管理功能都暴露给用户，那样太危险。用户肆意更改资源设置很容易造成设备损坏或无法启动，一旦无法启动，用户一定会来找你维修，但是你很冤。所以我们必须自己做一个主操作界面（也就是一个应用程序，或者称为外壳程序）。这样既可以设计实现自己理想的界面风格，又可以限制留给用户操作的功能。这个想法虽然好，但是如果允许同时运行多个程序，就必须先解决窗口切换问题。好在Windows CE 的API和PC下的差不多，实现也不算难。其实难的是如何把自定义的程序作为外壳程序，替换原来标准的程序。<BR><BR>注：Windows CE允许同时打开最多32个进程，这包括Windows CE启动时系统程序。<BR>注：以后称主操作界面程序为自定义外壳程序。<BR><BR>一、 添加自定义外壳<BR>　　原来打算不讲解PB方面的知识，但是在Windows CE下开发，PB和EVC关系紧密，必须两个都懂，才能做出理想的产品。所以在这里我简单说说PB和CE方面的知识。 <BR>　　1、PB是用于定制操作系统内核的工具，可以把它看成是一个IDE，集成了编辑器（可以编辑源码、注册表等）、编译器（编译操作系统内核、编译某一个模块）、各种向导（生成新内核向导、生成BSP向导、SDK导出向导、组件特征导出向导）、内核调试工具集合、x86模拟器、支持添加或删除组件等。通常情况下，通过PB来添加、删除组件，每个组件对应一个特定的功能。假如你希望你的产品具有上网功能，那你就添加某一种网络连接组件和浏览器组件。假如希望你的产品具有永久存储功能，那你就添加支持永久存储设备的组件，再添加支持某一种文件系统的组件。设置完毕后，编译整个内核。最后PB将编译后的所有功能模块组合在一起，形成一个.bin文件，默认为nk.bin。 <BR>　　2、nk.bin包含了你定义的Windows CE内核的全部。在设备启动时需要解压nk.bin并且将一些系统模块（DLL、EXE）加载到内存中。用于做解压和加载工作的程序，称为引导程序，可以放在ROM中，也可以放在永久存储设备中。当设备启动时先检测硬件，再加载引导程序，引导程序解压nk.bin，再把系统模块加载到特定内存区域中，然后将CPU交给CE内核。<BR>　　3、当你安装完Windows CE.net（Windows CE.net 安装分为三部分。分别安装PB、Platform manager、SDK）后，大部分功能模块的源码被复制到安装目录下。你可以修改这些源码，然后重新编译。这些源码为OEM提供了更广阔的开发空间。也是Windows CE比其它Windows操作系统更适合研究的原因。<BR>　　4、修改源码不是简单的事。使用PB开发一个内核很少需要修改源码，更多的工作是添加组件、添加文件、修改注册表数据、添加环境变量等。要添加自定义外壳，涉及到修改注册表和添加文件两个工作。<BR><BR>下面讲具体操作方法： <BR><BR>以下假设你已经利用生成内核向导建立了一个内核工程。<BR><BR>1、修改注册表： <BR>Windows CE启动时分别执行[HKEY_LOCAL_MACHINE\init]键下所有子键列出的程序。例如：<BR><PRE>      [HKEY_LOCAL_MACHINE\init]<BR>
      "Launch20"＝"device.exe"<BR>
      "Launch30"＝"gwes.exe"<BR>
      "Depend30"＝hex：14,00<BR></PRE>　　内核启动时执行device.exe和gwes.exe 。“LaunchXX”中的XX为序列数，内核依据这个序列数按由小到大的顺序来分别执行所有子键列出的应用程序。“DependXX”为“LaunchXX”的附属键，此键键值表示“LaunchXX”指定的程序需要依靠哪个程序才能启动。例如“Depend30”＝hex：14,00 。十六进制数14等于十进制数20，既gwes.exe需要device.exe先启动后才能启动。因为自定义外壳程序是最后启动的，所以你可以在你的平台选择一个最大的序列数，比如80，然后在common.reg中“[HKEY_LOCAL_MACHINE\init]”键下添加如下格式： <BR><PRE>"Launch80"＝"CustomShell.exe"   ///自定义的外壳程序，名为CustomShell.exe<BR>"Depend80"＝hex：14,00,1E,00    ///依靠gwes.exe 和 device.exe
</PRE>　　添加后千万别忘了把原来默认的标准外壳组件从你的内核中删除，找到“Shell and User Interface－Shell－Standard Shell”，单击鼠标右键，再单机“delete”。当然，你也可以把其它外壳程序都删除。<BR><BR>修改注册表工作到此结束。<BR><BR>2、添加自定义外壳程序： <BR>　　只要允许多个程序同时运行，必然涉及到窗口切换问题，但不同的产品所采取的窗口切换解决方案也不一样。假如你的产品携带标准键盘，那你可以注册一个或几个热键，当热键按下时，启动窗口切换程序，然后选择你要操作的窗口，然后窗口在最上层显示。这比较像Windows下任务管理器。如果你的产品没有携带标准键盘，而是几个按钮，那可以通过串口通讯方式或其它通讯方式解决，当按下按钮时，通过串口发送消息给系统，系统启动窗口切换程序。如果你的产品在外观上没有键盘，也没有任何按钮，那就必须做一个窗口，像任务栏一样，让它时刻保持在所有窗口最上层，以便响应用户操作。具体方法你自己选。我假设现在你用EVC已经把自定义外壳程序做完了。接着要做的是如何将自定义外壳程序放入内核中，也就是nk.bin里。从上述“修改注册表”中你会发现，要启动的程序都只有文件名，而没有路径名，这是因为CE只允许把启动程序放到nk.bin中，nk.bin中的所有文件在CE系统启动后被放在目录\Windows 中。这一步要做的就是把已经做好的自定义外壳程序放入nk.bin中。<BR>先把编译完的程序CustomShell.exe复制到内核工程目录下。假设你的内核名为ABC，内核工程所在路径为C:\ABC，采用CPU为National Geode。那么就复制到“C:\ABC\RelDir\NATIONAL_GEODE_X86Release”目录中。接着在project.bib中添加下列格式：<PRE>CustomShell.exe &nbsp;&nbsp;$(_FLATRELEASEDIR)\CustomShell.exe&nbsp;&nbsp;NK</PRE>
<P>这表示在PB编译内核时将CustomShell.exe加入到nk.bin中，并且在CE内核加载时将CustomShell.exe放入内存的系统区域中，内核启动后在\windows目录下可以看到这个文件。<BR>添加自定义外壳程序工作到此结束。<BR><BR>3、编译内核<BR>　　现在编译前期工作已经完成，单击“Build Platform”或“Rebuild Platform”开始编译。编译前相关设置在此不再多说。启动CE内核后就可以看到自己设计的外壳程序了。<BR><BR>二、实现窗口切换<BR><BR><IMG src="http://www.vckbase.com/document/journal/vckbase31/images/evcswtwinimg1.gif"> <BR>图1 假想的任务管理器界面<BR><BR>　　一个简单的窗口切换实例。打开此程序时，显示所有窗口（不包含隐藏窗口），单击某一个窗口名称，再单击“切换”按钮，相应的窗口出现在Z轴最上层。在这里我只给出了部分重要的代码。此处假设工程名为WndList。 <BR></P><PRE>///全局函数和全局变量
HWND     g_hWndArray[50];  //存所有窗口句柄
CString  g_strArray[50];   //存所有窗口标题
int      g_iWndCount = 0;  //计数器


BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lparam)
{
	CWnd* pWnd = CWnd::FromHandle(hWnd);
	if(hWnd != NULL &amp;&amp; pWnd-&gt;IsWindowVisible())
	{
		pWnd-&gt;GetWindowText(g_strArray[g_iWndCount]);
		if((!g_strArray[g_iWndCount].IsEmpty()) 
		&amp;&amp; (g_strArray[g_iWndCount].CompareNoCase(L"Desktop") != 0)) 
		{ 
			g_hWndArray[g_iWndCount] = hWnd; //保存句柄
			g_iWndCount++; 
		}
	} 
	return TRUE;
}

BOOL CWndList::OnInitDialog()
{
	::EnumWindows((WNDENUMPROC)EnumWindowsProc, 0); /////查找所有窗口
	////创建大字体并加入到列表对象中
	////将所有窗口句柄加入到列表中
}
///相应“切换”按钮
void CWndList::OnOK()
{
	::SetForegroundWindow(g_hWndArray[m_List.GetCurSel()]); //m_List为列表对象 
} </PRE>写作时间：2004-5-8<BR>　 
<P></P>未经本文作者同意，不准擅自转载本篇文章。<BR><BR>联系作者请邮至fllsoft@163.com 或 <A href="mailto:fllsoft@sina.com">fllsoft@sina.com</A> <PRE></PRE></TD></TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231900288953&amp;lmt=1231900288&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231900288953&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1118&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;eid=6083029&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Ffllsoft%2F&amp;frm=0&amp;ff=Simsun&amp;biw=777&amp;bih=440&amp;adx=0&amp;ady=3394&amp;ga_vid=1758257248.1231900289&amp;ga_sid=1231900289&amp;ga_hid=1523626341&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=62" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1118" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1118" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1118&amp;title=EVC%bf%aa%b7%a2%ca%b5%c0%fd%d2%bb%a3%ba%b4%b0%bf%da%c7%d0%bb%bb" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 你是说上下方向拉宽吧。并且下拉三角形上贴一个图片。这都不难做的。不用另外自己编写控件。另外PB只是自带XP风格的界面。更不可能通过PB来自定义控件的样式。解决问题还需用EVC。再有问题给我发邮件。 ( fllsoft 发表于 2004-6-8 11:21:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> fllsoft老兄：我开始也是提出在EVC下面直接做修改，或者去下载这个方面的控件，但是经理说最好是在pb中修改，这样显示比较好，但是我实在是不知道怎样修改，看能不能给些提示，谢谢！ ( benserver 发表于 2004-6-7 10:26:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 呵呵！你问的这些都是EVC的问题。和VC解决方法一样。MFC提供的控件很死板。不能按照我们的相法随意改变。如果实在不满意，你就只有自己写了。我们公司的产品的界面中的控件，大部分都是自己写的。 ( fllsoft 发表于 2004-5-18 15:30:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> fllsoft老兄，小弟最近在改下拉框的时候，遇到很多问题，请问如何将其拉宽一点，将其背景颜色改变，后面的下拉三角形上粘贴图片，急！望指教！方便的话请给出代码，谢谢！ ( benserver 发表于 2004-5-18 9:56:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> “整个东西全部安装要15G的空间”？<BR>有必要全部安装吗？你一定是把所有CE支持的CPU的SDK都安装了。需要在什么平台开发，就安装什么SDK。不用都安装。另外编译一个内核需要半个小时？我的机器P3&nbsp;866，512MBRAM，编译一个内核只需10分钟不到。 ( fllsoft 发表于 2004-5-17 14:31:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> PB方面的东西太难了，没有过硬的C＋＋基础是不行的，本人光配置其就花了好长的时间，整个东西全部安装要15G的空间，编译一下至少也要半小时以上，所以如果仁兄有这方面的经验还希望多多指教！ ( benserver 发表于 2004-5-17 14:23:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 是呀，现在EVC已有用在handphone上了。<BR>希望讲一下plateform&nbsp;bulider.&nbsp;或者大家探讨一下。<BR>My&nbsp;QQ:36477574 ( sander 发表于 2004-5-12 9:26:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 希望能多讲讲PB方面的知识，谢谢！ ( xiaojin 发表于 2004-5-11 9:55:00)<BR>&nbsp;<BR>.......................................................<BR><A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1118" target=_blank><SPAN class=small>More...</SPAN></A> </TD></TR></TBODY></TABLE></DIV></A>]]></description>
</item><item>
<title><![CDATA[初识 Embedded Visual C++]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42434</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:31:17</pubDate>
<description><![CDATA[<A>　
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>初识 Embedded Visual C++<BR></B><BR>作者：付林林<BR>　</P>　　大家一定会对 Windows CE 操作系统有所耳闻，它是微软已推出的三种操作系统内核之一，是一种实时操作系统。虽然它是一种实时操作系统，但是从96年发布v1.0到现在，它的主要用途都在民用领域，比如Pocket PC、SmartPhone、Automation。不像VxWorks，应用于航空航天、军事等领域。现在linux、VxWorks厂商也都看好嵌入式民用市场，激烈竞争在所难免。我看好Windows CE，因为微软在PC领域实在太强，用户也早已熟悉了微软的产品，把PC下的技术移植到嵌入式领域，首先在操作界面方面就占了上风。<BR>　　我有幸在两年前从事Windows CE下内核定制和软件开发工作，做的项目是AutoPC。入门也不算很难，当初VC++的水平也算凑合了，所以学EVC（embedded visual c++）也比较快。做了一些软件后，又开始学习platform builder――定制内核工具。因为我们的内核是需要自己定制的，不同于Pocket PC、SmartPhone。<BR>　　未来几年在Windows CE下开发软件的需求将越来越多，我希望有兴趣的程序员能投身其中，开始程序人生新的起点。如果有时间我将在这个专栏陆续写一些关于EVC的文章，希望能够抛砖引玉。而关于platform builder的文章，如果有时间我将在中文MSDN网站上发表。<BR>　　EVC是Windows CE下软件开发工具之一，还有一个EVB，用java也可以。EVB不用说，编译出的软件肯定相对较慢。java也不是什么地方都能用，要看平台是否支持。所以我认为EVC是首选开发工具。熟悉VC的程序员要学EVC比较容易，EVC的界面和VC差不多，同样支持SDK、MFC、ATL，所以直接就能上手编写代码。但需注意的事项也不少，而且要编写和系统底层有关的程序，就必须先了解Windows CE的特征，建议你先学习《Windows 核心编程》，然后再看看Windows CE的帮助文档，了解CE的操作系统特征。为什么这么建议呢，如果不熟悉Windows 核心编程的知识，而直接看Windows CE的英文帮助相关部分，那很难能看懂。 
<P>1、在PC下安装EVC 4.0。<BR>　　如果你要问我从哪能弄到EVC，这我无法告诉你，我用的是公司买的，EVC＋PB（platform builder）一套要N万元。Windows CE的开发模式一般是在PC下编程，然后通过网卡或串口下载到实际的硬件平台上调试。但EVC提供了一个模拟器，这样就可以不用将编译完的程序下载到实际平台上调试。即使这样也不要高兴太早，模拟器只能调试最简单的程序。稍复杂的程序，尤其是操作硬件的程序就必须下载到实际硬件平台上调试。<BR>　　在PC下安装EVC4.0和安装普通的软件一样，按照安装向导提示，先安装platform manager（平台管理器，必须使用这个工具），再安装EVC（IDE部分），接着安装standard SDK（模拟器使用的SDK），最后安装SP（service pack）。我每次都是先安装PB（platform builder），再安装EVC（注意：安装PB不是必要的）。<BR>　　整个安装过程中必须注意的事项：A、不要改动PB和EVC默认的安装路径。因为改变了默认安装路径，即使安装向导提示安装成功，也不能保证你能够成功启动PB或EVC。启动PB或EVC时会提示你某个组件没安装，但实际上已经安装了。B、在安装向导向你询问选择哪些CPU时，只选择x86和emulator。C、EVC4.0自带了SP1，现在有SP2（微软网站提供免费下载），如果想安装SP2，在安装开始时就不要选择安装SP1，安装完毕后，自行安装SP2。 D、PC操作系统可以是windows 2000、windows xp、windows 2003。E、无论出于什么原因，如果你第一次安装EVC失败了，那么以后再安装也很难成功，因为EVC的卸载也不是很干净，很多注册信息仍留在注册表中。我的解决办法是重装操作系统，当然，克隆会更快。祝愿你一次安装成功。 
<P>2、IDE和开发支持库 
<P><IMG src="http://www.vckbase.com/document/journal/vckbase31/images/evc1img1.gif"> 
<P>　　图片1 IDE部分截图 
<P>　　启动EVC后，你会发现整个IDE界面和VC基本一致，让你有了驾驭它的信心，IDE部分我不想多说，只提和VC不同的地方。A、“WCE configuration”工具栏和VC的略有差别，左数第三个下拉框有多个选择，用于选择基于哪个平台编译。如果你在安装时只选择了x86和emulator，那么这里就有四个选项：Win32 [WCE emulator] Debug/Release和Win32 [Wce x86] Debug/Release 。当前你只能选择emulator，也就是程序编译出来在模拟器上运行。B、工具菜单（tools）的内容和VC有很大差别。多出一些远程工具。可以用它们来访问模拟器平台，比如添加一个文件到模拟器，从模拟器复制一个文件到本地硬盘，访问模拟器的注册表，还可以对模拟器进行设置。 
<P><IMG src="http://www.vckbase.com/document/journal/vckbase31/images/evc1img2.gif"> 
<P>　　图片2 tools菜单 
<P>　　前面已经说了，SDK、MFC、ATL都能在EVC下使用。但里面的内容不可能和VC的完全一致。相比较有多有少，这是根据Windows CE的特征来决定的。比如MFC没有CRichEdit类，没有CHtmlView类等等，但比VC多出了CCeSocket类。其实对于程序员来说，开发支持库的变化无所谓，只要有开发资料，有帮助文档，就OK了。EVC在安装后，并没有产生EVC的帮助文件的快捷方式，如果你装在C盘，那帮助文件路径为：<BR>"C:\Windows CE Tools\eVC4\Htmlhelp\emtools\evc4help.chm"。这个目录帮助文件很多，这个是主要的文件，它可以链接到其它帮助文件。另外在开发前还要注意一些事项。A、Windows CE中预定义了_UNICODE宏，所以CString 类的字符都是unicode码，在将字符串赋值给CString对象前一定要加上 _T() 或 TEXT() 宏，这两个宏在预定义了_UNICODE情况下，将字符串每个字符转变成宽字符。也可以在字符串前面加“L”将每个字符转变成宽字符。B、安装的standard SDK内容较少，所以如果在编译程序时出现错误，比如链接错误，别忘了出错原因可能是编码的错，也可能是SDK不支持。要全部的支持就必须安装PB。C、默认情况下模拟器不支持中文，所以中文显示为乱码。D、使用开发向导，比如“WCE MFC AppWizard”时，在选择资源对应的语言时，并没有中文，所以你必须在应用程序向导生成工程文件后，手工去更改资源的对应语言，要显示中文，这一步必须做。<BR>注：我记得在网上看到过模拟器支持中文方法。你可以到一些嵌入式开发网站寻找。 
<P>3、编写、调试程序<BR>　　使用应用程序向导（AppWizard）生成框架，然后编码。或者不使用向导直接编码，在这里我就不多说了，和在VC下操作一样。我在这里只说一下调试方法。 <BR>　　假设编写好了代码，先要在“WCE configuration”工具栏上的第三个下拉框中选择“Win32 [WCE emulator] Debug”（如果代码没问题，选择Win32 [WCE emulator] Release）。接着单击“WCE configuration”工具栏上的执行或调试按钮。这时会显示一个进度条，说明模拟器正在启动，并加载要调试的程序，直到Windows CE桌面和应用程序界面显示出来。在模拟器中可以直接用鼠标和键盘操作。在下次调试前，一定要先关闭正调试的应用程序，否则模拟器提示出错。<BR>注：模拟器安装成功，在“本地连接”的属性对话框中，会多出一项：“virtual PC Emulated Ethernet Switch”。 
<P>本篇文章就写到这。如果有时间我会接着写第二篇。写作时间：2004-4-29 
<P>未经本文作者同意，不准擅自转载本篇文章。联系作者请邮至fllsoft@163.com 或 fllsoft@sina.com<BR></P></TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231900246406&amp;lmt=1231900246&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231900246406&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1107&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Ffllsoft%2F&amp;frm=0&amp;ga_vid=443824423.1231900246&amp;ga_sid=1231900246&amp;ga_hid=1801296868&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=15" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1107" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1107" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1107&amp;title=%b3%f5%ca%b6+Embedded+Visual+C%2b%2b" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 请教:从那里获得Platform&nbsp;builder3.0,我用的是WCE4.2,要做SQL&nbsp;Server&nbsp;CE的程序,看到资料要ADOCE3.1,但有看到WCE4.2取消了对ADOCE3.1直接支持,从MR的网站找的了ADOCE3.1的升级包,但安装是提示需要Platform&nbsp;builder3.0,不知道怎么获得.是否安装了PB后,ADOCE3.1可以直接在WCE4.2中使用了?<BR>E_mail:5188_wyj@Sina.com.cn ( 5188_wyj 发表于 2005-9-5 17:42:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> "其实对于程序员来说，开发支持库的变化无所谓，只要有开发资料，有帮助文档，就OK了。"<BR><BR>haha,&nbsp;re ( VC70新手 发表于 2005-7-19 8:43:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 欢迎大家到天极网嵌入式开发论坛http://club.yesky.com/bbs/jsp/list.jsp?forumID=378 ( fllsoft 发表于 2005-1-21 9:21:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 目前国内CE下编程的人太少了！要靠自己去摸索！ ( dukhilu 发表于 2004-11-30 16:49:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> C++很难学 ( 星龙冰 发表于 2004-5-31 10:43:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 你说的这个类我听说过。但我没用过。以前我也有过此需要，需要显示GIF、JPG一类的图片。研究发现，在pocket&nbsp;pc平台有类式htmlview的类。但在wince.net下没有这样的类。而且帮助中明确提出MFC没有CHtmlView类。如果要显示GIF、JPG，那一种方法是找支持这样的类。国外有做这样的类（也许要$）。另一种方法是利用IWebBrowser&nbsp;COM组件。WinCE.net安装后有关于IE的源码。就是使用这个COM组件。我曾利用它做过IE。具体办法你自己选择。 ( fllsoft 发表于 2004-5-10 8:56:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 小弟最近在使用EVC开发WINCE应用程序时遇到点小问题，请指教：由于在EVC中只能加载BMP格式图片，但是由于该格式的图片太大，故在本程序中想直接动态加载GIF格式图片，所以使用了VOImage这个类进行操作，其中的IMGDECMP.lib在编译的时候老是有问题，如果那位仁兄使用过类似的操作，望赐教。（我还怀疑我下载的IMGDECMP.lib有问题，如果仁兄有的话请共享给我，或者赐教小弟解决的方法，谢谢！benserver@163.com） ( benserver 发表于 2004-5-9 12:29:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 呵呵！强烈赞同！这小子说的话！要自己研究！ ( no.killer 发表于 2004-5-8 8:29:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 回应广大网友：<BR>www.cnemb.com的一个管理员就是我同事。我从开始在CE下开发到现在，没在任何国内嵌入式开发论坛上得到任何有意义的回答。目前国内的CE开发水平我略之一二。总结就是一句话：不要指望别人告诉你，自己研究。 ( fllsoft 发表于 2004-5-7 15:53:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 我做WinCE应用编程快一年了，开发工具就是EVC,和vc非常相似，就是经常要和硬件打交道，还是学到不少东西。<BR>不过好像做ＷＩＮＣＥ下面商业应用程序的很少啊。 ( ZF.Yi 发表于 2004-5-7 13:19:00)<BR>&nbsp;</TD></TR></TBODY></TABLE></DIV></A>]]></description>
</item><item>
<title><![CDATA[VC调试入门]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42433</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:28:42</pubDate>
<description><![CDATA[<A>　
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>VC调试入门<BR></B><BR><BR>作者：<A href="mailto:arongustc@hotmail.com">阿荣</A></P>
<P><BR><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> 概述<BR>调试是一个程序员最基本的技能，其重要性甚至超过学习一门语言。不会调试的程序员就意味着他即使会一门语言，却不能编制出任何好的软件。<BR>这里我简要的根据自己的经验列出调试中比较常用的技巧，希望对大家有用。<BR>本文约定，在选择菜单时，通过/表示分级菜单，例如File/Open表示顶级菜单File的子菜单Open。<BR>&nbsp;<BR><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> 设置<BR>为了调试一个程序，首先必须使程序中包含调试信息。一般情况下，一个从AppWizard创建的工程中包含的Debug Configuration自动包含调试信息，但是是不是Debug版本并不是程序包含调试信息的决定因素，程序设计者可以在任意的Configuration中增加调试信息，包括Release版本。<BR>为了增加调试信息，可以按照下述步骤进行： 
<UL>
<LI>打开Project settings对话框（可以通过快捷键ALT+F7打开，也可以通过IDE菜单Project/Settings打开) 
<LI>选择C/C++页，Category中选择general ，则出现一个Debug Info下拉列表框，可供选择的调试信息 方式包括： <BR>　 
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width=614 border=1>
<TBODY>
<TR>
<TD align=middle width=56><B>命令行</B></TD>
<TD align=middle width=160><B>Project settings</B></TD>
<TD align=middle width=394><B>说明</B></TD></TR>
<TR>
<TD width=56>无</TD>
<TD width=160>None</TD>
<TD width=394>没有调试信息</TD></TR>
<TR>
<TD width=56>/Zd</TD>
<TD width=160>Line Numbers Only</TD>
<TD width=394>目标文件或者可执行文件中只包含全局和导出符号以及代码行信息，不包含符号调试信息</TD></TR>
<TR>
<TD width=56>/Z7</TD>
<TD width=160>C 7.0- Compatible</TD>
<TD width=394>目标文件或者可执行文件中包含行号和所有符号调试信息，包括变量名及类型，函数及原型等</TD></TR>
<TR>
<TD width=56>/Zi</TD>
<TD width=160>Program Database</TD>
<TD width=394>创建一个程序库(PDB)，包括类型信息和符号调试信息。</TD></TR>
<TR>
<TD width=56>/ZI</TD>
<TD width=160>Program Database for Edit and Continue</TD>
<TD width=394>除了前面/Zi的功能外，这个选项允许对代码进行调试过程中的修改和继续执行。这个选项同时使#pragma设置的优化功能无效</TD></TR></TBODY></TABLE><BR>
<LI>选择Link页，选中复选框"Generate Debug Info"，这个选项将使连接器把调试信息写进可执行文件和DLL 
<LI>如果C/C++页中设置了Program Database以上的选项，则Link incrementally可以选择。选中这个选项，将使程序可以在上一次编译的基础上被编译（即增量编译），而不必每次都从头开始编译。 </LI></UL><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> 断点<BR>断点是调试器设置的一个代码位置。当程序运行到断点时，程序中断执行，回到调试器。断点是 最常用的技巧。调试时，只有设置了断点并使程序回到调试器，才能对程序进行在线调试。<BR><BR>设置断点：可以通过下述方法设置一个断点。首先把光标移动到需要设置断点的代码行上，然后 
<UL>
<LI>按F9快捷键 
<LI>弹出Breakpoints对话框，方法是按快捷键CTRL+B或ALT+F9，或者通过菜单Edit/Breakpoints打开。打开后点击Break at编辑框的右侧的箭头，选择 合适的位置信息。一般情况下，直接选择line xxx就足够了，如果想设置不是当前位置的断点，可以选择Advanced，然后填写函数、行号和可执行文件信息。 </LI></UL>去掉断点：把光标移动到给定断点所在的行，再次按F9就可以取消断点。同前面所述，打开Breakpoints对话框后，也可以按照界面提示去掉断点。<BR><BR>条件断点：可以为断点设置一个条件，这样的断点称为条件断点。对于新加的断点，可以单击Conditions按钮，为断点设置一个表达式。当这个表达式发生改变时，程序就 被中断。底下设置包括“观察数组或者结构的元素个数”，似乎可以设置一个指针所指向的内存区的大小，但是我设置一个比较的值但是改动 范围之外的内存区似乎也导致断点起效。最后一个设置可以让程序先执行多少次然后才到达断点。<BR><BR>数据断点：数据断点只能在Breakpoints对话框中设置。选择“Data”页，就显示了设置数据断点的对话框。在编辑框中输入一个表达式，当这个 表达式的值发生变化时，数据断点就到达。一般情况下，这个表达式应该由运算符和全局变量构成，例如：在编辑框中输入 g_bFlag这个全局变量的名字，那么当程序中有g_bFlag= !g_bFlag时，程序就将停在这个语句处。<BR><BR>消息断点：VC也支持对Windows消息进行截获。他有两种方式进行截获：窗口消息处理函数和特定消息中断。<BR>在Breakpoints对话框中选择Messages页，就可以设置消息断点。如果在上面那个对话框中写入消息处理函数的名字，那么 每次消息被这个函数处理，断点就到达（我觉得如果采用普通断点在这个函数中截获，效果应该一样）。如果在底下的下拉 列表框选择一个消息，则每次这种消息到达，程序就中断。<BR><BR><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> 值<BR><B>Watch</B><BR>VC支持查看变量、表达式和内存的值。所有这些观察都必须是在断点中断的情况下进行。<BR>观看变量的值最简单，当断点到达时，把光标移动到这个变量上，停留一会就可以看到变量的值。<BR>VC提供一种被成为Watch的机制来观看变量和表达式的值。在断点状态下，在变量上单击右键，选择Quick Watch， 就弹出一个对话框，显示这个变量的值。<BR>单击Debug工具条上的Watch按钮，就出现一个Watch视图（Watch1,Watch2,Watch3,Watch4），在该视图中输入变量或者表达式，就可以观察 变量或者表达式的值。注意：这个表达式不能有副作用，例如++运算符绝对禁止用于这个表达式中，因为这个运算符将修改变量的值，导致 软件的逻辑被破坏。<BR><BR><B>Memory</B><BR>由于指针指向的数组，Watch只能显示第一个元素的值。为了显示数组的后续内容，或者要显示一片内存的内容，可以使用memory功能。在 Debug工具条上点memory按钮，就弹出一个对话框，在其中输入地址，就可以显示该地址指向的内存的内容。<B><BR><BR>Varibles</B><BR>Debug工具条上的Varibles按钮弹出一个框，显示所有当前执行上下文中可见的变量的值。特别是当前指令涉及的变量，以红色显示。<BR><BR><B>寄存器</B><BR>Debug工具条上的Reigsters按钮弹出一个框，显示当前的所有寄存器的值。<BR><BR><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> 进程控制<BR>VC允许被中断的程序继续运行、单步运行和运行到指定光标处，分别对应快捷键F5、F10/F11和CTRL+F10。各个快捷键功能如下： <BR>　 
<TABLE id=AutoNumber2 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="91%" border=1>
<TBODY>
<TR>
<TD align=middle width="30%"><B>快捷键</B></TD>
<TD align=middle width="70%"><B>说明</B></TD></TR>
<TR>
<TD align=middle width="30%">F5</TD>
<TD width="70%">继续运行</TD></TR>
<TR>
<TD align=middle width="30%">F10</TD>
<TD width="70%">单步，如果涉及到子函数，不进入子函数内部</TD></TR>
<TR>
<TD align=middle width="30%">F11</TD>
<TD width="70%">单步，如果涉及到子函数，进入子函数内部</TD></TR>
<TR>
<TD align=middle width="30%">CTRL+F10</TD>
<TD width="70%">运行到当前光标处。</TD></TR></TBODY></TABLE>
<P><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> Call Stack<BR>调用堆栈反映了当前断点处函数是被那些函数按照什么顺序调用的。单击Debug工具条上的Call stack就显示Call Stack对话框。在CallStack对话框中显示了一个调用系列，最上面的是当前函数，往下依次是调用函数的上级函数。单击这些函数名可以跳到对应的函数中去。<BR><BR><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> 其他调试手段<BR>系统提供一系列特殊的函数或者宏来处理Debug版本相关的信息，如下： </P>
<TABLE id=AutoNumber3 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="86%" border=1>
<TBODY>
<TR>
<TD align=middle width="42%"><B>宏名/函数名</B></TD>
<TD align=middle width="58%"><B>说明</B></TD></TR>
<TR>
<TD align=middle width="42%">TRACE</TD>
<TD width="58%">使用方法和printf完全一致，他在output框中输出调试信息</TD></TR>
<TR>
<TD align=middle width="42%">ASSERT</TD>
<TD width="58%">它接收一个表达式，如果这个表达式为TRUE，则无动作，否则中断当前程序执行。对于系统中出现这个宏 导致的中断，应该认为你的函数调用未能满足系统的调用此函数的前提条件。例如，对于一个还没有创建的窗口调用SetWindowText等。</TD></TR>
<TR>
<TD align=middle width="42%">VERIFY</TD>
<TD width="58%">和ASSERT功能类似，所不同的是，在Release版本中，ASSERT不计算输入的表达式的值，而VERIFY计算表达式的值。</TD></TR></TBODY></TABLE>
<P><IMG src="http://www.vckbase.com/document/image/paragraph.gif"> 关注<BR>一个好的程序员不应该把所有的判断交给编译器和调试器，应该在程序中自己加以程序保护和错误定位，具体措施包括： 
<UL>
<LI>对于所有有返回值的函数，都应该检查返回值，除非你确信这个函数调用绝对不会出错，或者不关心它是否出错。 
<LI>一些函数返回错误，需要用其他函数获得错误的具体信息。例如accept返回INVALID_SOCKET表示accept失败，为了查明 具体的失败原因，应该立刻用WSAGetLastError获得错误码，并针对性的解决问题。 
<LI>有些函数通过异常机制抛出错误，应该用TRY-CATCH语句来检查错误 
<LI>程序员对于能处理的错误，应该自己在底层处理，对于不能处理的，应该报告给用户让他们决定怎么处理。如果程序出了异常， 却不对返回值和其他机制返回的错误信息进行判断，只能是加大了找错误的难度。 </LI></UL>另外：VC中要编制程序不应该一开始就写cpp/h文件，而应该首先创建一个合适的工程。因为只有这样，VC才能选择合适的编译、连接 选项。对于加入到工程中的cpp文件，应该检查是否在第一行显式的包含stdafx.h头文件，这是Microsoft Visual Studio为了加快编译 速度而设置的预编译头文件。在这个#include "stdafx.h"行前面的所有代码将被忽略，所以其他头文件应该在这一行后面被包含。<BR>对于.c文件，由于不能包含stdafx.h，因此可以通过Project settings把它的预编译头设置为“不使用”，方法是： 
<UL>
<LI>弹出Project settings对话框 
<LI>选择C/C++ 
<LI>Category选择Precompilation Header 
<LI>选择不使用预编译头。 </LI></UL></TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231899644562&amp;lmt=1231899644&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231899644562&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D924&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Farong%2F&amp;frm=0&amp;ga_vid=1692613982.1231899645&amp;ga_sid=1231899645&amp;ga_hid=454807170&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=16" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=924" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=924" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=924&amp;title=VC%b5%f7%ca%d4%c8%eb%c3%c5" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 谢谢 ( wufu304 发表于 2007-7-24 10:32:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 感觉不错，有收获 ( wilsonwoo 发表于 2007-5-17 17:59:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 感觉有收获，谢谢 ( bkmeteor 发表于 2007-5-2 10:23:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 还行,适合初学者阅读 ( wlh80 发表于 2007-4-30 10:38:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 谢谢啊&nbsp;&nbsp;觉得很有帮助 ( 胖丁可达鸭 发表于 2007-4-28 22:16:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 真的不错！<BR>-----------------------<BR>http://blog.csdn.net/wangjiwei2010 ( wjw6710 发表于 2007-4-13 9:50:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> vc&nbsp;vb&nbsp;dll&nbsp;控件学习网(&nbsp;源码　)<BR>http://konny520.diy.myrice.com ( konny 发表于 2007-3-5 15:50:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 很好,受益非浅.谢了.<BR>( wjjqw_123 发表于 2007-2-2 13:39:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 引用一遍：<BR><BR>http://www.china-askpro.com/msg36/qa35.shtml<BR><BR>&lt;br&gt;<BR><BR>&lt;a&nbsp;href='http://www.china-askpro.com/msg36/qa35.shtml'&gt;VC++中调试Release版本时怎样设置断点&lt;/a&gt; ( dzct 发表于 2006-9-7 10:06:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 很好啊<BR>如果可以把其他调试手段详细介绍一下就更好了<BR>( subsars 发表于 2006-6-21 11:19:00)<BR>&nbsp;</TD></TR></TBODY></TABLE></DIV></A>]]></description>
</item><item>
<title><![CDATA[功能强大的vc6调试器]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=seanno13&amp;id=42432</link>
<author>seanno13</author>
<pubDate>2009/1/14 10:28:18</pubDate>
<description><![CDATA[<A>　
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><B>功能强大的vc6调试器<BR></B><BR><BR>作者：<A href="mailto:yy2better@126.com">yy2better</A></P>
<P><BR>&nbsp;&nbsp;要成为一位优秀的软件工程师，调试能力必不可缺。本文将较详细介绍VC6调试器的主要用法。 <BR>&nbsp;&nbsp;windows平台的调试器主要分为两大类： <BR>&nbsp;&nbsp;1 用户模式(user-mode)调试器：它们都基于win32 Debugging API，有使用方便的界面，主要用于调试用户模式下的应用程序。这类调试器包括Visual C++调试器、WinDBG、BoundChecker、Borland C++ Builder调试器、NTSD等。 <BR>&nbsp;&nbsp;2 内核模式(kernel-mode)调试器：内核调试器位于CPU和操作系统之间，一旦启动，操作系统也会中止运行，主要用于调试驱动程序或用户模式调试器不易调试的程序。这类调试器包括WDEB386、WinDBG和softice等。其中WinDBG和softice也可以调试用户模式代码。 <BR>&nbsp;&nbsp;国外一位调试高手曾说，他70％调试时间是在用VC++，其余时间是使用WinDBG和softice。毕竟，调试用户模式代码，VC6调试器的效率是非常高的。因此，我将首先在本篇介绍VC6调试器的主要用法，其他调试器的用法及一些调试技能在后续文章中阐述。 <BR><BR><B>一 位置断点（Location Breakpoint）</B> <BR>&nbsp;&nbsp;大家最常用的断点是普通的位置断点，在源程序的某一行按F9就设置了一个位置断点。但对于很多问题，这种朴素的断点作用有限。譬如下面这段代码： </P><PRE>void CForDebugDlg::OnOK() 	
{
	for (int i = 0; i &lt; 1000; i++)	//A
	{
		int k = i * 10 - 2;	//B
		SendTo(k);		//C
		int tmp = DoSome(i);	//D
		int j = i / tmp;	//E
	}
}      
      </PRE>&nbsp;&nbsp;执行此函数，程序崩溃于E行，发现此时tmp为0，假设tmp本不应该为0，怎么这个时候为0呢？所以最好能够跟踪此次循环时DoSome函数是如何运行的，但由于是在循环体内，如果在E行设置断点，可能需要按F5（GO）许多次。这样手要不停的按，很痛苦。使用VC6断点修饰条件就可以轻易解决此问题。步骤如下。 <BR>&nbsp;&nbsp;1 Ctrl+B打开断点设置框，如下图： <BR><IMG src="http://www.vckbase.com/document/journal/vckbase36/images/debug1.jpg"> <BR><FONT color=#0000ff><B>Figure 1</B>设置高级位置断点</FONT> <BR>&nbsp;&nbsp;2 然后选择D行所在的断点，然后点击condition按钮，在弹出对话框的最下面一个编辑框中输入一个很大数目，具体视应用而定，这里1000就够了。 <BR>&nbsp;&nbsp;3 按F5重新运行程序，程序中断。Ctrl+B打开断点框，发现此断点后跟随一串说明：...487 times remaining。意思是还剩下487次没有执行，那就是说执行到513（1000－487）次时候出错的。因此，我们按步骤2所讲，更改此断点的skip次数,将1000改为513。 <BR>&nbsp;&nbsp;4 再次重新运行程序，程序执行了513次循环，然后自动停在断点处。这时，我们就可以仔细查看DoSome是如何返回0的。这样，你就避免了手指的痛苦，节省了时间。 <BR>&nbsp;&nbsp;再看位置断点其他修饰条件。如<B>Figure 1</B>所示，在“Enter the expression to be evaluated:”下面，可以输入一些条件，当这些条件满足时，断点才启动。譬如，刚才的程序，我们需要i为100时程序停下来，我们就可以输入在编辑框中输入“i==100”。 <BR>&nbsp;&nbsp;另外，如果在此编辑框中如果只输入变量名称，则变量发生改变时，断点才会启动。这对检测一个变量何时被修改很方便，特别对一些大程序。 <BR>&nbsp;&nbsp;用好位置断点的修饰条件，可以大大方便解决某些问题。 <BR><BR><B>二 数据断点（Data Breakpoint）</B> <BR>&nbsp;&nbsp;软件调试过程中，有时会发现一些数据会莫名其妙的被修改掉（如一些数组的越界写导致覆盖了另外的变量），找出何处代码导致这块内存被更改是一件棘手的事情（如果没有调试器的帮助）。恰当运用数据断点可以快速帮你定位何时何处这个数据被修改。譬如下面一段程序： <PRE>#include "stdafx.h"
#include <STRING.H>

int main(int argc, char* argv[])
{
	char szName1[10];
	char szName2[4];
	strcpy(szName1,"shenzhen");		
	printf("%s\n", szName1);		//A

	strcpy(szName2, "vckbase");		//B
	printf("%s\n", szName1);
	printf("%s\n", szName2);

	return 0;
}
      </PRE>&nbsp;&nbsp;这段程序的输出是 <PRE>      	szName1: shenzhen
	szName1: ase
	szName2: vckbase
     </PRE>szName1何时被修改呢？因为没有明显的修改szName1代码。我们可以首先在A行设置普通断点，F5运行程序，程序停在A行。然后我们再设置一个数据断点。如下图： <BR><IMG src="http://www.vckbase.com/document/journal/vckbase36/images/debug2.jpg"> <BR><FONT color=#0000ff><B>Figure 2</B> 数据断点</FONT> <BR>&nbsp;&nbsp;F5继续运行，程序停在B行，说明B处代码修改了szName1。B处明明没有修改szName1呀？但调试器指明是这一行，一般不会错，所以还是静下心来看看程序，哦，你发现了：szName2只有4个字节，而strcpy了7个字节，所以覆写了szName1。 <BR>&nbsp;&nbsp;数据断点不只是对变量改变有效，还可以设置变量是否等于某个值。譬如，你可以将Figure 2中红圈处改为条件”szName2[0]==''''y''''“,那么当szName2第一个字符为y时断点就会启动。 <BR>&nbsp;&nbsp;可以看出，数据断点相对位置断点一个很大的区别是不用明确指明在哪一行代码设置断点。 <BR><BR><B>三 其他</B> <BR>&nbsp;&nbsp;1 在call stack窗口中设置断点，选择某个函数，按F9设置一个断点。这样可以从深层次的函数调用中迅速返回到需要的函数。 <BR>&nbsp;&nbsp;2 Set Next StateMent命令（debug过程中，右键菜单中的命令） <BR>&nbsp;&nbsp;此命令的作用是将程序的指令指针（EIP）指向不同的代码行。譬如，你正在调试上面那段代码，运行在A行，但你不愿意运行B行和C行代码，这时，你就可以在D行，右键，然后“Set Next StateMent”。调试器就不会执行B、C行。只要在同一函数内，此指令就可以随意跳前或跳后执行。灵活使用此功能可以大量节省调试时间。 <BR>&nbsp;&nbsp;3 watch窗口 <BR>&nbsp;&nbsp;watch窗口支持丰富的数据格式化功能。如输入0x65,u，则在右栏显示101。 <BR>&nbsp;&nbsp;实时显示windows API调用的错误：在左栏输入@err,hr。 <BR>&nbsp;&nbsp;在watch窗口中调用函数。提醒一下，调用完函数后马上在watch窗口中清除它，否则，单步调试时每一步调试器都会调用此函数。 <BR>&nbsp;&nbsp;4 messages断点不怎么实用。基本上可以用前面讲述的断点代替。 <BR><B>总结</B> <BR>&nbsp;&nbsp;调试最重要的还是你要思考，要猜测你的程序可能出错的地方，然后运用你的调试器来证实你的猜测。而熟练使用上面这些技巧无疑会加快这个过程。最后，大家如果有关于调试方面的问题，我乐意参与探讨。 </TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<SCRIPT type=text/javascript><!--
google_ad_client = "pub-4159669282587342";
google_alternate_color = "FFFFFF";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="";
google_color_border = "B4D0DC";
google_color_bg = "ECF8FF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "6F6F6F";
//--></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript>
</SCRIPT>

<SCRIPT src="http://googleads.g.doubleclick.net/pagead/test_domain.js"></SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/render_ads.js"></SCRIPT>

<SCRIPT>window.google_render_ad();</SCRIPT>
<IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-4159669282587342&amp;dt=1231899913578&amp;lmt=1231899913&amp;alt_color=FFFFFF&amp;format=468x60_as&amp;output=html&amp;correlator=1231899913578&amp;url=http%3A%2F%2Fwww.vckbase.com%2Fdocument%2Fviewdoc%2F%3Fid%3D1262&amp;color_bg=ECF8FF&amp;color_text=6F6F6F&amp;color_link=0000CC&amp;color_url=008000&amp;color_border=B4D0DC&amp;ad_type=text_image&amp;ea=0&amp;ref=http%3A%2F%2Fwww.vckbase.com%2Fvckbase%2Fcolumnist%2Fyy2better%2Findex.html&amp;frm=0&amp;ga_vid=8054876.1231899914&amp;ga_sid=1231899914&amp;ga_hid=92023999&amp;flash=10.0.12.36&amp;u_h=800&amp;u_w=1280&amp;u_ah=770&amp;u_aw=1280&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=15" frameBorder=0 width=468 scrolling=no height=60 allowTransparency></IFRAME><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="http://www.vckbase.com/SYS/script/image/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1262" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=1262" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=1262&amp;title=%b9%a6%c4%dc%c7%bf%b4%f3%b5%c4vc6%b5%f7%ca%d4%c6%f7" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="http://www.vckbase.com/document/viewdoc/image/rec1.gif" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 我见过的最好的讲VC调试器的文章，谢谢！ ( liangquan 发表于 2008-4-13 20:18:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 此时，ALT+7&nbsp;打开call&nbsp;stack窗口，点击main行，就会发现就是B行。<BR>这段汇编代码其实就是B行的汇编展开。 ( yy2better 发表于 2004-12-31 13:46:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 为什么我的程序在B行没有停下来,而是进入了汇编. ( ckp 发表于 2004-12-25 16:11:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 好啊,E-mail:bsp@ffsky.com<BR>我只是刚开始用softice,有问题向你请教! ( bsp 发表于 2004-10-31 22:21:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> to&nbsp;bsp,&nbsp;你对softice有一些研究吧，我们交个朋友吧。<BR>email:&nbsp;yy2better@126.com ( yy2better 发表于 2004-10-20 21:01:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 你说得很对，vc6不提供读这块内存时的断点。<BR>softice有时候好像是不可替代的。 ( yy2better 发表于 2004-10-20 13:30:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> VC的内存断点是监视的地址的值发生变化时才中断呀,我想要在程序对其进行读的时候就中断,好像不行 ( bsp 发表于 2004-10-18 21:28:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 数据断点（data&nbsp;breakpoint)性能较差。用完后马上disable或删除，否则调试运行很慢。 ( yy2better 发表于 2004-10-18 12:33:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 可以设置内存断点。在data标签页设置，直接将内存地址放入其中，同时可以指定内存范围。<BR><BR>不支持API断点。 ( yy2better 发表于 2004-10-18 12:31:00)<BR>&nbsp;<BR><IMG height=11 src="http://www.vckbase.com/document/image/doc2.gif" width=11 align=absMiddle> 请问VC调试器可以像softice那样设内存读写断点和API断点吗? ( bsp 发表于 2004-10-17 11:36:00)</TD></TR></TBODY></TABLE></DIV></A>]]></description>
</item>
</channel>
</rss>