星期五, 二月 29, 2008

C/C++ 笔试、面试题目大汇总

1.求下面函数的返回值(微软)
int func(x) { int countx = 0; while(x) { countx ++; x = x&(x-1); } return countx; }
假定x = 9999。 答案:8
思路:将x转化为2进制,看含有的1的个数。
2. 什么是“引用”?申明和使用“引用”要注意哪些问题?
答:引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。
3. 将“引用”作为函数参数有哪些特点?
(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
4. 在什么时候需要使用“常引用”? 
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;
例1
int a ;const int &ra=a;ra=1; //错误a=1; //正确
例2
string foo( );void bar(string & s);
那么下面的表达式将是非法的:
bar(foo( ));bar("hello world");
原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
引用型参数应该在能被定义为const的情况下,尽量定义为const 。
5. 将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }
好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
注意事项:
(1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
(3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)流操作符重载返回值申明为“引用”的作用:
流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << x =" j" x="10)="100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。">int &put(int n);int vals[10];int error=-1;void main(){put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10; put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20; cout<=0 && n<=9 ) return vals[n]; else { cout<<"subscript error"; return error; }} (5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。 6. “引用”与多态的关系? 引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。 例4 Class A; Class B : Class A{...}; B b; A& ref = b; 7. “引用”与指针的区别是什么? 指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传ref和pointer的区别。 8. 什么时候需要“引用”? 流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
以上 2-8 参考:http://blog.csdn.net/wfwd/archive/2006/05/30/763551.aspx
9. 结构与联合有和区别?1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。 2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
10. 下面关于“联合”的题目的输出?
a)
#i nclude union{int i;char x[2];}a;
void main(){a.x[0] = 10; a.x[1] = 1;printf("%d",a.i);}答案:266 (低位低地址,高位高地址,内存占用情况是Ox010A)
b)
main() { union{ /*定义一个联合*/ int i; struct{ /*在联合中定义一个结构*/ char first; char second; }half; }number; number.i=0x4241; /*联合成员赋值*/ printf("%c%c\n", number.half.first, mumber.half.second); number.half.first='a'; /*联合中结构成员赋值*/ number.half.second='b'; printf("%x\n", number.i); getch(); } 答案: AB (0x41对应'A',是低位;Ox42对应'B',是高位)
6261 (number.i和number.half共用一块地址空间)
11. 已知strcpy的函数原型:char *strcpy(char *strDest, const char *strSrc)其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数,请编写函数 strcpy。
答案:char *strcpy(char *strDest, const char *strSrc){if ( strDest == NULL strSrc == NULL)return NULL ;if ( strDest == strSrc)return strDest ;char *tempptr = strDest ;while( (*strDest++ = *strSrc++) != ‘\0’);return tempptr ;}
12. 已知String类定义如下:
class String{public:String(const char *str = NULL); // 通用构造函数String(const String &another); // 拷贝构造函数~ String(); // 析构函数String & operater =(const String &rhs); // 赋值函数private:char *m_data; // 用于保存字符串};
尝试写出类的成员函数实现。
答案:
String::String(const char *str){ if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断 { m_data = new char[1] ; m_data[0] = '\0' ; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data,str); }
}
String::String(const String &another){ m_data = new char[strlen(another.m_data) + 1]; strcpy(m_data,other.m_data);}
String& String::operator =(const String &rhs){ if ( this == &rhs) return *this ; delete []m_data; //删除原来的数据,新开一块内存 m_data = new char[strlen(rhs.m_data) + 1]; strcpy(m_data,rhs.m_data); return *this ;}
String::~String(){ delete []m_data ;}
13. .h头文件中的ifndef/define/endif 的作用?
答:防止该头文件被重复引用。
14. #i nclude 与 #i nclude "file.h"的区别?
答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。
15.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?
首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数
extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo( int x, int y );  
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。未加extern "C"声明时的连接方式
假设在C++中,模块A的头文件如下:
// 模块A头文件 moduleA.h#ifndef MODULE_A_H#define MODULE_A_Hint foo( int x, int y );#endif  
在模块B中引用该函数:
// 模块B实现文件 moduleB.cpp#i nclude "moduleA.h"foo(2,3);  
实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!加extern "C"声明后的编译和连接方式加extern "C"声明后,模块A的头文件变为:
// 模块A头文件 moduleA.h#ifndef MODULE_A_H#define MODULE_A_Hextern "C" int foo( int x, int y );#endif  
在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C++与C及其它语言的混合编程。  
明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧:
extern "C"的惯用法(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:
extern "C"{#i nclude "cExample.h"}
而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。C++引用C函数例子工程中包含的三个文件的源代码如下:
/* c语言头文件:cExample.h */#ifndef C_EXAMPLE_H#define C_EXAMPLE_Hextern int add(int x,int y);#endif
/* c语言实现文件:cExample.c */#i nclude "cExample.h"int add( int x, int y ){return x + y;}
// c++实现文件,调用add:cppFile.cppextern "C" {#i nclude "cExample.h"}int main(int argc, char* argv[]){add(2,3); return 0;}
如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。
(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。
C引用C++函数例子工程中包含的三个文件的源代码如下:
//C++头文件 cppExample.h#ifndef CPP_EXAMPLE_H#define CPP_EXAMPLE_Hextern "C" int add( int x, int y );#endif
//C++实现文件 cppExample.cpp#i nclude "cppExample.h"int add( int x, int y ){return x + y;}
/* C实现文件 cFile.c/* 这样会编译出错:#i nclude "cExample.h" */extern int add( int x, int y );int main( int argc, char* argv[] ){add( 2, 3 ); return 0;}
15题目的解答请参考《C++中extern “C”含义深层探索》注解:
16. 关联、聚合(Aggregation)以及组合(Composition)的区别?
涉及到UML中的一些概念:关联是表示两个类的一般性联系,比如“学生”和“老师”就是一种关联关系;聚合表示has-a的关系,是一种相对松散的关系,聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系:
500){this.resized=true;this.style.width=500;}" border=0>
从实现的角度讲,聚合可以表示为:
class A {...} class B { A* a; .....}
而组合表示contains-a的关系,关联性强于聚合:组合类与被组合类有相同的生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系:
500){this.resized=true;this.style.width=500;}" border=0>
实现的形式是:
class A{...} class B{ A a; ...}
参考文章:http://blog.csdn.net/wfwd/archive/2006/05/30/763753.aspx
http://blog.csdn.net/wfwd/archive/2006/05/30/763760.aspx
17.面向对象的三个基本特征,并简单叙述之?
1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)
2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。
3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
18. 重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?
常考的题目。从定义上来说:
重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
重写:是指子类重新定义复类虚函数的方法。
从实现原理上来说:
重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
19. 多态的作用?
主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。
20. Ado与Ado.net的相同与不同?
除了“能够让应用程序处理存储于DBMS 中的数据“这一基本相似点外,两者没有太多共同之处。但是Ado使用OLE DB 接口并基于微软的COM 技术,而ADO.NET 拥有自己的ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于COM 体系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,这也就是说ADO.NET 和ADO是两种数据访问方式。ADO.net 提供对XML 的支持。
21. New delete 与malloc free 的联系与区别?答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.
22. #define DOUBLE(x) x+x ,i = 5*DOUBLE(5); i 是多少?答案:i 为30。
23. 有哪几种情况只能用intialization list 而不能用assignment?
答案:当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。
24. C++是不是类型安全的?答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
25. main 函数执行以前,还会执行什么代码?答案:全局对象的构造函数会在main 函数之前执行。
26. 描述内存分配方式以及它们的区别?1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
27.struct 和 class 的区别
答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和 class 在其他方面是功能相当的。
从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结构仅仅象一堆缺乏封装和功能的开放的内存位,而类就象活的并且可靠的社会成员,它有智能服务,有牢固的封装屏障和一个良好定义的接口。既然大多数人都这么认为,那么只有在你的类有很少的方法并且有公有数据(这种事情在良好设计的系统中是存在的!)时,你也许应该使用 struct 关键字,否则,你应该使用 class 关键字。
28.当一个类A 中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk)答案:肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。
29. 在8086 汇编下,逻辑地址和物理地址是怎样转换的?(Intel)答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器内地址,就得到了真正要访问的地址。
30. 比较C++中的4种类型转换方式?
请参考:http://blog.csdn.net/wfwd/archive/2006/05/30/763785.aspx,重点是static_cast, dynamic_cast和reinterpret_cast的区别和应用。
31.分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。答案:BOOL : if ( !a ) or if(a)int : if ( a == 0)float : const EXPRESSION EXP = 0.000001 if ( a <>-EXP)pointer : if ( a != NULL) or if(a == NULL)

