61阅读

编程入门-Windows SDK编程 API入门系列(转)

发布时间:2018-02-21 所属栏目:directx9.0是什么

一 : Windows SDK编程 API入门系列(转)

之一-那‘烦人’的Windows数据类型

原创文章,转载请注明作者及出处。(www.61k.com)

首发

CSDN文章地址:http://blog.csdn.net/beyondcode/archive/2009/03/23/4015769.aspx

Hello Everybody This is beyondcode

大家好 再次自我介绍一下 我是beyondcode, 这次心血来潮, 计划着做一系列关于Windows API 编程的教程,用于帮助一些在Windows API编程上有疑惑的,纳闷的,迷惑的新手朋友们。

先解释一些术语或名词吧  SDK是Software Development Kit的简写,也就是软件开发包的意思,其中就包含了我们写程序要用到的一些头文件,库,工具,帮助文档之类的。

Windows API编程是指调用Windows的接口函数来进行程序的编写,例如MessageBox就是一个API函数或者说接口函数。怎么说都可以,自己理解就行。如果你连这个都不太懂,我想也不会搜到这篇文章了吧~·

为什么做这个系列教程呢,请听我一一道来先,最近遇到一些事一些人,让我真的感觉在这方面的引导入门文章真的很是匮乏,加上Windows SDK头文件中那些复杂,庞大,'烦人'的宏定义与数据类型定义,对于一个新手来说(我所说的新手不单只刚接触编程的,还特指那些在其他语言领域有比较高造诣的朋友) 一个纯SDK写的helloworld程序都算是一个有些困难和挑战的任务了吧。 本着帮助别人,高兴自己的原则,我有了这个打算,当然对自己以前所学,所经历做一次回忆,也是这次计划的一部分。

声明一下,本系列教程是面向广大初次接触WIN32 SDK程序编写的新手朋友们的,如果你是高手,一笑而过吧~当然,除了一笑而过,也多谢你们提出指正文章中的错误,以免我误人子弟啊~~谢谢

Ok 废话不多说,进入正题,今天第一篇,讲什么?  对于一个新人来说,第一次接触SDK编程或者说API编程,什么最迷惑你们的,我们讲它,我觉得Windows SDK中那'烦人'的数据类型定义和宏定义应该算这个很角色吧。。

其实微软的本意也是善良的,为了减轻程序员的负担,和为了编程的方便,才花了那么多心思与精力定义出了这么一大套数据类型与宏定义,这也是我为什么在之前说它烦人都是加上引号的原因,因为他不是真的烦人,熟练了,你不但不觉得它烦,反而离不开它了,呵呵,日久深情也就是这么来的。

呵呵 先看几个数据类型定义吧

typedef float FLOAT;

typedef long LONG;

typedef short SHORT

typedef int INT;

typedef char CHAR;

float, long, short, int, char 这几个数据类型都是大家熟悉的C/C++的数据类型吧,微软将他们重新定义了一下,很简单,就是改变名字为大写了,这样做的目的大概是微软为了编码的方便吧,输入法大小写都不用切换了,多人性化呀 呵呵。。

再看几个数据类型定义的例子

typedef unsigned int UINT;

typedef unsigned int UINT32;

typedef signed int INT32;

typedef unsigned long DWORD;

typedef unsigned short WORD;

这些数据类型的定义就稍微有实质性作用一些了,注意观察,他们都比较短了,不用写那么长了,而且也还比较直观,如果我要定义一个无符号整形, 我就不用写 unsigned int a;

这么长了,只需UINT a; 多简单, 多明了,所以我说其实不烦人吧。

其中DWORD 算是SDK程序中可以经常看见的一个数据类型了,经常被使用,很多新手也就不明白,这是什么数据类型啊,现在看到了吧,其实就是无符号长整形unsigned long,给他取了个外号而已··没什么技术含量,所以不用怕,程序中究竟是写unsigned long 还是DWORD都看你自己心情,因为他们都代表同一种数据类型。

下面再介绍2个很重要的,经常被使用到的,无处不在的数据类型WPARAM,LPARAM

先看看他们定义吧

typedef LONG_PTR LPARAM;

typedef UINT_PTR WPARAM;

先告诉你,这2个数据类型很重要,不是危言耸听,以后你写SDK程序就知道了,看他们的定义如上,有些迷糊? 别,我们一步一步分析,我们分析LPARAM。首先定义LPARAM 为LONG_PTR也就是用LPARAM的地方也就可以写成LONG_PTR,LONG_PTR又是被定义成什么的呢?  

typedef long LONG_PTR;

看到了吗?  也就是long 所以归根结底,LPARAM 就是long型,所有LPARAM型的变量,你都可以直接使用long数据类型代替。不过不推荐这样,至于为什么,各位思考思考呢~~

以上这些数据类型是参考MSDN中的说明,或者可以查看WinDef.h这个头文件查看这些Windows数据类型的定义,那么也请各位自己推推看LARAM和WPARAM的真面目吧~

各位朋友在推导的过程中可能发现LONG_PTR的定义是这样写的

#if defined(_WIN64)

typedef __int64 LONG_PTR;

#else

typedef long LONG_PTR;

#endif

这是什么意思呢,能看懂英文都能知道这在定义些什么,如果定义了 _WIN64这个宏 那么就定义 LONG_PTR 为 __int64,否则定义LONG_PTR为long。 很简单吧 也就是说如果_WIN64这个宏在前面被定义了,那么这里的LONG_PTR就被定义为__int64这个在64位编程下的数据类型,否则就定义为long型,这样说应该比较好理解了吧。在这里,各位就不必深究__int64了, 在目前的主流32位编程下很少使用它啦。理解就ok了。这样定义是微软为了程序员编写的程序能在32位与64位下都能编译而采用的伎俩。

有关这些Windows的数据类型,想查看他们的真面目,其实很简单,在VC6.0,VS2008 这些集成开发环境里面,你只需要在一个数据类型上面点击右键,在弹出菜单中选择‘Goto Defination’ 或者是 ‘查看定义’就可以看到了,如果看到的还不是最终面目,在继续上面步骤。直到看到它的本质数据类型为止。通过这样,新手对于Windows的这些复杂的数据类型定义也就有了根本的认识,不再是迷迷糊糊,在以后的编程中也就不会出现不知道用哪种数据类型或者哪些数据类型之间可以相互转换的情况了。不过还需要多多观察与练习才是啊~~

扩展:windowsapi编程入门 / win32 api编程入门 / win32 api编程入门pdf

下面再来看一看windows中定义的一些宏

#define  VOID void

#define CONST  const

2个最简单的宏,也是只变成大写而已,难道又是为了方便程序员不切换输入法?还真的人性化呀。

Windows SDK中的宏定义是最庞大的,最复杂的,但也是最灵活的,为什么这样说,先不告诉你,我会在以后的系列文章中一点一点的讲解,累积,因为太多了,也比较复杂,我们就采取在需要用到的时候才讲解它,目前看来还没这个必要了解那么多,就了解上面2个很简单的好了,像其他如:WINAPI   CALLBACK   GetWindowText 这些宏现在讲了不但记不住还会增加你们的负担。,我们就在以后要用到的时候再做讲解。

到这里第一篇系列文章的内容也就差不多了。新手朋友们哪些地方迷惑的,提出来,我可以考虑是否加在后续的文章中进行解说。本SDK系列入门教程需要你们的支持。谢谢。

之二 -Unicode还是ASCII

今天,开始第二篇文章,这章我准备介绍一下Windows平台下编程中Unicode编码和ASCII编码的相关问题。

不知道各位新手朋友们遇到这样的问题没有呢,新建一个Windows应用程序,调用MessageBox这个函数,准备让它弹出一段提示文本,可是编译器在编译的时候却报错说,不能将 const char* 或者 const char[] 转换为 const wchar_t* 之类的提示呢,很多刚接触Windows API编程的朋友们在这里可能就卡住了,不知如何下手解决了,其实,这就是Unicode编码和ASCII编码的问题了。我下面就会一一道来

关于Unicode和ASCII具体的编码是怎么的,我这里就不详细介绍了,也介绍不了,如果需要深入了解,网上有很多这方面的专门文章,我这里就只对Unicode编码和ASCII编码在Windows平台下的编程相关的内容进行介绍。

我们都知道Unicode和ASCII最大的区别就是Unicode采用2个字节来存储一个字符,不管是英文,汉字,还是其他国家的文字,都有能用2个字节来进行编码,而ASCII采用一个字节存储一个字符,所以对于英文的编码,那是足够的了,可是对于汉字的编码,则必须采用一些特殊的方法,用2个ASCII字符来表示一个汉字。

我们在写程序的过程中,势必要和字符打交道,要输入,获取,显示字符,到底是选用Unicode字符呢还是ASCII字符呢,这都是各位自己的权利。但为了程序的通用性和符合目前操作系统的主流趋势,Unicode编码是被推荐的。由于Unicode字符要比ASCII字符占用的空间大一倍,编译出来的程序在体积上和占用的内存上必定要大一些,不过这并不是什么很大的问题。所以微软目前的SDK中保留了2套API,一套用于采用Unicode编码处理字符的程序的编写,一套用于采用ASCII编码处理字符的程序的编写。 例如,我们上面提到的MessageBox,它其实不是一个函数名,而是一个宏定义,我们先来看看它是怎么被定义的,再来讨论它。

#ifdef UNICODE

#define MessageBox  MessageBoxW

#else

#define MessageBox  MessageBoxA

#endif 

看到了吗?  很简单是不是, 如果定义了UNICODE 这个宏 那么就定义MessageBox为MessageBoxW,如果没有定义UNICODE这个宏, 那么就定义MessageBox 为MessageBoxA,MessageBox后面的W和A 就是代表宽字节(Unicode)和ASCII,这样,其实存在于SDK中的函数是MessageBoxW和MessageBoxA这两个函数.

MessageBox只是一个宏而已。所以在程序中,这3个名字你都可以使用,只不过需要注意的是,使用MessageBoxA的话,那么你要注意传给它的参数,字符都必须是单字节,也就是ASCII, 在程序中就是char,如果使用MessageBoxW的话,那么,字符都必须使用Unicode,程序中就是 wchar_t。 但是这样有个非常不方便的地方那就是,如果你使用W后缀系列的函数的话,那么你的程序使用的字符就是Unicode字符编码的,但是如果你需要用这个程序的源代码编译出字符采用ASCII编码的程序,那么需要改动的地方就太大了。凡是涉及到字符操作的地方都需要改变。那么 ,有没有比较好的办法不做更改就可以用同样的代码编译出ASCII版本的程序呢。  

当然有,就是我们在编程的时候尽量使用不带后缀的宏定义,如上例,就使用MessageBox,其中的参数也不明确使用char 还是wchar_t 而是使用微软给我们定义的TCHAR字符数据类型,它的定义和上面MessageBox函数的定义差不多,都是根据是否定义了UNICODE这个宏来判断是将TCHAR定义为char还是wchar_t,所以这样一来,这个TCHAR的数据类型就是可变的了,它根据工程的设置而定义为相应的最终字符类型,这样我们的程序就可以不做任何更改就可以轻松的编译出另外一个版本的了。是不是非常方便。

前面2篇文章纯文字的介绍比较多,因为很多是概念性的,需要理解,后面的文章我准备配合一些小示例程序,使用一些简单的API函数,遇到的相关的概念在一并介绍的方法进行。所以,前2篇文章如果各位朋友不是很能理解,不用担心,影响不是很大,经过后面的学习,你就会慢慢的理解前面所说的内容了。

之三 -那迷惑人的Windows字符和字符指针类型

大家好,通过前面两篇打头文章,我也看了留言,感谢那些给我提意见的人和指出错误之处的人。再次谢谢你们的支持。 另外,Windows SDK编程交流群已经建立了,欢迎各位志同道合者加入进行交流( 群号:81543028 )

本打算通过前面两篇文章的讲解,后来的系列就可以通过使用一些简单的,常用的API写一些示例程序的讲解进行,但是发现还有一个不得不先讲一讲的要点,Windows下和字符串操作有关的数据类型。我看留言中也有几位朋友提到了,那我就在这篇中讲它吧。不会很枯燥的,各位慢慢看下去就是了。

下面我罗列一些我们在Windows平台下编程经常使用到的和字符或字符串有关的数据类型。

char  和  wchar_t 

这两个类型大家绝对不会陌生吧,一个是单字节的字符类型,一个是宽字节的字符类型(也就是Unicode字符)。

扩展:windowsapi编程入门 / win32 api编程入门 / win32 api编程入门pdf

char   c = 'b';

wcha_t  wc = L'b';

上面我就分别定义了2个变量c和wc ,相信第一个定义大家都看的懂,就是定一个字符变量c,其中保存了'b'这个字符。 那么第二个呢?  我相信还是很多人都看的懂,要是你看不懂也没关系,现在就告诉你,也是定义一个字符变量wc, 只不过这个字符变量是Unicode字符变量,用2个字节来保存一个字符,而上面的c这个字符变量只有一个字节来保存,那么在'b'前面的L又是什么意思呢,它就表示这里的'b'这个字符是一个Unicode字符,所以第二个定义的意思就是将L'b'这个Unicode字符保存到wc这个Unicode字符变量中。

如果我要定义一个字符数组怎么定义呢? 用分别用单字节的char和宽字节的wchar_t来定义就应该是:

char c[10];

wchar_t wc[10];

如果是要带初始化的字符数组的声明,我们来看看怎么写

char c[] = "beyondcode";

wchar_t wc[] = L"beyondcode";

看到了吗,宽字节的操作其实和单字节的字符操作一样吧,只是在前面加上L表示是宽字节的字符或者字符串。

上面都是属于C/C++中的知识,并没有涉及太多Windows中的数据类型,那么各位朋友们在Windows编程中看到的满到处都是的 TCHAR,LPSTR, LPCSTR, LPWSTR, LPCWSTR, LPTSTR, LPCTSTR 这些数据类型又是怎么回事呢? 别急,我们一步一步的来,最后我会联系到那上面去的。

上面的你都知道或者是理解了的话,那我们继续,除了可以声明一个字符数组,我还可以定义一个字符指针变量来指向一个字符数组,当然这个字符数组可以是Unicode的宽字节字符数组,也可以是单字节字符数组,如下:

char c[] = "hello beyondcode"; //定义一个字符数组

wchar_t  wc[] = L"hello beyondcode"; //定义一个宽字节字符数组

char  *p = c; //定义一个字符指针,指向刚才的字符数组

wchar_t *wp = wc; //定义一个宽字节字符指针,指向刚才的宽字节字符数组

这样之后,我就可以通过指针来改变刚才我们定义的2个数组,例如:

p[0] = 'H';

wp[0] = L'H';

把上面2个数组的第一个字符通过指针改变成大写。这里是可以通过指针来修改的,因为我没有定义指针为常量指针,也就是没有加const 修饰符。如果我像下面这样定义的话,那么就不能通过这些指针来改变他们所指向的数据了,而是只有读取他们。

const  char  *p = c;

const  wchar_t  *wp = wc;

上面将的都是C/C++的基础知识,有点啰嗦,为了照顾新手朋友们嘛,下面我们就来看看Windows是怎么定义它的数据类型的

首先,定义了CHAR, WCHAR的这2个字符数据类型,就是我们上面讨论的两个字符数据类型改了一下名字而已。现在你还不昏吧··

typedef char CHAR;

typedef wchar_t WCHAR;

然后,用刚才定义的 CHAR, WCHAR这2个字符数据类型去定义了一系列其他字符指针类型。

typedef  CHAR  *LPSTR;

typedef  WCHAR  *LPWSTR;

这样一定义之后,LPSTR的就是 CHAR*, 而CHAR 又是char, 所以LPSTR的本质就是 char*,也就是我们上面熟悉的不能再熟悉的字符指针,  那LPWSTR不用我推导,相信你也推导出来了吧。不过我还是推导一下,LPWSTR是 WCHAR * , WCHAR是wchar_t,这样LPWSTR就是 wchar_t* ,也就是我们上面讨论的宽字节字符指针。上面这些定义都是在WinNT.h这个头文件中定义的,读者朋友们有兴趣在这个头文件里面去挖掘挖掘吧,上面2个定义我只是提取了重要的部分,其实在里面他还定义了其他很多别名.

看了LPSTR, LPWSTR是怎么一回事之后,我们再接再厉,看看LPCSTR,LPCWSTR这2个数据类型又是怎么一回事呢, 老规矩,先看windows的定义。

typedef  CONST  CHAR  *LPCSTR;

typedef  CONST  WCHAR *LPCWSTR;

和上面的比较,名字中就多了一个大写的C,这个C的含义就代表是const修饰符,也就是我们上面所说的常量指针,指向的内容不能通过这个指针被改变,但可以读取。定义中的大写的CONST也是一个宏,我在第一篇文章中就讲过了,代换出来也就是const, 所以请读者自己推导一下这两个数据类型的本质是什么。

所以,在windows平台下的编程过程中,凡是可以使用char* 的地方,你都可以使用LPSTR来代替,凡是可以使用wchar_t*的地方,你都可以使用LPWSTR来代替,至于怎么用,还是那句老话,看你个人心情,只不过Windows的API函数中关于字符串的都是使用LP这种数据类型。但是你还是可以给他传递char* 或者 wchar_t* ,只要他们的本质是一样的,那怎么不可以呢~~

下面,我们来看一看一些示例。

char c = 'c';  和 CHAR c = 'c';   是一样的

wchar_t wc = L'w'; 和 WCHAR wc = L'w';   是一样的

char* p  和 LPSTR p 是一样的

wchar_t* wp  和 LPWSTR wp   是一样的

再来看看动态内存分配怎么写的呢

char* p = new char[10]; //动态分配了十个字符

也可以写成

CHAR* p = new CHAR[10];

LPSTR p = new CHAR[10];

LPSTR p = new char[10];

宽字节的再来一次

wchar_t* wp = new wchar_t[10];

扩展:windowsapi编程入门 / win32 api编程入门 / win32 api编程入门pdf

也可以写成下面这些形式

WCHAR*  wp = new WCHAR[10];

LPWSTR  wp = new WCHAR[10];

LPWSTR  wp = new wchar_t[10];

上面定义的这些字符指针 p , wp都没有用const修饰符,所以可以通过他们来修改他们所指向的内容。这里留给读者一个问题,怎么定义有const修饰符的字符指针呢,都可以用什么形式来写呢,写得越多越好哟。。

通过上面这些,我想你大概已经了解了LPSTR, LPCSTR, LPWSTR, LPCWSTR这四个数据类型了,他们无非就是:

LPSTR -------    char*

LPCSTR ------- const char*

LPWSTR ------- wchar_t*

LPCWSTR --------  const wchar_t* 

下面我提一个问题,如果你在你的程序中使用的字符串都是通过LPWSTR,LPCWSTR这种宽字节(Unicode)字符指针来进行操作的,那么在Unicode环境下编译,完全没有问题,如果这时你需要编译一套ASCII版本的程序,那你会怎么办呢?   你说将用LPWSTR 和LPCWSTR的地方全部换成LPSTR和LPCSTR,再将字符串前面的L去掉就可以了,对,这是一种方法,但是!!所有人在这里都应该知道我要说但是,这也太麻烦了吧。难道没有通用点的方法吗?   有!!  所有人在这里也都知道我会说有,呵呵。  那就是使用微软的通用数据类型,说通用数据类型有点太专业了,其实也就那样,请听我慢慢分析来。我在上一篇文章中说过,凡是涉及字符串操作的API函数有2套,一个A系列的,一套W系列的,还有一套宏,能根据不同的工程环境定义成不同的API函数名。那么在字符类型上微软也使用几乎同样的技术,定义了一套宏能根据不同的工程环境定义成不同的字符数据类型。我上面就提到过的TCHAR,LPTSTR, LPCTSTR就是这样的类型。

首先说说TCHAR,它是被这样定义的:

#ifdef  UNICODE

typedef  WCHAR  TCHAR;

#else

typedef  char TCHAR

看到了吗? 它也是根据UNICODE这个宏被定义没有,如果被定义了,那么TCHAR代表的数据类型就是WCHAR, 也就是wchar_t, 如果没被定义,那么TCHAR 就代表的是char

同样LPTSTR,LPCTSTR也是这样的,考虑到篇幅,我就只列出LPTSTR来给大家看看了

#ifdef   UNICODE

typedef  LPWSTR  LPTSTR;

#else

typedef  LPSTR LPTSTR;

这个是我简化了的定义,真实面目有些复杂,不过意思也是如此,有兴趣可以自己看看,在WinNT.h这个头文件中。下面再次解释一下上面这个LPTSTR的定义, 还是老样子,根据UNICODE这个宏被定义与否来决定怎么定义LPTSTR ,如果是定义了UNICODE这个宏,表示当前工程环境是Unicode环境,那么LPTSTR就被定义为了LPWSTR, LPWSTR就是我们前面所讲的wchar_t* ,所以此时LPTSTR代表的数据类型就是wchar_t* ,  如果这时的工程没有定义UNICODE这个宏,那么就定义LPTSTR为LPSTR,而LPSTR就是我们前面所说的char* ,所以这是的LPTSTR就代表char*。懂了吗?各位,我都觉得自己有些啰嗦了··不好意思···

然后还有一个宏需要讲一下,由于我们使用通用数据类型,那么我事先就不知道我的源代码需要在Unicode下编译还是在ASCII环境下编译,所以如下这种情况

TCHAR tc = 'a';  或者是 TCHAR tc = L'a';  是否合适呢? 前面我已经说过了字符或字符串常量前面加L代表这是宽字节的字符或字符串,将一个宽字节字符赋值给一个TCHAR数据类型的变量tc,什么情况下是正确的呢?  各位思考一下呢?  

如果当前工程是Unicode环境,那么TCHAR数据类型就是wchar_t的宽字节类型,所以tc就是宽字节字符变量,那么上面第二个赋值语句就是正确的,而第一个就是错误的。

如果反过来,当前的工程是ASCII环境,那么TCHAR代表的是char这种数据类型,那么第一个赋值语句就是正确的,而第二个就是错误的了。

分析了这么多,我就是要讲一个宏 _T(), 只要将字符或者字符串常量放在_T()这个宏里面,那么这个宏就能根据当前的环境决定是否在字符或字符串前面加L,如下面:

TCHAR tc = _T('A');

如果这么写,在不需要改写源代码的情况下,就可以编译出Unicode和ASCII两套程序

而只需要改变工程的环境而已。

这篇文章的内容大概就这么多了,关于后续文章的内容安排,我会适当采纳各位朋友的留言来进行安排。

在这里我介绍的是Windows平台下的和字符串操作有关的数据类型,至于MFC中的CString类,c++标准库中的string,我就不做讲解了。

还有,前两篇的文章中我说微软定义这些数据类型为大写是为了编码的方便,不需要切换输入法,很多朋友都留言给我指出了,其实我是开了一个小玩笑,毕竟面对的是初学者,我觉得在目前这种环境下,没有必要给他们讲什么代码移植性啊,兼容性这些概念,那些在达到一定程度之后就会慢慢理解的,所以我自己想了一个小小的优点来说明那个问题,不过还是感谢你们的补充说明,谢谢··

之四 -一个相当简单的SDK程序

 大家好,还是我beyondcode,再次见面,前面介绍的那么多'理论知识',你们都懂了吗? 就算还没有彻底领悟,但至少还是有那么一点意识了吧,知道有那么一回事了吧。这一篇我打算通过一个小小小例子,来回忆一下我们以前介绍的相关知识,如Windows的数据类型,特别是和字符和字符串操作相关的数据类型,还有就是Unicode和ASCII在API函数上的具体体现。

另外,SDK编程交流群已经建立,很多朋友踊跃参加,系列文章和群的发展离不开你们。群号:81543028。

Ok,我们正式开始,我打算从一个简单的SDK程序开始,别怕,就几行代码而已··

扩展:windowsapi编程入门 / win32 api编程入门 / win32 api编程入门pdf

/* BY beyondcode */

#include <windows.h>

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)

{

MessageBoxA( NULL, "Hello beyondcode", "Title", MB_OK );

return 0;

}

程序你已经看到了,这恐怕就是一个最简单的带窗口的SDK程序了吧,如果你能写出代码行数比这个还少,又带窗口显示字符串的SDK程序,欢迎交流,呵呵,开个玩笑。

程序倒是简单,可是我还是要问一问,这个程序,你通过观察我在字符串的处理,还是在API函数的调用,还是主函数的参数写法,你能看出什么问题呢?.....................................对,就是我全部明确指出是单字节版本的,WinMain的第三个参数是LPSTR类型,调用的MessageBox是带A后缀的单字节版本,字符串常量"Hello beyondcode"和"Title"都没有使用L前缀。那么第二个问题来了, 如果我告诉你我现在的工程环境是 使用Unicode字符集 (工程使用的字符集可以在 【项目】->工程属性 弹出的属性页中的 【配置属性】中的【常规】左边的【字符集】中设置),那么我上面的程序能正常通过编译吗? 当然能,因为我已经试过了,不信你也可以试试,可是为什么呢? 这是因为我指定的参数和函数需要的参数都是单字节版本的,也就是说他们相互匹配。要是我这里将MessageBoxA改成MessageBoxW呢? 就会出错吧,因为MessageBoxW的第二个,和第三个参数是需要LPCWSTR,通过上一篇学习,我们知道也就是const wchar_t*, 而我给出的两个字符串常量却没有用L前缀.也就是说他们是单字节的,传给宽字节版本的MessageBoxW当然就类型不匹配了啊,所以就通不过编译了吧。

通过上面的学习,我再出一个问题,如果我此时的工程环境是使用Unicode字符集,而这里我不用MessageBoxA,也不用MessageBoxW,而是用MessageBox,其他的都不变,结果会怎么样呢?   不能理解的可以加群讨论哟~~~

好了,单字节版本的程序,我们已经看到了,我们再来看看我们怎么才能把它改成宽字节版本的呢?

其实需要改的地方不多,也就5处WinMain改成wWinMain, WinMain的第三个参数改成LPWSTR,MessageBoxA改成W,两个字符串常量加L就ok了。

/* BY beyondcode */

#include <windows.h>

int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd )

{

MessageBoxW( NULL, L"Hello Beyondcode", L"Title", MB_OK );

return 0;

}

如果我想写一个代码比较通用的版本,也就是可以不用改动代码,就能编译出Unicode和ASCII的两个版本的程序,我应该怎么写呢?  其实就是我上一篇重点讨论的,凡是涉及到字符串的都不明确指出是Unicode还是ASCII版本的,调用的API函数凡是涉及到字符串参数的都不明确指出调用是A后缀的还是W后缀的函数,而是调用没有后缀的函数,如上面的MessageBox,这样就能写出代码比较通用的程序了。那么我们现在来把我们上面的程序改一改,让它通用

/* BY beyondcode */

#include <windows.h>

#include <tchar.h>

int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd )

{

MessageBox( NULL, _T("Hello Beyondcode"), _T("Title"), MB_OK );

return 0;

}

WinMain被改成了_tWinMain ,_tWinMain也是一个宏,根据UNICODE这个宏被设置与否而被定义成WinMain或wWinMain,和LPTSTR是一样的,这里还需要注意的是要包含tchar.h这个头文件,因为_tWinMain和_T()这些宏是被定义在里面的。经过上面我们就写出了第一个SDK的可以编译出两个版本的比较通用的程序代码了。是不是有点成就感了呢。。

下面,我们继续在上面的程序中加一些功能,让它计算1到10的和,然后把结果显示给我们看,这个地方,很多SDK初学者就不知所措了,因为一个和是一个整数,怎么显示这个整数给我们呢,通过对话框? MessageBox,可是MessageBox显示的是字符串。而我们这里又不是控制台程序可以使用printf之类的格式化输出函数来输出数字,也不能使用cout之类的C++对象来输出,那我们怎么办呢? 通过对话框来显示结果是不错的选择,但是对话框需要的是字符串,那我们就把我们的结果格式化到一个字符串里面,然后传送给MessageBox让它显示出来。那么就需要用到格式化字符串函数,下面我们就介绍wsprintf这个函数

#ifdef UNICODE

#define wsprintf  wsprintfW

#else

#define wsprintf  wsprintfA

#endif // !UNICODE

说它是函数,是不确切的。因为它实际是一个宏,根据环境被定义成不同的函数名wsprintfW或者wsprintfA, 而我们为了程序的通用性,直接使用wsprintf,传递的参数凡是涉及到字符串常量的我们都是用_T()宏,字符串指针的我们都使用LPTSTR和LPCTSTR。

下面我就先贴出添加了功能的程序代码,然后在做分析,你可以先不看分析,自己看一看代码,不懂的猜一猜它的意思。

/* BY beyondcode */

#include <windows.h>

#include <tchar.h>

int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd )

{

int sum = 0;

for( int i = 1; i<=10; i++ )

扩展:windowsapi编程入门 / win32 api编程入门 / win32 api编程入门pdf

sum += i;

TCHAR strSum[256] = { 0 };

wsprintf( strSum, _T("%d"), sum );

MessageBox( NULL, strSum, _T("Title"), MB_OK );

return 0;

}

怎么样,也还不算复杂吧,计算1到10的那部分不用我讲了吧,最后的结果存放在sum这个变量里,我们现在的目的就是要让它显示在MessageBox弹出的对话框上面。

首先我们定义一个字符数组,我使用的是通用类型TCHAR,然后把它全部初始化为0。接着调用格式化字符函数wsprintf,它的第一个参数是LPTSTR类型的,指定经过格式化的字符串存放的地方,第二个参数是指定以什么格式来格式化后面的数据,这里我们要格式化一个整数,所以指定%d,这个和printf这些函数是一样的, 后面的参数就是我们要格式化的数据了。

这里还有一点可能有些新手朋友们不太懂,那就是,wsprintf要求的第一个参数是LPTSTR,而我传递的是一个TCHAR的数组名,这里我就在啰嗦一次咯,我们假设我们的环境是UNICODE的,那么LPTSTR相当于什么类型呢? 上一篇就讲过的啊,就是wchar_t* 就是宽字符指针,而我们知道数组名就是代表这个数组元素类型的指针,那么这里TCHAR数组的元素类型就是TCHAR,在Unicode环境下,TCHAR就是wchar_t也就是说strSum代表的是wchar_t类型的指针,也就是wchar_t*,所以看到了吗,他们是一样的类型。

通过上面的wsprintf函数的调用strSum这个字符数组中就包含了计算结果的字符串表示,然后我们通过MessageBox讲这个字符数组中的内容显示出来。在这里MessageBox的第二个参数类型是LPCTSTR,也就是const wchar_t*, 而我们上面说过我们的strSum代表的是 wchar_t*,这里同样可以传递给它又是为什么呢?这是因为阿,这里strSum在传递给MessageBox的时候就隐式转换成了const wchar_t*了。

有关格式化字符串的函数还有如下,详细用法各位可以查看MSDN,和上面所介绍的都差不多

sprintf 单字节版本的C/C++库函数

swprintf 宽字节版本的C/C++库函数

而我们上面的wsprintf和上面两个函数看起来很相似,大家不要搞混淆了啊,wsprintf最前面的w不是代表Wide,宽字节的意思了,而是Windows的W,代表是windows的API函数了,其实它是一个宏这在上面已经说过了,真正的API函数其实是wsprintfA和wsprintfW这两个,在不严格的情况下通常我们也说wsprintf是函数,只要大家懂就行了~

OK, 这一篇文章就到这里了,源代码就这么点,我也不上传了,各位有兴趣自己敲一下。下一篇,我讲会就怎么自己创建窗口进行介绍,谢谢大家的支持。

扩展:windowsapi编程入门 / win32 api编程入门 / win32 api编程入门pdf

二 : linux_Shell编程入门

linux Shell编程入门

下面,让我们一起来看看shell是如何工作的:

建立一个脚本

Linux中有好多中不同的shell,但是通常我们使用bash (bourne again shell) 进行shell编程,因为bash是免费的并且很容易使用。(www.61k.com)所以在本文中笔者所提供的脚本都是使用bash(但是在大多数情况下,这些脚本同样可以在bash的大姐,bourne shell中运行)。

如同其他语言一样,通过我们使用任意一种文字编辑器,比如nedit、kedit、emacs、vi等来编写我们的shell程序。程序必须以下面的行开始(必须在文件的第一行):

#!/bin/sh

符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序。当编辑好脚本时,如果要执行该脚本,还必须使其可执行。

要使脚本可执行:

chmod +x filename

然后,您可以通过输入: ./filename 来执行您的脚本。

linux shell编程(续1)

2007年05月21日 星期一 21:00

注释

在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我们真诚地建议您在程序中使用注释。如果您使用了注释,那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用及工作原理。

变量

在其他编程语言中您必须使用变量。在shell编程中,所有的变量都由字符串组成,并且您不需要对变量进行声明。要赋值给一个变量,您可以这样写:

变量名=值

取出变量值可以加一个美元符号($)在变量前面:

#!/bin/sh

#对变量赋值:

a="hello world"

# 现在打印变量a的内容:

echo "A is:"

echo $a

在您的编辑器中输入以上内容,然后将其保存为一个文件first。之后执行chmod +x first。使其可执行,最后输入./first执行该脚本。

这个脚本将会输出:

shell 编程 linux_Shell编程入门

有时候变量名很容易与其他文字混淆,比如:

shell 编程 linux_Shell编程入门

这并不会打印出"this is the 2nd",而仅仅打印"this is the ",因为shell会去搜索变量numnd的值,但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量:

shell 编程 linux_Shell编程入门

shell 编程 linux_Shell编程入门

这将打印: this is the 2nd

有许多变量是系统自动设定的,这将在后面使用这些变量时进行讨论。[www.61k.com)

如果您需要处理数学表达式,那么您需要使用诸如expr等程序(见下面)。除了一般的仅在程序内有效的shell变量以外,还有环境变量。由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录脚本中使用环境变量。

Shell命令和流程控制

在shell脚本中可以使用三类命令:

虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令。这些命令通常是用来进行文件和文字操作的。

常用命令语法及功能:

echo "some text": 将文字内容打印在屏幕上。

ls: 文件列表。

wc –l file wc -w file wc -c file: 计算文件行数 计算文件中的单词数 计算文件中的字符数。

cp sourcefile destfile: 文件拷贝。

mv oldname newname : 重命名文件或移动文件。

rm file: 删除文件。

grep 'pattern' file: 在文件内搜索字符串比如:grep 'searchstring' file.txt

cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut –b 5-9 file.txt千万不要和cat命令混淆,这是两个完全不同的命令。

cat file.txt: 输出文件内容到标准输出设备(屏幕)上。

file somefile: 得到文件类型。

read var: 提示用户输入,并将输入赋值给变量。

sort file.txt: 对file.txt文件中的行进行排序。

uniq: 删除文本文件中出现的行列比如: sort file.txt | uniq。

expr: 进行数学运算Example: add 2 and 3 expr 2 "+" 3。

find: 搜索文件比如:根据文件名搜索find . -name filename -print。

tee: 将数据输出到标准输出设备(屏幕) 和文件比如:somecommand | tee outfile。

basename file: 返回不包含路径的文件名比如: basename /bin/tux将返回 tux。

dirname file: 返回文件所在路径比如:dirname /bin/tux将返回 /bin。

head file: 打印文本文件开头几行。

tail file : 打印文本文件末尾几行。

sed: Sed是一个基本的查找替换程序。可以从标准输入(比如命令管道)读入文本,并将结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。不要和shell中的通配符相混淆。比如:将linuxfocus 替换为 LinuxFocus :cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file。

awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。cat file.txt

shell 编程 linux_Shell编程入门

shell 编程 linux_Shell编程入门

命令输出结果为:

shell 编程 linux_Shell编程入门

2) 概念: 管道, 重定向和 backtick

这些不是系统命令,但是他们真的很重要。(www.61k.com]

管道 (|)

shell 编程 linux_Shell编程入门

在file.txt中搜索包含有”hello”的行并计算其行数。在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。

重定向:将命令的结果输出到文件,而不是标准输出(屏幕)。

> 写入文件并覆盖旧文件。

>> 加到文件的尾部,保留旧文件内容。

反短斜线,使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。

shell 编程 linux_Shell编程入门

命令:

用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。如果您想将所有查找到的文件打一个包,则可以使用以下脚本:

shell 编程 linux_Shell编程入门

3) 流程控制

"if" 表达式 如果条件为真则执行then后面的部分:

if ....; then

....

elif ....; then

....

else

....

fi

大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等...

通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。

[ -f "somefile" ] :判断是否是一个文件

[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限

[ -n "$var" ] :判断$var变量是否有值

[ "$a" = "$b" ] :判断$a和$b是否相等

执行man test可以查看所有测试表达式可以比较和判断的类型。

直接执行以下脚本:

#!/bin/sh

if [ "$SHELL" = "/bin/bash" ]; then

echo "your login shell is the bash (bourne again shell)"

else

echo "your login shell is not bash but $SHELL"

fi

变量$SHELL包含了登录shell的名称,我们和/bin/bash进行了比较。

shell 编程 linux_Shell编程入门

快捷操作符

熟悉C语言的朋友可能会很喜欢下面的表达式:

[ -f "/etc/shadow" ] && echo "This computer uses shadow passwors"

这里 && 就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。(www.61k.com]您也可以认为是逻辑运算中的与操作。上例中表示如果/etc/shadow文件存在则打印" This computer uses shadow passwors"。同样或操作(||)在shell编程中也是可用的。这里有个例子:

#!/bin/sh

mailfolder=/var/spool/mail/james

[ -r "$mailfolder" ]' '{ echo "Can not read $mailfolder" ; exit 1; }

echo "$mailfolder has mail from:"

grep "^From " $mailfolder

该脚本首先判断mailfolder是否可读。如果可读则打印该文件中的"From" 一行。如果不可读则或操作生效,打印错误信息后脚本退出。这里有个问题,那就是我们必须有两个命令:

-打印错误信息

-退出程序

我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。一般函数将在下文提及。 不用与和或操作符,我们也可以用if表达式作任何事情,但是使用与或操作符会更便利很多。 case表达式可以用来匹配一个给定的字符串,而不是数字。

case ... in

...) do something here ;;

esac

让我们看一个例子。 file命令可以辨别出一个给定文件的文件类型,比如:

file lf.gz

这将返回:

lf.gz: gzip compressed data, deflated, original filename,

last modified: Mon Aug 27 23:09:18 2001, os: Unix

我们利用这一点写了一个叫做smartzip的脚本,该脚本可以自动解压bzip2, gzip 和zip 类型的压缩文件: #!/bin/sh

ftype=`file "$1"`

case "$ftype" in

"$1: Zip archive"*)

unzip "$1" ;;

"$1: gzip compressed"*)

gunzip "$1" ;;

"$1: bzip2 compressed"*)

bunzip2 "$1" ;;

*) error "File $1 can not be uncompressed with smartzip";;

esac

您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一个参数值。也就是说,当我们运行:

smartzip articles.zip

$1 就是字符串 articles.zip

select 表达式是一种bash的扩展应用,尤其擅长于交互式使用。用户可以从一组不同的值中进行选择。 select var in ... ; do

shell 编程 linux_Shell编程入门

break

done

.... now $var can be used ....

下面是一个例子:

#!/bin/sh