32.请说出const与#define 相比,有何优点?答案:1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。 2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
33.简述数组与指针的区别?数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。(1)修改内容上的差别char a[] = “hello”;a[0] = ‘X’;char *p = “world”; // 注意p 指向常量字符串p[0] = ‘X’; // 编译器不能发现该错误,运行时错误(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。char a[] = "hello world";char *p = a;cout<<>
#i nclude
using namespace std;
void Order(vector& data) //bubble sort{int count = data.size() ;int tag = false ; // 设置是否需要继续冒泡的标志位for ( int i = 0 ; i < j =" 0"> data[j+1]){tag = true ;int temp = data[j] ;data[j] = data[j+1] ;data[j+1] = temp ;}}if ( !tag )break ;}}
void main( void ){vectordata;ifstream in("c:\\data.txt");if ( !in){cout<<"file error!";exit(1);}int temp;while (!in.eof()){in>>temp;data.push_back(temp);}in.close(); //关闭输入文件流Order(data);ofstream out("c:\\result.txt");if ( !out){cout<<"file error!";exit(1);}for ( i = 0 ; i < head="="> instance of B){cout<<"constructed by parameter " << i="1" sec_max="MINNUMBER" maxnumber="data[0]" t1="Play(5);" t2="Play(t1);" minnumber="-32767"> maxnumber ){sec_max = maxnumber ;maxnumber = data[i] ;}else{if ( data[i] > sec_max )sec_max = data[i] ;}}return sec_max ;}
43. 写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。
KMP算法效率最好,时间复杂度是O(n+m)。
44. 多重继承的内存分配问题: 比如有class A : public class B, public class C {} 那么A的内存结构大致是怎么样的?这个是compiler-dependent的, 不同的实现其细节可能不同。如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。可以参考《深入探索C++对象模型》,或者:http://blog.csdn.net/wfwd/archive/2006/05/30/763797.aspx
45. 如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针) struct node { char val; node* next;} bool check(const node* head) {} //return false : 无环;true: 有环
一种O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然):bool check(const node* head){ if(head==NULL) return false; node *low=head, *fast=head->next; while(fast!=NULL && fast->next!=NULL) { low=low->next; fast=fast->next->next; if(low==fast) return true; } return false;}

C/C++程序员应聘常见面试题

  1.引言   本文的写作目的并不在于提供C/C++程序员求职面试指导,而旨在从技术上分析面试题的内涵。文中的大多数面试题来自各大论坛,部分试题解答也参考了网友的意见。   许多面试题看似简单,却需要深厚的基本功才能给出完美的解答。企业要求面试者写一个最简单的strcpy函数都可看出面试者在技术上究竟达到了怎样的程 度,我们能真正写好一个strcpy函数吗?我们都觉得自己能,可是我们写出的strcpy很可能只能拿到10分中的2分。读者可从本文看到strcpy 函数从2分到10分解答的例子,看看自己属于什么样的层次。此外,还有一些面试题考查面试者敏捷的思维能力。   分析这些面试题,本身包含很强的趣味性;而作为一名研发人员,通过对这些面试题的深入剖析则可进一步增强自身的内功。  2.找错题  试题1:
void test1(){ char string[10]; char* str1 = "0123456789"; strcpy( string, str1 );}
  试题2:
void test2(){ char string[10], str1[10]; int i; for(i=0; i<10; i++) {  str1[i] = 'a'; } strcpy( string, str1 );}
  试题3:
void test3(char* str1){ char string[10]; if( strlen( str1 ) <= 10 ) {  strcpy( string, str1 ); }}
解答:  试题1字符串str1需要11个字节才能存放下(包括末尾的’\0’),而string只有10个字节的空间,strcpy会导致数组越界;  对试题2,如果面试者指出字符数组str1不能在数组内结束可以给3分;如果面试者指出strcpy(string, str1)调用使得从str1内存起复制到string内存起所复制的字节数具有不确定性可以给7分,在此基础上指出库函数strcpy工作方式的给10分;  对试题3,if(strlen(str1) <= 10)应改为if(strlen(str1) < 10),因为strlen的结果未统计’\0’所占用的1个字节。  剖析:  考查对基本功的掌握:
(1)字符串以’\0’结尾;
(2)对数组越界把握的敏感度;
(3)库函数strcpy的工作方式,如果编写一个标准strcpy函数的总分值为10,下面给出几个不同得分的答案:

2分
void strcpy( char *strDest, char *strSrc ){  while( (*strDest++ = * strSrc++) != ‘\0’ );}

  4分
void strcpy( char *strDest, const char *strSrc ) //将源字符串加const,表明其为输入参数,加2分{  while( (*strDest++ = * strSrc++) != ‘\0’ );}

  7分