echo "What is your favourite OS?"

select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do

break

done

echo "You have selected $var"

下面是该脚本运行的结果:

What is your favourite OS?

1) Linux

2) Gnu Hurd

3) Free BSD

4) Other

#? 1

You have selected Linux

您也可以在shell中使用如下的loop表达式:

while ...; do

....

done

while-loop 将运行直到表达式测试为真。[www.61k.com)will run while the expression that we test for is true. 关键字"break" 用来跳出循环。而关键字"continue"用来不执行余下的部分而直接跳到下一个循环。

for-loop表达式查看一个字符串列表 (字符串用空格分隔) 然后将其赋给一个变量:

for var in ....; do

....

done

在下面的例子中,将分别打印ABC到屏幕上:

#!/bin/sh

for var in A B C ; do

echo "var is $var"

done

下面是一个更为有用的脚本showrpm,其功能是打印一些RPM包的统计信息:

#!/bin/sh

# list a content summary of a number of RPM packages

# USAGE: showrpm rpmfile1 rpmfile2 ...

# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm

for rpmpackage in $*; do

if [ -r "$rpmpackage" ];then

echo "=============== $rpmpackage =============="

rpm -qi -p $rpmpackage

else

echo "ERROR: cannot read file $rpmpackage"

shell 编程 linux_Shell编程入门

fi

done

这里出现了第二个特殊的变量$*,该变量包含了所有输入的命令行参数值。[www.61k.com]如果您运行showrpm openssh.rpm w3m.rpm webgrep.rpm

此时 $* 包含了 3 个字符串,即openssh.rpm, w3m.rpm and webgrep.rpm.

引号

在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓扩展的意思是程序会把通配符(比如*)替换成合适的文件名,它变量替换成变量值。为了防 止程序作这种替换,您可以使用引号:让我们来看一个例子,假设在当前目录下有一些文件,两个jpg文件, mail.jpg 和tux.jpg。

#!/bin/sh

echo *.jpg

这将打印出"mail.jpg tux.jpg"的结果。

引号 (单引号和双引号) 将防止这种通配符扩展:

#!/bin/sh

echo "*.jpg"

echo '*.jpg'

这将打印"*.jpg" 两次。

单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量扩展。 #!/bin/sh

echo $SHELL

echo "$SHELL"

echo '$SHELL'

运行结果为:

/bin/bash

/bin/bash

$SHELL

最后,还有一种防止这种扩展的方法,那就是使用转义字符——反斜杆:

echo *.jpg

echo $SHELL

这将输出:

*.jpg

$SHELL

Here documents

当要将几行文字传递给一个命令时,here documents(译者注:目前还没有见到过对该词适合的翻译)一种不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果我们四有那个 here documents就不必用echo函数一行行输出。 一个 "Here document" 以 << 开头,后面接上一个字符串,这个字符串还必须出现在here document的末尾。下面是一个例子,在该例子中,我们对多个文件进行重命名,并且使用here documents打印帮助:

#!/bin/sh

# we have less than 3 arguments. Print the help text:

if [ $# -lt 3 ] ; then

cat <

ren -- renames a number of files using sed regular expressions

shell 编程 linux_Shell编程入门

USAGE: ren 'regexp' 'replacement' files...

EXAMPLE: rename all *.HTM files in *.html:

ren 'HTM$' 'html' *.HTM

HELP

exit 0

fi

OLD="$1"

NEW="$2"

# The shift command removes one argument from the list of

# command line arguments.

shift

shift

# $* contains now all the files:

for file in $*; do

if [ -f "$file" ] ; then

newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"`

if [ -f "$newfile" ]; then

echo "ERROR: $newfile exists already"

else

echo "renaming $file to $newfile ..."

mv "$file" "$newfile"

fi

fi

done

这是一个复杂一些的例子。(www.61k.com]让我们详细讨论一下。第一个if表达式判断输入命令行参数是否小于3个 (特殊变量$# 表示包含参数的个数) 。如果输入参数小于3个,则将帮助文字传递给cat命令,然后由cat命令将其打印在屏幕上。打印帮助文字后程序退出。 如果输入参数等于或大于3个,我们就将第一个参数赋值给变量OLD,第二个参数赋值给变量NEW。下一步,我们使用shift命令将第一个和第二个参数从 参数列表中删除,这样原来的第三个参数就成为参数列表$*的第一个参数。然后我们开始循环,命令行参数列表被一个接一个地被赋值给变量$file。接着我 们判断该文件是否存在,如果存在则通过sed命令搜索和替换来产生新的文件名。然后将反短斜线内命令结果赋值给newfile。这样我们就达到了我们的目 的:得到了旧文件名和新文件名。然后使用mv命令进行重命名。

函数

如果您写了一些稍微复杂一些的程序,您就会发现在程序中可能在几个地方使用了相同的代码,并且您也会发现,如果我们使用了函数,会方便很多。一个函数是这个样子的:

functionname()

{

# inside the body $1 is the first argument given to the function

# $2 the second ...

body

}

您需要在每个程序的开始对函数进行声明。

下面是一个叫做xtitlebar的脚本,使用这个脚本您可以改变终端窗口的名称。这里使用了一个叫做help

shell 编程 linux_Shell编程入门

的函数。(www.61k.com)正如您可以看到的那样,这个定义的函数被使用了两次。

#!/bin/sh

# vim: set sw=4 ts=4 et:

help()

{

cat <

xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole

USAGE: xtitlebar [-h] "string_for_titelbar"

OPTIONS: -h help text

EXAMPLE: xtitlebar "cvs"

HELP

exit 0

}

# in case of error or if -h is given we call the function help:

[ -z "$1" ] && help

[ "$1" = "-h" ] && help

# send the escape sequence to change the xterm titelbar:

echo -e "33]0;$107"

#

在脚本中提供帮助是一种很好的编程习惯,这样方便其他用户(和您)使用和理解脚本。 命令行参数

我们已经见过$* 和 $1, $2 ... $9 等特殊变量,这些特殊变量包含了用户从命令行输入的参数。迄今为止,我们仅仅了解了一些简单的命令行语法(比如一些强制性的参数和查看帮助的-h选项)。 但是在编写更复杂的程序时,您可能会发现您需要更多的自定义的选项。通常的惯例是在所有可选的参数之前加一个减号,后面再加上参数值 (比如文件名)。

有好多方法可以实现对输入参数的分析,但是下面的使用case表达式的例子无遗是一个不错的方法。 #!/bin/sh

help()

{

cat <

This is a generic command line parser demo.

USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2

HELP

exit 0

}

while [ -n "$1" ]; do

case $1 in

-h) help;shift 1;; # function help is called

-f) opt_f=1;shift 1;; # variable opt_f is set

-l) opt_l=$2;shift 2;; # -l takes an argument -> shift by 2

--) shift;break;; # end of options

-*) echo "error: no such option $1. -h for help";exit 1;;

*) break;;

esac

shell 编程 linux_Shell编程入门

done

echo "opt_f is $opt_f"

echo "opt_l is $opt_l"

echo "first arg is $1"

echo "2nd arg is $2"

您可以这样运行该脚本:

cmdparser -l hello -f -- -somefile1 somefile2

返回的结果是:

opt_f is 1

opt_l is hello

first arg is -somefile1

2nd arg is somefile2

这个脚本是如何工作的呢?脚本首先在所有输入命令行参数中进行循环,将输入参数与case表达式进行比较,如果匹配则设置一个变量并且移除该参数。(www.61k.com]根据unix系统的惯例,首先输入的应该是包含减号的参数。

实例

一般编程步骤

现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并且写一个伪脚本(framework.sh),该脚本包含了大多数脚本都需要的框架结构,是一个非常不错的主意。这时候,在写一个新的脚本时我们只需要执行一下copy命令:

cp framework.sh myscript

然后再插入自己的函数。

让我们再看两个例子:

二进制到十进制的转换

脚本 b2d 将二进制数 (比如 1101) 转换为相应的十进制数。这也是一个用expr命令进行数学运算的例子:

#!/bin/sh

# vim: set sw=4 ts=4 et:

help()

{

cat <

b2h -- convert binary to decimal

USAGE: b2h [-h] binarynum

OPTIONS: -h help text

EXAMPLE: b2h 111010

will return 58

HELP

exit 0

}

error()

{

# print an error and exit

echo "$1"

shell 编程 linux_Shell编程入门

exit 1

}

lastchar()

{

# return the last character of a string in $rval

if [ -z "$1" ]; then

# empty string

rval=""

return

fi

# wc puts some space behind the output this is why we need sed: numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `

# now cut out the last char

rval=`echo -n "$1" | cut -b $numofchar`

}

chop()

{

# remove the last character in string and return it in $rval if [ -z "$1" ]; then

# empty string

rval=""

return

fi

# wc puts some space behind the output this is why we need sed: numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `

if [ "$numofchar" = "1" ]; then

# only one char in string

rval=""

return

fi

numofcharminus1=`expr $numofchar "-" 1`

# now cut all but the last char:

rval=`echo -n "$1" | cut -b 0-${numofcharminus1}`

}

while [ -n "$1" ]; do

case $1 in

-h) help;shift 1;; # function help is called

--) shift;break;; # end of options

-*) error "error: no such option $1. -h for help";;

*) break;;

esac

done

# The main program

shell 编程 linux_Shell编程入门

sum=0

weight=1

# one arg must be given:

[ -z "$1" ] && help

binnum="$1"

binnumorig="$1"

while [ -n "$binnum" ]; do

lastchar "$binnum"

if [ "$rval" = "1" ]; then

sum=`expr "$weight" "+" "$sum"`

fi

# remove the last position in $binnum

chop "$binnum"

binnum="$rval"

weight=`expr "$weight" "*" 2`

done

echo "binary $binnumorig is decimal $sum"

#

该脚本使用的算法是利用十进制和二进制数权值 (1,2,4,8,16,..),比如二进制"10"可以这样转换成十进制:

0 * 1 + 1 * 2 = 2

为了得到单个的二进制数我们是用了lastchar 函数。[www.61k.com)该函数使用wc -c计算字符个数,然后使用cut命令取出末尾一个字符。Chop函数的功能则是移除最后一个字符。

文件循环程序

或许您是想将所有发出的邮件保存到一个文件中的人们中的一员,但是在过了几个月以后,这个文件可能会变得很大以至于使对该文件的访问速度变慢。下面的 脚本rotatefile 可以解决这个问题。这个脚本可以重命名邮件保存文件(假设为outmail)为outmail.1,而对于outmail.1就变成了outmail.2 等等等等... #!/bin/sh

# vim: set sw=4 ts=4 et:

ver="0.1"

help()

{

cat <

rotatefile -- rotate the file name

USAGE: rotatefile [-h] filename

OPTIONS: -h help text

EXAMPLE: rotatefile out

This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1

and create an empty out-file

The max number is 10

version $ver

shell 编程 linux_Shell编程入门

HELP

exit 0

}

error()

{

echo "$1"

exit 1

}

while [ -n "$1" ]; do

case $1 in

-h) help;shift 1;;

--) break;;

-*) echo "error: no such option $1. -h for help";exit 1;;

*) break;;

esac

done

# input check:

if [ -z "$1" ] ; then

error "ERROR: you must specify a file, use -h for help"

fi

filen="$1"

# rename any .1 , .2 etc file:

for n in 9 8 7 6 5 4 3 2 1; do

if [ -f "$filen.$n" ]; then

p=`expr $n + 1`

echo "mv $filen.$n $filen.$p"

mv $filen.$n $filen.$p

fi

done

# rename the original file:

if [ -f "$filen" ]; then

echo "mv $filen $filen.1"

mv $filen $filen.1

fi

echo touch $filen

touch $filen

这个脚本是如何工作的呢?在检测用户提供了一个文件名以后,我们进行一个9到1的循环。(www.61k.com]文件9被命名为10,文件8重命名为9等等。循环完成之后,我们将原始文件命名为文件1同时建立一个与原始文件同名的空文件。

调试

最简单的调试命令当然是使用echo命令。您可以使用echo在任何怀疑出错的地方打印任何变量值。这也是绝大多数的shell程序员要花费80%的时间来调试程序的原因。Shell程序的好处在于不需要重新编译,插入一个echo命令也不需要多少时间。

shell 编程 linux_Shell编程入门

shell也有一个真实的调试模式。[www.61k.com)如果在脚本"strangescript" 中有错误,您可以这样来进行调试: sh -x strangescript

这将执行该脚本并显示所有变量的值。

shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用:

sh -n your_script

这将返回所有语法错误。

我们希望您现在可以开始写您自己的shell脚本,希望您玩得开心。

三 : Winsock编程入门

Winsock编程入门--1.初始化Winsock

什么是Winsock

   Winsock是Windows下的网络编程接口,它是由Unix下的BSD Socket发展而来,是一个与网络协议无关的编程接口。[www.61k.com)

构建编程环境

   Winsock在常见的Windows平台上有两个主要的版本,即Winsock1和Winsock2。编写与Winsock1兼容的程序你需要引用头文件WINSOCK.H,如果编写使用Winsock2的程序,则需要引用WINSOCK2.H。此外还有一个MSWSOCK.H头文件,它是专门用来支持在Windows平台上高性能网络程序扩展功能的。使用WINSOCK.H头文件时,同时需要库文件WSOCK32.LIB,使用WINSOCK2.H时,则需要WS2_32.LIB,如果使用MSWSOCK.H中的扩展API,则需要MSWSOCK.LIB。正确引用了头文件,并链接了对应的库文件,你就构建起编写WINSOCK网络程序的环境了。

初始化Winsock

   每个Winsock程序必须使用WSAStartup载入合适的Winsock动态链接库,如果载入失败,WSAStartup将返回SOCKET_ERROR,这个错误就是WSANOTINITIALISED,WSAStartup的定义如下:

int WSAStartup(
   WORD wVersionRequested,
   LPWSADATA lpWSAData
);

wVersionRequested指定了你想载入的Winsock版本,其高字节指定了次版本号,而低字节指定了主版本号。你可以使用宏MAKEWORD(x, y)来指定版本号,这里x代表主版本,而y代表次版本。lpWSAData是一个指向WSAData结构的指针,WSAStartup会向该结构中填充其载入的Winsock动态链
库的信息。

lpWSAData是一个指向WSAData结构的指针,WSAStartup会向该结构中填充其载入的Winsock动态链
库的信息。

lpWSAData是一个指向WSAData结构的指针,WSAStartup会向该结构中填充其载入的Winsock动态链
库的信息。

typedef struct WSAData
{
   WORD          wVersion;
   WORD          wHighVersion;
   char          szDescription[WSADESCRIPTION_LEN + 1];
   char          szSystemStatus[WSASYS_STATUS_LEN + 1];
   unsigned short iMaxSockets;
   unsigned short iMaxUdpDg;
   char FAR *    lpVendorInfo;
} WSADATA, * LPWSADATA; 

   wVersion为你将使用的Winsock版本号,wHighVersion为载入的Winsock动态库支持的最高版本,注意,它们的高字节代表次版本,低字节代表主版本。
   szDescription与szSystemStatus由特定版本的Winsock设置,实际上没有太大用处。
   iMaxSockets表示最大数量的并发Sockets,其值依赖于可使用的硬件资源。
   iMaxUdpDg表示数据报的最大长度;然而,获取数据报的最大长度,你需要使用WSAEnumProtocols对协议进行查询。
   最大数量的并发Sockets并不是什么神奇的数字,它是由可用的物理资源来决定的.
   lpVendorInfo是为Winsock实现而保留的制造商信息,这个在Windows平台上并没有什么用处.

   自Windows 95以后的操作系统都支持Winsock 2.2的版本.即使是这样,你也不能认为这些Windows平台支持最新的Winsock版本,为了让你的程序能够运行于大多数平台,最好使用Winsock1.1规范.
    
   当你使用完Winsock接口后,要调用下面的函数对其占用的资源进行释放:

   int WSACleanup(void);

   如果调用该函数失败也没有什么问题,因为操作系统为自动将其释放,对应于每一个WSAStartup调用都应该有一个WSACleanup调用.

错误处理
    
   Winsock函数调用失败大多会返回 SOCKET_ERROR(实际上就是-1),你可以调用WSAGetLastError得到错误的详细信息:

   int WSAGetLastError (void);

    对该函数的调用将返回一个错误码,其码值在WINSOCK.H或WINSOCK2.H(根据其版本)中已经定义,这些预定义值都以WSAE开头.同时你还可以使用WSASetLastError来自定义错误码值.

以下是一个使用上述知识初始化Winsock的一个例子(记得要引用Winsock的lib库).

#include <winsock2.h>

void main(void)
{
  WSADATA wsaData;
    
    int Ret;

  // Initialize Winsock version 2.2

  if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
  {
     // NOTE: Since Winsock failed to load we cannot use
     // WSAGetLastError to determine the specific error for
     // why it failed. Instead we can rely on the return
     // status of WSAStartup.

     printf("WSAStartup failed with error %d\n", Ret);
     return;
  }

  // Setup Winsock communication code here

  // When your application is finished call WSACleanup
  if (WSACleanup() == SOCKET_ERROR)
  {
     printf("WSACleanup failed with error %d\n", WSAGetLastError());

扩展:winsock网络编程经络 / winsock编程 / winsock网络编程 pdf


  }
}

扩展:winsock网络编程经络 / winsock编程 / winsock网络编程 pdf

四 : DIRECTX9编程入门

·窗口类WNDCLASS

struct WNDCLASS {

UINT style;

WNDPROC lpfnWndProc;

int cbClsExtra;

int cbWndExtra;

HINSTANCE hInstance;

HICON hIcon;

HCURSOR hCursor;

HBRUSH hbrBackground;

LPCSTR lpszMenuName;

LPCSTR lpszClassName;

};

style:用来定义窗口的行为。如果打算共同使用GDI和D3D的话,可以使用CS_OWNDC作为参数。

lpfnWndProc:一个函数指针,指向与这个窗口类绑定在一起的处理窗口消息的函数。

cbClsExtra和cbWndExtra:为窗口和为分配内存空间。很少使用到这两个参数,一般设为0;

hInstance:应用程序的实例句柄。你可以使用GetModuleHandle()来得到它,也可以从Win32程序的入口函数WinMain那里得到它。当然,你也可以把它设为NULL(不知有什么用)

hIcon,hCursor,hbrBackground:设置默认的图标、鼠标、背景颜色。不过在这里设置这些其实并不怎么重要,因为我们可以在后面定制自己的渲染方法。

lpszMenuName:用来创建菜单

lpszClassName:窗口类的名字。我们可以通过这个名字来创建以这个窗口类为模板的窗口。甚至可以通过这个名字来得到窗口的句柄。

设置好窗口类结构的内容后,使用RegisterClass(const WNDCLASS *lpWndClass)函数来注册它。关闭窗口后可以用UnregisterClass(LPCSTR lpClassName, HINSTANCE hInstance)来撤销注册。

·创建窗口CreateWindow

HWND CreateWindow(

LPCTSTR lpClassName,

LPCTSTR lpWindowName,

DWORD dwStyle,

int x, y,

int nWidth, nHeight,

HWND hWndParent,

HMENU hMenu,

HINSTANCE hInstance,

LPVOID lpParam

);

lpClassName:窗口类的名字。即窗口类结构体中的lpszClassName成员。 lpWindowName:如果你的应用程序有标题栏,这个就是你标题栏上显示的内容。

dwStyle:窗口的风格决定你的窗口是否有标题栏、最大最小化按钮、窗口边框等属性。在全屏的模式下,WS_POPUP|WS_VISIBLE是常用的设置,因为它产生一个不带任何东西的全屏窗口。在窗口的模式下,你可以设置很多窗口的风格,具体可以查看相关资料,这里不详细说明,不过WS_OVERLAPPED|WS_SYSMENU|WS_VISIBLE是一组常用的风格。

x和y:窗口创建的位置。(x,y)表示窗口的左上角位置。

nWidth和nHeight:用来设置窗口的宽度和高度,以像素为单位。如果你想创建一个全屏的窗口,使用GetSystemMetrics(SM_CXSCREEN)和GetSystemMetrics(SM_CYSCREEN)可以得到当前显示器屏幕的大小

hWndParent:指定这个新建窗口的父窗口。在D3D应用程序中很少用,一般设为NULL。

hMenu:菜单句柄。

hInstance:应用程序的实例句柄。你可以使用GetModuleHandle()来得到它,也可以从Win32程序的入口函数WinMain那里得到它。当然,你也可以把它设为NULL(不知有什么用)

lpParam:一个很神秘的参数。除非你知道自己在做什么,否则还是把它设为NULL吧。

·销毁窗口DestroyWindow

销毁窗口有两种方法,一种是隐式的,一种是显式的。我们都知道Windows操作系统是一个基于消息驱动的系统。流动于系统中的消息使我们的窗口跑起来。在很多软件开发特别是商业软件的开发过程中,窗口的产生和销毁都是交由系统去做的,因为这些不是这类开发的关注所在。但是游戏开发不一样,尽管你也可以只向系统发送一条WM_DESTROY消息来销毁窗口,我们还是希望窗口是销毁的明明白白的。由于窗口的注册、产生和使用都是由我们亲手来做的,那么当然窗口的销毁也得由我们亲自来做。不过还是得说明一点,使用WM_DESTROY消息和DestroyWindow函数来销毁窗口在本质上并无太大差别,使用哪种方法可以说是根据个人的爱好吧。

销毁窗口后是不是就完事了呢?不,还没有,因为应用程序的消息队列里可能还有没处理完的消息,为了彻底的安全,我们还得把那些消息都处理完。所以结束应用程序的时候,可以使用以下方法:

MSG msg;

DestroyWindow(h_wnd);

while(PeekMessage(&msg , NULL , 0 , 0 , PM_REMOVE))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

·窗口消息处理过程

窗口消息的处理函数是一个回调函数,什么是回调函数?就是由操作系统负责调用的函数。CALLBACK这个宏其实就是__stdcall,这是一种函数调用的方式,在这里不多说这些了,有兴趣的可以参考一些Windows编程的书籍,里面会有很详尽的说明。

Windows里面有很多消息,这些消息都跑去哪里了呢?其实它们都在自己的消息队列里等候。消息是怎么从队列里出去的呢?就是通过GetMessage和PeekMessage这两个函数。那么消息从队列里出去后又到哪里了呢?嗯,这时候消息就正式进入了我们的窗口消息处理过程,也即是窗口类中lpfnWndProc所指定的函数。一个消息处理函数有四个参数,下面分别说说:

参数1:HWND p_hWnd

消息不都是传到以窗口类为模板产生的窗口吗?为什么还要使用窗口句柄来指明窗口呢?别忘了一个窗口类是可以产生多个窗口的呀,如果一个应用程序里面有多个窗口,并且它们之中的一些窗口是共用一个窗口类的,那么就得用一个窗口句柄来指明究竟这个消息是哪个窗口发过来的。

参数2:UINT p_msg

这是一个消息类型,就是WM_KEYDOWN , WM_CLOSE , WM_TIMER这些东东。

参数3:WPARAM p_wparam

这个参数内容就是消息的主要内容。如果是WM_KEYDOWN消息,那么p_wparam就是用来告诉你究竟是哪个键被按下。

参数4:LPARAM p_lparam

这个参数的内容一般是消息的一些附加内容。

最后说明一下DefWindowProc的作用。有时候我们把一个消息传到窗口消息处理函数里面,但是里面没有处理这个消息的内容。怎么办?很容易,交给DefWindowProc处理就对了。

·创建IDirect3D接口

DirectX是一组COM组件,COM是一种二进制标准,每一个COM里面提供了至少一个接口,而接口就是一组相关的函数,我们使用DirectX,其实就是

使用那些函数。COM和C++中的类有点像,只不过COM使用自己的方法来创建实例。创建COM实例的一般方法是使用coCreateInstance函数。有关coCreateInstance的使用方法,可以参考有关COM方面的资料,这里暂时不详细说明了,因为DirectX提供了更简洁的方法来创建DirectX组件的实例。这一章我要讲的就是Direct3D组件的使用方法。

为了使用D3D中的函数,我们得先定义一个指向IDirect3D9这个接口的指针,顺便说明一下为什么要定义这个指针。首先,我们要知道接口的内容就是一些纯虚拟函数,所以接口是不能被实例化的,但是我们可以定义一个指向接口的指针。其次,我们要知道利用多态性我们可以使用一个基类指针来访问派生类中的方法。既然接口是不能被实例化的,那么我们肯定是使用从接口派生出来的类(或结构)的方法。怎么获到这个派生类的指针呢?就是通过之前定义的接口指针(也即是基类指针)来获得。所以我们所需做的就是把一个接口指针的地址传给某个函数,让这个函数来帮我们获到正确的派生类指针,这样我们就可以使用接口指针来做一些实际的东西了。实际上,我们只需要知道接口里面有什么方法以及它能完成什么工作就行了,至于这些方法是怎么实现的我们不必去关心。我们要做的就是定义一个接口指针,把它传给某个函数,函数使我们的接口指针有意义,接着我们使用接口,就这么简单。定义完这个接口指针后,例如IDirect3D9 *g_pD3D;现在我们使用Direct3DCreate9这个函数来创建一个D3D接口:

g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );

Direct3DCreate9这个函数只有一个参数,它表明要创建接口的版本。如果你想创建一个老的接口版本当然也可以,不过没有人会那样做吧。

创建接口后就可以创建D3D设备了,什么是D3D设备?你可以想象为你机上的那块显卡!什么?你有几块显卡!!没关系,那就创建多几个D3D设备接口吧。创建D3D设备需要的参数很多,如果把那些参数都挤在一个函数里面,那就太长了,所以就把一些参数放进结构体里面,只要先设定好这些结构体,再把这些结构体当作参数传给创建D3D设备的函数,那就清晰多了。首先要讲的就是D3DPRESENT_PARAMETERS这个结构。下面是它的定义:

struct D3DPRESENT_PARAMETERS{

UINT BackBufferWidth;

UINT BackBufferHeight;

D3DFORMAT BackBufferFormat;

UINT BackBufferCount;

D3DMULTISAMPLE_TYPE MultiSampleType;

DWORD MultiSampleQuality;

D3DSWAPEFFECT SwapEffect;

HWND hDeviceWindow;

BOOL Windowed;

BOOL EnableAutoDepthStencil;

D3DFORMAT AutoDepthStencilFormat;

DWORD Flags;

UINT FullScreen_RefreshRateInHz;

UINT PresentationInterval;

};

BackBufferWidth和BackBufferHeight:后备缓冲的宽度和高度。在全屏模式下,这两者的值必需符合显卡所支持的分辨率。例如(800,600),(640,480)。

BackBufferFormat:后备缓冲的格式。这个参数是一个D3DFORMAT枚举类型,它的值有很多种,例如D3DFMT_R5G6B5,这说明后备缓冲的格式是每个像素16位,其实红色(R)占5位,绿色(G)占6位,蓝色(B)占5位,为什么绿色会多一位呢?据说是因为人的眼睛对绿色比较敏感。DX9只支持16位和32位的后备缓冲格式,24位并不支持。如果对这D3DFORMAT不熟悉的话,可以把它设为D3DFMT_UNKNOWN,这时候它将使用桌面的格式。

BackBufferCount:后备缓冲的数目,范围是从0到3,如果为0,那就当成1来处理。大多数情况我们只使用一个后备缓冲。使用多个后备缓冲可以使画面很流畅,但是却会造成输入设备响应过慢,还会消耗很多内存。

MultiSampleType和MultiSampleQuality:前者指的是全屏抗锯齿的类型,后者指的是全屏抗锯齿的质量等级。这两个参数可以使你的渲染场景变得更好看,但是却消耗你很多内存资源,而且,并不是所有的显卡都支持这两者的所设定的功能的。在这里我们分别把它们设为D3DMULTISAMPLE_NONE和0。

SwapEffect:交换缓冲支持的效果类型,指定表面在交换链中是如何被交换的。它是D3DSWAPEFFECT枚举类型,可以设定为以下三者之一:D3DSWAPEFFECT_DISCARD,D3DSWAPEFFECT_FLIP,D3DSWAPEFFECT_COPY。如果设定为D3DSWAPEFFECT_DISCARD,则后备缓冲区的东西被复制到屏幕上后,后备缓冲区的东西就没有什么用了,可以丢弃(discard)了。如果设定为D3DSWAPEFFECT_FLIP,则表示在显示和后备缓冲之间进行周期循环。设定D3DSWAPEFFECT_COPY的话,我也不太清楚有什么作用*^_^*。一般我们是把这个参数设为D3DSWAPEFFECT_DISCARD。

hDeviceWindow:显示设备输出窗口的句柄

Windowed:如果为FALSE,表示要渲染全屏。如果为TRUE,表示要渲染窗口。渲染全屏的时候,BackBufferWidth和BackBufferHeight的值就得符合显示模式中所设定的值。

EnableAutoDepthStencil:如果要使用Z缓冲或模板缓冲,则把它设为TRUE。

AutoDepthStencilFormat:如果不使用深度缓冲,那么这个参数将没有用。如果启动了深度缓冲,那么这个参数将为深度缓冲设定缓冲格式(和设定后备缓冲的格式差不多)

Flags:可以设置为0或D3DPRESENTFLAG_LOCKABLE_BACKBUFFER。不太清楚是用来做什么的,看字面好像是一个能否锁定后备缓冲区的标记。

FullScreen_RefreshRateInHz:显示器的刷新率,单位是HZ,如果设定了一个显示器不支持的刷新率,将会不能创建设备或发出警告信息。为了方便,一般设为D3DPRESENT_RATE_DEFAULT就行了。

PresentationInterval:如果设置为D3DPRENSENT_INTERVAL_DEFAULT,则说明在显示一个渲染画面的时候必要等候显示器刷新完一次屏幕。例如你的显示器刷新率设为80HZ的话,则一秒内你最多可以显示80个渲染画面。另外你也可以设置在显示器刷新一次屏幕的时间内显示1到4个画面。如果设置为D3DPRENSENT_INTERVAL_IMMEDIATE,则表示可以以实时的方式来显示渲染画面,虽然这样可以提高帧速(FPS),但是却会产生图像撕裂的情况。

·创建IDirect3DDevice界面

当你把D3DPRESENT_PARAMETERS的参数都设置好后,就可以创建一个D3D设备了,和创建D3D接口一样,先定义一个接口指针IDirect3DDevice9 * g_pD3DDevice;然后使用D3D接口里面的CreateDevice函数来创建设备。CreateDevice的声明为:

HRESULT CreatDevice(

UINT Adapter,

D3DDEVTYPE DeviceType,

HWND hFocusWindow,

DWORD BehaviorFlags,

D3DPRESENT_PARAMETERS *pPresentationParameters,

IDirect3DDevice9** ppReturnedDeviceInterface

};

第一个参数说明要为哪个设备创建设备指标,我之前说过一台机可以有好几个显卡,这个参数就是要指明为哪块显卡创建可以代表它的设备指标。但是我怎么知道显卡的编号呢?可以使用D3D接口里面的函数来获得,例如GetAdapterCounter可以知道系统有几块显卡;GetAdapterIdentifier可以知道显卡的具体属性。一般我们设这个参数为D3DADAPTER_DEFAULT。

第二个参数指明正在使用设备类型。一般设为D3DEVTYPE_HAL。 第三个参数指明要渲染的窗口。如果为全屏模式,则一定要设为主窗口。

第四个参数是一些标记,可以指定用什么方式来处理顶点。

第五个参数就要用到上面所讲的D3DPRESENT_PARAMETERS。

第六个参数是返回的界面指针。

·开始渲染

有了设备接口指针,就可以开始渲染画面了。渲染是一个连续不断的过程,所以必定要在一个循环中完成,没错,就是第一章讲的那个消息循环。在渲染开始之前我们要用IDirect3DDevice9::Clear函数来清除后备缓冲区。 HRESULT Clear(

DWORD Count,

const D3DRECT *pRects,

DWORD Flags,

D3DCOLOR Color,

float Z,

DWORD Stencil

);

Count:说明你要清空的矩形数目。如果要清空的是整个客户区窗口,则设为0;

pRects:这是一个D3DRECT结构体的一个数组,如果count中设为5,则这个数组中就得有5个元素。它可以使我们只清除屏幕中的某一部分。

Flags:一些标记组合,它指定我们要清除的目标缓冲区。只有三种标记:D3DCLEAR_STENCIL , D3DCLEAR_TARGET , D3DCLEAR_ZBUFFER。 分别为清除模板缓冲区、清除目标缓冲区(通常为后备缓冲区)、清除深度缓冲区。

Color:清除目标区域所使用的颜色。

float:设置Z缓冲的Z初始值。小于或等于这个Z初始值的Z值才会被改写,但它的值只能取0到1之间。如果还不清楚什么是Z缓冲的话,可以自己找相关数据看一下,这里不介绍了,呵呵。

Stencil:设置范本缓冲的初始值。它的取值范围是0到2的n次方减1。其中n是范本缓冲的深度。

清除后备缓冲区后,就可以对它进行渲染了。渲染完毕,使用Present函数来把后备缓冲区的内容显示到屏幕上。

HRESULT Present(

const RECT *pSourceRect,

const RECT *pDestRect,

HWND hDestWindowOverride,

const RGNDATA *pDirtyRegion

);

pSourceRect:你想要显示的后备缓冲区的一个矩形区域。设为NULL则表示要把整个后备缓冲区的内容都显示。

pDestRect:表示一个显示区域。设为NULL表示整个客户显示区。 hDestWindowOverride:你可以通过它来把显示的内容显示到不同的窗口去。设为NULL则表示显示到主窗口。

pDirtyRegion:高级使用。一般设为NULL。

·顶点属性与顶点格式

顶点可谓是3D世界中的基本元素。在计算机所能描绘的3D世界中,任何物体都是由多边形构成的,可以是三边形,也可以是四边形等。由于三边形,即三角形所具有的特殊性质决定其在3D世界中得到广泛的使用。构成三角形需要三个点,这些点的性质就是这章所要讲的内容。

也许你已经知道顶点的结构定义,你可能会奇怪为什么D3D会知道我们“随便”定义的那些结构呢?其实那些顶点的定义可不是那么随便的哦。下面列举在Direct3D中,顶点所具有的所有属性。

(1)位置:顶点的位置,可以分别指定x,y,x三个值,也可以使用D3DXVECTOR3结构来定义。

(2)RHW:齐次坐标W的倒数。如果顶点为变换顶点的话,就要有这个值。设置这个值意味着你所定义的顶点将不需要Direct3D的辅助(不能作变换、旋转、放大缩小、光照等),要求你自己对顶点数据进行处理。至于W是什么,W和XYZ一样,只是一个四元组的一部分。RHW的英文是Reciprocal of the Homogenous W,即1/W,它是为了处理矩阵的工作变得容易一些(呼,线性代数的东东快都忘了,要恶补一下才行)。一般设RHW的值为1.0。

(3)混合加权:用于矩阵混合。高级应用,这里不讲了(其实我不会,^_^)

(4)顶点法线:学过高等数学就应该知道法线是什么吧?在这里是指经过顶点且和由顶点引出的边相垂直的线,即和三角形那个面垂直。用三个分量来描述它的方向,这个属性用于光照计算。

(5)顶点大小:设定顶点的大小,这样顶点就可以不用只占一个像素了。

(6)漫反射色:即光线照射到物体上产生反射的着色。理解这个比较麻烦,因为3D光照和真实光照没什么关系,不能像理解真实光照那样去理解3D光照。

(7)镜面反射色:它可以让一个3D物体的表面看起来很光滑。

(8)纹理坐标:如果想要在那些用多边形组成的物体上面贴上纹理,就要使用纹理坐标。由于纹理都是二维的,所以用两个值就可以表示纹理上面某一点的位置。在纹理坐标中,只能在0.0到1.0之间取值。例如(0.0 , 0.0)表示纹理的左上角,(1.0 , 1.0)表示纹理的右下角。

好了,请记住上面属性的顺序。我们定义一个顶点结构的时候,不一定要包括全部的属性,但是一定要按照上面的顺序来定义。例如:

struct MYVERTEX

{

D3DXVECTOR3 position;

float rhw;

D3DCOLOR color;

}

上面定义了一个有漫反射色的变换顶点。

定义完了顶点的结构后,我们就要告诉D3D我们定义的是什么格式。为了方便,我们通常会用#define来定义一个叫做描述“灵活顶点格式”(FVF:Flexible Vertex Format)的宏。例如:#define MYFVF D3DFVF_XYZ | D3DFVF_NORMAL。根据之前定义的顶点属性结构体,我们要定义相对应的宏。假如顶点结构中有位置属性,那么就要使用D3DFVF_XYZ;如果是变换顶点的话,就要使用D3DFVF_XYZRHW;如果使用了漫反射色属性的话,就要使用D3DFVF_DIFFUSE。这些值是可以组合使用的,像上面那样用“|”符号作为连结符。定义完灵活顶点格式后,使用IDirect3DDevice9::SetFVF函数来告诉D3D我们所定义的顶点格式,例如:g_pD3DDevice->SetFVF( MYFVF );

·顶点缓冲

处理顶点信息的地方有两个,一个是在数组里,另一个是在D3D所定义的顶点缓冲里。换个说法的话就是一个在我们所能直接操作的内存里,另一个在D3D管理的内存里。对于我们这些对操作系统底层了解不多的菜鸟来说,直接操作内存实在是太恐怖了,所以还是交给D3D帮我们处理吧,虽然不知道背后有些什么操作。要想把顶点信息交给D3D处理,我们就要先创建一个顶点缓冲区,可以使用IDirect3DDevice9->CreateVertexBuffer,它的原型是:

HRESULT CreateVertexBuffer(

UINT Length,

DWORD Usage,

DWORD FVF,

D3DPOOL Pool,

IDirect3DVertexBuffer9** ppVertexBuffer,

HANDLE* pSharedHandle

);

Length:缓冲区的长度。通常是顶点数目乘以顶点大小,使用Sizeof( MYVERTEX )就可以知道顶点的大小了。

Usage:高级应用。设为0就可以了。

FVF:就是我们之前定义的灵活顶点格式。

Pool:告诉D3D将顶点缓冲存储在内存中的哪个位置。高级应用,通常可取的三个值是:D3DPOOL_DEFAULT,D3DPOOL_MANAGED,D3DPOOL_SYSTEMMEM。多数情况下使用D3DPOOL_DEFAULT就可以了。

ppVertexBuffer:返回来的指向IDirect3DVertexBuffer9的指针。之后对顶点缓冲进行的操作就是通过这个指针啦。到这里还要再提醒一下,对于这些接口指针,在使用完毕后,一定要使用Release来释放它。

pSharedHandle:设为NULL就行了。

得到一个指向IDirect3DVertexBuffer9的指针后,顶点缓冲也就创建完毕了。现在要做的就是把之前保存在数组中的顶点信息放在顶点缓冲区里面。首先,使用IDirect3DVertexBuffer9::Lock来锁定顶点缓冲区:

HRESULT Lock(

UINT OffsetToLock,

UINT SizeToLock,

void **ppbData,

DWORD Flags

);

OffsetToLock:指定要开始锁定的缓冲区的位置。通常在起始位置0开始锁定。

SizeToLock:指定在锁定的缓冲区的大小。设为0的话就是表示要锁定整个缓冲区。