void strcpy(char *strDest, const char *strSrc) { //对源地址和目的地址加非0断言,加3分 assert( (strDest != NULL) && (strSrc != NULL) ); while( (*strDest++ = * strSrc++) != ‘\0’ );}

  10分
//为了实现链式操作,将目的地址返回,加3分!char * strcpy( char *strDest, const char *strSrc ) { assert( (strDest != NULL) && (strSrc != NULL) ); char *address = strDest;  while( (*strDest++ = * strSrc++) != ‘\0’ );   return address;}
  从2分到10分的几个答案我们可以清楚的看到,小小的strcpy竟然暗藏着这么多玄机,真不是盖的!需要多么扎实的基本功才能写一个完美的strcpy啊!  (4)对strlen的掌握,它没有包括字符串末尾的'\0'。  读者看了不同分值的strcpy版本,应该也可以写出一个10分的strlen函数了,完美的版本为:
int strlen( const char *str ) //输入参数const
{ assert( strt != NULL ); //断言字符串地址非0 int len; while( (*str++) != '\0' )  {   len++;  }  return len;}

  试题4:
void GetMemory( char *p ){ p = (char *) malloc( 100 );}void Test( void ) { char *str = NULL; GetMemory( str );  strcpy( str, "hello world" ); printf( str );}

  试题5:
char *GetMemory( void ){  char p[] = "hello world";  return p; }void Test( void ){  char *str = NULL;  str = GetMemory();  printf( str ); }

  试题6:
void GetMemory( char **p, int num ){ *p = (char *) malloc( num );}void Test( void ){ char *str = NULL; GetMemory( &str, 100 );//应该加上是否申请成功 strcpy( str, "hello" );  printf( str ); }

  试题7:
void Test( void ){ char *str = (char *) malloc( 100 ); strcpy( str, "hello" ); free( str );  ... //省略的其它语句}
  解答:  试题4传入中GetMemory( char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完
char *str = NULL;GetMemory( str );
  后的str仍然为NULL;  试题5中
char p[] = "hello world"; return p;
  的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期。  试题6的GetMemory避免了试题4的问题,传入GetMemory的参数为字符串指针的指针,但是在GetMemory中执行申请内存及赋值语句
*p = (char *) malloc( num );
  后未判断内存是否申请成功,应加上:
if ( *p == NULL ){ ...//进行申请内存失败处理}

  试题7存在与试题6同样的问题,在执行
char *str = (char *) malloc(100);
  后未进行内存是否申请成功的判断;另外,在free(str)后未置str为空,导致可能变成一个“野”指针,应加上:
str = NULL;

试题6的Test函数中也未对malloc的内存进行释放。
剖析:

试题4~7考查面试者对内存操作的理解程度,基本功扎实的面试者一般都能正确的回答其中50~60的错误。但是要完全解答正确,却也绝非易事。
对内存操作的考查主要集中在:
(1)指针的理解;
(2)变量的生存期及作用范围;
(3)良好的动态内存申请和释放习惯。  再看看下面的一段程序有什么错误:
swap( int* p1,int* p2 ){ int *p; *p = *p1; *p1 = *p2; *p2 = *p;}
  在swap函数中,p是一个“野”指针,有可能指向系统区,导致程序运行的崩溃。在VC++中DEBUG运行时提示错误“Access Violation”。该程序应该改为:
swap( int* p1,int* p2 ){ int p; p = *p1; *p1 = *p2; *p2 = p;}

  3.内功题  试题1:分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)  解答:   BOOL型变量:if(!var)   int型变量: if(var==0)   float型变量:   const float EPSINON = 0.00001;   if ((x >= - EPSINON) && (x <= EPSINON)   指针变量:  if(var==NULL)  剖析:  考查对0值判断的“内功”,BOOL型变量的0判断完全可以写成if(var==0),而int型变量也可以写成if(!var),指针变量的判断也可以写成if(!var),上述写法虽然程序都能正确运行,但是未能清晰地表达程序的意思。  一 般的,如果想让if判断一个变量的“真”、“假”,应直接使用if(var)、if(!var),表明其为“逻辑”判断;如果用if判断一个数值型变量 (short、int、long等),应该用if(var==0),表明是与0进行“数值”上的比较;而判断指针则适宜用if(var==NULL),这 是一种很好的编程习惯。  浮点型变量并不精确,所以不可将float变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。如果写成if (x == 0.0),则判为错,得0分。  试题2:以下为Windows NT下的32位C++程序,请计算sizeof的值
void Func ( char str[100] ){ sizeof( str ) = ?}void *p = malloc( 100 );sizeof ( p ) = ?
  解答:
sizeof( str ) = 4sizeof ( p ) = 4

剖析:
Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。
数组名的本质如下:  (1)数组名指代一种数据结构,这种数据结构就是数组;  例如:
char str[10];cout << sizeof(str) << endl;
  输出结果为10,str指代数据结构char[10]。  (2)数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;
char str[10]; str++; //编译出错,提示str不是左值 
  (3)数组名作为函数形参时,沦为普通指针。
Windows NT 32位平台下,指针的长度(占用内存的大小)为4字节,故sizeof( str ) 、sizeof ( p ) 都为4。

试题3:写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?
least = MIN(*p++, b);

  解答:
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
MIN(*p++, b)会产生宏的副作用

剖析:
这个面试题主要考查面试者对宏定义的使用,宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对“参数”进行的是一对一的替换。  程序员对宏定义的使用要非常小心,特别要注意两个问题:  (1)谨慎地将宏定义中的“参数”和整个宏用用括弧括起来。所以,严格地讲,下述解答:
#define MIN(A,B) (A) <= (B) ? (A) : (B)#define MIN(A,B) (A <= B ? A : B )
都应判0分;
(2)防止宏的副作用。
  宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))对MIN(*p++, b)的作用结果是:
((*p++) <= (b) ? (*p++) : (*p++))
这个表达式会产生副作用,指针p会作三次++自增操作。
除此之外,另一个应该判0分的解答是:
#define MIN(A,B) ((A) <= (B) ? (A) : (B));
这个解答在宏定义的后面加“;”,显示编写者对宏的概念模糊不清,只能被无情地判0分并被面试官淘汰。

试题4:为什么标准头文件都有类似以下的结构?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus

extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
解答:
头文件中的编译宏
#ifndef __INCvxWorksh#define __INCvxWorksh#endif
的作用是防止被重复引用。
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo(int x, int y);
该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是考这种机制来实现函数重载的。
为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C语言中就可以调用C++的函数了。

  试题5:编写一个函数,作用是把一个char组成的字符串循环右移n个。比如原来是“abcdefghi”如果n=2,移位后应该是“hiabcdefgh”   函数头是这样的:
//pStr是指向以'\0'结尾的字符串的指针//steps是要求移动的nvoid LoopMove ( char * pStr, int steps ){ //请填充...}
  解答:  正确解答1:
void LoopMove ( char *pStr, int steps ){ int n = strlen( pStr ) - steps; char tmp[MAX_LEN];  strcpy ( tmp, pStr + n );  strcpy ( tmp + steps, pStr);  *( tmp + strlen ( pStr ) ) = '\0'; strcpy( pStr, tmp );}
  正确解答2:
void LoopMove ( char *pStr, int steps ){ int n = strlen( pStr ) - steps; char tmp[MAX_LEN];  memcpy( tmp, pStr + n, steps );  memcpy(pStr + steps, pStr, n );  memcpy(pStr, tmp, steps ); }
  剖析:  这个试题主要考查面试者对标准库函数的熟练程度,在需要的时候引用库函数可以很大程度上简化程序编写的工作量。  最频繁被使用的库函数包括:  (1) strcpy  (2) memcpy  (3) memset  试题6:已知WAV文件格式如下表,打开一个WAV文件,以适当的数据结构组织WAV文件头并解析WAV格式的各项信息。  WAVE文件格式说明表

偏移地址
字节数
数据类型
内 容





文件头
00H
4
Char
"RIFF"标志
04H
4
int32
文件长度
08H
4
Char
"WAVE"标志
0CH
4
Char
"fmt"标志
10H
4

过渡字节(不定)
14H
2
int16
格式类别
16H
2
int16
通道数
18H
2
int16
采样率(每秒样本数),表示每个通道的播放速度
1CH
4
int32
波形音频数据传送速率
20H
2
int16
数据块的调整数(按字节算的)
22H
2

每样本的数据位数
24H
4
Char
数据标记符"data"
28H
4
int32
语音数据的长度
  解答:  将WAV文件格式定义为结构体WAVEFORMAT:
typedef struct tagWaveFormat{  char cRiffFlag[4];  UIN32 nFileLen;  char cWaveFlag[4];  char cFmtFlag[4];  char cTransition[4];  UIN16 nFormatTag ;  UIN16 nChannels;  UIN16 nSamplesPerSec;  UIN32 nAvgBytesperSec;  UIN16 nBlockAlign;  UIN16 nBitNumPerSample;  char cDataFlag[4];  UIN16 nAudioLength; } WAVEFORMAT;
  假设WAV文件内容读出后存放在指针buffer开始的内存单元内,则分析文件格式的代码很简单,为:
WAVEFORMAT waveFormat;memcpy( &waveFormat, buffer,sizeof( WAVEFORMAT ) );
  直接通过访问waveFormat的成员,就可以获得特定WAV文件的各项格式信息。  剖析:  试题6考查面试者组织数据结构的能力,有经验的程序设计者将属于一个整体的数据成员组织为一个结构体,利用指针类型转换,可以将memcpy、memset等函数直接用于结构体地址,进行结构体的整体操作。 透过这个题可以看出面试者的程序设计经验是否丰富。  试题7:编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:
class String{  public:   String(const char *str = NULL); // 普通构造函数   String(const String &other); // 拷贝构造函数   ~ String(void); // 析构函数   String & operate =(const String &other); // 赋值函数  private:   char *m_data; // 用于保存字符串 };
  解答:
//普通构造函数String::String(const char *str) { if(str==NULL)  {  m_data = new char[1]; // 得分点:对空字符串自动申请存放结束标志'\0'的空  //加分点:对m_data加NULL 判断  *m_data = '\0';  }  else {  int length = strlen(str);   m_data = new char[length+1]; // 若能加 NULL 判断则更好   strcpy(m_data, str);  }}// String的析构函数String::~String(void) { delete [] m_data; // 或delete m_data;}//拷贝构造函数String::String(const String &other)    // 得分点:输入参数为const型{  int length = strlen(other.m_data);  m_data = new char[length+1];     //加分点:对m_data加NULL 判断 strcpy(m_data, other.m_data); }//赋值函数String & String::operate =(const String &other) // 得分点:输入参数为const型{  if(this == &other)   //得分点:检查自赋值  return *this;  delete [] m_data;     //得分点:释放原有的内存资源 int length = strlen( other.m_data );  m_data = new char[length+1];  //加分点:对m_data加NULL 判断 strcpy( m_data, other.m_data );  return *this;         //得分点:返回本对象的引用}
  剖析:  能够准确无误地编写出String类的构造函数、拷贝构造函数、赋值函数和析构函数的面试者至少已经具备了C++基本功的60%以上!  在这个类中包括了指针类成员变量m_data,当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数,这既是对C++程序员的基本要求,也是《Effective C++》中特别强调的条款。  仔细学习这个类,特别注意加注释的得分点和加分点的意义,这样就具备了60%以上的C++基本功!  试题8:请说出static和const关键字尽可能多的作用  解答:  static关键字至少有下列n个作用:  (1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;  (2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;  (3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;  (4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;  (5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。  const关键字至少有下列n个作用:  (1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;  (2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;  (3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;  (4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;  (5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。例如:
const classA operator*(const classA& a1,const classA& a2);
  operator*的返回结果必须是一个const对象。如果不是,这样的变态代码也不会编译出错:
classA a, b, c;(a * b) = c; // 对a*b的结果赋值
  操作(a * b) = c显然不符合编程者的初衷,也没有任何意义。  剖析:  惊讶吗?小小的static和const居然有这么多功能,我们能回答几个?如果只能回答1~2个,那还真得闭关再好好修炼修炼。  这个题可以考查面试者对程序设计知识的掌握程度是初级、中级还是比较深入,没有一定的知识广度和深度,不可能对这个问题给出全面的解答。大多数人只能回答出static和const关键字的部分功能。  4.技巧题  试题1:请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1  解答:
int checkCPU(){ {  union w  {    int a;   char b;  } c;  c.a = 1;  return (c.b == 1); }}
  剖析:  嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。例如,16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址
存放内容
0x4000
0x34
0x4001
0x12
  而在Big-endian模式CPU内存中的存放方式则为:
内存地址
存放内容
0x4000
0x12
0x4001
0x34
  32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址
存放内容
0x4000
0x78
0x4001
0x56
0x4002
0x34
0x4003
0x12
  而在Big-endian模式CPU内存中的存放方式则为:
内存地址
存放内容
0x4000
0x12
0x4001
0x34
0x4002
0x56
0x4003
0x78
  联合体union的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。如果谁能当场给出这个解答,那简直就是一个天才的程序员。  试题2:写一个函数返回1+2+3+…+n的值(假定结果不会超过长整型变量的范围)   解答:
int Sum( int n ){  return ( (long)1 + n) * n / 2;  //或return (1l + n) * n / 2;}
  剖析:   对于这个题,只能说,也许最简单的答案就是最好的答案。下面的解答,或者基于下面的解答思路去优化,不管怎么“折腾”,其效率也不可能与直接return ( 1 l + n ) * n / 2相比!
int Sum( int n ){ long sum = 0; for( int i=1; i<=n; i++ ) {  sum += i; } return sum;}
  所以程序员们需要敏感地将数学等知识用在程序设计中。

星期三, 二月 27, 2008

运用 DRM 对流媒体文件加密

以前用 C++ 做过的 DRM 项目,现抽取出其中核心的代码, 演示一下如何借助 DRM 对流媒体文件进行保护。虽然微软的 DRM 技术被破解了, 但那是以用户通过认证并下载证书为前提的。由是观之, 运用 DRM 对流媒体文件施以保护仍有一定意义,这毕竟比什么都不做要强嘛!/* drm.h*/#ifndef __DRM_H__#define __DRM_H__
#include #import "wmrmobjs.dll"
class DRMObject {public: DRMObject(char* inFile, char* outFile, char* licenseURL, char* corpName); bool Encrypt();
public: std::string key; std::string seed; std::string keyID; std::string contentID; std::string publicKey; std::string privateKey;
private: std::string _inFile; std::string _outFile; std::string _corpName; std::string _licenseURL;};
#endif// drm.cxx#include "drm.h"#include #include
DRMObject::DRMObject(char* inFile, char* outFile, char* licenseURL, char* corpName){ _inFile = inFile; _outFile = outFile; _corpName = corpName; _licenseURL = licenseURL;}
bool DRMObject::Encrypt() { try { HRESULT hr;
// Initialize the COM library on the current thread hr = CoInitialize(NULL); if (FAILED(hr)) _com_issue_error(hr);
WMRMOBJSLib::IWMRMKeysPtr keysObj("Wmrmobjs.WMRMKeys"); WMRMOBJSLib::IWMRMHeaderPtr headerObj("Wmrmobjs.WMRMHeader"); WMRMOBJSLib::IWMRMProtectPtr protectObj("Wmrmobjs.WMRMProtect");
// Generate keyID,seed,contentID keyID = keysObj->GenerateKeyID(); seed = keysObj->GenerateSeed(); contentID = keysObj->GenerateKeyID();
// Generate key keysObj->KeyID = keyID.c_str(); keysObj->Seed = seed.c_str(); key = keysObj->GenerateKey();
// Set protected header headerObj->KeyID = keyID.c_str(); headerObj->ContentID = contentID.c_str(); headerObj->LicenseAcqURL = _licenseURL.c_str(); headerObj->Attribute["Copyright"] = _corpName.c_str();
// verify the key headerObj->SetCheckSum(key.c_str());
//Generate public and private key _variant_t privKey, pubKey; VariantInit(&privKey); VariantInit(&pubKey); keysObj->GenerateSigningKeys(&privKey, &pubKey); headerObj->IndividualizedVersion = "2.2";
// Save private and public kyes privateKey = (_bstr_t)privKey; publicKey = (_bstr_t)pubKey;
// Sign key headerObj->Sign((_bstr_t)privKey);
// Set protect object _bstr_t header; header = headerObj->Header; protectObj->Header = header; protectObj->Key = key.c_str(); protectObj->V1KeyID = keyID.c_str();
// start encrypt protectObj->InputFile = _inFile.c_str(); protectObj->ProtectFile(_outFile.c_str()); } catch (const _com_error& e) { std::cerr << "COM Exception: " << e.ErrorMessage() << std::endl; if (e.ErrorInfo()) std::cerr << (char*)e.ErrorInfo() << std::endl; return false; }
CoUninitialize(); return true;}// drm_test.cxx#include "drm.h"#include using namespace std;
int main() { DRMObject* drmObj = new DRMObject("d:\\test.wma", "d:\\test2.wma", "http://localhost/test.asp", "test corp"); if (drmObj->Encrypt()) { cout << "KeyID: " <<>keyID <<>seed <<>key <<>contentID <<>publicKey <<>privateKey << endl; } delete drmObj;}

星期日, 二月 24, 2008

模拟window桌面实现

正在开发中的游戏有个全屏功能--可以在window桌面背景上运行,就像一些视频播放器在桌面背景上播放一样的,花了个上午整了个Demo放出来留个纪念。
实现功能:显示图标,双击图标执行相应的程序,右击图标弹出该图标对应得菜单,点击非图标区则弹出桌面菜单。需要完整工程可以点此下载:DesktopWindow.rar。程序效果图如下:



在这个程序里,定义了一个XShellItem的数据结构,保持桌面图标的iten id(ITEMIDLiST),图标以及文字图标。
struct XShellItem ...{ ITEMIDLIST* itemId;
int x; int y; int w; int h;
int nameX; int nameY; int nameW; int nameH;
BOOL hit;
CStringW name; Bitmap* icon; Bitmap* nameIcon;
XShellItem() : itemId(NULL), x(0), y(0), w(0), h(0), nameX(0), nameY(0), nameW(0), nameH(0), name(L""), hit(FALSE), icon(NULL), nameIcon(NULL) ...{ } ~XShellItem() ...{ } };然后定义一个数组CAtlArray itemArray;用来保存所有桌面图标对象,在InitShellFolder()中对它进行初始化:
// 获取桌面图标的相关数据 BOOL InitShellFolder() ...{ HRESULT hRslt = SHGetDesktopFolder(&folder); if (FAILED(hRslt)) ...{ return FALSE; }
CComPtr ids; hRslt = folder->EnumObjects(0, SHCONTF_FOLDERS SHCONTF_NONFOLDERS, &ids); if (FAILED(hRslt)) ...{ return FALSE; }
CAtlList items; for (;;) ...{ ITEMIDLIST* id = 0; ULONG cIds = 0;
hRslt = ids->Next(1, &id, &cIds); if (hRslt != S_OK) ...{ break; }
CStringW name; STRRET str = ...{ 0}; hRslt = folder->GetDisplayNameOf(id, SHGDN_NORMAL SHGDN_INFOLDER, &str); if (SUCCEEDED(hRslt)) ...{ LPWSTR pname = 0; StrRetToStrW(&str, id, &pname); name = pname; CoTaskMemFree(pname); }
XShellItem item;
item.itemId = id; item.name = name; items.AddTail(item); }
SIZE_T iItem = 0; SIZE_T cItems = items.GetCount();
itemArray.SetCount(cItems);
POSITION pos = items.GetHeadPosition(); while (pos != 0) ...{ XShellItem& si = items.GetNext(pos); itemArray[iItem] = si; iItem++; }
HDC hDC = CreateCompatibleDC(0);
Graphics g(hDC); g.Clear(Color(0, 0, 0, 0));
ICONMETRICS im = ...{ 0}; im.cbSize = sizeof(im); SystemParametersInfo(SPI_GETICONMETRICS, sizeof(im), &im, 0);
SolidBrush br_t(Color(255, 255, 255)); Font font_i(hDC, &(im.lfFont)); float fcy = font_i.GetHeight(&g) * 2 + 2; DeleteDC(hDC);
Gdiplus::StringFormat sf(Gdiplus::StringFormat::GenericTypographic()); sf.SetAlignment(Gdiplus::StringAlignmentCenter); sf.SetTrimming(Gdiplus::StringTrimmingEllipsisWord);
iconSpacingWidth = im.iHorzSpacing + OFFSET_WIDTH; iconSpacingHeight = im.iVertSpacing + OFFSET_HEIGHT;
int iconWidth = GetSystemMetrics(SM_CXICON); int iconHeight = GetSystemMetrics(SM_CYICON);
for (SIZE_T i = 0; i < cItems; i++) ...{ XShellItem& item = itemArray[i];
// SHGetFileInfo HICON hIcon = 0; HIMAGELIST hImgList; SHFILEINFO stSHFileInfo; CImageList cImgList;
// 获取图标 hImgList = (HIMAGELIST)::SHGetFileInfo( (LPCWSTR) item.itemId, 0, &stSHFileInfo, sizeof(SHFILEINFO), SHGFI_PIDL SHGFI_ICON SHGFI_LARGEICON SHGFI_SYSICONINDEX);
// DIBSection 8bit BITMAPINFO bmi; BITMAPINFOHEADER& bmih = bmi.bmiHeader; bmih.biSize = sizeof(bmih); bmih.biWidth = ICON_WIDTH; bmih.biHeight = -ICON_HEIGHT; // BMP反转 bmih.biPlanes = 1; bmih.biBitCount = 32; bmih.biCompression = BI_RGB; bmih.biSizeImage = 0; bmih.biXPelsPerMeter = 0; bmih.biYPelsPerMeter = 0; bmih.biClrUsed = 0; bmih.biClrImportant = 0;
HDC memDC = CreateCompatibleDC(0); void* pDib = 0; HBITMAP hBmp = CreateDIBSection(memDC, &bmi, DIB_RGB_COLORS, &pDib, 0, 0); GdiFlush();
HGDIOBJ old = SelectObject(memDC, hBmp);
// ImageList_Draw WindowsXP ImageList_SetBkColor(hImgList, 0x0); ImageList_Draw(hImgList, stSHFileInfo.iIcon, memDC, 0, 0, ILD_NORMAL); SelectObject(memDC, old); DeleteDC(memDC);
cImgList.Attach(hImgList); hIcon = cImgList.ExtractIcon(stSHFileInfo.iIcon); cImgList.Detach();
if (hIcon != 0) ...{
// Bitmap::FromHICON 0~255 item.icon = Bitmap::FromHICON(hIcon); item.w = iconWidth; item.h = iconHeight;
Gdiplus::RectF rc(float(2), float(2), float(iconSpacingWidth - 4), fcy);
Gdiplus::Bitmap * nameIcon = new Bitmap(NAME_WIDTH, NAME_HEIGHT, &g); Gdiplus::Graphics * g2 = Gdiplus::Graphics::FromImage(nameIcon); g2->Clear(Gdiplus::Color(Gdiplus::ARGB(0)));
g2->DrawString(item.name, item.name.GetLength(), &font_i, rc, &sf, &br_t);
item.nameIcon = nameIcon; item.nameW = NAME_WIDTH; item.nameH = NAME_HEIGHT;
delete g2; }
DestroyIcon(hIcon); DeleteObject(hBmp); DestroyIcon(stSHFileInfo.hIcon); }
return TRUE; }注意这里面并没有设置图标对象的位置,因为当窗口改变大小的时候,相应地也要调整图标的描绘位置,所以图标位置是在SetShellItemPosition()中动态调整的.
// 根据窗口大小设置图标位置 void SetShellItemPosition() ...{ int iconWidth = GetSystemMetrics(SM_CXICON); int iconHeight = GetSystemMetrics(SM_CYICON); static const int OFFSET_Y = 20; int x = 0; int y = OFFSET_Y; SIZE_T cItems = itemArray.GetCount(); for (SIZE_T i = 0; i < cItems; i++) ...{ XShellItem& item = itemArray[i]; if (item.icon) ...{ item.x = x + (iconSpacingWidth - iconWidth) / 2; item.y = y; }
if (item.nameIcon) ...{ item.nameX = x; item.nameY = y + iconHeight + 2; }
WTL::CRect rect; GetClientRect(&rect); y += iconSpacingHeight; if (y + iconSpacingHeight >= rect.bottom) ...{ x += iconSpacingWidth; y = OFFSET_Y; } } }描绘图标就很简单了,呵呵,不贴了,下面来说说弹出图标菜单,执行图标对应的程序以及弹出桌面菜单。
执行图标对应的程序,需要以先前保持的图标itemid作为参数,代码如下:
void RunShellItem(ITEMIDLIST* pIID) ...{ SHELLEXECUTEINFO info; info.cbSize = sizeof(SHELLEXECUTEINFO); info.fMask = SEE_MASK_INVOKEIDLIST; info.hwnd = m_hWnd; info.lpVerb = NULL; info.lpFile = NULL; info.lpParameters = NULL; info.lpDirectory = NULL; info.nShow = SW_SHOWNORMAL; info.hInstApp = NULL; info.lpIDList = pIID; ShellExecuteEx(&info); }弹出桌面菜单的代码如下:
// 桌面菜单 void DesktopMenu() ...{ HWND program = FindWindowEx(0, 0, _T("Progman"), _T("Program Manager")); HWND view = FindWindowEx(program, 0, _T("SHELLDLL_DefView"), 0);
//HWND list = FindWindowEx(view, 0, _T("SysListView32"), 0); ::SetForegroundWindow(view);
POINT pt; GetCursorPos(&pt);
LPARAM lp = pt.y << 16 (pt.x - 32); ::PostMessage(view, WM_LBUTTONDOWN, 0, lp); ::PostMessage(view, WM_RBUTTONUP, 0, lp); }弹出图标菜单的代码如下,这里定义了两个全局的IContextMenu对象:static IContextMenu2* g_pIContext2 = NULL;static IContextMenu3* g_pIContext3 = NULL;
以便在消息回调函数中使用。具体代码如下:
// 图标菜单 void RightMenu(ITEMIDLIST* pIID) ...{ HWND hwnd = m_hWnd;
LPCONTEXTMENU pContextMenu = NULL; LPCONTEXTMENU pCtxMenuTemp = NULL;
g_pIContext2 = NULL; g_pIContext3 = NULL;
int menuType = 0;
HRESULT hRslt = folder->GetUIObjectOf( hwnd, 1, (LPCITEMIDLIST*) &(pIID), IID_IContextMenu, 0, (void**) &pCtxMenuTemp); if (FAILED(hRslt)) ...{ return; }
POINT pt; GetCursorPos(&pt);
if (pCtxMenuTemp->QueryInterface(IID_IContextMenu3, (void**) &pContextMenu) == NOERROR) ...{ menuType = 3; } else if (pCtxMenuTemp->QueryInterface(IID_IContextMenu2, (void**) &pContextMenu) == NOERROR) ...{ menuType = 2; }
if (pContextMenu) ...{ pCtxMenuTemp->Release(); } else ...{ pContextMenu = pCtxMenuTemp; menuType = 1; }
if (menuType == 0) ...{ return; }
HMENU hMenu = CreatePopupMenu(); hRslt = pContextMenu->QueryContextMenu(hMenu, 0, 1, 0x7fff, CMF_NORMAL CMF_EXPLORE); if (FAILED(hRslt)) ...{ return; }
#ifndef _WIN64 #pragma warning(disable : 4244 4311)#endif
// subclass window WNDPROC oldWndProc = NULL; if (menuType > 1) ...{
// only subclass if it is IID_IContextMenu2 or IID_IContextMenu3 oldWndProc = (WNDPROC) SetWindowLongPtr(GWL_WNDPROC, (LONG) HookWndProc); if (menuType == 2) ...{ g_pIContext2 = (LPCONTEXTMENU2) pContextMenu; } else ...{ g_pIContext3 = (LPCONTEXTMENU3) pContextMenu; } } else ...{ oldWndProc = NULL; }
int cmd = ::TrackPopupMenu( hMenu, TPM_LEFTALIGN TPM_BOTTOMALIGN TPM_RETURNCMD TPM_LEFTBUTTON, pt.x, pt.y, 0, hwnd, 0);
// unsubclass if (oldWndProc) ...{ SetWindowLongPtr(GWL_WNDPROC, (LONG) oldWndProc); }
#ifndef _WIN64 #pragma warning(default : 4244 4311)#endif if (cmd != 0) ...{ CMINVOKECOMMANDINFO ci = ...{ 0}; ci.cbSize = sizeof(CMINVOKECOMMANDINFO); ci.hwnd = hwnd; ci.lpVerb = (LPCSTR) MAKEINTRESOURCE(cmd - 1); ci.nShow = SW_SHOWNORMAL;
pContextMenu->InvokeCommand(&ci); }
pContextMenu->Release(); g_pIContext2 = NULL; g_pIContext3 = NULL; ::DestroyMenu(hMenu); }
static LRESULT CALLBACK HookWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) ...{ switch (message) ...{ case WM_MENUCHAR: // only supported by IContextMenu3 if (g_pIContext3) ...{ LRESULT lResult = 0; g_pIContext3->HandleMenuMsg2(message, wParam, lParam, &lResult); return(lResult); } break; case WM_DRAWITEM: case WM_MEASUREITEM: if (wParam) ...{ break; // if wParam != 0 then the message is not menu-related }
case WM_INITMENUPOPUP: if (g_pIContext2) ...{ g_pIContext2->HandleMenuMsg(message, wParam, lParam); } else ...{ g_pIContext3->HandleMenuMsg(message, wParam, lParam); }
return(message == WM_INITMENUPOPUP ? 0 : TRUE); break; default: break; }
return ::CallWindowProc((WNDPROC) GetProp(hWnd, TEXT("oldWndProc")), hWnd, message, wParam, lParam); }

星期日, 二月 03, 2008

一个比较好的对DIB位图进行操作的类

// Dib.h: interface for the CDib class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_DIB_H__AC952C3A_9B6B_4319_8D6E_E7F509348A88__INCLUDED_)
#define AFX_DIB_H__AC952C3A_9B6B_4319_8D6E_E7F509348A88__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CDib : public CObject
{
public:
CDib();
virtual ~CDib();
//operations
public:
// 用于操作DIB的函数声明
BOOL ConstructPalette(HGLOBAL,CPalette* ); //构造逻辑调色板
LPSTR GetBits(LPSTR); //取得位图数据的入口地址
DWORD GetWidth(LPSTR); //取得位图的宽度
DWORD GetHeight(LPSTR); //取得位图的高度
WORD GetPalSize(LPSTR); //取得调色板的大小
WORD GetColorNum(LPSTR); //取得位图包含的颜色数目
WORD GetBitCount(LPSTR); //取得位图的颜色深度
HGLOBAL LoadFile(CFile&); //从文件中加载位图
BOOL SaveFile(HGLOBAL hDib, CFile& file);


// 在对图象进行处理时,针对位图的字节宽度必须是4的倍数的这一要求,
// 我们设计了函数GetRequireWidth,来处理这种比较特殊的情况
int GetReqByteWidth(int ); //转换后的字节数GetRequireByteWidth
long GetRectWidth(LPCRECT ); //取得区域的宽度
long GetRectHeight(LPCRECT); //取得区域的高度
public:
void ClearMemory();
void InitMembers();
public:
LPBITMAPINFO lpbminfo; // 指向BITMAPINFO结构的指针
LPBITMAPINFOHEADER lpbmihrd; //指向BITMAPINFOHEADER结构的指针
BITMAPFILEHEADER bmfHeader; //BITMAPFILEHEADER结构
LPSTR lpdib; //指向DIB的指针
LPSTR lpDIBBits; // DIB像素指针
DWORD dwDIBSize; //DIB大小

HGLOBAL m_hDib;//DIB对象的句柄
CPalette* m_palDIB;//调色板指针
};

#endif // !defined(AFX_DIB_H__AC952C3A_9B6B_4319_8D6E_E7F509348A88__INCLUDED_)

/////////////////////////////////////////////////////////////////////实现文件//////////////////////////////////////////////////////////////////////////////
// Dib.cpp: implementation of the CDib class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Dib.h"

#include

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CDib::CDib()
{
InitMembers();
}

CDib::~CDib()
{
ClearMemory();
}

/*************************************************************************
* 函数名称:ConstructPalette(HGLOBAL hDIB, CPalette* pPal)
* 函数参数:
* HGLOBAL hDIB,DIB对象的句柄
* CPalette* pPal,调色板的指针
* 函数类型:BOOL
* 函数说明:该函数按照DIB创建一个逻辑调色板
************************************************************************/

BOOL CDib::ConstructPalette(HGLOBAL hDIB, CPalette* pPal)
{

HANDLE hLogPal;// 逻辑调色板的句柄
int iLoop;// 循环变量
BOOL bSuccess = FALSE;// 创建结果
if (hDIB == NULL)//判断是否是有效的DIB对象
{
return FALSE;// 返回FALSE
}
lpdib = (LPSTR) ::GlobalLock((HGLOBAL) hDIB);// 锁定DIB
lpbminfo= (LPBITMAPINFO)lpdib;
long wNumColors =GetColorNum(lpdib);// 获取DIB中颜色表中的颜色数目
if (wNumColors != 0)
{
hLogPal = ::GlobalAlloc(GHND, sizeof(LOGPALETTE)// 分配为逻辑调色板内存
+ sizeof(PALETTEENTRY)
* wNumColors);
if (hLogPal == 0)// 如果失败则退出
{
::GlobalUnlock((HGLOBAL) hDIB);// 解除锁定
return FALSE;
}
LPLOGPALETTE lpPal = (LPLOGPALETTE) ::GlobalLock((HGLOBAL) hLogPal);

lpPal->palVersion = 0x300;// 设置调色板版本号
lpPal->palNumEntries = (WORD)wNumColors;// 设置颜色数目
for (iLoop=0; iLoop<(int)wNumColors;iLoop++)// 读取调色板
{
lpPal->palPalEntry[iLoop].peRed =lpbminfo->bmiColors[iLoop].rgbRed;// 读取三原色分量
lpPal->palPalEntry[iLoop].peGreen =lpbminfo->bmiColors[iLoop].rgbGreen;
lpPal->palPalEntry[iLoop].peBlue =lpbminfo->bmiColors[iLoop].rgbBlue;
lpPal->palPalEntry[iLoop].peFlags =0;// 保留位
}
bSuccess=pPal->CreatePalette(lpPal);// 按照逻辑调色板创建调色板,并返回指针
::GlobalUnlock((HGLOBAL) hLogPal);// 解除锁定
::GlobalFree((HGLOBAL) hLogPal);// 释放逻辑调色板
}
::GlobalUnlock((HGLOBAL) hDIB);// 解除锁定
return bSuccess;// 返回结果
}

/*************************************************************************
* 函数名称:GetBits(LPSTR lpdib)
* 函数参数:
* LPSTR lpdib,指向DIB对象的指针
* 函数类型:LPSTR
* 函数功能:计算DIB像素的起始位置,并返回指向它的指针
************************************************************************/

LPSTR CDib::GetBits(LPSTR lpdib)
{
return (lpdib + ((LPBITMAPINFOHEADER)lpdib)->biSize+GetPalSize(lpdib));
// return (lpdib + *(LPDWORD)lpdib+GetPalSize(lpdib));
}

/*************************************************************************
* 函数名称:GetWidth(LPSTR lpdib)
* 函数参数:
* LPSTR lpdib,指向DIB对象的指针
* 函数类型:DWORD
* 函数功能:该函数返回DIB中图象的宽度
************************************************************************/

DWORD CDib::GetWidth(LPSTR lpdib)
{
return ((LPBITMAPINFOHEADER)lpdib)->biWidth;//返回DIB宽度
}


/*************************************************************************
* 函数名称:GetHeight(LPSTR lpdib)
* 函数参数:
* LPSTR lpdib ,指向DIB对象的指针
* 函数类型:DWORD
* 函数功能:该函数返回DIB中图象的高度
************************************************************************/


DWORD CDib::GetHeight(LPSTR lpdib)
{
return ((LPBITMAPINFOHEADER)lpdib)->biHeight;//返回DIB高度

}
/*************************************************************************
* 函数名称:GetPalSize(LPSTR lpdib)
* 函数参数:
* LPSTR lpdib,指向DIB对象的指针
* 函数类型:WORD
* 函数功能:该函数返回DIB中调色板的大小
************************************************************************/

WORD CDib::GetPalSize(LPSTR lpdib)
{
return (WORD)(GetColorNum(lpdib) * sizeof(RGBQUAD));// 计算DIB中调色板的大小
}

/*************************************************************************
* 函数名称:GetColorNum(LPSTR lpdib)
* 函数参数:
* LPSTR lpdib,指向DIB对象的指针
* 函数类型:WORD
* 函数功能:该函数返回DIB中调色板的颜色的种数
************************************************************************/
WORD CDib::GetColorNum(LPSTR lpdib)
{
long dwClrUsed = ((LPBITMAPINFOHEADER)lpdib)->biClrUsed; // 读取dwClrUsed值

if (dwClrUsed != 0)
{
return (WORD)dwClrUsed;// 如果dwClrUsed不为0,直接返回该值
}
else
{
dwClrUsed=(long)pow(2,24);
return (WORD)dwClrUsed;
}
}

/*************************************************************************
* 函数名称:GetBitCount(LPSTR lpdib)
* 函数参数:
* LPSTR lpdib,指向DIB对象的指针
* 函数类型:WORD
* 函数功能:该函数返回DIBBitCount
************************************************************************/
WORD CDib::GetBitCount(LPSTR lpdib)
{
return ((LPBITMAPINFOHEADER)lpdib)->biBitCount;// 返回位宽
}


/*************************************************************************
* 函数名称:LoadFile(CFile& file)
* 函数参数:
* CFile& file,要读取得文件文件CFile
* 函数类型:HGLOBAL
* 函数功能:将指定的文件中的DIB对象读到指定的内存区域中
*************************************************************************/

HGLOBAL CDib::LoadFile(CFile& file)
{

DWORD dwFileSize;

dwFileSize= file.GetLength();//获取文件大小
if (file.Read((LPSTR)&bmfHeader, sizeof(bmfHeader)) != sizeof(bmfHeader))// 读取DIB文件头
{
return NULL;// 大小不一致,返回NULL
}
// 如果文件类型不是"BM",其16进制值为0x4d42,则返回并进行相应错误处理
if (bmfHeader.bfType !=0x4d42)
{
return NULL;// 如果不是则返回NULL
}
m_hDib= (HGLOBAL) ::GlobalAlloc(GMEM_MOVEABLE GMEM_ZEROINIT, dwFileSize-sizeof(BITMAPFILEHEADER));// 分配DIB内存
if (m_hDib==NULL)
{
return NULL;// 分配失败,返回NULL
}
/////////////////////////////////////////////////////////////////////////
//给CDib类的成员变量赋值
lpdib = (LPSTR) ::GlobalLock((HGLOBAL) m_hDib);// 锁定
if (file.ReadHuge(lpdib, dwFileSize - sizeof(BITMAPFILEHEADER)) !=// 读像素
dwFileSize - sizeof(BITMAPFILEHEADER) )//大小不一致
{
::GlobalUnlock((HGLOBAL) m_hDib); // 解除锁定
::GlobalFree((HGLOBAL) m_hDib); // 释放内存
return NULL;
}
::GlobalUnlock((HGLOBAL) m_hDib);// 解除锁定
return m_hDib;// 返回DIB句柄
}

/*************************************************************************
* 函数名称:GetReqByteWidth(int bits)
* 函数参数:
* int bits,位数
* 函数类型:int
* 函数功能:获取需要的行字节数,应为4的倍数
*************************************************************************/
int CDib::GetReqByteWidth(int bits)
{
int getBytes=(bits + 31) / 32 * 4;
return getBytes;
}

/*************************************************************************
* 函数名称:GetRectWidth(LPCRECT lpRect)
* 函数参数:
* LPCRECT lpRect,指向矩形区域的指针
* 函数类型:long
* 函数功能:获取矩形区域的宽度
*************************************************************************/

long CDib::GetRectWidth(LPCRECT lpRect)
{
long nWidth=lpRect->right - lpRect->left;
return nWidth;
}
/*************************************************************************
* 函数名称:GetRectHeight(LPCRECT lpRect)
* 函数参数:
* LPCRECT lpRect,指向矩形区域的指针
* 函数类型:long
* 函数功能:获取矩形区域的高度
*************************************************************************/
long CDib::GetRectHeight(LPCRECT lpRect)
{
long nHeight=lpRect->bottom - lpRect->top;
return nHeight;
}

/*************************************************************************
* 函数名称:InitMembers()
* 函数类型: void
* 函数功能:初始化类的成员变量
*************************************************************************/
void CDib::InitMembers()
{
m_hDib=NULL;
lpdib=NULL;
lpDIBBits=NULL;
}

/*************************************************************************
* 函数名称:ClearMemory()
* 函数类型: void
* 函数功能:复位类的成员变量
*************************************************************************/
void CDib::ClearMemory()
{
if(m_hDib!=NULL)
::GlobalFree(m_hDib);
lpdib=NULL;
}

/*************************************************************************
* 函数名称:SaveFile(HGLOBAL hDib, CFile& file)
* 函数参数:
* HGLOBAL hDib,要保存的DIB
* CFile& file,保存文件CFile
* 函数类型:BOOL
* 函数功能:将指定的DIB对象保存到指定的CFile中
*************************************************************************/

BOOL CDib::SaveFile(HGLOBAL hDib, CFile& file)
{
if (hDib == NULL)
{
return FALSE;// 如果DIB为空,返回FALSE
}
lpbmihrd = (LPBITMAPINFOHEADER) ::GlobalLock((HGLOBAL) hDib);// 读取BITMAPINFO结构,并锁定
if (lpbmihrd == NULL)
{
return FALSE;// 为空,返回FALSE
}
bmfHeader.bfType ='BM'; // 填充文件头
dwDIBSize = *(LPDWORD)lpbmihrd + GetPalSize((LPSTR)lpbmihrd);// 文件头大小+颜色表大小
DWORD dwBmBitsSize;// 像素的大小
dwBmBitsSize =GetReqByteWidth((lpbmihrd->biWidth)*((DWORD)lpbmihrd->biBitCount)) * lpbmihrd->biHeight;// 大小为Width * Height
dwDIBSize += dwBmBitsSize;// 计算后DIB每行字节数为4的倍数时的大小
lpbmihrd->biSizeImage = dwBmBitsSize;// 更新biSizeImage
bmfHeader.bfSize = dwDIBSize + sizeof(BITMAPFILEHEADER);// 文件大小
bmfHeader.bfReserved1 = 0;// 两个保留字
bmfHeader.bfReserved2 = 0;
bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + lpbmihrd->biSize// 计算偏移量bfOffBits
+ GetPalSize((LPSTR)lpbmihrd);
file.Write(&bmfHeader, sizeof(BITMAPFILEHEADER));// 写文件头
file.WriteHuge(lpbmihrd, dwDIBSize);// 写DIB头和像素

::GlobalUnlock((HGLOBAL) hDib);// 解除锁定
return TRUE;// 返回TRUE
}

window 消息说明

WM_PAINT = $000F;
要求一个窗口重画自己
WM_CLOSE = $0010;
当一个窗口或应用程序要关闭时发送一个信号
WM_QUERYENDSESSION = $0011;
当用户选择结束对话框或程序自己调用ExitWindows函数
WM_QUIT = $0012;
用来结束程序运行或当程序调用postquitmessage函数
WM_QUERYOPEN = $0013;
当用户窗口恢复以前的大小位置时,把此消息发送给某个图标
WM_ERASEBKGND = $0014;
当窗口背景必须被擦除时(例在窗口改变大小时)
WM_SYSCOLORCHANGE = $0015;
当系统颜色改变时,发送此消息给所有顶级窗口
WM_ENDSESSION = $0016;
当系统进程发出WM_QUERYENDSESSION消息后,此消息发送给应用程序,
通知它对话是否结束
WM_SYSTEMERROR = $0017;
WM_SHOWWINDOW = $0018;
当隐藏或显示窗口是发送此消息给这个窗口
WM_ACTIVATEAPP = $001C;
发此消息给应用程序哪个窗口是激活的,哪个是非激活的;
WM_FONTCHANGE = $001D;
当系统的字体资源库变化时发送此消息给所有顶级窗口
WM_TIMECHANGE = $001E;
当系统的时间变化时发送此消息给所有顶级窗口
WM_CANCELMODE = $001F;
发送此消息来取消某种正在进行的摸态(操作)
WM_SETCURSOR = $0020;
如果鼠标引起光标在某个窗口中移动且鼠标输入没有被捕获时,就发消息给某个窗口
WM_MOUSEACTIVATE = $0021;
当光标在某个非激活的窗口中而用户正按着鼠标的某个键发送此消息给当前窗口
WM_CHILDACTIVATE = $0022;
发送此消息给MDI子窗口当用户点击此窗口的标题栏,或当窗口被激活,移动,改变大小
WM_QUEUESYNC = $0023;
此消息由基于计算机的训练程序发送,通过WH_JOURNALPALYBACK的hook程序
分离出用户输入消息
WM_GETMINMAXINFO = $0024;
此消息发送给窗口当它将要改变大小或位置;
WM_PAINTICON = $0026;
发送给最小化窗口当它图标将要被重画
WM_ICONERASEBKGND = $0027;
此消息发送给某个最小化窗口,仅当它在画图标前它的背景必须被重画
WM_NEXTDLGCTL = $0028;
发送此消息给一个对话框程序去更改焦点位置
WM_SPOOLERSTATUS = $002A;
每当打印管理列队增加或减少一条作业时发出此消息
WM_DRAWITEM = $002B;
当button,combobox,listbox,menu的可视外观改变时发送
此消息给这些空件的所有者
WM_MEASUREITEM = $002C;
当button, combo box, list box, list view control, or menu item 被创建时
发送此消息给控件的所有者
WM_DELETEITEM = $002D;
当the list box 或 combo box 被销毁 或 当 某些项被删除通过LB_DELETESTRING, LB_RESETCONTENT, CB_DELETESTRING, or CB_RESETCONTENT 消息
WM_VKEYTOITEM = $002E;
此消息有一个LBS_WANTKEYBOARDINPUT风格的发出给它的所有者来响应WM_KEYDOWN消息
WM_CHARTOITEM = $002F;
此消息由一个LBS_WANTKEYBOARDINPUT风格的列表框发送给他的所有者来响应WM_CHAR消息