ppbData:用来保存返回的指向顶点缓冲区的指针。通过这个指针来向顶点缓冲区填充数据。

Flags:高级应用。通常设为0。

填充为顶点缓冲区后,使用IDirect3DDevice9::Unlock来解锁。

最后在渲染的时候使用IDirect3DDevice9::SetStreamSource来告诉D3D要渲染哪个顶点缓冲区里面的顶点。

HRESULT SetStreamSource(

UINT StreamNumber,

IDirect3DVertexBuffer9 *pStreamData,

UINT OffsetInBytes,

UINT Stride

);

StreamNumber:设置数据流的数量。顶点缓冲最多可以使用16个数据流。确定所支持的数据流的数量,可以检查D3DCAPS中的MaxStreams成员的值。通常设为0,表示使用单数据流。

pStreamData:要与数据流绑定的数据。在这里我们要把顶点缓冲区与数据流绑定。

OffsetInBytes:设置从哪个位置开始读数据。设为0表示从头读起。

Stride:数据流里面数据单元的大小。在这里是每个顶点的大小。

·索引缓冲

很多时候,相邻的三角形会共用一些顶点,例如组成四方形的两个三角形就共用了一条边,即共用了两个顶点信息。如果不使用索引,我们需要六个顶点的信息来绘制这个四方形,但实际上绘制一个四方形只要四个顶点信息就足够了。如果使用了索引就不一样了,在顶点缓冲区里我们可以只保存四个顶点的信息,然后通过索引来读取顶点信息。要使用索引得先创建一个索引缓冲。也许读到这里你会有个疑问,创建一个索引缓冲不就更浪费内存空间了吗?其实不然,索引缓冲区的元素保存的是数字,一个数字所占用的内存肯定要比一个顶点所占用的小得多啦。当你节省了几千个顶点,你就会发现浪费那么一点点索引缓冲区是很值得的。

创建索引缓冲的函数是:IDirect3DDevice9::CreateIndexBuffer

HRESULT CreateIndexBuffer(

UINT Length,

DWORD Usage,

D3DFORMAT Format,

D3DPOOL Pool,

IDirect3DIndexBuffer9** ppIndexBuffer

);

Length:索引缓冲区的长度。通常使用索引数目乘以sizeof(WORD)或sizeof(DWORD)来设置,因为索引号的数据类型是字节(WORD)或双字节(DWORD),嗯,一个WORD只有两个字节,DWORD也就只有四个字节,比顶点的大小小多了吧。

Usage:和CreateVertexBuffer中的Usage设置一样。一般设为0。 Format:设置索引格式。不是D3DFMT_INDEX16就是D3DFMT_INDEX32的啦。

Pool:又是和CreateVertexBuffer中的一样。一般设为D3DPOOL_DEFAULT。

ppIndexBuffer:指向IDirect3DIndexBuffer9的指针。操作索引缓冲区就靠它的啦。记得使用完后要Release啊。

和填充顶点缓冲区一样,要填充索引缓冲区,要先使用IDirect3DIndexBuffer9::Lock来锁定缓冲区。

HRESULT Lock(

UINT OffsetToLock,

UINT SizeToLock,

void **ppbData,

DWORD Flags

);

是不是和IDirect3DVertexBuffer9::Lock一样呢?具体说明也可以参照上面的内容。填充完之后使用IDirect3DIndexBuffer9::UnLock来解锁。

最后使用IDirect3DDevice9::SetIndices来告诉设备要使用哪个索引。 HRESULT Setindices(

IDirect3DindexBuffer9* pIndexData,

UINT BaseVertexIndex

);

pIndexData:设置使用哪个索引缓冲。

BaseVertexIndex:设置以顶点缓冲区中的哪个顶点为索引0。

有关顶点的知识就说到这了。一下章说说点、线、三角形这种D3D所支持的图元(drawing primitives)。

·D3D中的图元简介

在D3D中,一共有三种基本图元,分别是点、线和三角形。点是最简单的图元,由它可以构成一种叫点列(point list)的图元类型。线是由两个不重合的点构成的,一些不相连的线组成的集合就叫线列(line list),而一些首尾相连但不形成环路的线的集合就叫线带(line strips)。同理,单独的三角形集合就叫三角形列(triangle list),类似于线带的三角形集合就叫三角形带(triangle strips),另外,如果多个三角形共用一个顶点作为它们的一个顶点的话,那么这个集合就叫三角形扇(triangle fans)。还是画图比较容易理解吧:

这些图元有什么用呢?基本上我们可以使用这些图元来画我们想要的任何物体。例如画一个四方形可以使用三角形带来画,画一个圆则使用三角形扇。

现在介绍一种不需要顶点缓冲来渲染的方法,就是使用IDirect3DDevice9::DrawPrimitiveUP函数。UP就是User Pointer的意思,也即是说要使用用户定义的内存空间。

HRESULT DrawPrimitiveUP(

D3DPRIMITIVETYPE PrimitiveType,

unsigned int PrimitiveCount,

const void *pVertexStreamZeroData,

unsigned int VertexStreamZeroStride

);

PrimitiveType:要绘画的图元的种类。就是上面介绍的那六种类型。

PrimitiveCount:要绘画的图元的数量。假设有n个顶点信息,绘画的图元类型是点列的话,那么图元的数量就是n;如果绘画的图元类型是线列的话,那么图元的数量就是n/2;如果是线带的话就是n-1;三角形列就是n/3;三角形带就是n-2;三角形扇出是n-2。

pVertexStreamZeroData:存储顶点信息的数组指针

VertexStreamZeroStride:顶点的大小

·使用顶点缓冲来绘画图元

很多时候我们使用顶点来定义图形之后,就把这些顶点信息放进顶点缓冲里面,然后再进行渲染。使用点顶缓冲的好处以及如何创建顶点缓冲我已经在上一章已讲过了,现在讲讲怎么把顶点缓冲里面的图元给画出来。其实也很简单,和上面的IDirect3DDevice9::DrawPrimitiveUP函数差不多,我们使用IDirect3DDevice9::DrawPrimitive函数。不过在使用这个函数之前,我们得告诉设备我们使用哪个数据源,使用IDirect3DDevice9::SetStreamSource函数可以设定数据源。 HRESULT SetStreamSource(

UINT StreamNumber,

IDirect3DVertexBuffer9 *pStreamData,

UINT OffsetInBytes,

UINT Stride

);

StreamNumber:设置和哪个数据流梆定。如果使用单数据流的话,这里设为0。最多支持16个数据流。

pStreamData:要绑定的数据。也就是我们创建的顶点缓冲区里面的数

据。

OffsetInBytes:设置从哪个字节开始读起。如果要读整个缓冲区里面的数据的话,这里设为0。

Stride:单个数据元素的大小。如果数据源是顶点缓冲的话,那么这里

就是每个顶点信息的大小(Sizeof(vertex))。

设置好数据源后,就可以使用IDirect3DDevice9::DrawPrimitive来绘画

了。

HRESULT DrawPrimitive(

D3DPRIMITIVETYPE PrimitiveType,

unsigned int StartVertex,

unsigned int PrimitiveCount

);

PrimitiveType:要绘画的图元的种类。

StarVertex: 设置从顶点缓冲区中的第几个顶点画起。没有特殊情况当然是想把全部的顶点画出来啦,所以一般这里设置从0开始。

PrimitiveCount:要绘画的图元的数量。

·向量(也叫矢量,英文叫vector)

向量就是包含大小(长度)和方向的一个量。向量有2维的,也有3维甚至4维的。在DX的所有结构体中,有一个结构体是用来表示3维向量的,它就是D3DVECTOR,这个结构体很简单,只有三个成员:x、y、z。一般来说,如果不涉及到向量运算的话,用这个结构体来定义一个向量就可以了。我们可以它来表示方向以及顶点在3D世界中的位置等。如果你要对那些向量进行一些运算的话,使用D3DVECTOR就很不方便了,因为在D3DVECTOR这个结构体中没有重载任何的运算符,如果想要做一个加法运算,就得分别对结构体中的每一个成员进行运算了。嘿嘿,不用怕,在DX里面有个叫D3DX的东东(包含d3dx.h头文件),它里面定义了很多方便我们进行数学计算的函数和结构。其中就有D3DXVECTOR2,D3DXVECTOR3,D3DXVECTOR4这三个结构体。看它们的名字就应该知道它们的作用了吧。对于2维和4维的结构体这里就不讲了,其实它们也很简单,和D3DXVECTOR3差不多。不过要说明一点的是D3DXVECTOR3是从D3DVECTOR派生过来的,说明它和D3DVECTOR一样,有x、y、z这三个成员,除此之外,D3DXVECTOR3还重载了小部分算术运算符,这样我们就可以像对待整型那样对D3DXVECTOR3的对象进行加减乘除以及判断是否相等的运算了。同时,由于D3DXVECTOR3是从D3DVECTOR派生过来的,所以两者的对象可以互相赋值,在这两种类型中随便转换。

还是简单说一下向量的数学运算吧。矢量的加减法很简单,就是分别把两个向量的各个分量作加减运算。向量的乘除法也很简单,它只能对一个数值进行乘除法,运算的结果就是向量中的各个分量分别对那个数值进行乘除法后得出的结果。向量的模就是向量的长度,就是各个分量的平方的和的开方。向量的标准化就是使得向量的模为1,这对在3D世界中实现光照是很有用的。对于向量的运算,还有两个“乘法”,那就是点乘和叉乘了。点乘的结果就是两个向量的模相乘,然后再与这两个向量的夹角的余弦值相乘。或者说是两个向量的各个分量分别相乘的结果的和。很明显,点乘的结果就是一个数,这个数对我们分析这

两个向量的特点很有帮助。如果点乘的结果为0,那么这两个向量互相垂直;如果结果大于0,那么这两个向量的夹角小于90度;如果结果小于0,那么这两个向量的夹角大于90度。对于叉乘,它的运算公式令人头晕,我就不说了,大家看下面的公式自己领悟吧??

//v3 = v1 X v2

v3.x = v1.y*v2.z – v1.z*v2.y

v3.y = v1.z*v2.x – v1.x*v2.z

v3.z = v1.x*v2.y – v1.y*v2.x

是不是很难记啊,如果暂时记不了就算了。其实我们主要还是要知道叉乘的意义。和点乘的结果不一样,叉乘的结果是一个新的向量,这个新的向量与原来两个向量都垂直,至于它的方向嘛,不知大家是否还记得左手定则。来,伸出你的左手,按照第一个向量(v1)指向第二个向量(v2)弯曲你的手掌,这时你的拇指所指向的方向就是新向量(v3)的方向了。通过叉乘,我们很容易就得到某个平面(由两个向量决定的)的法线了。

终于写完了上面的文字,描述数学问题可真是费劲,自己又不愿意画图,辛苦大家了。如果你觉得上面的文字很枯燥,那也没关系。因为上面的不是重点,下面介绍的函数才是希望大家要记住的。

D3DX中有很多很有用的函数,它们可以帮助我们实现上面所讲的所有运算。不过下面我只说和D3DXVECTOR3有关的函数:

计算点乘:FLOAT D3DXVec3Dot(

CONST D3DXVECTOR3* pV1,

CONST D3DXVECTOR3* pV2)

计算叉乘:D3DXVECTOR3* D3DXVec3Cross(

D3DXVECTOR3* pOut,

CONST D3DXVECTOR3* pV1,

CONST D3DXVECTOR3* pV2)

计算模:FLOAT D3DXVec3Length(

CONST D3DXVECTOR3* pV)

标准化向量:D3DXVECTOR3* D3DXVec3Normalize(

D3DXVECTOR3* pOut,

CONST D3DXVECTOR3 pV)

对于D3DXVECTOR3的加减乘除运算,上面已经讲了,用+ - * / 就行了。

·矩阵与矩阵运算

什么是矩阵?这个概念还真不好解释,不过学过线性代数的人肯定都知道矩阵长什么样,那我在这里就不解释了。在D3D中,定义矩阵的结构体是D3DMATRIX:

typedef struct _D3DMATRIX {

union {

struct {

float _11, _12, _13, _14;

float _21, _22, _23, _24;

float _31, _32, _33, _34;

float _41, _42, _43, _44;

};

float m[4][4];

};

} D3DMATRIX;

看这个结构的样子,你就应该很清楚怎么使用它来定义一个矩阵了吧。在这里我顺便说一下C++中union的特性吧。像上面定义的结构体所示,在union里面有两个部分,一个是结构体,另一个是二维数组,它有16个元素。在union中,所有的成员都是共用一个内存块的,这是什么意思呢?继续看上面的代码,结构体中的成员_11和成员m数组的第一个元素是共用一个内存空间,即它们的值是一样的,你对_11赋值的同时也对m[0][0]进行了赋值,_11和m[0]

[0]的值是一样的。这样有什么好处呢?比如你定义了一个矩阵变量D3DMATRIX mat;你想访问矩阵中第三行第四列的元素,可以这样做:mat._34;另外也可以这样:mat.m[2][3](数组是从位置0开始储存的哦)。看起来使用后者比较麻烦,不过当你把中括号里面的数换成i和j,使用mat.m[i][j]来访问矩阵中的元素,你就应该知道它的好处了吧。

实际上直接使用D3DMATRIX的情况不多,因为在D3DX中有个更好的结构体,那就是D3DXMATRIX。和D3DXVECTOR3相似,D3DXMATRIX是从D3DMATRIX继承过来的,它重载了很多运算符,使得矩阵的运算很简单。矩阵的运算方法我不打算多说了,下面只介绍和矩阵性质有关的三个函数。

产生一个单位矩阵:D3DXMATRIX *D3DXMatrixIdentity(

D3DXMATRIX *pout);//返回结果

求转置矩阵:D3DXMATRIX *D3DXMatrixTranspose(

D3DXMATRIX *pOut,//返回的结果

CONST D3DXMATRIX *pM );//目标矩阵

求逆矩阵:D3DXMATRIX *D3DXMatrixInverse(

D3DXMATRIX *pOut,//返回的结果

FLOAT *pDeterminant,//设为0

CONST D3DXMATRIX *pM );//目标矩阵

至于什么是单位矩阵,什么是转置矩阵,什么是逆矩阵我就不说了,可以看一下线性代数的书,一看就明白了。简单的加减乘除法可以使用D3DXMATRIX结构体里面重载的运算符。两个矩阵相乘也可以用函数来实现,这将在接下来的矩阵变换中讲到。

·矩阵变换

矩阵的基本变换有三种:平移,旋转和缩放。

平移:

D3DXMATRIX *D3DXMatrixTranslation(

D3DXMATRIX* pOut,//返回的结果

FLOAT x, //X轴上的平移量

FLOAT y, //Y轴上的平移量

FLOAT z) //Z轴上的平移量

;

绕X轴旋转:

D3DXMATRIX *D3DXMatrixRotationX(

D3DXMATRIX* pOut, //返回的结果

FLOAT Angle //旋转的弧度

);

绕Y轴旋转:

D3DXMATRIX *D3DXMatrixRotationY(

D3DXMATRIX* pOut, //返回的结果

FLOAT Angle //旋转的弧度

);

绕Z轴旋转:

D3DXMATRIX *D3DXMatrixRotationZ(

D3DXMATRIX* pOut, //返回的结果

FLOAT Angle //旋转的弧度

);

绕指定轴旋转:

D3DXMATRIX *D3DXMatrixRotationAxis(

D3DXMATRIX *pOut,//返回的结果

CONST D3DXVECTOR3 *pV,//指定轴的向量

FLOAT Angle//旋转的弧度

);

缩放:

D3DXMATRIX *D3DXMatrixScaling(

D3DXMATRIX* pOut, //返回的结果

FLOAT sx, //X轴上缩放的量

FLOAT sy, //Y轴上缩放的量

FLOAT sz //Z轴上缩放的量

);

好了,这章就写这么一些东西。如果你觉得好像没学到什么的话,可能是因为不知道上面的知识有什么用吧。下一章我将介绍世界空间、视图空间(也叫摄像机空间)以及投影,这三者对应的是世界矩阵、视图矩阵和投影矩阵。搞清楚这三个空间的作用后,我们就可以利用这章的知识使我们的3D世界动起来了。

好了,这章比较简单。写到这章的时候我才发现这不是入门手册,有一些重要但是我觉得没必要讲的东西我都没有讲明。如果是新手看我写的这些东西,搞不好还会被我迷惑了,呵呵。所以还是建议大家看DXSDK里面的说明文档,虽然是英文的,但是很详细,我现在都还没有看完呢。

嗯,前面四章把最基本的东西讲完了,使用前面的知识我们可以画一些简单的静止图形。下一章就开始讲矩阵了,它可以使我们的图形动起来。

无论计算机图形技术如何发展,只要它以二维的屏幕作为显示介质,那么它显示的图像即使多么的有立体感,也还是二维的。有时我会想,有没有以某个空间作为显示介质的的可能呢,不过即使有,也只能是显示某个范围内的图像,不可能有无限大的空间作为显示介质,如果有,那就是现实世界了。

既然显示器的屏幕是二维的,那么我们就要对图像作些处理,让它可以欺骗我们的眼睛,产生一种立体的真实感。在D3D中,这种处理就是一系列的空间变换,从模型空间变到世界空间,再变到视图空间,最后投影到我们的显示器屏幕上。

·世界空间与世界矩阵

什么是模型空间呢?每个模型(3D物体)都有它自己的空间,空间的中心(原点)就是模型的中心。在模型空间里,只有模型上的不同点有位置的相对关系。那什么是世界空间呢?世界就是物体(模型)所存在的地方。当我们把一个模型放进世界里面去,那么它就有了一个世界坐标,这个世界坐标是用来标记世界中不同的模型所处的位置的。在世界空间里,世界的中心就是原点(0, 0, 0),也就是你显示器屏幕中间的那一点。我们可以在世界空间里摆放很多个模型,并且设置它们在世界空间中的坐标,这样模型与模型之间就有了相对的位置。

世界矩阵有什么用呢?我们可以利用它来改变世界空间的坐标。这样,在世界空间里面的模型就可以移动、旋转和缩放了。

我们可以使用上一章末尾所讲的那几个函数来产生世界矩阵。例如产生一个绕X轴旋转的转阵:D3DXMatrixRotationX(&matrix,1)。利用matrix这个矩阵,就可以使世界空间中的物体绕X轴转动1弧度。

可以结合后面的例子来理解世界矩阵。

·视图空间与视图矩阵

世界空间建立起来后,我们不一定能看到模型,因为我们还没有“眼睛”啊。在视图空间里,我们可以建立我们在三维空间中的眼睛:摄像机。我们就是通过这个虚拟的摄像机来观察世界空间中的模型的。所以视图空间也叫摄像机空间。

要建立起这个虚拟的摄像机,我们需要一个视图矩阵,产生视图矩阵的

一个函数是:

D3DXMATRIX *D3DXMatrixLookAtLH(

D3DXMATRIX* pOut,

CONST D3DXVECTOR3* pEye,

CONST D3DXVECTOR3* pAt,

CONST D3DXVECTOR3* pUp

);

pOut:返回的视图矩阵指针

pEye:设置摄像机的位置

pAt:设置摄像机的观察点

pUp:设置方向“上”

这个函数的后缀LH是表示左手系的意思,聪明的你一定能够猜出肯定有个叫D3DXMatrixLookAtRH的函数。至于左手系和右手系的区别,这里就不多说了,记住左手系中的Z正方向是指向显示器里面的就行了。只能弄懂了视图矩阵的含义,建立视图矩阵完成可以不依赖函数,自己手动完成。视图矩阵其实就是定义了摄像机在世界空间中的位置、观察点、方向“上”这些信息。

可以结合后面的例子来理解视图矩阵。

·投影与投影矩阵

定义投影矩阵很像是定义摄像机的镜头,下面看它的函数声明:

D3DXMATRIX *D3DXMatrixPerspectiveFovLH(

D3DXMATRIX* pOut,

FLOAT fovY,

FLOAT Aspect,

FLOAT zn,

FLOAT zf

);

pOut:返回的投影矩阵指针

fovY:定义镜头垂直观察范围,以弧度为单位。对于这个参数,下面是我的理解:如果定义为D3DX_PI/4(90度角),那么就是表示以摄像机的观察方向为平分线,上方45度角和下方45度角就是摄像机所能看到的垂直范围了。嗯,可以想象一下自己的眼睛,如果可以把自己眼睛的fovY值设为D3DX_PI/2(180度角),那么我们就可以不用抬头就看得见头顶的东西了。如果设为D3DX_PI的话。。。我先编译一下试试(building?)。哈哈,结果啥也看不见。很难想象如果自己能同时看到所有方向的物体,那么将是一个怎样的画面啊。

Aspect:设置纵横比。如果定义为1,那么所看到的物体大小不变。如果定义为其它值,你所看到的物体就会变形。不过一般情况下这个值设为显示器屏幕的长宽比。(终于明白为什么有些人会说电视上的自己看起来会比较胖了??)

zn:设置摄像机所能观察到的最远距离

zf:设置摄像机所能观察到的最近距离

·一小段代码

请看以下代码片段:

D3DXMATRIXA16 matWorld;

D3DXMatrixIdentity( &matWorld );

D3DXMatrixRotationX( &matWorld, timeGetTime()/1000.0f );

g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );

D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );

D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );

D3DXMATRIXA16 matView;

D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );

g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

D3DXMATRIXA16 matProj;

D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/2, 1.0f, 1.0f, 500.0f );

g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );

通过上面三个转换,就建立了一个我们可以通过显示器屏幕来观察的3D世界。上面三个转换分别是:

从模型空间到世界空间的世界转换:SetTransform( D3DTS_WORLD, &matWorld )。

从世界空间到视图空间的视图转换:SetTransform( D3DTS_VIEW, &matView )。

从视图空间到到屏幕的投影转换:SetTransform( D3DTS_PROJECTION, &matProj )。

现在来观察matWorld,matView,matProj这三个矩阵的特点。我们使用D3DXMatrixRotationX函数来产生了一个绕X轴旋转的转换矩阵,通过设置世界转换,在世界空间里面的物体将绕X轴作旋转。然后我们定义了三个三维的向量,用来设置摄像机的位置,观察方向和定义方向“上”。使用D3DXMatrixLookAtLH函数来把这三个向量放进视图矩阵里面去。然后通过设置视图转换,我们就建立了一个虚拟的摄像机。最后通过D3DXMatrixPerspectiveFovLH函数,我们得到一个投影矩阵,用来设置虚拟摄像机的镜头。

我还是解释一下上面说的那个方向“上”是什么东西吧。这个“上”其实指的就是摄像机在刚建立的时候是如何摆放的,是向左边侧着摆,还是向右边侧着摆,还是倒过来摆,都是通过这个方向“上”来指定的。按照正常的理解,摄像机的“上”方向就是Y轴的正方向,但是我们可以指定方向“上”为Y轴的负方向,这样世界建立起来后就是颠倒的了。不过颠倒与否,也是相对来说的了,试问在没有引力的世界中,谁能说出哪是上哪是下呢?是不是看得一头雾水啊?只要自己亲手改变一下这些参数,就可以体会到了。

设置上面三个转换的先后顺序并不一定得按照世界到视图到投影这个顺序,不过习惯上按照这种顺序来写,感觉会好一点。

·使用矩阵相乘来创建世界矩阵

在世界空间中的物体运动往往是很复杂的,比如物体自身旋转的同时,还绕世界的原点旋转。怎么实现这种运动呢?通过矩阵相乘来把两个矩阵“混”在一起。现在我们假设某一物体建立在世界的原点上,看以下代码:

//定义三个矩阵

D3DXMATRIX matWorld, matWorldY,matMoveLeft;

//一个矩阵把物体移到(30,0,0)处,一个矩阵使物体绕原点(0,0,0)旋转

D3DXMatrixTranslation(&matMoveRight,30,0,0);

D3DXMatrixRotationY(&matWorldY, radian/1000.0f);

//第一次矩阵相乘。先旋转,再平移

D3DXMatrixMultiply(&matWorld, &matWorldY, &matMoveRight);

//第二次矩阵相乘。在第一次矩阵相乘的结果上,再以Y轴旋转

D3DXMatrixMultiply(&matWorld, &matWorld, &matWorldY);

//设置世界矩阵

m_pD3DDevice->SetTransform( D3DTS_WORLD, &matWorld );

矩阵相乘的时候,矩阵的先后顺序很重要,如果顺序弄错了,物体就不会按我们预料的那样运动。从最后一次矩阵相乘看起,最后相乘的两个矩阵是matWorld和matWorldY,其中matWorld又是由matWorldY和matMoveRight相乘得来的,那么这三个矩阵相乘的顺序就是(matWorldY,matMoveRight,matWorldY)。这个顺序意味着什么呢?第一个matWorldY使物体绕Y轴旋转,这时候的物体还处于原点,所以它绕Y轴旋转也就是绕自身的旋转。它转呀转呀,这时候matMoveRight来了,它把物体从(0,0,0)移到了(30,0,0),这时候物体就不再是绕Y轴旋转了,它是在(30,0,0)这个位置继续绕自身旋转。然后matWorldY又来了,它使物体再次以Y轴旋转,不过此时物体不在原点了,所以物体就以原点为中心作画圆的运动(它自身的旋转仍在继续),这个圆的半径是

30。如果换一个顺序,把matMoveRight放在第一的话,那么就是先移动再旋转再旋转(第二次旋转没用),这时候物体就只是画圆运动而已,它自身没有旋转。如果把matMoveRight放在最后,那么就是先旋转再旋转(第二次旋转没用)再移动,这时候物体就没有作画圆运动了,它只是在(30,0,0)这个位置上作自身旋转。好了,理解这个需要一点点想象力。你可以先写好几个矩阵相乘的顺序,自己想象一下相乘的结果会使物体作什么运动,然后再编译执行程序,看看物体的运动是不是和自己想像中的一样,这样可以锻炼自己的空间思维能力。

好了,又写完一章了。下一章可能要过一些日子才能写。因为自己还没找到工作,国庆过后就得出发去找工了,接下来的日子要作一些找工前的准备,所以就没什么时间继续写了。至于什么时候写第七篇,呵呵,应该不用很久,找到工作后立刻回来这里报道~~大家祝我好运吧^_^

五 : webdriver编程入门

一、 准备篇——webdriver的工作原理

通过研究selenium-webdriver的源码,发现其实webdriver的实现原理并不高深莫测无法揣度。(www.61k.com)在这里以webdriver ruby binding的firefox-webdriver实现为例,简单介绍一下webdriver的工作原理。

当测试脚本启动firefox的时候,selenium-webdriver 会首先在新线程中启动firefox浏览器。如果测试脚本指定了firefox的profile,那么就以该profile启动,否则的话就新启1个profile,并启动firefox;

firefox一般是以-no-remote的方法启动,启动后selenium-webdriver会将firefox绑定到特定的端口,绑定完成后该firefox实例便作为webdriver的remote server存在;

客户端(也就是测试脚本)创建1个session,在该session中通过http请求向remote server发送restful的请求,remote server解析请求,完成相应操作并返回response;

客户端接受response,并分析其返回值以决定是转到第3步还是结束脚本; 这就是webdriver的工作流程,看起来很复杂实际上当了解了webdriver的实现原理后,理解上述问题应该比较简单。

webdriver是按照server – client的经典设计模式设计的。

server端就是remote server,可以是任意的浏览器。当我们的脚本启动浏览器后,该浏览器就是remote server,它的职责就是等待client发送请求并做出相应; client端简单说来就是我们的测试代码,我们测试代码中的一些行为,比如打开浏览器,转跳到特定的url等操作是以http请求的方式发送给被测试浏览器,也就是remote server;remote server接受请求,并执行相应操作,并在response中返回执行状态、返回值等信息;

举个实际的例子,下面代码的作用是”命令”firefox转跳到google主页:

driver = Selenium::WebDriver.for :firefoxdriver.navigate.to "http://google.com"在执行driver.navigate.to “http://google.com” 这句代码时,client,也就是我们的测试代码向remote server发送了如下的请求:

system.setproperty webdriver编程入门

POST session/285b12e4-2b8a-4fe6-90e1-c35cba245956/urlpost_data

{"url":"http://google.com"} 通过post的方式请求

localhost:port/hub/session/session_id/url地址,请求浏览器完成跳转url的操作。[www.61k.com) 如果上述请求是可接受的,或者说remote server是实现了这个接口,那么remote server会跳转到该post data包含的url,并返回如下的response

{"name":"get","sessionId":"285b12e4-2b8a-4fe6-90e1-

c35cba245956","status":0,"value":""}该response中包含如下信息

name:remote server端的实现的方法的名称,这里是get,表示跳转到指定url; sessionId:当前session的id;

status:请求执行的状态码,非0表示未正确执行,这里是0,表示一切ok不许担心;

value:请求的返回值,这里返回值为空,如果client调用title接口,则该值应该是当前页面的title;

如果client发送的请求是定位某个特定的页面元素,则response的返回值可能是这样的:

{"name":"findElement","sessionId":"285b12e4-2b8a-4fe6-90e1-

c35cba245956","status":0,"value":{"ELEMENT":"{2192893e-f260-44c4-bdf6-

7aad3c919739}"}}name,sessionId,status跟上面的例子是差不多的,区别是该请求的返回值是ELEMENT:{2192893e-f260-44c4-bdf6-7aad3c919739},表示定位到元素的id,通过该id,client可以发送如click之类的请求与server端进行交互。

那么remote server端的这些功能是如何实现的呢?答案是浏览器实现了webdriver的统一接口,这样client就可以通过统一的restful的接口去进行浏览器的自动化操作。目前webdriver支持ie, chrome, firefox, opera等主流浏览器,其主要原因是这些浏览器实现了webdriver约定的各种接口。

具体见http://code.google.com/p/seleniu ... l#Command_Reference。

二、实战篇——快速入门

system.setproperty webdriver编程入门

selenium webdriver学习历程(一)------------快速开始

学习selenium已经两年了,从1.X到2.X,一直在关注它。[www.61k.com]中间由于工作原因中断了一段时间,但是一直无法割舍,最近又去官网看了一下,更新还挺快的。selenium1.X的时代将被取代,selenium-webdriver的大航海时代开始了。。。

安装selenium webdriver(eclipse+jdk+selenium webdriver2.20+firefox 15)

1、安装firefox,本人使用firefox10。确保firefox安装在默认环境下(不是的话会报错)。

2、安装jdk,确保安装了jdk,本人喜欢使用java。但selenium webdriver也支持其它语言,如ruby、python、C#等。

3、安装eclipse,个人喜好。

4、安装selenium webdriver。解压下载的selenium webdriver包,可以在eclipse建一个user library,便与项目的引入。

第一个test

现在以第一个selenium webdriver的test来感受一下它的魅力。

Java代码

1. import org.openqa.selenium.By;

2. import org.openqa.selenium.WebDriver;

3. import org.openqa.selenium.WebElement;

4. import org.openqa.selenium.firefox.FirefoxDriver;

5.

6.

7. public class FirstExampe {

8.

9.

10. public static void main(String[] args) {

11. WebDriver driver = new FirefoxDriver();

12.

13.

14. driver.get("http://www.google.com.hk");

15. WebElement element = driver.findElement(By.name("q"));

16. element.sendKeys("hello Selenium!");

17. element.submit();

system.setproperty webdriver编程入门

18. try {

19. Thread.sleep(3000);

20. } catch (InterruptedException e) {

61阅读提醒您本文地址:

21. e.printStackTrace();

22. }

23. System.out.println("Page title is: " + driver.getTitle());

24.

25. driver.quit();

26. }

27.

28. }

正常运行后,这几行代码将会打开firefox浏览器,然后转跳到google首页。(www.61k.com)在搜索框中输入hello Selenium并提交搜索结果。等待3秒后会在命令行打印出当前页面的title,输出如下:

Java代码

1. Page title is: hello Selenium! - Google 搜尋

并关闭ff浏览器。

selenium webdriver对浏览器的简单操作

打开一个测试浏览器

对浏览器进行操作首先需要打开一个浏览器,接下来才能对浏览器进行操作。但要注意的是,因为Chrome Driver是Chromium 项目自己支持和维护的,所以你必需另外下载安装Chrome Driver,详细介绍查下他们的wiki 。

Java代码

1. import java.io.File;

2.

system.setproperty webdriver编程入门

3. import org.openqa.selenium.WebDriver;

4. import org.openqa.selenium.firefox.FirefoxBinary;

5. import org.openqa.selenium.firefox.FirefoxDriver;

6. import org.openqa.selenium.ie.InternetExplorerDriver;

7.

8. public class OpenBrowsers {

9.

10.

11. public static void main(String[] args) {

12. //打开默认路径的firefox

13. WebDriver diver = new FirefoxDriver();

14.

15. //打开指定路径的firefox,方法1

16. System.setProperty("webdriver.firefox.bin","D:Program FilesMozilla Firefoxfirefox.exe");

17. WebDriver dr = new FirefoxDriver();

18.

19. //打开指定路径的firefox,方法2

20. File pathToFirefoxBinary = new File("D:Program FilesMozilla Firefoxfirefox.exe");

21. FirefoxBinary firefoxbin = new FirefoxBinary(pathToFirefoxBinary);

22. WebDriver driver1 = new FirefoxDriver(firefoxbin,null);

23.

24. //打开ie

25. WebDriver ie_driver = new InternetExplorerDriver();

26.

27. //打开chrome

28. System.setProperty("webdriver.chrome.driver", "D:chromedriver.exe");

29. System.setProperty("webdriver.chrome.bin",

30. "C:Documents and SettingsgongjfLocal Settings"

31. +"Application DataGoogleChromeApplicationchrome.exe");

32.

33.

34. }

35.

36. }

打开指定路经ie和chrome方法和ff一样。(www.61k.com)

打开1个具体的url

打开一个浏览器后,我们需要跳转到特定的url下,看下面代码:

system.setproperty webdriver编程入门

Java代码

1. import org.openqa.selenium.WebDriver;

2. import org.openqa.selenium.firefox.FirefoxDriver; 3.

4. public class OpenUrl {

5. public static void main(String []args){

6. String url = "http://www.51.com";

7. WebDriver driver = new FirefoxDriver(); 8.

9. //用get方法

10. driver.get(url);

11.

12. //用navigate方法,然后再调用to方法

13. driver.navigate().to(url);

14. }

15. }

如何关闭浏览器

测试完成后,需要关闭浏览器

Java代码

1. import org.openqa.selenium.WebDriver;

2. import org.openqa.selenium.firefox.FirefoxDriver; 3.

4. public class CloseBrowser {

5. public static void main(String []args){

6. String url = "http://www.51.com";

7. WebDriver driver = new FirefoxDriver(); 8.

9. driver.get(url);

10.

11. //用quit方法

12. driver.quit();

13.

14. //用close方法

15. driver.close();

16. }

17. }

system.setproperty webdriver编程入门

如何返回当前页面的url和title

有时候我们需要返回当前页面的url或者title做一些验证性的操作等。[www.61k.com]代码如下: Java代码

1. import org.openqa.selenium.WebDriver;

2. import org.openqa.selenium.firefox.FirefoxDriver;

3.

4. public class GetUrlAndTitle {

5. public static void main(String []args){

6. String url = "http://www.51.com";

7. WebDriver driver = new FirefoxDriver();

8.

9. driver.get(url);

10.

11. //得到title

12. String title = driver.getTitle();

13.

14. //得到当前页面url

15. String currentUrl = driver.getCurrentUrl();

16.

17. //输出title和currenturl

18. System.out.println(title+"n"+currentUrl);

19.

20. }

21. }

其他方法

getWindowHandle() 返回当前的浏览器的窗口句柄

? getWindowHandles() 返回当前的浏览器的所有窗口句柄

? getPageSource() 返回当前页面的源码 ?

小结

从上面代码可以看出操作浏览器的主要方法都来自org.openqa.selenium.WebDriver这个接口中。看了一下源代码这些方法都 是在

61阅读提醒您本文地址:

system.setproperty webdriver编程入门

org.openqa.selenium.remote.RemoteWebDriver这个类中实现的,然后不同浏览的driver类继承 RemoteWebDriver。[www.61k.com)

在用selenium 1.X的时候常常会用到getEval()方法来执行一段js脚本来对页面进行处理,以处理一些遇到的问题。当然selenium webdriver也提供这样的一个方法:executeScript()

Java代码

1. import org.openqa.selenium.JavascriptExecutor;

2. import org.openqa.selenium.WebDriver;

3.

4. public class SimpleExample {

5.

6.

7. public static void main(String[] args) {

8. WebDriver driver = new FirefoxDriver();

9.

10. ((JavascriptExecutor)driver).executeScript("alert("hello,this is a alert!")");

11. }

12.

13. }

上面是一个最简单的例子,打开一个浏览器,然后弹层一个alert框。注意这里的driver要被强制转换成JavascriptExecutor。

下面演示在打开51.com首页如何得到帐号输入框中显示的字符,并打印输出。 Java代码

1.

2.

3.

4.

5.

6.

7.

8. import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; public class FirstExampe { public static void main(String[] args) { WebDriver driver = new FirefoxDriver();

system.setproperty webdriver编程入门

9. driver.get("http://www.51.com");

10. String js = "var user_input = document.getElementById("passport_51_user").title;return user_input;";

11.

12. String title = (String)((JavascriptExecutor)driver).executeScript( js);

13. System.out.println(title);

14. }

15.

16. }

输出结果为:

Java代码

1. 用户名/彩虹号/邮箱

selenium-webdriver提供了强大的元素定位方法,支持以下三种方法。[www.61k.com) ?

?

? 单个对象的定位方法 多个对象的定位方法 层级定位

定位单个元素

在定位单个元素时,selenium-webdriver提示了如下一些方法对元素进行定位。

?

?

?

?

?

?

?

? By.className(className)) By.cssSelector(selector) By.id(id) By.linkText(linkText) By.name(name) By.partialLinkText(linkText) By.tagName(name) By.xpath(xpathExpression)

system.setproperty webdriver编程入门

注意:selenium-webdriver通过findElement()findElements()等find方法调用"By"对象来定位 和查询元素。(www.61k.com]By类只是提供查询的方式进行分类。findElement返回一个元素对象否则抛出异常,findElements返回符合条件的元素 List,如果不存在符合条件的就返回一个空的list。

使用className进行定位

当所定位的元素具有class属性的时候我们可以通过classname来定位该元素。 下面的例子定位了51.com首页上class为"username"的li。

Java代码

1. import org.openqa.selenium.WebDriver;

2. import org.openqa.selenium.WebElement;

3.

4. import org.openqa.selenium.By;

5.

6. public class ByClassName {

7.

8.

9. public static void main(String[] args) {

10. WebDriver driver = new FirefoxDriver();

11. driver.get("http://www.51.com");

12. WebElement element = driver.findElement(By.className("username"));

13. System.out.println(element.getTagName());

14.

15. }

16. }

输出结果:

Java代码

1. li

使用id属性定位

51.com首页的帐号输入框的html代码如下:

Java代码

system.setproperty webdriver编程入门

1. <input id="passport_51_user" type="text" value="" tabindex="1" title="用户名/彩虹号/

邮箱"

2. name="passport_51_user">

在下面的例子中我们用id定位这个输入框,并输出其title,借此也可以验证代码是否工作正常。[www.61k.com]

Java代码

1. import org.openqa.selenium.By;

2. import org.openqa.selenium.WebDriver;

3. import org.openqa.selenium.WebElement;

4. import org.openqa.selenium.firefox.FirefoxDriver;

5.

6. public class ByUserId {

7.

8. /**

9. * @param args

10. */

11. public static void main(String[] args) {

12. // TODO Auto-generated method stub

13. WebDriver dr = new FirefoxDriver();

14. dr.get("http://www.51.com");

15.

16. WebElement element = dr.findElement(By.id("passport_51_user"));

17. System.out.println(element.getAttribute("title"));

18. }

19.

20. }

输出结果:

Java代码

1. 用户名/彩虹号/邮箱

使用name属性定位

51.com首页的帐号输入框的html代码如下:

Java代码

system.setproperty webdriver编程入门

1. <input id="passport_51_user" type="text" value="" tabindex="1" title="用户名/彩虹号/

邮箱"

2. name="passport_51_user">

61阅读提醒您本文地址:

使用name定位

Java代码

1. WebElement e = dr.findElement(By.name("passport_51_user"));

使用css属性定位

51.com首页的帐号输入框的html代码如下:

Java代码

1. <input id="passport_51_user" type="text" value="" tabindex="1" title="用户名/彩虹号/

邮箱"

2. name="passport_51_user">

使用css定位

Java代码

1. WebElement e1 = dr.findElement(By.cssSelector("#passport_51_user"));

使用其他方式定位

在定位link元素的时候,可以使用link和link_text属性;

另外还可以使用tag_name属性定位任意元素;

定位多个元素

上面提到findElements()方法可以返回一个符合条件的元素List组。[www.61k.com)看下面例子。 Java代码

system.setproperty webdriver编程入门

1. import java.io.File;

2. import java.util.List;

3.

4. import org.openqa.selenium.By;

5. import org.openqa.selenium.WebDriver;

6. import org.openqa.selenium.WebElement;

7. import org.openqa.selenium.firefox.FirefoxBinary;

8. import org.openqa.selenium.firefox.FirefoxDriver;

9.

10. public class FindElementsStudy {

11.

12. /**

13. * @author gongjf

14. */

15. public static void main(String[] args) {

16. WebDriver driver = new FirefoxDriver();

17. driver.get("http://www.51.com");

18.

19. //定位到所有<input>标签的元素,然后输出他们的id

20. List<WebElement> element = driver.findElements(By.tagName("input"));

21. for (WebElement e : element){

22. System.out.println(e.getAttribute("id"));

23. }

24.

25. driver.quit();

26. }

27. }

输出结果:

Java代码

1.

2.

3.

4.

5.

6.

7.

8.

9.

passport_cookie_login gourl passport_login_from passport_51_user passport_51_password passport_qq_login_2 btn_reg passport_51_ishidden passport_auto_login

上面的代码返回页面上所有input对象。(www.61k.com)很简单,没什么可说的。

system.setproperty webdriver编程入门

层级定位

层级定位的思想是先定位父元素,然后再从父元素中精确定位出其我们需要选取的子元素。(www.61k.com)

层级定位一般的应用场景是无法直接定位到需要选取的元素,但是其父元素比较容易定位,通过定位父元素再遍历其子元素选择需要的目标元素,或者需要定位某个元素下所有的子元素。

下面的代码演示了如何使用层级定位class为"login"的div,然后再取得它下面的所有label,并打印出他们的文本

Java代码

1. import java.io.File;

2. import java.util.List;

3.

4. import org.openqa.selenium.By;

5. import org.openqa.selenium.WebDriver;

6. import org.openqa.selenium.WebElement;

7. import org.openqa.selenium.firefox.FirefoxBinary;

8. import org.openqa.selenium.firefox.FirefoxDriver;

9.

10. public class LayerLocator {

11.

12. /**

13. * @author gongjf

14. */

15. public static void main(String[] args) {

16.

17. WebDriver driver = new FirefoxDriver();

18. driver.get("http://www.51.com");

19.

20. //定位class为"login"的div,然后再取得它下面的所有label,并打印出他们的值

21. WebElement element = driver.findElement(By.className("login"));

22. List<WebElement> el = element.findElements(By.tagName("label"));

23. for(WebElement e : el)

24. System.out.println(e.getText());

25.

26. }

27.

28. }

输出结果:

system.setproperty webdriver编程入门

Java代码

1.

2.

3.

4. 帐号: 密码: 隐身 下 次自动登

定位页面元素over了,下次写一下对frame的处理。[www.61k.com]

如何定位frame中元素

有时候我们在定位一个页面元素的时候发现一直定位不了,反复检查自己写的定位器没有任何问题,代码也没有任何问题。这时你就要看一下这个页 面元素是否在一个iframe中,这可能就是找不到的原因之一。如果你在一个default content中查找一个在iframe中的元素,那肯定是找不到的。反之你在一个iframe中查找另一个iframe元素或default content中的元素,那必然也定位不到。

selenium webdriver中提供了进入一个iframe的方法:

WebDriver org.openqa.selenium.WebDriver.TargetLocator.frame(String nameOrId) 也提供了一个返回default content的方法:

WebDriver org.openqa.selenium.WebDriver.TargetLocator.defaultContent()

这样使我们面对iframe时可以轻松应对。

以下面的html代码为例,我们看一下处现iframe。

Html代码

1.

2.

3.

4. main.html <html> <head>

system.setproperty webdriver编程入门

5.

6.

7.

8.

9. <title>FrameTest</title> </head> <body> <div id = "id1">this is a div!</div> <iframe id = "frame" frameborder="0" scrolling="no" style="left:0;position:absolute;" src = "frame.html"></iframe>

61阅读提醒您本文地址:

10. </body>

11. </html>

12.

13.

14.

15. frame.html

16.

17. <html>

18. <head>

19. <title>this is a frame!</title>

20. </head>

21. <body>

22. <div id = "div1">this is a div,too!</div>

23. <label>input:</label>

24. <input id = "input1"></input>

25. </body>

26. </html>

Java代码

1. import org.openqa.selenium.By;

2. import org.openqa.selenium.WebDriver;

3. import org.openqa.selenium.firefox.FirefoxDriver;

4.

5. public class FameStudy {

6.

7.

8. public static void main(String[] args) {

9. WebDriver dr = new FirefoxDriver();

10. String url = "YourPathtomain.html";

11. dr.get(url);

12.

13. //在default content定位id="id1"的div

14. dr.findElement(By.id("id1"));

15.

16. //此时,没有进入到id="frame"的frame中时,以下两句会报错

17. dr.findElement(By.id("div1"));//报错

18. dr.findElement(By.id("input1"));//报错

system.setproperty webdriver编程入门

19.

20. //进入id="frame"的frame中,定位id="div1"的div和id="input1"的输入框。(www.61k.com]

21. dr.switchTo().frame("frame");

22. dr.findElement(By.id("div1"));

23. dr.findElement(By.id("input1"));

24.

25. //此时,没有跳出frame,如果定位default content中的元素也会报错。

26. dr.findElement(By.id("id1"));//报错

27.

28. //跳出frame,进入default content;重新定位id="id1"的div

29. dr.switchTo().defaultContent();

30. dr.findElement(By.id("id1"));

31. }

32.

33. }

switch_to方法会new1个TargetLocator对象,使用该对象的frame方法可以将当前识别的”主体”移动到需要定位的frame上去。

在selenium 1.X里面得到弹出窗口是一件比较麻烦的事,特别是新开窗口没有id、name的时候。当时还整理了处理了几种方法,详见: 。在selenium webdriver中得到新开窗口相对简单的多,它无关新开窗口的id、name等属性。以下面的html为例: Html代码

1. <span style="white-space: normal; background-color: #ffffff;">test.html</span> 2.

3.

4. <html>

5.

6. <head><title>Test Popup Window</title></head>

7.

8. <body>

9.

10. <a id = "51" href = "http://www.51.com/" target = "_blank">Let's go!</a> 11.

system.setproperty webdriver编程入门

12. </body>

13.

14. </html>

下面的代码演示了如何去得到弹出的新窗口

Java代码

1. import java.util.Iterator;

2. import java.util.Set;

3.

4. import org.openqa.selenium.By;

5. import org.openqa.selenium.WebDriver;

6. import org.openqa.selenium.firefox.FirefoxDriver;

7.

8. public class PopupWindowTest {

9.

10.

11. /**

12. * @author gongjf

13. */

14. public static void main(String[] args) {

15. System.setProperty("webdriver.firefox.bin","D:Program FilesMozilla Firefoxfirefox.exe");

16. WebDriver dr = new FirefoxDriver();

17. String url ="YourPathtomain.html";

18. dr.get(url);

19. dr.findElement(By.id("51")).click();

20. //得到当前窗口的句柄

21. String currentWindow = dr.getWindowHandle();

22. //得到所有窗口的句柄

23. Set<String> handles = dr.getWindowHandles();

24. Iterator<String> it = handles.iterator();

25. while(it.hasNext()){

26. if(currentWindow == it.next()) continue;

27. WebDriver window = dr.switchTo().window(it.next());

28. System.out.println("title,url = "+window.getTitle()+","+window.getCurrentUrl());

29. }

30. }

31.

32. }

system.setproperty webdriver编程入门

输出结果:

Java代码

1. title,url = 51.com 真人配对玩游戏,http://www.51.com/

捕获或者说定位弹出窗口的关键在于获得弹出窗口的句柄。[www.61k.com)(句柄,我的理解是浏览器窗口的一个唯一标识,记得以前玩"按键精灵"也有这玩样。)

在上面的代码里,使用windowhandle方法来获取当前浏览器窗口的句柄,使用了windowhandles方法获取所有弹出的浏览器窗口的句柄,然后通过排除当前句柄的方法来得到新开窗口的句柄。

在获取新弹出窗口的句柄后,使用switchto.window(newwindow_handle)方法,将新窗口的句柄作为参数传入既可捕获到新窗口了。

alert、confirm、prompt这样的js对话框在selenium1.X时代也是难啃的骨头,常常要用autoit来帮助处理。

试用了一下selenium webdriver中处理这些对话框十分方便简洁。以下面html代码为例:

61阅读提醒您本文地址:

Html代码

1. Dialogs.html

Html代码

system.setproperty webdriver编程入门

1. <html>

2.

3. <head>

4.

5. <title>Alert</title>

6.

7. </head>

8.

9. <body>

10.

11. <input id = "alert" value = "alert" type = "button" onclick = "alert('欢迎!请按确认继续!');"/>

12. <input id = "confirm" value = "confirm" type = "button" onclick = "confirm('确定吗?');"/>

13. <input id = "prompt" value = "prompt" type = "button" onclick = "var name = prompt('请输入你的名字:','请输入

14.

15. 你的名字'); document.write(name) "/>

16.

17.

18. </body>

19.

20. </html>

以上html代码在页面上显示了三个按钮,点击他们分别弹出alert、confirm、prompt对话框。(www.61k.com]如果在prompt对话框中输入文字点击确定之后,将会刷新页面,显示出这些文字 。

selenium webdriver 处理这些弹层的代码如下:

Java代码

1.

2.

3.

4.

5.

6.

7.

8.

9. import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; public class DialogsStudy { /** * @author gongjf

system.setproperty webdriver编程入门

10. */

11. public static void main(String[] args) {

12. // TODO Auto-generated method stub

13. System.setProperty("webdriver.firefox.bin","D:Program FilesMozilla Firefoxfirefox.exe");

14. WebDriver dr = new FirefoxDriver();

15. String url = "file:///C:/Documents and Settings/gongjf/桌面

/selenium_test/Dialogs.html";// "/Your/Path/to/main.html"

16. dr.get(url);

17.

18. //点击第一个按钮,输出对话框上面的文字,然后叉掉

19. dr.findElement(By.id("alert")).click();

20. Alert alert = dr.switchTo().alert();

21. String text = alert.getText();

22. System.out.println(text);

23. alert.dismiss();

24.

25. //点击第二个按钮,输出对话框上面的文字,然后点击确认

26. dr.findElement(By.id("confirm")).click();

27. Alert confirm = dr.switchTo().alert();

28. String text1 = confirm.getText();

29. System.out.println(text1);

30. confirm.accept();

31.

32. //点击第三个按钮,输入你的名字,然后点击确认,最后

33. dr.findElement(By.id("prompt")).click();

34. Alert prompt = dr.switchTo().alert();

35. String text2 = prompt.getText();

36. System.out.println(text2);

37. prompt.sendKeys("jarvi");

38. prompt.accept();

39.

40. }

41.

42. }

从以上代码可以看出dr.switchTo().alert();这句可以得到alertconfirmprompt对话框的对象,然后运用其方法对它进行操作。[www.61k.com)对话框操作的主要方法有:

getText() 得到它的文本值

? accept() 相当于点击它的"确认" ?

system.setproperty webdriver编程入门

dismiss() 相当于点击"取消"或者叉掉对话框

? sendKeys() 输入值,这个alertconfirm没有对话框就不能用了,不然会报错。(www.61k.com] ? 下面我们来看一下selenium webdriver是如何来处理select下拉框的,以

http://passport.51.com/reg2.5p这个页面为例。这个页面中有4个 下拉框,下面演示4种选中下拉框选项的方法。select处理比较简单,直接看代码吧:)

Java代码

1. import org.openqa.selenium.By;

2. import org.openqa.selenium.WebDriver;

3. import org.openqa.selenium.WebElement;

4. import org.openqa.selenium.firefox.FirefoxDriver;

5. import org.openqa.selenium.support.ui.Select;

6.

7. public class SelectsStudy {

8.

9. /**

10. * @author gongjf

11. */

12. public static void main(String[] args) {

13. // TODO Auto-generated method stub

14. System.setProperty("webdriver.firefox.bin","D:Program FilesMozilla Firefoxfirefox.exe");

15. WebDriver dr = new FirefoxDriver();

16. dr.get("http://passport.51.com/reg2.5p");

17.

18. //通过下拉列表中选项的索引选中第二项,即2011年

19. Select selectAge = new Select(dr.findElement(By.id("User_Age")));

20. selectAge.selectByIndex(2);

21.

22. //通过下拉列表中的选项的value属性选中"上海"这一项

23. Select selectShen = new Select(dr.findElement(By.id("User_Shen")));

24. selectShen.selectByValue("上海");

25.

26. //通过下拉列表中选项的可见文本选 中"浦东"这一项

27. Select selectTown = new Select(dr.findElement(By.id("User_Town")));

61阅读提醒您本文地址:

28. selectTown.selectByVisibleText("浦东");

29.

30. //这里只是想遍历一下下拉列表所有选项,用click进行选中选项

31. Select selectCity = new Select(dr.findElement(By.id("User_City")));

32. for(WebElement e : selectCity.getOptions())

33. e.click();

system.setproperty webdriver编程入门

34. }

35.

36. }

从上面可以看出,对下拉框进行操作时首先要定位到这个下拉框,new 一个Selcet对象,然后对它进行操作。[www.61k.com)

Web 测试中我们经常会接触到Cookies,一个Cookies主要属性有”所在域、name、value、有效日期和路径",下面来讲一下怎么操作Cookies。

Java代码

1. import java.util.Set;

2.

3. import org.openqa.selenium.Cookie;

4. import org.openqa.selenium.WebDriver;

5. import org.openqa.selenium.firefox.FirefoxDriver;

6.

7. public class CookiesStudy {

8.

9. /**

10. * @author gongjf

11. */

12. public static void main(String[] args) {

13. // TODO Auto-generated method stub

14. System.setProperty("webdriver.firefox.bin","D:Program FilesMozilla Firefoxfirefox.exe");

15. WebDriver dr = new FirefoxDriver();

16. dr.get("http://www.51.com");

17.

18. //增加一个name = "name",value="value"的cookie

19. Cookie cookie = new Cookie("name", "value");

20. dr.manage().addCookie(cookie);

21.

22. //得到当前页面下所有的cookies,并且输出它们的所在域、name、value、有效日期和路径

23. Set<Cookie> cookies = dr.manage().getCookies();

system.setproperty webdriver编程入门

24. System.out.println(String.format("Domain -> name -> value -> expiry -> path"));

25. for(Cookie c : cookies)

26. System.out.println(String.format("%s -> %s -> %s -> %s -> %s",

27. c.getDomain(), c.getName(), c.getValue(),c.getExpiry(),c.getPath()));

28.

29.

30. //删除cookie有三种方法

31.

32. //第一种通过cookie的name

33. dr.manage().deleteCookieNamed("CookieName");

34. //第二种通过Cookie对象

35. dr.manage().deleteCookie(cookie);

36. //第三种全部删除

37. dr.manage().deleteAllCookies();

38. }

上面的代码首先在页面中增加了一个cookie,然后遍历页面的所有cookies,并输出他们的主要属性。[www.61k.com]最后就是三种删除cookie的方法。遍历cookies输出的结果: Java代码

1. Domain -> name -> value -> expiry -> path

2. .51.com -> FO_RFLP -> %7CaHR0cDovL3d3dy41MS5jb20v%7C%7C%7C -> null -> /

3. .51.com -> __utmz -

> 67913429.1331544776.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none) -> Tue Sep 11 05:32:56 CST 2012 -> /

4. www.51.com -> name -> value -> Tue Mar 12 17:33:00 CST 2030 -> /

5. www.51.com -> PHPSESSID -> 51d37fc72eb0ea66e4ef1971b688698b -> null -> /

6. .51.com -> __utma -

> 67913429.453585250.1331544776.1331544776.1331544776.1 -

> Wed Mar 12 17:32:56 CST 2014 -> /

7. www.51.com -> www_cookie_adv -> 1 -> Mon Mar 12 18:32:55 CST 2012 -> /

8. .51.com -> __utmc -> 67913429 -> null -> /

9. www.51.com -> NSC_xxx -> 44595a553660 -> null -> /

10. .51.com -> __utmb -> 67913429.1.10.1331544776 -

> Mon Mar 12 18:02:56 CST 2012 -> /

11. www.51.com -> www_jiaoyou_guide -> 0c83c0b5f569512d5a832bf0b4397a05 -> null -> /

system.setproperty webdriver编程入门

素里面

元素拖放drag and drop

Q群里有时候会有人问,selenium webdriver怎么实现把一个元素拖放到另一个元素里面。(www.61k.com)这一节总一下元素的拖放。

下面这个页面是一个演示拖放元素的页面,你可以把左右页面中的条目拖放到右边的div框中。

现在来看看selenium webdriver是怎么实现drag and drop的吧。let?s go! Java代码

1. import org.openqa.selenium.By;

2. import org.openqa.selenium.WebDriver;

3. import org.openqa.selenium.WebElement;

4. import org.openqa.selenium.firefox.FirefoxDriver;

5. import org.openqa.selenium.interactions.Actions;

6.

7. public class DragAndDrop {

8.

9. /**

10. * @author gongjf

11. */

12. public static void main(String[] args) {

13. // TODO Auto-generated method stub

14. System.setProperty("webdriver.firefox.bin","D:Program FilesMozilla Firefoxfirefox.exe");

15. WebDriver dr = new FirefoxDriver();

16. dr.get("");

17.

18. //首先new出要拖入的页面元素对象和目标对象,然后进行拖入。

61阅读提醒您本文地址:

19. WebElement element = dr.findElement(By.id("item1"));

20. WebElement target = dr.findElement(By.id("drop"));

21. (new Actions(dr)).dragAndDrop(element, target).perform();

22.

23. //利用循环把其它item也拖入

24. String id="item" ;

25. for(int i=2;i<=6;i++){

system.setproperty webdriver编程入门

26. String item = id+i;

27. (new Actions(dr)).dragAndDrop(dr.findElement(By.id(item)), target).perform();

28. }

29. }

30.

31. }

代码很简单,需要注意的是(new Actions(dr)).dragAndDrop(element, target).perform();这句话中,dragAndDrop(element, target)这个方法是定义了“点击element元素对象,然后保持住,直到拖到目标元素对象里面才松开”这一系列动作的Actions,如果你不调 用perform()方法,这个Actions是不会执行的。(www.61k.com]over!

web的自动化测试中,我们经常会遇到这样一种情况:当我们的程序执行时需要页面某个元素,而此时这个元素还未加载完成,这时我们的程序就会报错。怎么办?等待。等待元素出现后再进行对这个元素的操作。

在selenium-webdriver中我们用两种方式进行等待:明确的等待和隐性的等待。

明确的等待

明确的等待是指在代码进行下一步操作之前等待某一个条件的发生。最不好的情况是使用Thread.sleep()去设置一段确认的时间去等待。但为 什么说最不好呢?因为一个元素的加载时间有长有短,你在设置sleep的时间之前要自己把握长短,太短容易超时,太长浪费时间。selenium webdriver提供了一些方法帮助我们等待正好需要等待的时间。利用WebDriverWait类和ExpectedCondition接口就能实现这一点。

system.setproperty webdriver编程入门

下面的html代码实现了这样的一种效果:点击click按钮5秒钟后,页面上会出现一个红色的div块。(www.61k.com)我们需要写一段自动化脚本去捕获这个出现的div,然后高亮之。

Html代码

1.

2.

3.

4.

5.

6.

7. Wait.html <html> <head> <title>Set Timeout</title> <style> .red_box {background-

color: red; width = 20%; height: 100px; border: none;}

8. </style>

9. <script>

10. function show_div(){

11. setTimeout("create_div()", 5000);

12. }

13.

14. function create_div(){

15. d = document.createElement('div');

16. d.className = "red_box";

17. document.body.appendChild(d);

18. }

19. </script>

20. </head>

21. <body>

22. <button id = "b" onclick = "show_div()">click</button>

23. </body>

24. </html>

下面的代码实现了高亮动态生成的div块的功能:

Java代码

1. import org.openqa.selenium.By;

2. import org.openqa.selenium.JavascriptExecutor;

system.setproperty webdriver编程入门

3. import org.openqa.selenium.WebDriver;

4. import org.openqa.selenium.WebElement;

5. import org.openqa.selenium.firefox.FirefoxDriver;

6. import org.openqa.selenium.support.ui.ExpectedCondition;

7. import org.openqa.selenium.support.ui.WebDriverWait;

8.

9.

10. public class WaitForSomthing {

11.

12. /**

13. * @author gongjf

14. */

15. public static void main(String[] args) {

16. // TODO Auto-generated method stub

17. System.setProperty("webdriver.firefox.bin","D:Program FilesMozilla Firefoxfirefox.exe");

18. WebDriver dr = new FirefoxDriver();

19. String url = "file:///C:/Documents and Settings/gongjf/桌面

/selenium_test/Wait.html";// "/Your/Path/to/Wait.html"

20. dr.get(url);

21. WebDriverWait wait = new WebDriverWait(dr,10);

22. wait.until(new ExpectedCondition<WebElement>(){

23. @Override

24. public WebElement apply(WebDriver d) {

25. return d.findElement(By.id("b"));

26. }}).click();

27.

28. WebElement element = dr.findElement(By.cssSelector(".red_box"));

29. ((JavascriptExecutor)dr).executeScript("arguments[0].style.border = "5px solid yellow"",element);

30.

31. }

32. }

上 面的代码WebDriverWait类的构造方法接受了一个WebDriver对象和一个等待最长时间(10秒)。(www.61k.com)然后调用until方法,其中重写了 ExpectedCondition接口中的apply方法,让其返回一个WebElement,即加载完成的元素,然后点击。默认情况下,WebDriverWait每500毫秒调用一次ExpectedCondition,直到有成功的返回,当然如果超过设定的值还没有成功的返回,将抛出异常。

隐性等待

system.setproperty webdriver编程入门

隐性等待是指当要查找元素,而这个元素没有马上出现时,告诉WebDriver查询Dom一定时间。[www.61k.com]默认值是0,但是设置之后,这个时间将在WebDriver对象实例整个生命周期都起作用。上面的代码就变成了这样:

61阅读提醒您本文地址:

Java代码

1. import java.util.concurrent.TimeUnit;

2.

3. import org.openqa.selenium.By;

4. import org.openqa.selenium.JavascriptExecutor;

5. import org.openqa.selenium.WebDriver;

6. import org.openqa.selenium.WebElement;

7. import org.openqa.selenium.firefox.FirefoxDriver;

8. import org.openqa.selenium.support.ui.ExpectedCondition;

9. import org.openqa.selenium.support.ui.WebDriverWait;

10.

11.

12. public class WaitForSomthing {

13.

14. /**

15. * @author gongjf

16. */

17. public static void main(String[] args) {

18. // TODO Auto-generated method stub

19. System.setProperty("webdriver.firefox.bin","D:Program FilesMozilla Firefoxfirefox.exe");

20. WebDriver dr = new FirefoxDriver();

21.

22. //设置10秒

23. dr.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); 24.

25. String url = "file:///C:/Documents and Settings/gongjf/桌面

/selenium_test/Wait.html";// "/Your/Path/to/Wait.html"

26. dr.get(url);

27. //注释掉原来的

28. /*WebDriverWait wait = new WebDriverWait(dr,10);

29. wait.until(new ExpectedCondition<WebElement>(){

30. @Override

31. public WebElement apply(WebDriver d) {

32. return d.findElement(By.id("b"));

33. }}).click();*/

34. dr.findElement(By.id("b")).click();

35. WebElement element = dr.findElement(By.cssSelector(".red_box"));

system.setproperty webdriver编程入门

36. ((JavascriptExecutor)dr).executeScript("arguments[0].style.border = "5px solid yellow"",element);

37.

38. }

39. }

两者选其一,第二种看起来一劳永逸呀。(www.61k.com)哈哈

截图

在自动化测试中常常会用到截图功能。最近用了一下selenium-webdriver的截图功能还算不错,可以截取页面全图,不管页面有多长。

下面的代码演示了如何使用webdriver进行截图:

Java代码

1. import java.io.File;

2. import java.io.IOException;

3.

4. import org.apache.commons.io.FileUtils;

5. import org.openqa.selenium.OutputType;

6. import org.openqa.selenium.TakesScreenshot;

7. import org.openqa.selenium.WebDriver;

8. import org.openqa.selenium.firefox.FirefoxDriver;

9. public class ShotScreen {

10.

11. /**

12. * @author gongjf

13. * @throws IOException

14. * @throws InterruptedException

15. */

16. public static void main(String[] args) throws IOException, InterruptedException {

17.

system.setproperty webdriver编程入门

18. System.setProperty("webdriver.firefox.bin","D:Program FilesMozilla Firefoxfirefox.exe");

19. WebDriver dr = new FirefoxDriver();

20. dr.get("http://www.51.com");

21.

22. //这里等待页面加载完成

23. Thread.sleep(5000);

24. //下面代码是得到截图并保存在D盘下

25. File screenShotFile = ((TakesScreenshot)dr).getScreenshotAs(OutputType.FILE);

26. FileUtils.copyFile(screenShotFile, new File("D:/test.png"));}}

看了一下OutputType接口和TakesScreenshot接口,吐槽一下,貌似这两个接口不是同一个开发写的或者注释没有更新怎么的。(www.61k.com)在OutputType里面的注释说: Java代码

1. /**

2. * Defines the output type for a screenshot. See org.openqa.selenium.Screenshot for usage and

3. * examples.

4. ...

然后在那找了半天的org.openqa.selenium.Screenshot 接口,晕,后来想应该是org.openqa.selenium.TakesScreenshot。

在TakesScreenshot里有如下注释:

Java代码

1. /**

2. * Capture the screenshot and store it in the specified location.

3. *

4. * <p>For WebDriver extending TakesScreenshot, this makes a best effort

5. * depending on the browser to return the following in order of preference:

6. * <ul>

7. * <li>Entire page</li>

8. * <li>Current window</li>

9. * <li>Visible portion of the current frame</li>

10. * <li>The screenshot of the entire display containing the browser</li>

11. * </ul>

system.setproperty webdriver编程入门

12. *

13. * <p><span style="color: #ff0000;">For WebElement extending TakesScreenshot, this makes a best effort

14. * depending on the browser to return the following in order of preference:

15. * - The entire content of the HTML element

16. * - The visisble portion of the HTML element</span>

17. *

18. * @param <X> Return type for getScreenshotAs.

19. * @param target target type, @see OutputType

61阅读提醒您本文地址:

20. * @return Object in which is stored information about the screenshot.

21. * @throws WebDriverException on failure.

22. */

试了一下截取Webelement最终发现WebElement接口没有实现这个类。[www.61k.com)搞了半天也只是会了截取页面的全图。截取当前的frame也截取的页面全图。难道这个功能没有完善,好吧,这样说自我安慰一下。

selenium-webdriver 面向接口编程,找一个需要的功能还真是挺难的。

和键盘的操作

在selenium webdriver学习(十)------------如何把一个元素拖放到另一个元素里面 的时候,用到了一个Actions类。这一节主要分析一下这个Actions类。

这个actions类,主要定义了一些模拟用户的鼠标mouse,键盘keyboard操作。对于这些操作,使用perform()方法进行执行。

actions类可以完成单一的操作,也可以完成几个操作的组合。

单一的操作

单一的操作是指鼠标和键盘的一个操作。如鼠标左键按下、弹起或输入一个字符串等。

前面涉及到鼠标键盘操作的一些方法,都可以使用actions类中的方法实现,比如:click,sendkeys。

system.setproperty webdriver编程入门

Java代码

1.

2.

3.

4. WebElement element = dr.findElement(By.id("test")); WebElement element1 = dr.findElement(By.id("test1")); element.sendKeys("test"); element1.click;

用Actions类就可以这样实现:

Java代码

1.

2.

3.

4.

5.

6.

7.

8. //新建一个action Actions action=new Actions(driver); //操作 WebElement element=dr.findElement(By.id("test")); WebElement element1=dr.findElement(By.id("su")); action.sendKeys(element,"test").perform(); action.moveToElement(element1); action.click().perform();

看起来用Actions类实现click和sendKeys有点烦索

组合操作

组合操作就是几个动作连在一起进行操作。(www.61k.com]如对一个元素的拖放。

Java代码

1. (new Actions(dr)).dragAndDrop(dr.findElement(By.id(item)), target).perform();

可以直接调用dragAndDrip()方法,也可以像下面濱示的一样把几个操作放一起实现

Java代码

1.

2.

3.

4. Action dragAndDrop = builder.clickAndHold(someElement) .moveToElement(otherElement) .release(otherElement) .build().perform();

system.setproperty webdriver编程入门

其他鼠标或键盘操作方法可以具体看一下API里面的

org.openqa.selenium.interactions.Actions类

以前在selenium RC 里面有一个getTable方法,是得到一个单元格中的文本。[www.61k.com)其详细描述如下:

Java代码

1. /** Gets the text from a cell of a table. The cellAddress syntax <span style="white-space: normal; background-color: #ffffff;">tableLocator.row.column</span>

2. , where row and column start at 0.

3. @param tableCellAddress a cell address, e.g. <span style="white-

space: normal; background-color: #ffffff;">"foo.1.4"</span>

4. @return the text from the specified cell

5. */

6. String getTable(String tableCellAddress);

就是传入一个参数,这个参数的格式必须是tableLocator.row.column,如"foo.1.4",foo用于得到table对象,1.4代表在table里第1行第4列。行、列从0开始。

在selenium webdriver里,没有这样的方法,也就是说没有专门操作table的类。但我们可以自己封闭一个,这并不难。以上面的getTable方法为例,我们自己也可以创建这样功能的一个方法。

Java代码

1. public String getCellText(By by,String tableCellAddress)

system.setproperty webdriver编程入门

我叫它getCellText,它有两个参数,第一个是By对象用于得到table对象, tableCellAddress 如"1.4",代表在table里第1行第4列。[www.61k.com)行、列从0开始。 以下面html代码为例:

Html代码

1. <html>

2. <head>

3. <title>Table</title>

4.

5. </head>

6. <body>

7. <table border="1" id="myTable">

8. <tr>

9. <th>Heading(row 0 ,cell 0)</th>

10. <th>Another Heading(row 0 ,cell 1)</th>

11. <th>Another Heading(row 0 ,cell 2)</th>

12. </tr>

13. <tr>

14. <td>row 1, cell 0</td>

15. <td>row 1, cell 1</td>

16. <td>row 1, cell 2</td>

17. </tr>

18. <tr>

19. <td>row 2, cell 0</td>

20. <td>row 2, cell 1</td>

21. <td>row 2, cell 2</td>

22. </tr>

23. </table>

24. </body>

25. </html>

示例代码如下:

system.setproperty webdriver编程入门

Java代码

1. import java.util.List;

2. import org.openqa.selenium.By;

3. import org.openqa.selenium.NoSuchElementException;

4. import org.openqa.selenium.WebDriver;

5. import org.openqa.selenium.WebElement;

61阅读提醒您本文地址:

6. import org.openqa.selenium.firefox.FirefoxDriver;

7.

8. public class Table {

9.

10. /**

11. * @author gongjf

12. */

13. private WebDriver driver;

14. Table(WebDriver driver){

15. this.driver = driver;

16. }

17.

18. /** 从一个table的单元格中得到文本值. 参数tableCellAddress的格式为

19. row.column, 行列从0开始.

20. @param by 用于得到table对象

21. @param tableCellAddress 一个单元格地址, 如. "1.4"

22. @return 从一个table的单元格中得到文本值

23. */

24. public String getCellText(By by,String tableCellAddress) {

25. //得到table元素对象

26. WebElement table = driver.findElement(by);

27. //对所要查找的单元格位置字符串进行分解,得到其对应行、列。(www.61k.com)

28. int index = tableCellAddress.trim().indexOf('.');

29. int row = Integer.parseInt(tableCellAddress.substring(0, index));

30. int cell = Integer.parseInt(tableCellAddress.substring(index+1));

31. //得到table表中所有行对象,并得到所要查询的行对象。

32. List<WebElement> rows = table.findElements(By.tagName("tr"));

33. WebElement theRow = rows.get(row);

34. //调用getCell方法得到对应的列对象,然后得到要查询的文本。

35. String text = getCell(theRow, cell).getText();

36. return text;

37. }

38. private WebElement getCell(WebElement Row,int cell){

39. List<WebElement> cells;

40. WebElement target = null;

41. //列里面有"<th>"、"<td>"两种标签,所以分开处理。

42. if(Row.findElements(By.tagName("th")).size()>0){

43. cells = Row.findElements(By.tagName("th"));

system.setproperty webdriver编程入门

44. target = cells.get(cell);

45. }

46. if(Row.findElements(By.tagName("td")).size()>0){

47. cells = Row.findElements(By.tagName("td"));

48. target = cells.get(cell);

49. }

50. return target;

51.

52. }

53.

54.

55. public static void main(String[] args) {

56. WebDriver driver;

57. System.setProperty("webdriver.firefox.bin","D:Program FilesMozilla Firefoxfirefox.exe");

58. driver = new FirefoxDriver();

59. driver.get("file:///C:/Documents and Settings/Gongjf/桌面

/selenium_test/table.html");

60.

61. Table table = new Table(driver);

62. By by = By.id("myTable");

63. String address = "0.2";

64.

65. System.out.println(table.getCellText(by, address));

66.

67.

68. }

69.

70. }

运行代码将输出

Java代码

1. Another Heading(row 0 ,cell 2)

ps: 这里我只是以得到一个table中单元格的文本为例,但是从代码可以看出,对table的基本操作都有涉及到。[www.61k.com]有用到的同学可以自己包装一个完整的table类。

system.setproperty webdriver编程入门

这一节主要涉及 selenium webdriver处理Firefox profile的一些知识。[www.61k.com]

什么是Firefox profile

要了解Firefox profile请访问这里,它详细解绍了Firefox proflie。在Firefox里,如何管理Firefox profile 请访问这里。看完它们,相信你对Firefox profile会有所了解。好了,必备的知识准备完了,让我们来看看selenium webdriver 是怎么操作Firefox profile的吧。

设置profile中的一个preference

Java代码

1. FirefoxProfile profile = new FirefoxProfile();

2. profile.setPreference("aaa", "bbbb");

3. WebDriver driver = new FirefoxDriver(profile);

以上代码在Firefox Profile文件中设置一个名aaa,值为bbb的preference.(ps:这个preference只是一个举例,没有任何意义。要看 firefox profile有哪些preference,可以在firefox浏览器地址栏中输入:about:config). 代码运行后,在firefox浏览器地址栏中输入:about:config,可以看到它。

启用已经存在的profile

首先来了解一下为什么要已经存在的profile,其中一个原因是已经存在的profile里面保存有cookie等信息,可以保持用户的登录状态。

system.setproperty webdriver编程入门

启动已经存在的profile,因profile不同而有两种方法。(www.61k.com]一种是如果这个profile使用firefox配置管理器(Firefox's profile manager)而已经存在了。我们用下面的方法: Java代码

1. ProfilesIni allProfiles = new ProfilesIni();

2. FirefoxProfile profile = allProfiles.getProfile("WebDriver");

3. WebDriver driver = new FirefoxDriver(profile);

另一种是没有在自己的firefox里面注册过的,比如从另一台机子中的firefox得到的,我们可以用下面的代码:

Java代码

1. File profileDir = new File("path/to/your/profile");

2. FirefoxProfile profile = new FirefoxProfile(profileDir);

3. WebDriver driver = new FirefoxDriver(profile);

61阅读提醒您本文地址:

临时指定插件

有时我们需要临时让启动的firefox带一个插件,如firebug,来定位问题等。首先我们要下载这个插件的xpi安装包。剩下的就让selenium webdriver 来完成,如下: Java代码

1. File file = new File("<span style="background-

color: #ffffff;">path/to/your/</span>firebug-1.8.1.xpi");

Java代码

1. FirefoxProfile firefoxProfile = new FirefoxProfile();

2. firefoxProfile.addExtension(file);

3. firefoxProfile.setPreference("extensions.firebug.currentVersion", "1.8.1"); //避免启动画面

4. WebDriver driver = new FirefoxDriver(firefoxProfile);

这样启动的firefox中就安装了插件firebug.

启用默认情况下被firefox禁用的功能

system.setproperty webdriver编程入门

以本地事件例,很简单直接设置为true就可以了。[www.61k.com)

Java代码

1. FirefoxProfile profile = new FirefoxProfile();

2. profile.setEnableNativeEvents(true);

3. WebDriver driver = new FirefoxDriver(profile);

其它设置见selenium webdriver API中的org.openqa.selenium.firefox.FirefoxProfile.

启用firefox代理

这个更简单,直接上代码了。

Java代码

1.

2.

3.

4.

5.

6.

7.

8.

9.

over !

String PROXY = "localhost:8080";//如果不是本机,localhost替换成IP地址 org.openqa.selenium.Proxy proxy = new org.openqa.selenium.Proxy(); proxy.setHttpProxy(PROXY) .setFtpProxy(PROXY) .setSslProxy(PROXY); DesiredCapabilities cap = new DesiredCapabailities(); cap.setPreference(CapabilityType.PROXY, proxy); WebDriver driver = new FirefoxDriver(cap);

61阅读提醒您本文地址:

本文标题:编程入门-Windows SDK编程 API入门系列(转)
本文地址: http://www.61k.com/1150518.html

61阅读| 精彩专题| 最新文章| 热门文章| 苏ICP备13036349号-1