第五章 数组和广义表
——非线性数据结构
5.1 数组的定义和运算
☆二维数组的逻辑结构形式定义为:
2_Array=( D, R )
其中
D={ aij | i=c1,c1+1,...,d1, j=c2,c2+1,...,d2, aijD0}
R={ROW, COL}
ROW={
| c1id1, c2jd2-1,
aij, ai,j+1D }
COL ={< aij, ai+1,j >| c1id1-1 , c2jd2 , aij,
ai+1,jD }
D0为某个数据对象集合;
c1, c2, d1, d2均为整数;
(c1, d1) 和 (c2, d2)分别为下标i和j的一对界偶
(即上、下界)。
☆ 数组的操作
1 给定一组下标,存取相应的数据元素;
2 给定一组下标,修改相应数据元素中某一个或几个
数据项值。
5.2 数组的顺序存贮结构
二维数组存储:
1以列序为主序;
2以行序为主序.
行序为主序元素存储位置由下式决定:
(A[ c1..d1 , c2..d2 ])
LOC[i, j] = LOC[c1, c2]+[( d2c2+1)(ic1)+(jc2)]×L
其中, LOC[i, j]是aij的存储位置, LOC[c1, c2]是ac1c2的存储位置, 即二维数组的起始存储位置,
亦称基地址。
设每个元素占L个存贮单元。
5.3. 矩阵的压缩存贮
——相对于同值较多或零值较多的矩阵
压缩存储: 为多个值相同的元素只分配一个存储空间, 对零元不分配空间。
特殊矩阵:值相同或零元素分布有一定规律;
稀疏矩阵:值相同或零元素分布无一定规律;
5.3.1. 特殊矩阵
1. 对称矩阵 ai j = aj i —— n阶对称矩阵
A[1..n, 1..n]
为每一对对称元素分配一个存储空间, 则可将n×n
个元素压缩存储到n(n+1)/2个元素(含对角线元素)
的空间中。一维数组s[1.. n(n+1)/2]作为n价对称矩
阵A的存储结构, 则s[k]和矩阵元素a i j之间存在
一一对称关系:
2. 三角矩阵
上或下三角所有元素均为常数的n阶矩阵,公式同
对称矩阵。
3. n对角矩阵
只有n条对角线上有元素。
如三对角矩阵s[k]和a i j对应的位置计算
公式如下:
k=2×(i1)+j
{即:k=(i1)×31+ j-(i-2) }
5.3.2.稀疏矩阵 (m×n阶矩阵)
压缩存贮:
一. 三元组表
CONST maxnum={非零元素的个数};
TYPE tuple3tp=RECORD
i, j : integer;
v : elemtp;
END;
Sparmattp
=RECORD {mu数组行数,nu数组
mu, nu, tu: integer; 列数, tu非零元个数}
data: ARRAY[1 . . maxnum] OF tuple3tp;
END;
(一) 求转置矩阵
两种处理方法:
1. 适用于非零元素少 tu<◎扫描a数组a.nu次(列次),找第一列所有非零元作为b数组的第一行的非零元,找第二列所有非零元作为b数组的第二行的非零元,………,找第a.nu列所有非零元作为b数组的第b.mu(a.nu)行的非零元,
Proc trans_sparmat(var b : sparmattp ;
a : sparmattp);
{a和b分别表示稀疏矩阵A和B, B是A的转置矩阵, 现由a求b}
b.mu := a.nu; b.nu := a.mu; b.tu := a.tu;
if b.tu<>0 then
[ q:=1;
for col:=1 to a.nu do {扫描a数组a.nu 遍}
for p:=1 to a.tu do
if a.data[p].j=col {若三元组中列号为所寻列, then [ b.data[q].i:=a.data[p].j; 则置换之}
b.data[q].j:=a.data[p].i;
b.data[q].v:=a.data[p].v;
q:=q+1
]
]
Endp; {trans_sparmat}
2. 快速转置
◎ 附设数组
num[1..a.nu] 记a数组每列非零元个数
cpot[1..b.mu] 记a数组每列非零元在b数组中
的起始位置
for t:=1 to a.tu do
num[a.data[t].j]:=num[a.data[t].j]+1;
cpot[1]:=1;
for t:=2 to b.mu do
cpot[t]:=cpot[t-1]+num[t-1];
temp:=cpot[1]; cpot[1]:=1;
for col:=2 to a.nu do
[ p:=cpot[col];
cpot[col]= cpot[col1]+temp;
temp:=p; ]
Proc fast_transpos ( var b : sparmattp ;
a : sparmattp );
{将三元组表a表示的稀疏矩阵转置为三元组表b表示的稀疏矩阵}
b.mu := a.nu; b.nu := a.mu; b.tu := a.tu;
if b.tu<>0 then
[for col:=1 to a.nu do num[col]:=0; {初始化}
for t:=1 to a.tu do
num[a.data[t].j]:=num[a.data[t].j]+1;
{求a中每一列中非零元个数}
cpot[1]:=1; {求b.data的每行的起始位置}
for col:=2 to a.nu do
cpot[col]:=cpot[col1]+num[col1];
for p:=1 to a.tu do {转置}
[ col:=a.data[p].j; q:=cpot[col];
b.data[q].i:=a.data[p].j;
b.data[q].j:=a.data[p].i;
b.data[q].v:=a.data[p].v;
cpot[col]:=cpot[
第三章 栈和队列
栈、队列 :运算受限的线性表。
3.1 栈
☆栈的定义及运算
栈(stack):是限定仅在表尾进行插入或删除操作的线
性表。
表尾称栈顶(top), 表头称栈底(bottom)。
特征:后进先出的线性表,Last In Frist Out,
L I F O线性表。
操作:
inistack(s) 初始化操作。设定一个空栈s ;
empty(s) 判栈空函数。若s为空栈, 则返回值
"true", 否则返回值"false";
push(s, x) 入栈函数。s为已知栈,若s不满,则插入x为栈s的新的栈顶元素,函数返回值true;否则函数返回值false;
pop(s) 出栈函数。s为已知栈,若栈不空, 则返回函数值为栈顶元素,且从栈中删除栈顶元素,否则返回函数值为空元素null;
gettop(s) 取栈顶元素函数。s为已知栈,若栈不空,则返回函数值为栈顶元素,否则返回函数值为空元素 null;
clear(s) 栈置空操作。将s置为空栈.
current_size(s) 求当前栈中元素个数函数。
☆ 栈的顺序存储结构
Const arrmax={栈中允许存放元素个数的最大值}
Type sqstktp=record
elem : array[1..arrmax] of elemtp
top : 0..arrmax
end;
Proc initstack(var s : sqstktp);
{设定一个空的栈s}
s.top:=0 ;
Endp;
Func empty(s : sqstktp) : boolean;
{若栈s为空栈, 则函数返回值"true",否则返回"false"}
reture(s.top=0);
Endf;
Func push(var s : sqstktp; x : elemtp) : boolean;
{若栈不满, 则在栈顶插入x,函数返回值"true",否则返回"false"}
if s.top=arrmax
then return( false );
else [ s.top := s.top+1;
s.elem[s.top]:=x ;
return(true) ]
Endf;
Func pop(var s : sqstktp) : elemtp;
{ 若栈不空, 则删去栈顶元素并返回栈顶元素,否则返
回空元素null }
if s.top=0 then return(null)
else [ s.top := s.top 1; return(s.elem[s.top+1])]
Endf;
Func gettop(s : sqstktp) : elemtp;
{若栈不空 , 则返回栈顶元素 , 否则返回空元素null }
if s.top<>0
then return( s.elem[s.top] )
else return( null )
Endf;
Proc clear( var s : sqstktp);
{ 置 s 为 空 栈 }
s.top:=0 ;
Endp;
Func current_size( s : sqstktp) : integer;
{ 返 回 当 前 栈 中 元 素 个 数 }
return(s.top);
Endf;
☆ 栈的溢出
“上溢”:当top=arrmax时,插入;
“下溢”:当top=0时,删除;
☆ 解决栈的溢出问题
1 双栈共享
2 多栈共享
(3+6)*(4+5)/3-1+2#
OPTR : # ( )
OPND :
#3+6*4+5-3+2#
☆ 表达式求值 -------------语言编译中的最基本问题
1 定义算符的优先关系
‘(’=‘)’, ‘#’=‘#’
‘)’与‘(’, ‘#’与‘)’, ‘#’与‘(’, 不可比(遇到则出错)
2 算法
用两个工作栈:OPTR 用存运算符;
OPND 用存操作数或运算结果
1)置操作数栈OPND为空栈;
置运算符栈的栈底元素OPTR为’#’;
Op 为运算符集合;
op=[‘+’,‘-’,‘*’,‘/’,‘(‘, ’)’,‘#’];
2)读表达式的字符(表达式以’#’结束)
若是操作数则进OPND栈;
若是运算符, 则和OPTR栈的栈顶运算符
比较优先级
◎栈顶元素 > 字符:
出OPND栈顶两数, 出OPTR栈顶运算符作运
算,结果进OPND栈;
◎栈顶元素 = 字符:若字符是 ‘)’, 则退栈;
◎栈顶元素 < 字符:字符进栈;
3)重复2)直至整个表达式求值完毕( 即OPTR栈的
栈顶元素和当前读入的字符均为’#’)。
3+6*(4+5)/3-1+2#
FUNC exp_reduced : operandtype; {运算数类型}
{算术表达式求值的算符优先算法。假定从终端输入的表达式无语法错误,以’#’作结束符。设OPTR和OPND分别为运算符栈和操作数,OP为运算符的集合}
inistack(OPTR);
push(OPTR, ‘#’); {栈初始化, 并在运算符栈的栈底
inistack(OPND) ; 压入表达式左端的字符’#’}
read(w); {从终端接收一个字符}
while not ( (w=’#’) and (gettop(OPTR)=’#’) ) do
if not (w In OP )
then [ push(OPND , w) ; read(w) ]
else Case precede(gettop(OPTR) , w ) of
{比较优先权}
’<’ : [ push( OPTR, w ) ; read(w) ];
{栈顶元素优先权低}
’=’ : [ x:=pop(OPTR) ; read(w) ];
{脱括号读下一字符}
’>’ : [ theta:=pop(OPTR);
b:=pop(OPND); a:=pop(OPND);
push( OPND, operate(a, theta, b) )
] {退栈并将运结果入栈}
Endc;
return( gettop(OPND) )
ENDF; {exp_reduce
Func locate(p:linkisttp;x:elemtp):linkisttp;
{函数值返回带头结点的单链表p中首次出现元素x的位置;
若x不存在,则返回nil }
q:=p.next;
while (q<>nil) cand (q.data<>x) do
q:=q.next;
return(q)
Endf; {locate}
Proc insert( var p:qinkisttp;b:elemtp; i:integer );
{在不带头结点的单链表p中第i个元素前插入元素b;
则i应满足下列条件:1<=i<=length(p)+1 }
If i=1
Then[ new(s);s.data:=b;s.next:=p;p:=s]
Else [ q:=p;j:=1;
while (q<>nil) and (j[ q:=q.next;j:=j+1 ];
if (i<1) or (q=nil)
then error(’no this position’)
else [ new(s);s.data:=b;
s.next:=q.next;
q.next:=s
]
]
Endp; {insert}
Proc delete( var p:linkisttp;i:integer );
{ 删除不带头结点的单链表p中第i个元素;则i应满足下
列条 1<=i<=length(p) }
q:=p;j:=1;
if (i=1) and (q<>nil)
then [p:=q.next; dispose(q)]
else
[ while (q<>nil) and (j[ q:=q.next; j:=j+1 ];
if (q=nil) cor (q.next=nil) or (i<1)
then error(’no this position’)
else [q1:=q.next;
q.next:=q1.next;
dispose(q1)
]
Endp; {delete}
Proc clear( var p:linkisttp);
{将不带头结点的单链表p置为空表}
while p<>nil do
[ q=p;p:=p.next;dispose(q)]
Endp; {clear}
** 循环链表:表尾节点的指针域的值等于头指针的值
2.2.2 双向链表的存储结构
Type
dupointer=dunodetype;
dunodetype=record
data: elemtp;
prior, next: dupointer;
end;
p : p.next.prior = p = p.prior.next
双向链表的十个基本操作
Proc initiate( var q: dupointer );
{将q初始化成带头结点的双向循环链表 }
new(q);
q.next:=q.prior:=q
Endp; {init}
Func length( q: dupointer ): integer;
{函数值返回带头结点的双向循环链表q的长度;
若q为空表,则返回0 }
p:=q.next; i:=0;
while p<>q do
[p:=p.next; i:=i+1];
return(i);
Endf; {length}
Func get(q: dupointer; i: integer ): elemtp;
{函数值返回带头结点的双向循环链表q中第i个元素结点的数据值;若第i个结点不存在,则返回null}
p:=q.next; k:=1;
while (p<>q) and (k[p:=p.next; k:=k+1;];
if (p<>q) and (i=k )
then return(p.data)
else return(null)
Endf; {get}
Func prior(q:dupointer; elm:elemtp):elemtp;
{函数值返回带头结点的双向循环链表q中元素 elm
的直接前驱;若elm或elm的直接前驱不存在,
则返回null }
p:=q.next;
while (p<>q) and (p.data<>elm) do
p:=p.next;
if (p.prior<>q) and (p<>q)
then return(p. prior.data)
else return(null)
Endf; {prior}
Func next( q:dupointer; elm:elmtp ): elemtp;
{函数值返回带头结点的双向循环链表q中元素elm的直接后继;若elm或elm的直接后继不存在,
则返回null }
p:=q.next;
while (p<>q) and (p.data<>elm) do
p:=p.next;
if (p<>q) and (p.next<>q)
then return(p.next.data)
else return(null)
Endf; {next}
Func locate(q: dupointer; x: elemtp): dupointer;
{函数值返回带头结点的双向循环链表q中首次出现元素 x的位置;若x不存在,则返回nil }
p:=q.next;
while (p<>q) and (p.data<>x) do
p:=p.next;
if p<>q then return(p)
else return(nil)
Endf; {locate}
Proc insert(q: dupointer; i: integer; b: elemtp );
{在带头结点的双向循环链表q中第i个元素前插入元素b;则i应满足下列条件: 1<=i<=length(q)+1 }
p:=q.next; k:=1;
while (p<>q) and (k[ k:=k+1; p:=p.next ];
if (p=q) or (i<1)
then error(‘no this position’)
else [ new(s); s.data:=b;
s.next:=p.next;
s
线性结构的特点是:在数据元素的非空有限集中
1)存在唯一的一个被称做"第一个"的数据元素;
2)存在唯一的一个被称做"最后一个"的数据元素;
3)除第一个外,每个数据元素均有唯一个直接前驱;
4)除最后一个外每个数据元素均有唯一个直接后继。
数据结构:逻辑结构; 存储结构
线性表存储结构:顺序存储结构;链式存储结构;
2.1 线性表及其基本运算
线性表(Linear_list):一个线性表是n个数据元素的
有限序列。在同一线性表中的数据元素必定
具有相同特性。
记 为:L = ( A1, ... , An ) ;
直接前驱:i2, 3, ..., n时,Ai-1是Ai的直接前驱;
直接后继:i1, ..., n-1时,Ai+1是Ai的直接后继;
长 度:n定义为线性表L的长度;
n=0时称为空表。
对线性表进行的基本运算:
1. Initiate( L ); 初始化操作,设定一个空的线性表L。
2. Length( L ); 求线性表L长度函数。
3. Get( L; i );函数返回值为取线性表L中第i个元素;
如果不存在第i个元素,则函数值为Null。
4. Prior( L; elm ); 函数返回值为线性表L中元素elm直接前驱;如果elm的直接前驱不存在,则函数值为Null。
5. Next ( L; elm );函数返回值为线性表L中元素elm直接后继;如果elm的直接后继不存在,则函数值为Null。
6. Locate( L; x );定位函数。函数值为线性表L中元
素x的位置;如果x不存在, 则函数值为0 (nil)。
7. Insert( L; i; b );前插操作。在给定的线性表L中的
第i个元素之前插入元素b;其中i要满足:
1<=i<=length(L)+1。
8. Delete( L; i);删除操作。在给定的线性表L中删除
第i个元素;其中i要满足: 1<=i<=length(L)。
9. Empty( L ): 判空表函数。如果线性表L为空,则
函数值为’true’,否则为’false’。
10.Clear( L ): 将线性表L置空操作。
Proc union( var la: linear_list; lb: linear_list );
{ 将所有在线性表lb中存在而线性表la中不存在的
数据元素插入到线性表 la中去 }
n:=length(la); {确定线性表la的长度 }
for i:=1 to length(lb) do
[ x:= get(lb, i); {取lb中第i个数据元素 }
k:=locate(la, x); {在la中进行搜索}
if k=0 (nil) then {将la中不存在的元素插入la
[ insert(la, n+1, x); n:=n+1 ] 中,并修改n的值}
]
Endp; {union}
Proc merge_list(la, lb: linear-list; var lc : linear-list);
{已知线性表la和lb中元素依值非递减有序,本算法归并la和lb得到线性表lc, lc中元素仍依值非递减有序}
initiate(lc); i:=1; j:=1; k:=0; {初始化}
while (i<=length(la)) and (j<=length(lb)) do
if get(la, i)<=get(lb, j) { la和lb均非空 }
then [ insert(lc, k+1, get(la, i));
k:=k+1; i:=i+1 ]
else [ insert(lc, k+1, get(lb, j));
k:=k+1; j:=j+1 ];
while i<=length(la) do
[ insert(lc, k+1, get(la, i));
k:=k+1; i:=i+1 ];
while j<=length(lb) do
[ insert(lc, k+1, get(lb, j));
k:=k+1; j:=j+1 ];
Endp; { merge_list }
2.2 线性表的顺序存储结构及基本操作
顺序存储结构
Const maxlen = 线性表可能达到的最大长度;
Type
Sqlisttp = record
Elem : array [1..maxlen] of elemtp ;
Last : 0..maxlen ;
end;
顺序存储结构的十个基本操作:
Proc initiate( var L : sqlisttp );
{ 将线性表L初始化为空表 }
L.last:=0
Endp; {initiate}
Func length( L : sqlisttp ) : integer ;
{ 函数值返回线性表L的长度 }
return(L.last);
Endf; {length}
Func get( L : sqlisttp; i : integer ) : elemtp;
{ 若1 i length(L)函数值返回线性表L中第i个
元素结点的数据值; 否则返回 null }
if (i<=0) or (i>L.last) then return(null);
return( L.elem[i]);
Endf; {get}
Func prior( L : sqlisttp; elm : elemtp ) : elemtp;
{ 函数值返回线性表L中元素elm的直接前驱;
若elm或elm的直接前驱不存在则返回null }
i:=1;
while (i<=L.last) cand (L.elem[i]<>elm) do
i:=i+1;
if (i=1) or (i>L.last)
then return(null)
else return(L.elem[i-1])
Endf; {prior}
Func next( L : sqlisttp; elm : elemtp ) : elemtp;
{ 函数值返回线性表L中元素elm的直接后继;
若elm或elm的直接后继不存在, 则返回null }
i:=1;
while (ielm) do
i:=i+1;
if i>=L.last
then return(null)
else return(L.elem[i+1])
Endf;
课程简介:
计算机科学与技术专业核心课程,是操作系统、软件工程、数据库原理及计算机网络等重要课程的先修课,也是许多学校计算机专业研究生入学及用人单位招聘考试的主要科目之一。
课程主要介绍计算机科学中程序设计的基本数据结构及在其上的基本操作、描述;讨论典型的查找、排序算法及实现技巧与复杂性。使学生深透地理解数据的逻辑结构和物理结构的基本概念及有关算法,熟悉它们在计算机科学中最基本的应用,并使学生了解数据对象的特性,学会数据组织的方法,并初步具备分析和解决现实世界问题在计算机中如何表示的能力。培养良好的程序设计技能,为后续课程奠定坚实的基础。
课程的教学注重理论与实践的结合,对实际动手能力有较高的要求,实验课程要求学生完成相当数量的设计题目,在实际设计中即巩固了理论知识,同时设计能力也得到了提高。
数据结构
第一章 绪 论
一•数据结构的发展背景、简史
★ 背景
计算机的发展:
科学计算——科学管理——进入社会
★ 简史
60年代末国外开始正式有《数据结构》课名
内 容:同图论中的图、树的理论
扩 充:集合、代数论、格,
关系等——离散结构
进一步:存贮结构、文件管理、及其算法
80年代初国内才开始正式在大学的计算机系开设这门课现在好多
学校理工科的本科生都作为选修课,研究生作为必修课。
二•数据结构在计算机科学中的地位
★ 核心课程之一。
★ 程序 数据结构+算法
对上式的解释:算法就是施加于数据结构上的运算,
这个过程就构成了程序。
──对于计算机而言:再伟大的思想也得由程序实现。
★ 两大部分:1.数据结构(逻辑结构、存贮结构);2.算法
三•数据结构的内容、定义、基本术语
★ 数据:一种对客观事物的符号表示,在计算机科学是指所有能输入到
计 算机中并被计算机程序处理的符号的总称。 它是计算机程序
加工的"原料"。
★ 数据元素:是数据的基本单位。在计算机程序中通常作为一个整体进行考虑和
处理。一个数据元素可由若干个数据项组成,数据项是数据的不可
分割的最小单位。
★ 数据对象:是一个性质相同的数据元素的集合,是数据的一个子集。
★ 数据结构:是带有结构的数据元素的集合。
严格地说,数据结构是一个二元组:
Data_Structure = ( D , S )
其中: D是数据元素的有限集;
S是D上关系的有限集。
★ 结构:是指数据元素相互之间的关系;根据元素之间关系的不同特性,
可分以下四类基本结构:
(1)集合结构:数据元素之间只有"同属于一个集合"的关系;
(2)线性结构:数据元素之间存在一个对一个的关系;
(3)树形结构:数据元素之间存在一个对多个的关系;
(4)图状结构:数据元素之间存在多个对多个的关系; (网状结构)
★数据结构的两个层次:
(1)数据的逻辑结构:即结构定义中的"关系"描述的是数据元素之间的逻辑
关系,简称数据结构;
(2)数据的存储结构:即数据结构在计算机中的表示(又称映象),简称存储
结构。它包括数据元素的表示和关系的表示;
★ 数据的逻辑结构和存储结构是密切相关的两个方面,算法的设计取决于选
定的数据(逻辑)结构,而算法的实现依赖于采用的存储结构。
★数据元素之间的关系在计算机中的表示方法:
(1) 顺序映象:其特点是借助元素在存储器中的相对位置来表示数据元素
之间的逻辑关系,对应的存储结构是顺序存储结构;
(2) 链式映象(非顺序映象):其特点是借助指示元素存储地址的指针
(pointer)来表示数据元素之间的逻辑关系,对应的存
储结构是链式存储结构;
数据类型:是一个值的集合和定义在这个值集上的一组操作的总称;按 "值"的
不同性质,高级程序语言中的数据类型可分为两类:
(1)原子类型:原子类型的值是不可分解的,如PASCAL语言中的标准类型
Real, Integer, Char, Boolean,枚举类型,子界类型 和 指针类型;
(3) 结构类型:结构类型的值是由若干成分按某种结构组成的,因此是可以分
解的,并且其成分可以是非结构的,也可以是结构的;
类Pascal
四•算法的定义、特性、描述和分析
算法:就是一个有穷规则的集合,其中的规则规定了一个解决某一特定
类型问题的运算序列。
算法的五个重要特性:
1)有穷性:
一个算法(对任何合法的输入值)必须在执行有穷步之后结束,并且每一步都
可在有穷时间内完成;
2)确定性:
算法中每一条指令必须有确切的含义,不会产生歧义。并且在任何条件下,
算法只有唯一的一条执行路径,即对于相同的输入只能得出相同的输出;
3)可行性:
一个算法是能行的,即算法中描述的操作都是可以通过已经实现的基本运算
执行有限次来实现的
4)输 入:
一个算法必须有零个或多个的输入,这些输入取
C++语言吸引如此之多的智力投入,以至于这个领域的优秀作品,包括重量级的软件产品、程序库以及书籍著作等,数不胜数。对于读者来说,倘若限于经济等客观因素而必须做出折衷取舍,我推荐至少阅读以下几本书:
1. The C++ Programming Language (Special 3rd Edition) by Bjarne Stroustrup
2. The Design and Evolution of C++ by Bjarne Stroustrup
如果你只打算购买一本书,那就选择1,如果还想要第二本 — 那就是这两本了。它们的作者是 C++语言的创建者。Stroustrup的文字语言丝毫不逊色于他所创建的程序语言。它们可以使你免于误入歧途。
3. Effective C++: 50 Specific Ways to Improve Your Programs and Designs by Scott Meyers
4. More Effective C++: 35 New Ways to Improve Your Programs and Designs by Scott Meyers
5. Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library by Scott Meyers
我强烈推荐Meyers这个系列。它们是菜鸟进阶必读之作。游刃有余的技术,高超的写作技巧。Meyers可能是世界上最优秀的C++技术作家。
6. The C++ Standard Library : A Tutorial and Reference by Nicolai M. Josuttis
C++标准库字典式著作。内容全面,结构清晰。
7. C++ Templates: The Complete Guide by David Vandevoorde, Nicolai M. Josuttis
Alexandrescu的Modern C++ Design阅读门槛很高,这本书可以充当你的垫脚石。单单阅读The C++ Standard Library或许并不足以使你具备定制、扩充标准库组件的能力,这本书可以助你一臂之力。对于任何希望进入模板编程领域的C++程序员来说,这是一本必读之作。
8. Modern C++ Design: Generic Programming and Design Patterns Applied by Andrei Alexandrescu
一本天才的著作!泛型模式,无限延伸你的视野,足以挑战任何一名C++程序员的思维极限。这本书几乎可以满足你对C++模板的所有幻想。
9. Design Patterns by Erich Gamma , Richard Helm, Ralph Johnson, John Vlissides
设计可复用的面向对象的软件,你需要掌握设计模式。这并不是一本专门针对C++程序员的著作,但它采用了C++(和Smalltalk)作为主要示例语言,C++程序员尤其易于从中受益。学习设计模式,这本书需要一而再、再而三的咀嚼。
这是公正客观的推荐吗?可能不是。实际上,没有任何人能够做到绝对客观 — 至少The Design and Evolution of C++是否应该进入这个推荐列表就能惹起喋喋不休的争吵。
还有许多优秀的著作值得推荐,但为了将推荐数目控制到个位数,它们没能入选。换一个人也可能会给出另外的“九本书”列表,但至少大半会和这个列表重叠。
巧合的是,这几本书均出自Addison-Wesley。它们绝大多数在市面上能找到相应的中文版,几乎每一中文版都有很好的质量。祝各位阅读快乐。
提 到 类 似Windows95 式 的 专 业 安 装 盘 的 制 作, 也 许 想 来 都 让 人 望 而 生 畏。 但 在 这 里, 我 们 将 介 绍 一 种 可 在 十 几 分 钟 内, 完 成 一 个 由 操 作 简 便 的 安 装 向 导、 自 动 安 全 地 程 序 卸 载、 用 户 选 择 组 件 安 装 以 及 精 美 安 装 画 面 构 成 的 功 能 强 大、 安 全 可 靠, 并 经 过 精 心 包 装 的 软 件 安 装 程 序。
---- 我 们 选 用 制 作 专 业 安 装 盘 的 集 成 开 发 工 具 是 由 美 国InstallShield 软 件 公 司 开 发 的InstallShield5.0。 它 的 简 单 性, 易 操 作 性, 安 全 性 以 及 强 大 的 安 装 盘 制 作 功 能 受 到 了 全 球 同 行 的 认 同, 所 以 得 到 了 随 同Microsoft 公 司 的Studio 97-VC5.0 捆 绑 销 售 的 殊 荣。
---- 下 面 我 们 对InstallShield5.0 进 行 一 个 简 单 介 绍:
---- 1. InstallShield5.0 制 作 安 装 盘 的 基 本 构 架
---- InstallShield5.0 制 作 安 装 盘 是 在 一 定 基 本 构 架 上 完 成 的。 这 一 基 本 构 架 的 结 构 图 如 下 所 示:
---- 由 图 可 知, 基 本 构 架 由4 级 组 成:
---- 1) Setup Type( 安 装 类 型);
---- 2) Component( 组 件);
---- 3) File Group( 文 件 组);
---- 4) Files( 文 件)。
---- 在InstallShield5.0 制 作 安 装 盘 时, 必 须 按 照 上 图 建 立 的 各 级 链 接 关 系。 步 骤 如 下:
---- 1) 把 文 件(Files) 按 照 类 型( 执 行 文 件、 帮 助 文 件、 范 例 文 件、 系 统 文 件、 动 态 连 接 文 件) 的 不 同 分 别 链 接 到 相 应 的 文 件 组(File Group) 中;
---- 2) 根 据 软 件 制 作 者 的 意 图, 把 各 文 件 组(File Group) 分 别 链 接 入 相 应 的 组 件(Component) 或 子 组 件 中;
---- 3) 把 各 组 件(Component) 或 子 组 件 与 相 应 安 装 类 型(Setup Type) 相 链 接。 通 过 各 级 链 接, 可 以 实 现 用 户 对 组 件 的 选 择 安 装。
---- 2. 举 例 说 明InstallShield5.0 的 使 用 方 法
---- 下 面 介 绍 通 过 修 改InstallShield5.0 提 供 的 现 有 模 板 来 制 作 安 装 程 序。
---- 第 一 步. 打 开InstallShield5.0 的 模 板
---- 1) 选 中 菜 单 项’File’ 中 的 命 令’New’ ;
---- 2) 选 择 书 签’Template’, 选 中 选 项’Template Two’;
---- 注 意: 不 要 选 择’Template one’, 否 则 无 法 实 现 选 择 性 安 装 组 件 的 功 能。
---- 第 二 步. 在 文 件 组(File Group) 中 链 接 文 件(Files)
---- 1) 在Project Workspace ( 图1 的 左 操 作 窗), 打 开 书 签’File Groups’;
---- 2) 用 鼠 标 左 键 点 击 文 件 夹’Program Executable Files’ 旁 边 的 加 号’+’, 出 现 图 标’Links’;
---- 3) 点 击 图 标’Links’, 右 边 文 件 窗 打 开;
---- 4) 选 中 文 件 窗 的 文 件 ’MYAPP.EXE’ 后, 用 鼠 标 右 键 删 除 并 确 认;
---- 5) 在 用 鼠 标 右 键 在 文 件 窗( 图1 的 右 操 作 窗) 中 插 入 文 件, 该 文 件 是 我 们 软 件 中 的 执 行 文 件, 命 名 为soft1.exe;
---- 6) 按 照 修 改’Program Executable Files’ 文 件 组 中’Links’ 文 件 的 方 式, 逐 一 修 改’Help Files’、’Program DLLs’ 和’Example Files’ 三 个 文 件 组。 如 果 在 软 件 中 不 存 在 某 文 件 组, 则 在 选 中 该 文 件 组 后, 用 鼠 标 右 键 快 捷 菜 单 删 除。
---- 注: 在 软 件 安 装 完 成 后, 文 件 组’Example Files’ 中 存 放 的 执 行 范 例, 由 安 装 程 序 完 成 与’Program Executable Files’ 中 执 行 文 件 的 关 联 工 作。 如: 把*.txt 文 件 与 可 执 行 程 序Notepad.exe 相 关 联。
---- 如 果 软 件 中 有’Program DLLs’ 文 件 组, 则 进 行 下 面 的 第 三 步; 否 则, 跳 过 第 三 步, 直 接 进 行 第 四 步。
---- 第 三 步. 修 改 组 件(Components)
---- 1) 在Project Workspace ( 图1 的 左 操 作 窗), 打 开 书 签’Components’;
---- 2) 用 鼠 标 左 键 点 击 文 件 夹’Program Component’, ’Program Component’ 的 属 性 窗 口 在 右 部 打 开;
---- 3) 在’Program Component’ 的 属 性 窗 口 中 双 击 最 后 一 项’Included File Groups’;
---- 4) 在 打 开 的 窗 口 中, 选 中’Program DLL Files’ 后, 点 击 删 除 钮(Remove), 并 确 认(OK);
---- 第 四 步. 修 改’ 开 始’ 菜 单 中 的 程 序 快 捷 项
---- 在’ 开 始’ 菜 单 中’ 程 序’ 组 中, 建 立 我 们 相 关 程 序( 执 行 文 件、 帮 助 文 件) 的 快 捷 项, 以 便 用 户 方 便 使 用 我 们 的 软 件。
---- 1) 在Project Workspace ( 图1 的 左 操 作 窗), 打 开 书 签’Scripts’;
---- 2) 从InstallShield 菜 单’Edit’ 中 选 中’Replace...’, 替 换 对 话 窗 打 开;
---- 3)
制作功能完备的数据库安装程序
经乾
制作安装程序是数据库软件开发的最后一步,InstallShield无疑是众多可选工具中
的佼佼者。它除了提供直观方便的向导外,还提供内容丰富的编程。利用这种类似VC的
脚本语言可以创建功能强大的安装程序。
在很多情况下,没有必要自己动手编写代码,只需从InstallShield的帮助文件中把
示例代码复制过来,稍加修改就可使用。当利用Project Wizard创建一个新的项目,就
会生成一个脚本文件Setup.rul,它只是一个框架,功能仅限于将所选的组件
(Components)复制到指定的位置。本文介绍如何以最少的代码完善Setup.rul的功能。
一、验证序列号
在使用Project Wizard时,选择用户信息(User Information)对话框,它将在
ShowDialogs函数中显示:询问用户名、公司名、序列号。可在此对话框显示之后判断
序列号的有效性,以决定是否继续安装。
Dlg_SdRegisterUserEx:
nResult=DialogShowSdRegisterUserEx();
if (nResult=BACK) goto Dlg_SdLicense;
if (svSerial!=″35296-0017544-62261″) then
//如果序列号与给定值不匹配
MessageBox(″Serial Number is incorrect!″,SEVERE);
Exit; //退出安装
endif;
二、安装时播放声音
PlayMMedia (MMEDIA_MIDI , ″c://Windows//Temp//_istmp0.dir//start.mid″,
MMEDIA_PLAYCONTINUOUS, 0);
//连续播放MIDI文件
三、显示图片
安装程序在运行的过程中可以显示一些图片来介绍软件的新特性,这在
InstallShield中称为BillBoards,可显示的图片类型为wmf、bmp、dib。自动显示图片
的方法是把图片命名为Bbrdn.bmp(其中n为序数1,2,3…),并将它们加到Setup Files
面板中。若你的软件可能安装到低分辨率的机器上,你就应该再准备一套低分辨率的图
片,并按顺序命名为Bbrdln(l表示“low”,n为序数1,2,3…)。自动显示图片的缺点
是:图片在数据复制过程中以一定的时间间隔自动显示,不受程序控制。下面介绍一种
控制图片显示的方法:
szBitmap=″C://Windows//Bubbles.bmp″;
//包括完整路径信息的图片名
nID_BITMAP=12;
//若图片来自Dll,它是图片在资源文件中的ID,若图片来自一个文件,任意设一个
值
nCorner=UPPER_LEFT;
//指定图片显示的位置,此处为左上角
SetDisplayEffect(EFF_FADE);//设置显示效果
PlaceBitmap(szBitmap, nID_BITMAP, 10, 10, nCorner);//在左上角(10,10)处显
示图片
四、创建程序组和快捷方式
要在SetupFolders函数中编写代码。创建如下图所示的程序组和快捷方式。
代码如下:
function SetupFolders()
STRING szProgram,szFolder;
begin
szFolder=″膝关节病案管理系统″;
CreateProgramFolder( szFolder );
ShowProgramFolder( szFolder, SW_SHOW );
szProgram=TARGETDIR ^ ″//DBSet.EXE″;
LongPathToQuote(szProgram, TRUE);//给命令行加上引号,因为路径中可能包含
空格
AddFolderIcon( szFolder,
″数据库访问设置″,
szProgram,
TARGETDIR,
″″,
0,
″″,
REPLACE);//创建快捷方式″数据库访问设置″
Delay(1);//延时1秒
szProgram= UNINST+′ -f″ ′+TARGETDIR ^ ′//Uninst.isu″ ′;
AddFolderIcon( szFolder,
″卸载病案管理系统″,
szProgram,
TARGETDIR,
″″,
0,
″″,
REPLACE ); //不能用LongPathToQuote给该程序项加引号,否则
//不能正常执行!
ReloadProgGroup (szFolder);//刷新程序组
Return 0;
end;
AddFolderIcon函数的功能是在它的第一个参数szFolder处插入快捷方式。
szFolder可以取如下值:
FOLDER_DESKTOP 在桌面创建快捷方式
FOLDER_STARTMENU在″开始″菜单中创建快捷方式
FOLDER_STARTUP 在″启动″组中创建快捷方式
FOLDER_PROGRAMS 在″程序″组中创建快捷方式
五、修改注册表
如果你的软件有一些设置(如是否有启动声音等)要保存在注册表中,就必须修改
注册表。注册表的修改是在SetupRegistry中完成的。
下面的代码演示了如何在注册表的 HKEY_LOCAL_MACHINE/SOFTWARE/Joint主键下创
建一个DWORD类型的标记Wav来记录Joint程序是否有启动声音。Wav值为1时,表示有启
动声音,为0时表示没有。
function SetupRegistry()
NUMBER nRootKey,nType;
STRING szKey,szClass;
begin
nRootKey=HKEY_LOCAL_MACHINE;
RegDBSetDefaultRoot(nRootKey);
szKey=″SOFTWARE//Joint
szClass=″″;
RegDBCreateKeyEx(szKey, szClass); ″;
nType = REGDB_NUMBER;
RegDBSetKeyValueEx(szKey, ″Wav″, nType, ″1″,-1);
return 0;
end;
用InstallShield制作专业Windows安装软件
苏贵洋
InstallShield可以说是软件安装的最好制作工具。其简单的操作、完全可视化的界面、方便的调试功能、多平台(Windows3.1, Windows95, WindowsNT3.5, Windows NT4.0 )语言(中文、各种西文)的支持使它几乎成为了安装制作的标准。难怪微软在它的拳头产品Visual C++4.X中开始,就以捆绑的形式包括了InstallShield3。而今,随着日月的推移,InstallShield更是羽翼丰满,又陆续推出了InstallShield Professional 5.X(包括East Edition,West Edition)和InstallShield 6.0,功能更为强大,操作更为简便。对于Shareware软件的制作者,包装上一个漂亮的安装程序,实在是非常必要。本文简要探讨一下用InstallShield制作安装软件的方法,希望对感兴趣的朋友有所帮助。
在InstallShield的各种最新版本中,其专门制作的East Edition,可应用于汉语(GB、BIG5)、英语、印度尼西亚语、日语、韩语、俄语、土耳其语、泰国语等多语种。本文便以InstallShield East Edition 5.1为探讨对象进行讨论。InstallShield的其它语种版本的操作和East Edition几乎完全相同。
进入InstallShield East Edition 5.1后便是两个“向导”面对你。
习惯用VB的朋友可以用VB5 Project Wizard,它需要你的系统中安装了VB5。
更为常用的是Project Wizard,它可以定制更为普便适用的安装程序。进入Project Wizard后如图1:在第一栏中写入你的程序的名字,第二栏中写入公司的名字,在第三栏中选择开发程序使用的根据,第四栏中选择程序的种类,最后一栏中填入程序的可执行文件的完整路径和名字。单击下一步,进入选择定制对话框:安装自己的喜好选择希望安装过程进行时出现的对话框,按Priview键可以预览一下相应对话框的效果。接下来是选择要安装目标平台。也即程序将被运行在什么平台之上。InstallShield共提供了四种平台供选择(Win31, win95, WinNT3.5, Windows NT4.0 )。然后是多语种支持,也就是安装时可以提供的语言支持。再向下依次是安装类型选择、指定构成成份、指定文件组,最后是所有选项的概括。完成选择后便可得到你自己定做的Project,进入InstallShield的可视化编程环境。
下面让我们一步一步地完成我们的制作。
整个InstallShield的可视化程度很高。支持鼠标右键的操作,在各个位置的鼠标右键可激活各种功能;编译、调试方便,简直是VC++这种大型程序的简写本,却又比VC++方便了许多。
一、在左面的选项中选择Scripts。
这是全部制作的核心部分,也是我们所最应集中精力攻克的小小关口。当然不更改Scripts中的源程序也能制作一个标准的安装程序,但如果能够控制Scripts的编程,整个安装程序的制作水准就可以达到一个新的高度,这也许是您与普通制作者区别的开始吧。其实InstallShield的Scripts非常类似于C和C++的程序编写,相信对大多数程序员来说是非常容易掌握的。而对于没有什么C和C++的基础的朋友来讲,看完这篇文章后,相信你也能很好地去制作自己的Scripts,因为它具有相当程度的重复性,更容易被“依葫芦画瓢”。
Scripts的布局同C语言非常类似。首先是头文件的包含,紧接着是字符串的定义、函数原形的说明、全局变量的说明。你可以做的是在此加上你的函数说明、全局变量的说明。
Scripts中遵循C的规则,同样有局部变量和定义。遵循C++的注释规则,可以用// 注释内容来进行单行注释,也可以用/* 注释内容 */来注释一块语句。所有的关键字、宏定义都以不同颜色的字体标出,非常直观。Scripts 的编程遵循结构化编程的思想,提供了大量类似Windows API的函数库供调用,每个宏定义都有详细的说明,每个函数除了详细地说明,解释每个变量的意义外,还都附有相应的例子,参考这些说明和例子,可以让新手在非常短的时间内上路。对用VC++开发过Windows程序的朋友来讲,简直就是一种6=2×3的转变而已,可以非常轻松地掌握。
在程序的最前段,是program…………………endprogram,它相当于C中的main() {……}主函数。后面是一个个子函数体的程序,排列顺序是按调用顺序排列。全部函数都非常简单易懂,并且一般不需太大改动。下面将几个常用、经常改动的函数加以说明。
(1) function SetupFolders()
function SetupFolders()
NUMBER nResult;
STRING szFolderDir;
begin
szProgramFolder = FOLDER_PROGRAMS^
SHELL_OBJECT_FOLDER; //建立程序组或快捷方式的位置
szItemName = "眼部图象多功能处理系统"; //应用程序名称
szProgram = TARGETDIR^’eye95.exe’; //可执行文件名
szParam = "";
LongPathToQuote (szProgram, TRUE);//合并路径
LongPathToShortPath (szParam);//转为短路径,专为Win31和WinNT3.5而用
szCommandLine = szProgram + " " + szParam; //生成命令行
szWorkingDir = TARGETDIR;//工作路径
szIconPath = "";
nIcon = 0;
szShortCutKey = "";
也 谈 用InstallShield 制 作 纯 中 文 化 的 安 装 程 序
[email protected]
---- 在 李 中 华 先 生 《 利 用InstallShield 5 英 文 版 开 发 纯 中 文 安 装 程 序 的 秘 密 》 一 文 中 谈 到 用InstallShield 5.0 制 作 中 文 版 的 安 装 程 序, 本 人 制 作 了 多 套 中 文 版 的 安 装 程 序, 也 谈 谈 用Install Shield 5.0 制 作 中 文 版 安 装 程 序。
---- Install Shield 允 许 定 制 对 话 框。 我 们 可 以 在Install Shield 项 目 中 加 入 自 己 定 制_Isuser.dll。_Isuser.dll 可 以 包 含 中 文 的 显 示 对 话 框。
---- 1。_Isuer.dll 的 获 得。
---- 在Install Shield 5.0 程 序 的 目 录 中, 有 一 个./custom dialog/VC++ 4 Project 目 录( 本 人 所 用 为VC 附 带 的Install Shield), 用VC 打 开 此 目 录 下 的 项 目 文 件, 对 资 源 编 辑。
---- a. 加 入 一 个 对 话 框, 指 明 其ID 号 为 想 要 改 变 的 对 话 框ID. 对 话 框ID 可 由
---- Install Shield 的tools 菜 单 下Dialog sampler 获 得。
---- 也 可 打 开Install Shield /Redistributable/Compressed Files/0009-English/
---- Intel 32 目 录 下_IsRes.dll,( 用Resuorce 方 式), 拷 贝 要 改 变 的 资 源 对 话 框 到 项 目 的 资 源 中, 改 其 语 言 为 中 文, 把 提 示 话 语 翻 成 中 文 即 可。
---- b. 对 要 改 变 的 对 话 框 都 操 作 一 遍。
---- c. 编 译, 生 成Release 版 的_Isuser.dll; 由 此, 获 得 了 自 己 的 提 示 对 话 框。 保 留, 可 作 为 今 后 制 作 安 装 程 序 使 用。
---- 2。 加 入_Isuer.dll 到InstallShield 项 目 中。
---- 在Install Shield 项 目 的Setup Files 项 中English->operating system independent 中 点 击 鼠 标 右 键, 弹 出 菜 单 中 选 择Insert Files, 把 制 作 好 的_Isuser.dll 加 入 进 来。
---- 3。 生 成 安 装 程 序
---- 制 作 好 其 他 的 安 装 信 息, 选 择Build /Media Build Wizard , 选 择 合 适 的 发 布 方 式, 生 成, 将 自 动 连 接_Isuser.dll, 安 装 程 序 的 编 译 将 把 定 制 的 信 息 加 入 到 安 装 程 序 中。
---- 中 文 化 的 安 装 程 序 即 制 成, 是 不 是 纯 中 文 的, 要 看_Isuser.dll 中 有 没 有 包 含 其 中 文 化 的 对 话 框 了。 一 般 地, 只 要 用 到 的 对 话 框 都" 汉 化" 了, 即 自 己 已 定 制 了 提 示, 就 是 纯 中 文 的 安 装 程 序 了。
软件安装专家:Setup Specialist
顾剑
软件设计者们都知道,一个安装程序设计的好坏,在很大程度上会影响使用者对该软件的印象和评价,因此,软件设计者们往往在对安装程序的设计上花很大工夫。但是,做出一个好的安装程序是不容易的。首先要符合Windows程序的安装标准,且要做到实用、美观、支持卸载。对很多软件编写者来讲,要做到这几点非常困难。而Setup Specialist就是针对这个问题设计的一个帮助软件编写者快速设计软件安装程序的工具。用户无须使用任何编程语言,只要根据选单一步步进行设置就可做出一个精美而专业的安装程序。
(一)、Setup Specialist功能简介
图一
Setup Specialist的界面如图1所示。共由选单栏、快捷工具栏、选项卡三部分组成。
Setup Specialist的主要功能是在选项卡中实现的。Setup Specialist共有11个选项卡,将11个选项卡从左至右一一加以填写,就可以完成安装程序的制作。选项卡的具体功能择要所述如下:
1、General
在此选项卡中,我们要完成产品名(Product Name)、软件版本号、软件设计商或软件公司(Company Name)名字的填写。此外还有一项Language,是安装界面使用的默认语言,有德语和英语两种选择。需要注意的是,这里选择仅是安装提示使用的语言,所以,当我们填写诸如"Product Name"、"Company Name"等栏目时,仍可使用中文。其它选项卡的栏目同样遵循这个原则。
在此卡片中还可对操作系统(Operating System)进行选择。Setup Specialist的功能很强,除了可以制作32位的ForWindows/Windows NT的安装程序外,还可以制作16位的For Windows 3.1/3.2的安装程序。用户可在这项中通过下拉选单选择要生成哪一位的安装程序。
2、Visual Design
此选项卡为视觉效果设计。
用户可在此填写安装程序的标题(Setup Title)。Setup Specialist提供了一种默认的常用名称,即"%CompanyName% %ProductName% Setup",即以"公司名+软件名"做标题。当然,你也可以按照例子,使用其它类型的标题。比如将标题设为"%ProductName% %ProductVersion% Setup",那么Setup程序的标题由软件的类型和版本号组成。
在安装程序中往往要给出一些通告,告知用户该软件的安装环境和最低硬件需求,这些信息就可在"Requirements"中填写,包括Operating Environment(运行环境)、Hardware(最低硬件要求)等。其中Hardware项目较多,择要说明如下:
System colors:显示色彩。有16色、256色、高彩、真彩几种选择。如果你的软件对颜色没有要求,可选择"No Requirements"。
Resolution:分辨率设置。如果你的程序对显示分辨率有要求,可在此下拉选单中点选,如果无此要求,可选"No Requirements"。
CPU:在此可选软件最低适用机型。有386、486、Pentium、PentiumII等项,如果该程序运行需要协处理器,请将"Math Coprocessor"一项复选上。
此外你希望你做好的安装程序到某一时间自动作废,不能再运行,请将"Enable expiration check"一项复选上,同时在"Expiration"栏中填写作废时间。
3、UI Components
软件的安装界面往往是由多个说明图组成,UI Components中提供了很多常用的说明图,用户可以根据需要对图片进行取舍,也可引用别的图片。UI Components中有6个默认选择的步骤画面,分别为:Welcome Message:(欢迎画面)、Choose Destination Location:(选择安装路径画面)、Select Program Folder:(软件安装完毕时是否弹出建立程序项选择画面)、Start Copying Files:(加入"开始复制文件"的画面)、Setup Complete:( "安装完毕"的画面)、Uninstall:(软件卸载画面)。除了以上几项之外,你还可以根据需要添加一些别的画面,比如:
Readme Information:文本说明画面,你可以在此项的"Setting"选项卡中选择Readme.txt文件的路径。
Verify Password:密码较验画面。如果你的安装程序设有密码,则必须加上此画面。至于密码的设置,可点击此项中的"Settings",在"Enter the password for this"中填写安装密码。
User Information:加入此图片,安装软件时将会弹出要求使用者填写个人资料的画面。如果你设计的软件要求联机注册,那么这项显然是不可或缺的。
Setup Type:安装类型选择画面。即允许用户选择典型安装、最简安装、最小化安装等安装形式。
Custom Setup:自定义安装的内容设置。
Billboard:公告牌设置。若点击此栏的"Settings",再点击"Add",可加入公告牌图片。
Software License Agreement:是否加入安装许可画面。
除了系统默认的画面外,用户还可以点击"Bitmap"选项卡,选择你自己制作的图片。
4、Files
此项将加入安装的目标文件,也就是你欲将其做成安装盘的原始文件。我们可以看到有在此选项卡中有两个选择:Main和AutoExpand。用户可先点选"Main",再将原始文件加入右侧的"Files"文件列表中。方法是先在Windows95的资源管理器中选定目标文件,然后用左键点住,将其拖至Setup Speciali
利 用InstallShield 5 英 文 版 开 发 纯 中 文 安 装 程 序 的 秘 密
江 苏 省 姜 堰 市 第 五 中 学 李 中 华
---- [ 编 注: 应 用 此 文 时, 应 该 注 意 一 下 软 件 版 权 的 问 题]
---- InstallShield 是 当 前 最 为 流 行 的 安 装 程 序 开 发 工 具, 其 所 生 成 的 安 装 界 面 和 安 装 过 程 已 为 大 多 数 用 户 所 熟 悉 并 接 受, 目 前 市 场 上 大 多 数 应 用 软 件 都 是 用 它 来 进 行 安 装 的。 而 且InstallShield 为 一 些 著 名 的 开 发 工 具 提 供 了 专 用 版 本, 如 InstallShield 5 for VC++、InstallShield 5 for VB、InstallShield 5 for Dehpi、InstallShield 5 for PowerBuilder 等 等, 各 个 专 用 版 本 之 间 基 本 上 没 有 太 大 的 区 别。
---- InstallShield 目 前 的 最 高 版 本 为5.0。 其 主 要 特 点 是 流 行 的 可 视 化 和Wizard( 向 导) 开 发, 并 拥 有 自 己 的 一 套 开 发 语 言。 但 是, 有 关 它 的 资 料 却 很 难 能 找 到, 市 场 上 也 没 有 有 关 它 的 书 籍。 其 开 发 环 境 为 英 文, 却 有 很 多 种 语 言 版 本 用 以 生 成 各 种 语 言 版 本 的 安 装 程 序, 主 要 是 以 几 个 文 件 来 实 现。 笔 者 手 中 的 版 本 是 与PowerBuilder 6.0 一 ?捆 绑 销 售 的InstallShield 5 for PowerBuilder 6.0 的 英 文 版 本。 在 用InstallShield 5 为PowerBuilder 应 用 程 序 制 作 安 装 盘 时 作 者 发 现 了 其 实 现 各 种 语 言 版 本 安 装 程 序 的 秘 密, 并 成 功 地 利 用 英 文 版 本 开 发 出 了 纯 中 文 的 安 装 程 序( 与《 金 山 词 霸》、《WPS 97》 的 安 装 程 序 一 模 一 样)。 笔 者 发 现 很 多 的 国 产 软 件 在 用InstallShield 制 作 安 装 程 序 时 总 不 能 完 美 地 实 现 中 文 界 面, 现 将 这 些 经 验 与 大 家 共 享, 主 要 面 向 那 些 对InstallShield 5 的 开 发 环 境 已 经 熟 悉 的 用 户。
---- 首 先 来 剖 析 一 下 其 安 装 界 面 生 成 的 秘 密。
---- InstallShield 5 将 一 些 通 用 的、 不 必 改 动 的 信 息( 如 版 权 警 告 信 息、 路 径 选 择 信 息、 换 盘 提 示 信 息 等) 做 好 并 放 在 动 态 链 接 库 文 件 中, 而 一 些 易 改 变 的 信 息 由 开 发 人 员 利 用 改 变 函 数 的 参 数 来 生 成。 因 此 对 于 那 些 放 在 动 态 链 接 库 文 件 中 的 通 用 显 示 信 息 无 法 通 过 编 程 来 控 制。 而 且, 即 便 那 些 能 够 改 变 的 信 息 是 中 文 时, 由 于 其 字 号 偏 小 显 得 相 当 难 看。
---- 实 际 上InstallShield 5 主 要 利 用 以 下 三 个 文 件 来 控 制 语 言 版 本:
setup.exe、_setup.dll、_isres.dll。
---- 其 中,setup.exe、_setup.dll 用 于 显 示 启 动 窗 口, 即 我 们 常 见 的 准 备 安 装 向 导 窗 口。 该 窗 口 在 没 有 起 动 画 面 时 显 示 于 屏 幕 正 中 央, 否 则 显 示 于 右 下 方。 准 备 安 装 向 导 窗 口 以 后 的 信 息 全 部 是 利 用_isres.dll 来 控 制 的。
---- 现 在 您 应 该 可 以 想 到: 如 果 我 将 以 上InstallShield 5 目 录 下 相 关 子 目 录 中 的 这 三 个 文 件 换 为 中 文 版 的( 从 一 些 成 功 的 中 文 安 装 程 序 中 找), 以 后 生 成 的 安 装 程 序 不 就 变 成 中 文 版 的 吗 ? 想 法 很 对, 然 而 完 成 起 来 却 不 那 么 简 单。
---- 在 以 前 的 版 本 中, 上 述 三 个 文 件 是 与 安 装 盘 一 同 发 布 的, 很 容 易 找 到。 然 而 到 了5.0 版 本 中, 您 也 许 只 能 发 现 头 两 个 文 件, 然 而 第 三 个 关 键 文 件( 其 大 多 数 内 容 均 在 其 内) 却 怎 么 也 找 不 着。 也 许 您 有5.0 以 前 的 版 本 的 中 文 版 的_isres.dll 文 件, 将 它 拷 过 来 替 换 原 有 文 件( 位 于InstallShield 5 安 装 目 录 下 的)/Redistributable/Compressed Files/0009-English/Intel 32( 英 文 版, 其 它 相 似) 下。 再 生 成 安 装 盘 就 行 了。 但 是 早 期 版 本 的 中 文 字 号 较 小, 不 美 观。 如 何 找 到 美 观 大 方( 如WPS 97、 金 山 词 霸 Ⅲ 等) 的5.0 版 本 的 动 态 链 接 库 文 件"_isres.dll" 呢 ?
---- InstallShield 5 生 成 安 装 盘 时 将_isres.dll 与 其 它 一 些 系 统 文 件 也 进 行 了 压 缩, 压 缩 文 件 为_sys1.cab。 该CAB 文 件 与Microsoft 的CAB 格 式 不 同, 无 法 利 用 一 些 解 通 用CAB 的 软 件 打 开。 安 装 程 序 运 行 时 将 它 打 开 使 用, 使 用 一 过 立 即 神 不 知 鬼 不 觉 地 将 它 删 除。 而 且_isres.dll 由 安 装 程 序 打 开 后 被 改 得 面 目 全 非, 一 般 人 极 难 发 觉。 它 究 竟 在 哪 儿 呢 ?
---- 熟 悉InstallShield 的 人 肯 定 会 想 到 当 然 位 于Windows 目 录 下 的Temp 目 录 下。 道 理 在 于 安 装 程 序 执 行 时 会 利 用 该 目 录 生 成 临 时 文 件。 原 来InstallShield 生 成 的 安 装 程 序
InstallShield脚本语言的编写
蒋燚
InstallShield脚 本 语 言 是 类 似 C语 言 , 利 用 InstallShield的 向 导 或 模 板 都 可 以 生 成 基 本 的 脚 本 程 序 框 架 , 可 以 在 此 基 础 上 按 自 己 的 意 愿 进 行 修 改 和 添 加 。
一 .基 本 语 法 规 则
1.变 量
BOOL 布 尔 型 值 为 TRUE(1)或 FALSE(0)
CHAR 字 符 型 一 字 节 长 的 (8bit)的 字 符
HWND 窗 口 句 柄 用 来 存 放 窗 口 句 柄
INT 整 型 两 字 节 长 的 整 数
LIST 列 表 型 指 向 InstallShield列 表 , 用 ListCreate和 ListDestroy
LONG 扩 展 数 值 型
LPSTR 扩 展 指 针
NUMBER 数 值 型 存 放 四 字 节 长 的 数 值 , 范 围 从 -2147483648到 +2147483647
POINTER 指 针 型
SHORT 短 数 值 型
STRING 字 符 串 型 十 分 类 似 VC中 的 LPCTSTR
变 量 如 同 标 准 的 C语 言 , 在 使 用 前 需 要 事 先 声 名 。 变 量 通 常 在 两 个 位 置 进 行 声 名 , 一 是 主 程 序 外 部 , 这 样 的 变 量 为 全 局 变 量 , 二 是 各 函 数 的 变 量 声 名 区 , 这 样 的 变 量 是 局 部 变 量 。
2.操 作 符
一 般 的 与 C语 言 相 同 操 作 符 , 在 这 里 不 做 详 解 , 以 下 主 要 介 绍 比 较 特 殊 的 操 作 符 ,
(1) + , - , * , /
以 上 四 个 操 作 符 与 C语 言 中 意 义 和 用 法 都 相 同 。
(2) &&
与 操 作 , 与 C语 言 中 用 法 相 同 , 例 : x1 && x2
(3) ||
或 操 作 , 与 C语 言 中 用 法 相 同 , 例 : x1 || x2
(4) !
非 操 作 , 与 C语 言 中 用 法 相 同 , 例 : !x1
(5) *
指 针 操 作 , 类 似 C语 言 中 的 *
(6) & , | , ^ , ~ , << , >>
分 别 为 位 与 , 位 或 , 按 位 异 或 , 按 位 取 反 , 左 移 和 右 移 , 其 意 义 和 用 法 都 与 C语 言 中 基 本 相 同 。
(7) .
该 操 作 符 用 于 结 构 , 用 来 得 到 结 构 的 子 项 , 与 Delphi的 . 用 法 类 似 , 例 如 :
typedef SETTINGSREC
begin
BOOL bSwitchOn;
STRING szMssg[255];
INT nVal;
end;
SETTINGSREC settings;
program
settings.bSwitchOn = FALSE;
settings.szMssg = "Off";
settings.nVal = 0;
(8) =
既 可 作 为 赋 值 号 , 同 时 也 做 等 于 符 , 例 如 :
str1 = "String";
if str1="String" then
endif;
(9) &
取 地 址 符 , 与 C语 言 用 法 类 似 。
(10) < , > , = , <= , >= , !=
分 别 表 示 小 于 , 大 于 , 等 于 , 小 于 等 于 , 大 于 等 于 , 不 等 于
(11) + , ^ , %
用 于 字 符 串 的 操 作 。
(12) ->
结 构 指 针 , 与 C语 言 中 用 法 类 似 。
(13) @
用 于 得 到 Resource窗 口 中 定 义 的 字 符 串 , 例 :
szReferenceFile = svDir ^ @PRODUCT_KEY;
3.函 数
InstallShield的 函 数 使 用 前 同 样 需 要 声 名 , 函 数 的 参 数 传 递 方 式 十 分 类 似 C语 言 , 例 如 下 面 的 函 数 声 名 :
prototype HandleMoveDataError( NUMBER );
该 声 名 中 函 数 名 为 HandleMoveDataError, 传 递 一 个 NUMBER类 型 的 参 数 。 调 用 该 函 数 时 也 基 本 与 C语 言 中 相 同 。
函 数 体 的 标 准 格 式 为 :
function functionname(nResult)
// 函 数 变 量 声 名 区
begin
// 程 序 区
end;
通 常 的 函 数 返 回 一 个 NUMBER型 的 数 。
4.主 程 序 结 构
主 程 序 以 program开 始 , 以 endprogram结 尾 ,
二 .框 架 程 序 的 基 本 结 构
程 序 开 始 为 函 数 与 变 量 的 声 明 区
通 过 向 导 建 立 的 框 架 程 序 包 含 以 下 一 些 主 函 数 :
prototype ShowDialogs();
// 显 示 安 装 向 导 对 话 框
prototype MoveFileData();
// 移 动 文 件 数 据
prototype HandleMoveDataError( NUMBER );
// 移 动 数 据 出 错 处 理
prototype ProcessBeforeDataMove();
// 移 动 文 件 数 据 前 的 处 理
prototype ProcessAfterDataMove();
// 移 动 文 件 数 据 后 的 处 理
prototype SetupRegistry();
// 安 装 注 册 , 用 户 可 在 此 加 入 一 些 代 码 , 通 常 用 于 对 注 册 表 的 操 作
在VC中提供了两种很方便的编辑控件(CEdit 和CRichEditCtrl),一般来说这两种控件已经满足了我们大部分的需要,不过只有CEdit控件能响应我们鼠标右键消息,通过右键我们很容易的操作我们的编辑,而在CRichEditCtrl控件中我们不能得到这样的操作,同时CRichEditCtrl是能够包含各种格式的内容,就好像Word一样能够写入各种不同的字体,不过CRichEditCtrl没有给我们提供这样的要求,今天我的目的也就是给他添加这样的扩展操作。
本代码运行效果图
一. 首先我们要从CRichEditCtrl类派生一个我们自己的类CMyRichEdit,我们的操作也是在其中完成的。
二. 然后我们添加鼠标右键消息,在其中添加代码如下:
//设置为焦点
SetFocus();
//创建一个弹出式菜单
CMenu popmenu;
popmenu.CreatePopupMenu();
//添加菜单项目
popmenu.AppendMenu(0, ID_RICH_UNDO, "&Undo");
popmenu.AppendMenu(0, MF_SEPARATOR);
popmenu.AppendMenu(0, ID_RICH_CUT, "&Cut");
popmenu.AppendMenu(0, ID_RICH_COPY, "C&opy");
popmenu.AppendMenu(0, ID_RICH_PASTE, "&Paste");
popmenu.AppendMenu(0, ID_RICH_CLEAR, "C&lear");
popmenu.AppendMenu(0, MF_SEPARATOR);
popmenu.AppendMenu(0, ID_RICH_SELECTALL, "Select &All");
popmenu.AppendMenu(0, MF_SEPARATOR);
popmenu.AppendMenu(0, ID_RICH_SETFONT, "Select &Font");
//初始化菜单项
UINT nUndo=(CanUndo() ? 0 : MF_GRAYED );
popmenu.EnableMenuItem(ID_RICH_UNDO, MF_BYCOMMAND|nUndo);
UINT nSel=((GetSelectionType()!=SEL_EMPTY) ? 0 : MF_GRAYED) ;
popmenu.EnableMenuItem(ID_RICH_CUT, MF_BYCOMMAND|nSel);
popmenu.EnableMenuItem(ID_RICH_COPY, MF_BYCOMMAND|nSel);
popmenu.EnableMenuItem(ID_RICH_CLEAR, MF_BYCOMMAND|nSel);
UINT nPaste=(CanPaste() ? 0 : MF_GRAYED) ;
popmenu.EnableMenuItem(ID_RICH_PASTE, MF_BYCOMMAND|nPaste);
//显示菜单
CPoint pt;
GetCursorPos(&pt);
popmenu.TrackPopupMenu(TPM_RIGHTBUTTON, pt.x, pt.y, this);
popmenu.DestroyMenu();
三. 然后在.h文件中加入如下的ID定义:
#define ID_RICH_UNDO 101
#define ID_RICH_CUT 102
#define ID_RICH_COPY 103
#define ID_RICH_PASTE 104
#define ID_RICH_CLEAR 105
#define ID_RICH_SELECTALL 106
#define ID_RICH_SETFONT 107
不过这些值还可以通过VC++编译器中的菜单View->Resource Symbols进行添加。 四. 添加消息相应操作,由于这些ID是我们自己定义的,所以我们只能手动添加:
1.在头文件中添加:
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnCopy() { Copy(); }
afx_msg void OnCut() { Cut(); }
afx_msg void OnPaste() { Paste(); }
afx_msg void OnSelectall() { SetSel(0, -1); }
afx_msg void OnUndo() { Undo(); }
afx_msg void OnClear() { Clear(); }
afx_msg void OnSelectfont();//改变字体
2.在实现文件的消息映射宏中添加如下:
ON_COMMAND(ID_RICH_COPY, OnCopy)
ON_COMMAND(ID_RICH_CUT, OnCut)
ON_COMMAND(ID_RICH_PASTE, OnPaste)
ON_COMMAND(ID_RICH_SELECTALL, OnSelectall)
ON_COMMAND(ID_RICH_UNDO, OnUndo)
ON_COMMAND(ID_RICH_CLEAR, OnClear)
ON_COMMAND(ID_RICH_SETFONT, OnSelectfont)
3.最后添加字体变换函数:
CHARFORMAT cf;
LOGFONT lf;
memset(&cf, 0, sizeof(CHARFORMAT));
memset(&lf, 0, sizeof(LOGFONT));
//判断是否选择了内容
BOOL bSelect = (GetSelectionType() != SEL_EMPTY) ? TRUE : FALSE;
if (bSelect)
{
GetSelectionCharFormat(cf);
}
else
{
GetDefaultCharFormat(cf);
}
//得到相关字体属性
BOOL bIsBold = cf.dwEffects & CFE_BOLD;
BOOL bIsItalic = cf.dwEffects & CFE_ITALIC;
BOOL bIsUnderline = cf.dwEffects & CFE_UNDERLINE;
BOOL bIsStrickout = cf.dwEffects & CFE_STRIKEOUT;
//设置属性
lf.lfCharSet = cf.bCharSet;
lf.lfHeight = cf.yHeight/15;
lf.lfPitchAndFamily = cf.bPitchAndFamily;
lf.lfItalic = bIsItalic;
lf.lfWeight = (bIsBold ? FW_BOLD : FW_NORMAL);
lf.lfUnderline = bIsUnderline;
lf.lfStrikeOut = bIsStrickout;
sprintf(lf.lfFaceName, cf.szFaceName);
CFontDialog dlg(&lf);
dlg.m_cf.rgbColors = cf.crTextColor;
if (dlg.DoModal() == IDOK)
{
dlg.GetCharFormat(cf);//获得所选字体的属性
if (bSelect)
SetSelectionCharFormat(cf); //为选定的内容设定所选字体
else
SetWordCharFormat(cf); //为将要输入的内容设定字
向导是一种用来简化用户操作的程序。在Microsoft 的所有产品中都存在向导,如Office2000 中的Web 页向导就是一个十分典型的向 导(如下图所示),还有常用的VC++向导。
一个基本的向导程序应该包含以下几个基本按钮: 取消、上一步、下一步、完成、帮助。
一、标准向导程序
在 VC++中,可以使用类CPropertySheet和类CPropertyPage方便地编写一个向导程序。
首先我们来介绍一下类CPropertySheet 和类CPropertyPage。
1. 类CPropertyPage 是从CDiaglog中派生出来的,具有Diaglog的基本性质,需要注意的是它的样式必须是Child。
2. 类CPropertySheet 是一个属性表,也是一个窗体,相当一个容器,用来存放所有的CpropertyPage。它不是 从CDialog 派生出来的,但是它可以象普通对话框类似的操作, 如DoModal(),当用 DoModal()显示 后,它就包含了“取消”、“上一步”、“下一步” 等基本按钮。
下面给出一个实例
① 新建一个 VC++ MFC AppWizard 工程,命名为TraditionalWizard,并选择Dialog Based 样式。
② 在自动生成 的Dialog 资源中加入一个按钮IDC_BENGINWIZ 用来启动向导。
③ 创建 CPropertyPage。新建Dialog 资源,命名为IDD_STEP1,注意一定要将新建对话框的Style属性设置成Child 和边界属性设置为Thin,并且不要生成一个新类。
用ClassWizard 生成一个新类,命名为CStep1,基类为CPropertyPage,且将Dialog ID 设置为刚生成的资源IDD_STEP1。这样就生成了一个新属性页Step1。如此操作就可以 同样生成Step2、Step3 属性页。为了方便显示,在每个对话框都放置了一个控件,用来表示当前是哪一步。
④ 创建 CPropertySheet。新建一个类,命名为CWizard,基类为CPropertySheet。并将属性页和属性表关联起来。代码为
//将代码放在按钮IDC_BEGINWIZ的Click事件中
CWizard MyWizard(_T("我的向导 "),this,1); //生成一个属性表
CStep1 MyStep1; //属性页1
CStep2 MyStep2; //属性页2
CStep3 MyStep3; //属性页3
MyWizard.AddPage(&MyStep1); //添加属性页1
MyWizard.AddPage(&MyStep2); //添加属性页2
MyWizard.AddPage(&MyStep3); //添加属性页3
MyWizard.SetWizardMode(); //将属性表设置成向导样式
MyWizard.SetActivePage(&MyStep1); //设置第一页为第一步
MyWizard.DoModal(); //显示属性表
⑤协调显示。在每一页为当前页时,都会触发OnSetActive事件,故对每一个属性页都要重载该函数,在CStep1类上选择Add Virtual Function ...。因为显示第一页时,不存在“上一步”,故在CStep1的 OnSetActive函数中需要添加如下代码:
//代码放在OnSetActive函数中
CPropertySheet* pParent=(CPropertySheet*)GetParent(); // 获得属性表的指针
pParent->SetWizardButtons(PSWIZB_NEXT); // 设置属性表的显示按钮只为下一步
SetDlgItemText(IDC_TEXT1,"这是向导的第一步");
同样在显示中间页时应该设置成即有“上一步”,也有“下一步”,代码为:
CPropertySheet* pParent=(CPropertySheet*)GetParent();
pParent->SetWizardButtons(PSWIZB_NEXT|PSWIZB_BACK);
SetDlgItemText(IDC_TEXT2,"这是向导的第二步");
最后在显示最后一页时只显示“完成”和“上一步”,代码为:
CPropertySheet* pParent=(CPropertySheet*)GetParent();
pParent->SetWizardButtons(PSWIZB_FINISH|PSWIZB_BACK);
SetDlgItemText(IDC_TEXT3,"这是向导的第三步");
这样一个基本的向导程序就完成了,其效果如图所示
二、自定义向导程序
通过上面的例子,我们不难发现标准的向导基本能满足要求,但仍然存在一些缺陷:
1.不能改变向导按钮的样式,如想在“上一步”、“下一步就”按钮上添加图标
2.不能象上面的Web向导一样有个“完成”按钮进行默认设置
3.不能修改向导按钮的位置
上述缺陷是因为我们采用了CPropertySheet类,而CPropertySheet类不是一个可修改的资源。
为了达到个性化向导的目的,我们可以不使用CPropertySheet类和CPropertyPage类。
设计的基本思路:
1. 采用标准的向导的工作方式。每一步就是一个对话框,向导本身也是一个对话框,用来容纳每步对话框.
2. 每步的对话框应 该没有Title、没有边界、样式为Child,当点击“下一步”或“上一步”时,将这个 对话框定位到要显示的位置。
3. 因为向导一般都包含很多步,为了管理这些页,我们可以创建一个链表来管理每一步的对话框。
4. 为了方便对话框定位,可以事先定义好位置。
三、自定义向导的实现
1. 工程的建立与基本界面的生成
生成一个MFC APPWIZARD 新工程,命名为CustomWizard,在Step1 中选择基于Dialog Based样式。
在自动生成的Dialog 资源中加入一个按钮IDC_BENGINWIZ 用来启动向导。
新建一个对话框 资源,命名为IDC_WIZARD,用来显示自定义向导界面,如图
依次创建向导的每页 的对话框资源,命名为IDD_STEP1,IDD_STEP2,IDD_STEP3,
(图4)
2. 生成所需要的类
为了方便叙述,表1将所用的类进行了归纳
(表1)
类名 基类
树型控件用来显示具有一定层次结构的数据项时方便、直观,被广泛地应用在各种软件中,如资源管理器中的磁盘目录就用的是树型
控件,我们在编程中也会经常用到,但 MFC 中提供的 CTreeCtrl 类并不直接支持拖动节点等高级特性,这使我们程序员编程时有很大限
制,又给软件用户带来了一些不便。下面就让我们自己动手来解决这个问题,实现树型控件中节点的拖动。
FrameRect 用指定的刷子围绕一个矩形画一个边框
GdiComment 为指定的增强型图元文件设备场景添加一条注释信息
GdiFlush 执行任何未决的绘图操作
GdiGetBatchLimit 判断有多少个GDI绘图命令位于队列中
GdiSetBatchLimit 指定有多少个GDI绘图命令能够进入队列
GetArcDirection 画圆弧的时候,判断当前采用的绘图方向
GetBkColor 取得指定设备场景当前的背景颜色
GetBkMode 针对指定的设备场景,取得当前的背景填充模式
GetBrushOrgEx 判断指定设备场景中当前选定刷子起点
GetCurrentObject 获得指定类型的当前选定对象
GetCurrentPositionEx 在指定的设备场景中取得当前的画笔位置
GetEnhMetaFile 取得磁盘文件中包含的一个增强型图元文件的图元文件句柄
GetEnhMetaFileBits 将指定的增强型图元文件复制到一个内存缓冲区里
GetEnhMetaFileDescription 返回对一个增强型图元文件的说明
GetEnhMetaFileHeader 取得增强型图元文件的图元文件头
GetEnhMetaFilePaletteEntries 取得增强型图元文件的全部或部分调色板
GetMetaFile 取得包含在一个磁盘文件中的图元文件的图元文件句柄
GetMetaFileBitsEx 将指定的图元文件复制到一个内存缓冲区
GetMiterLimit 取得设备场景的斜率限制(Miter)设置
GetNearestColor 根据设备的显示能力,取得与指定颜色最接近的一种纯色
GetObjectAPI 取得对指定对象进行说明的一个结构
GetObjectType 判断由指定句柄引用的GDI对象的类型
GetPath 取得对当前路径进行定义的一系列数据
GetPixel 在指定的设备场景中取得一个像素的RGB值
GetPolyFillMode 针对指定的设备场景,获得多边形填充模式
GetROP2 针对指定的设备场景,取得当前的绘图模式
GetStockObject 取得一个固有对象(Stock)
GetSysColorBrush 为任何一种标准系统颜色取得一个刷子
GetWinMetaFileBits 通过在一个缓冲区中填充用于标准图元文件的数据,将一个增强型图元文件转换成标准windows图元文件
InvertRect 通过反转每个像素的值,从而反转一个设备场景中指定的矩形
LineDDA 枚举指定线段中的所有点
LineTo 用当前画笔画一条线,从当前位置连到一个指定的点
MoveToEx 为指定的设备场景指定一个新的当前画笔位置
PaintDesk 在指定的设备场景中描绘桌面墙纸图案
PathToRegion 将当前选定的路径转换到一个区域里
Pie 画一个饼图
PlayEnhMetaFile 在指定的设备场景中画一个增强型图元文件
PlayEnhMetaFileRecord 回放单独一条增强型图元文件记录
PlayMetaFile 在指定的设备场景中回放一个图元文件
PlayMetaFileRecord 回放来自图元文件的单条记录
PolyBezier 描绘一条或多条贝塞尔(Bezier)曲线
PolyDraw 描绘一条复杂的曲线,由线段及贝塞尔曲线组成
Polygon 描绘一个多边形
Polyline 用当前画笔描绘一系列线段
PolyPolygon 用当前选定画笔描绘两个或多个多边形
PolyPolyline 用当前选定画笔描绘两个或多个多边形
Rectangle 用当前选定的画笔描绘矩形,并用当前选定的刷子填充
RoundRect 用当前选定的画笔画一个圆角矩形,并用当前选定的刷子在其中填充
selectClipPath 将设备场景当前的路径合并到剪切区域里
selectObject 为当前设备场景选择图形对象
SetArcDirection 设置圆弧的描绘方向
SetBkColor 为指定的设备场景设置背景颜色
SetBkMode 指定阴影刷子、虚线画笔以及字符中的空隙的填充方式
SetBrushOrgEx 为指定的设备场景设置当前选定刷子的起点
SetEnhMetaFileBits 用指定内存缓冲区内包含的数据创建一个增强型图元文件
SetMetaFileBitsEx 用包含在指定内存缓冲区内的数据结构创建一个图元文件
SetMiterLimit 设置设备场景当前的斜率限制
SetPixel 在指定的设备场景中设置一个像素的RGB值
SetPixelV 在指定的设备场景中设置一个像素的RGB值
SetPolyFillMode 设置多边形的填充模式
SetROP2 设置指定设备场景的绘图模式。与vb的DrawMode属性完全一致
SetWinMetaFileBits 将一个标准Windows图元文件转换成增强型图元文件
StrokeAndFillPath 针对指定的设备场景,关闭路径上打开的所有区域
StrokePath 用当前画笔描绘一个路径的轮廓。打开的图形不会被这个函数关闭
UnrealizeObject 将一个刷子对象选入设备场景之前,如刷子的起点准备用SetBrushOrgEx修改,则必须先调用本函数
WidenPath 根据选定画笔的宽度,重新定义当前选定的路径
9. API之设备场景函数
CombineRgn 将两个区域组合为一个新区域
CombineTransform 驱动世界转换。它相当于依顺序进行两次转换
createCompatibleDC 创建一个与特定设备场景一致的内存设备场景
createDC 为专门设备创建设备场景
createEllipticRgn 创建一个椭圆
createEllipticRgnIndirect 创建一个内切于特定矩形的椭圆区域
createIC 为专用设备创建一个信息场景
createPolygonRgn 创建一个由一系列点围成的区域
createPolyPolygonRgn 创建由多个多边形构成的区域。每个多边形都应是封闭的
createRectRgn 创建一
1. API之网络函数
WNetAddConnection 创建同一个网络资源的永久性连接
WNetAddConnection2 创建同一个网络资源的连接
WNetAddConnection3 创建同一个网络资源的连接
WNetCancelConnection 结束一个网络连接
WNetCancelConnection2 结束一个网络连接
WNetCloseEnum 结束一次枚举操作
WNetConnectionDialog 启动一个标准对话框,以便建立同网络资源的连接
WNetDisconnectDialog 启动一个标准对话框,以便断开同网络资源的连接
WNetEnumResource 枚举网络资源
WNetGetConnection 获取本地或已连接的一个资源的网络名称
WNetGetLastError 获取网络错误的扩展错误信息
WNetGetUniversalName 获取网络中一个文件的远程名称以及/或者UNC(统一命名规范)名称
WNetGetUser 获取一个网络资源用以连接的名字
WNetOpenEnum 启动对网络资源进行枚举的过程
2. API之消息函数
BroadcastSystemMessage 将一条系统消息广播给系统中所有的顶级窗口
GetMessagePos 取得消息队列中上一条消息处理完毕时的鼠标指针屏幕位置
GetMessageTime 取得消息队列中上一条消息处理完毕时的时间
PostMessage 将一条消息投递到指定窗口的消息队列
PostThreadMessage 将一条消息投递给应用程序
RegisterWindowMessage 获取分配给一个字串标识符的消息编号
ReplyMessage 答复一个消息
SendMessage 调用一个窗口的窗口函数,将一条消息发给那个窗口
SendMessageCallback 将一条消息发给窗口
SendMessageTimeout 向窗口发送一条消息
SendNotifyMessage 向窗口发送一条消息
3. API之文件处理函数
CloseHandle 关闭一个内核对象。其中包括文件、文件映射、进程、线程、安全和同步对象等
CompareFileTime 对比两个文件的时间
CopyFile 复制文件
createDirectory 创建一个新目录
createFile 打开和创建文件、管道、邮槽、通信服务、设备以及控制台
createFileMapping 创建一个新的文件映射对象
deleteFile 删除指定文件
DeviceIoControl 对设备执行指定的操作
DosDateTimeToFileTime 将DOS日期和时间值转换成一个 win32 FILETIME 值
FileTimeToDosDateTime 将一个 win32 FILETIME 值转换成DOS日期和时间值
FileTimeToLocalFileTime 将一个FILETIME结构转换成本地时间
FileTimeToSystemTime 根据一个FILETIME结构的内容,装载一个SYSTEMTIME结构
FindClose 关闭由FindFirstFile函数创建的一个搜索句柄
FindFirstFile 根据文件名查找文件
FindNextFile 根据调用FindFirstFile函数时指定的一个文件名查找下一个文件
FlushFileBuffers 针对指定的文件句柄,刷新内部文件缓冲区
FlushViewOfFile 将写入文件映射缓冲区的所有数据都刷新到磁盘
GetBinaryType 判断文件是否可以执行
GetCompressedFileSize 判断一个压缩文件在磁盘上实际占据的字节数
GetCurrentDirectory 在一个缓冲区中装载当前目录
GetDiskFreeSpace 获取与一个磁盘的组织有关的信息,以及了解剩余空间的容量
GetDiskFreeSpaceEx 获取与一个磁盘的组织以及剩余空间容量有关的信息
GetDriveType 判断一个磁盘驱动器的类型
GetExpandedName 取得一个压缩文件的全名
GetFileAttributes 判断指定文件的属性
GetFileInformationByHandle 这个函数提供了获取文件信息的一种机制
GetFileSize 判断文件长度
GetFileTime 取得指定文件的时间信息
GetFileType 在给出文件句柄的前提下,判断文件类型
GetFileVersionInfo 从支持版本标记的一个模块里获取文件版本信息
GetFileVersionInfoSize 针对包含了版本资源的一个文件,判断容纳文件版本信息需要一个多大的缓冲区
GetFullPathName 获取指定文件的完整路径名
GetLogicalDrives 判断系统中存在哪些逻辑驱动器字母
GetLogicalDriveStrings 获取一个字串,其中包含了当前所有逻辑驱动器的根驱动器路径
GetOverlappedResult 判断一个重叠操作当前的状态
GetPrivateProfileInt 为初始化文件(.ini文件)中指定的条目获取一个整数值
GetPrivateProfileSection 获取指定小节(在.ini文件中)所有项名和值的一个列表
GetPrivateProfileString 为初始化文件中指定的条目取得字串
GetProfileInt 取得win.ini初始化文件中指定条目的一个整数值
GetProfileSection 获取指定小节(在win.ini文件中)所有项名和值的一个列表
GetProfileString 为win.ini初始化文件中指定的条目取得字串
GetShortPathName 获取指定文件的短路径名
GetSystemDirectory 取得Windows系统目录(即System目录)的完整路径名
GetTempFileName 这个函数包含了一个临时文件的名字,它可由应用程序使用
GetTempPath 获取为临时文件指定的路径
GetVolumeInformation 获取与一个磁盘卷有关的信息
GetWindowsDirectory 获取Windows目录的完整路径名
hread 参考lread
hwrite 参考lwrite函数
lclose 关闭指定的文件
lcreat 创建一个文件
llseek 设置文件中进行读写的当前位置
LockFile 锁定文件的某一部分,使
作者:xujian
本文适合初学者
前言
在用VC编程时,界面制作远不如Delphi、VB容易。我又常常用到基于Dialog编写应用程序。而在直接在Dialog使用Toolbar和Menu的资料很少。而我有机会可以总结一些经验,供大家分享,希望能得到指教。
下载本文示例工程 大小:11.2K
运行效果如下图
我们先建立一个基于Dialog 的程序,我给他起了个名字叫:DlgMenuToolbar。
一、如何往基于Dialog的程序添加菜单
[1.1] 先添加菜单(IDR_MENU1)资源,并加上需要的菜单项。
[1.2] 编辑对话框资源IDD_DLGMENUTOOLBAR_DIALOG的属性,在属性对话框中选择IDR_MENU1即可。
[1.3] 假如您不希望在对话框属性中直接设置菜单,而通过代码在程序中动态生成可以采用如下方法:
[1.3.1]在CDlgMenuToolbarDlg类声名中添加成员变量CMenu m_menu
再在CDlgMenuToolbarDlg::OnInitDialog() 中添加如下代码: //加载菜单
m_menu.LoadMenu(IDR_MENU1);
//设置当前菜单
SetMenu(&m_menu);
//当你不需要菜单时可以用 SetMenu(NULL);来取消当前菜单
二、如何往基于Dialog的程序添加工具栏
[2.1] 先添加工具栏(IDR_TOOLBAR1)资源,并画好各个按钮。
[2.2] 在CDlgMenuToolbarDlg类声名中添加成员变量 CToolBar m_wndtoolbar;
[2.3] 在CDlgMenuToolbarDlg::OnInitDialog() 中添加如下代码 //添加一个平面工具条
if (!m_wndtoolbar.CreateEx( this,TBSTYLE_FLAT , WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS,
CRect(4,4,0,0)) || !m_wndtoolbar.LoadToolBar(IDR_TOOLBAR1) )
{
TRACE0("failed to create toolbar/n");
return FALSE;
}
m_wndtoolbar.ShowWindow(SW_SHOW);
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
三、为工具栏添加工具提示
[3.1] 在CDlgMenuToolbarDlg类定义中手工添加消息映射函数的定义,如下黑体部分 //{{AFX_MSG(CDlgMenuToolbarDlg)
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg BOOL OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
[3.2] 在CDlgMenuToolbarDlg.cpp添加函数的实现代码 //工具栏提示
BOOL CDlgMenuToolbarDlg::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
{
ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);
// UNICODE消息
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
//TCHAR szFullText[512];
CString strTipText;
UINT nID = pNMHDR->idFrom;
if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
{
// idFrom为工具条的HWND
nID = ::GetDlgCtrlID((HWND)nID);
}
if (nID != 0) //不为分隔符
{
strTipText.LoadString(nID);
strTipText = strTipText.Mid(strTipText.Find(’’/n’’,0)+1);
#ifndef _UNICODE
if (pNMHDR->code == TTN_NEEDTEXTA)
{
lstrcpyn(pTTTA->szText, strTipText, sizeof(pTTTA->szText));
}
else
{
_mbstowcsz(pTTTW->szText, strTipText, sizeof(pTTTW->szText));
}
#else
if (pNMHDR->code == TTN_NEEDTEXTA)
{
_wcstombsz(pTTTA->szText, strTipText,sizeof(pTTTA->szText));
}
else
{
lstrcpyn(pTTTW->szText, strTipText, sizeof(pTTTW->szText));
}
#endif
*pResult = 0;
// 使工具条提示窗口在最上面
::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,SWP_NOACTIVATE|
SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
return TRUE;
}
return TRUE;
}
[3.3] 在CDlgMenuToolbarDlg.cpp中添加消息映射,请看如下代码中的黑体部分 BEGIN_MESSAGE_MAP(CDlgMenuToolbarDlg, CDialog)
//{{AFX_MSG_MAP(CDlgMenuToolbarDlg)
ON_WM_PAINT()
ON_NOTIFY_EX( TTN_NEEDTEXT, 0, OnToolTipText )
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
四、其它
为了使你的程序看起来更酷,还可以在CDlgMenuToolbarDlg::OnPaint()中修改代码实现Dialog 填充颜色。
CPaintDC dc(this);
CRect rect;
GetClientRect(rect);
dc.FillSolidRect(rect, RGB(60,110,170));
好了,编译运行看看效果吧!希望本文能够给您一些提示!
资源的限制条件
在设计任何服务器应用程序时,其强健性是主要的目标。也就是说,
你的应用程序要能够应对任何突发的问题,例如并发客户请求数达到峰值、可用内存临时出现不足、以及其它短时间的现象。这就要求程序的设计者注意Windows NT和2000系统下的资源限制条件的问题,从容地处理突发性事件。
你可以直接控制的、最基本的资源就是网络带宽。通常,使用用户数据报协议(UDP)的应用程序都可能会比较注意带宽方面的限制,以最大限度地减少包的丢失。然而,在使用TCP连接时,服务器必须十分小心地控制好,防止网络带宽过载超过一定的时间,否则将需要重发大量的包或造成大量连接中断。关于带宽管理的方法应根据不同的应用程序而定,这超出了本文讨论的范围。
虚拟内存的使用也必须很小心地管理。通过谨慎地申请和释放内存,或者应用lookaside lists(一种高速缓存)技术来重新使用已分配的内存,将有助于控制服务器应用程序的内存开销(原文为“让服务器应用程序留下的脚印小一点”),避免操作系统频繁地将应用程序申请的物理内存交换到虚拟内存中(原文为“让操作系统能够总是把更多的应用程序地址空间更多地保留在内存中”)。你也可以通过SetWorkingSetSize()这个Win32 API让操作系统分配给你的应用程序更多的物理内存。
在使用Winsock时还可能碰到另外两个非直接的资源不足情况。一个是被锁定的内存页面的极限。如果你把AFD.SYS的缓冲关闭,当应用程序收发数据时,应用程序缓冲区的所有页面将被锁定到物理内存中。这是因为内核驱动程序需要访问这些内存,在此期间这些页面不能交换出去。如果操作系统需要给其它应用程序分配一些可分页的物理内存,而又没有足够的内存时就会发生问题。我们的目标是要防止写出一个病态的、锁定所有物理内存、让系统崩溃的程序。也就是说,你的程序锁定内存时,不要超出系统规定的内存分页极限。
在Windows NT和2000系统上,所有应用程序总共可以锁定的内存大约是物理内存的1/8(不过这只是一个大概的估计,不是你计算内存的依据)。如果你的应用程序不注意这一点,当你的发出太多的重叠收发调用,而且I/O没来得及完成时,就可能偶尔发生ERROR_INSUFFICIENT_RESOURCES的错误。在这种情况下你要避免过度锁定内存。同时要注意,系统会锁定包含你的缓冲区所在的整个内存页面,因此缓冲区靠近页边界时是有代价的(译者理解,缓冲区如果正好超过页面边界,那怕是1个字节,超出的这个字节所在的页面也会被锁定)。
另外一个限制是你的程序可能会遇到系统未分页池资源不足的情况。所谓未分页池是一块永远不被交换出去的内存区域,这块内存用来存储一些供各种内核组件访问的数据,其中有的内核组件是不能访问那些被交换出去的页面空间的。Windows NT和2000的驱动程序能够从这个特定的未分页池分配内存。
当应用程序创建一个套接字(或者是类似的打开某个文件)时,内核会从未分页池中分配一定数量的内存,而且在绑定、连接套接字时,内核又会从未分页池中再分配一些内存。当你注意观察这种行为时你将发现,如果你发出某些I/O请求时(例如收发数据),你会从未分页池里再分配多一些内存(比如要追踪某个待决的I/O操作,你可能需要给这个操作添加一个自定义结构,如前文所提及的)。最后这就可能会造成一定的问题,操作系统会限制未分页内存的用量。
在Windows NT和2000这两种操作系统上,给每个连接分配的未分页内存的具体数量是不同的,未来版本的Windows很可能也不同。为了使应用程序的生命期更长,你就不应该计算对未分页池内存的具体需求量。
你的程序必须防止消耗到未分页池的极限。当系统中未分页池剩余空间太小时,某些与你的应用程序毫无关系的内核驱动就会发疯,甚至造成系统崩溃,特别是当系统中有第三方设备或驱动程序时,更容易发生这样的惨剧(而且无法预测)。同时你还要记住,同一台电脑上还可能运行有其它同样消耗未分页池的其它应用程序,因此在设计你的应用程序时,对资源量的预估要特别保守和谨慎。
处理资源不足的问题是十分复杂的,因为发生上述情况时你不会收到特别的错误代码,通常你只能收到一般性的WSAENOBUFS或者ERROR_INSUFFICIENT_RESOURCES 错误。要处理这些错误,首先,把你的应用程序工作配置调整到合理的最大值(译者注:所谓工作配置,是指应用程序各部分运行中所需的内存用量,请参考 http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp ,关于内存优化,译者另有译文),如果错误继续出现,那么注意检查是否是网络带宽不足的问题。之后,请确认你没有同时发出太多的收发调用。最后,如果还是收到资源不足的错误,那就很可能是遇到了未分页内存池不足的问题了。要释放未分页内存池空间,请关闭应用程序中相当部分的连接,等待系统自行渡过和修正这个瞬时的错误。
(译者) 刘西齐
Windows NT和Windows 2000的套接字架构
对于开发大响应规模的Winsock应用程序而言,对Windows NT和Windows 2000的套接字架构有基本的了解是很有帮助的。
与其它类型操作系统不同,Windows NT和Windows 2000的传输协议没有一种风格像套接字那样的、可以和应用程序直接交谈的界面,而是采用了一种更为底层的API,叫做传输驱动程序界面(Transport Driver Interface,TDI)。Winsock的核心模式驱动程序负责连接和缓冲区管理,以便向应用程序提供套接字仿真(在AFD.SYS文件中实现),同时负责与底层传输驱动程序对话。
谁来负责管理缓冲区?
正如上面所说的,应用程序通过Winsock来和传输协议驱动程序交谈,而AFD.SYS负责为应用程序进行缓冲区管理。也就是说,当应用程序调用send()或WSASend()函数来发送数据时,AFD.SYS将把数据拷贝进它自己的内部缓冲区(取决于SO_SNDBUF设定值),然后send()或WSASend()函数立即返回。也可以这么说,AFD.SYS在后台负责把数据发送出去。不过,如果应用程序要求发出的数据超过了SO_SNDBUF设定的缓冲区大小,那么WSASend()函数会阻塞,直至所有数据发送完毕。
从远程客户端接收数据的情况也类似。只要不用从应用程序那里接收大量的数据,而且没有超出SO_RCVBUF设定的值,AFD.SYS将把数据先拷贝到其内部缓冲区中。当应用程序调用recv()或WSARecv()函数时,数据将从内部缓冲拷贝到应用程序提供的缓冲区。
多数情况下,这样的架构运行良好,特别在是应用程序采用传统的套接字下非重叠的send()和receive()模式编写的时候。不过程序员要小心的是,尽管可以通过setsockopt()这个API来把SO_SNDBUF和SO_RCVBUF选项值设成0(关闭内部缓冲区),但是程序员必须十分清楚把AFD.SYS的内部缓冲区关掉会造成什么后果,避免收发数据时有关的缓冲区拷贝可能引起的系统崩溃。
举例来说,一个应用程序通过设定SO_SNDBUF为0把缓冲区关闭,然后发出一个阻塞send()调用。在这样的情况下,系统内核会把应用程序的缓冲区锁定,直到接收方确认收到了整个缓冲区后send()调用才返回。似乎这是一种判定你的数据是否已经为对方全部收到的简洁的方法,实际上却并非如此。想想看,即使远端TCP通知数据已经收到,其实也根本不代表数据已经成功送给客户端应用程序,比如对方可能发生资源不足的情况,导致AFD.SYS不能把数据拷贝给应用程序。另一个更要紧的问题是,在每个线程中每次只能进行一次发送调用,效率极其低下。
把SO_RCVBUF设为0,关闭AFD.SYS的接收缓冲区也不能让性能得到提升,这只会迫使接收到的数据在比Winsock更低的层次进行缓冲,当你发出receive调用时,同样要进行缓冲区拷贝,因此你本来想避免缓冲区拷贝的阴谋不会得逞。
现在我们应该清楚了,关闭缓冲区对于多数应用程序而言并不是什么好主意。只要要应用程序注意随时在某个连接上保持几个WSARecvs重叠调用,那么通常没有必要关闭接收缓冲区。如果AFD.SYS总是有由应用程序提供的缓冲区可用,那么它将没有必要使用内部缓冲区。
高性能的服务器应用程序可以关闭发送缓冲区,同时不会损失性能。不过,这样的应用程序必须十分小心,保证它总是发出多个重叠发送调用,而不是等待某个重叠发送结束了才发出下一个。如果应用程序是按一个发完再发下一个的顺序来操作,那浪费掉两次发送中间的空档时间,总之是要保证传输驱动程序在发送完一个缓冲区后,立刻可以转向另一个缓冲区。
(译者) 刘西齐
通常要开发网络应用程序并不是一件轻松的事情,不过,实际上只要掌握几个关键的原则也就可以了——创建和连接一个套接字,尝试进行连接,然后收发数据。真正难的是要写出一个可以接纳少则一个,多则数千个连接的网络应用程序。本文将讨论如何通过Winsock2在Windows NT? 和 Windows 2000上开发高扩展能力的Winsock应用程序。文章主要的焦点在客户机/服务器模型的服务器这一方,当然,其中的许多要点对模型的双方都适用。
API与响应规模
通过Win32的重叠I/O机制,应用程序可以提请一项I/O操作,重叠的操作请求在后台完成,而同一时间提请操作的线程去做其他的事情。等重叠操作完成后线程收到有关的通知。这种机制对那些耗时的操作而言特别有用。不过,像Windows 3.1上的WSAAsyncSelect()及Unix下的select()那样的函数虽然易于使用,但是它们不能满足响应规模的需要。而完成端口机制是针对操作系统内部进行了优化,在Windows NT 和 Windows 2000上,使用了完成端口的重叠I/O机制才能够真正扩大系统的响应规模。
完成端口
一个完成端口其实就是一个通知队列,由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成,某个可以对该操作结果进行处理的工作者线程就会收到一则通知。而套接字在被创建后,可以在任何时候与某个完成端口进行关联。
通常情况下,我们会在应用程序中创建一定数量的工作者线程来处理这些通知。线程数量取决于应用程序的特定需要。理想的情况是,线程数量等于处理器的数量,不过这也要求任何线程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作,以免线程阻塞。每个线程都将分到一定的CPU时间,在此期间该线程可以运行,然后另一个线程将分到一个时间片并开始执行。如果某个线程执行了阻塞型的操作,操作系统将剥夺其未使用的剩余时间片并让其它线程开始执行。也就是说,前一个线程没有充分使用其时间片,当发生这样的情况时,应用程序应该准备其它线程来充分利用这些时间片。
完成端口的使用分为两步。首先创建完成端口,如以下代码所示:
HANDLE hIocp;
hIocp = CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
NULL,
(ULONG_PTR)0,
0);
if (hIocp == NULL) {
// Error
}
完成端口创建后,要把将使用该完成端口的套接字与之关联起来。方法是再次调用CreateIoCompletionPort ()函数,第一个参数FileHandle设为套接字的句柄,第二个参数ExistingCompletionPort 设为刚刚创建的那个完成端口的句柄。
以下代码创建了一个套接字,并把它和前面创建的完成端口关联起来:
SOCKET s;
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
// Error
if (CreateIoCompletionPort((HANDLE)s,
hIocp,
(ULONG_PTR)0,
0) == NULL)
{
// Error
}
???
}
这时就完成了套接字与完成端口的关联操作。在这个套接字上进行的任何重叠操作都将通过完成端口发出完成通知。注意,CreateIoCompletionPort()函数中的第三个参数用来设置一个与该套接字相关的“完成键(completion key)”(译者注:完成键可以是任何数据类型)。每当完成通知到来时,应用程序可以读取相应的完成键,因此,完成键可用来给套接字传递一些背景信息。
在创建了完成端口、将一个或多个套接字与之相关联之后,我们就要创建若干个线程来处理完成通知。这些线程不断循环调用GetQueuedCompletionStatus ()函数并返回完成通知。
下面,我们先来看看应用程序如何跟踪这些重叠操作。当应用程序调用一个重叠操作函数时,要把指向一个overlapped结构的指针包括在其参数中。当操作完成后,我们可以通过GetQueuedCompletionStatus()函数中拿回这个指针。不过,单是根据这个指针所指向的overlapped结构,应用程序并不能分辨究竟完成的是哪个操作。要实现对操作的跟踪,你可以自己定义一个OVERLAPPED结构,在其中加入所需的跟踪信息。
无论何时调用重叠操作函数时,总是会通过其lpOverlapped参数传递一个OVERLAPPEDPLUS结构(例如WSASend、 WSARecv等函数)。这就允许你为每一个重叠调用操作设置某些操作状态信息,当操作结束后,你可以通过GetQueuedCompletionStatus()函数获得你自定义结构的指针。注意OVERLAPPED字段不要求一定是这个扩展后的结构的第一个字段。当得到了指向OVERLAPPED结构的指针以后,可以用CONTAINING_RECORD宏取出其中指向扩展结构的指针(译者注:以上两小段一会是OVERLAPPEDPLUS结构,一会是OVERLAPPED结构,本人也看不太懂,请高手赐教)。
OVERLAPPED 结构的定义如下:
typedef struct _OVERLAPPEDPLUS {
OVERLAPPED ol;
SOCKET s, sclient;
int OpCode;
WSABUF wbuf;
DWORD dwBytes, dwFlags;
// other useful information
} OVERLAPPEDPLUS;
#define OP_READ 0
#define OP_WRITE 1
#define OP_ACCEPT 2
下面让我们来看看Fi
我们经常在AboutBox中显示一幅关于公司或自己讯息的位图,有没有想过让这幅位图有更酷的效果?比如加上淡入淡出效果?只要有了这个CAlphaCtrl控件就可以轻松实现。
CAlphaCtrl是从CStatic继承而来。使用时只要把CalphaCtrl加入窗体,然后调用LoadAlphaBitmap(UINT uID, int iTimer)函数就可以实现位图的淡入淡出。其中uID是位图的资源ID,iTimer是位图显示时间间隔,值愈小显示愈快。
下面就来说一说CalphaCtrl是如何实现的。关键的一个实现函数是一个win32 API: AlphaBlend,此函数可以实现图像的透明显示,相关的参数和资料请自行参阅MSDN,值得注意的是使用此函数时要链接到msimg32.lib库。
第一步,我们先在CalphaCtrl类中增加几个Data Member:CBitmap Bmp;
BOOL bCanPaint; UINT nBmpID; int nTimer;
第二步,在CalphaCtrl类中增加一个Member Function:
void AlphaDisplay(CDC &pDC, CClientDC &dc, BLENDFUNCTION& rBlendProps, int width, int heigh, byte nLevel)
{
//nLevel是透明度,0表示不显示,255则完全显示
rBlendProps.SourceConstantAlpha = nLevel;
AlphaBlend( dc.m_hDC, 0, 0, width, heigh, pDC.m_hDC, 0, 0,
width, heigh, rBlendProps );
}
第三步,增加一个名为tdDisplay的全局函数,此函数为一个线程函数,用于位图的显示。
UINT tdDisplay(LPVOID lpParam)
{
CAlphaCtrl* AlphaCtrl = (CAlphaCtrl*)lpParam; CClientDC dc(AlphaCtrl);
CDC pDC;
pDC.CreateCompatibleDC(&dc);
pDC.SelectObject(&AlphaCtrl->Bmp); BLENDFUNCTION rBlendProps;
rBlendProps.BlendOp = AC_SRC_OVER;
rBlendProps.BlendFlags = 0;
rBlendProps.AlphaFormat = 0; BITMAP bmInfo;
::GetObject( AlphaCtrl->Bmp.m_hObject, sizeof(BITMAP), &bmInfo );
INT nWidth, nHeigh;
nWidth = bmInfo.bmWidth;
nHeigh = bmInfo.bmHeight;AlphaCtrl->SetWindowPos(NULL, 0, 0, nWidth, nHeigh, SWP_NOMOVE);
int i = 0;
while(i <= 255)
{
AlphaCtrl->AlphaDisplay(pDC, dc, rBlendProps, nWidth, nHeigh, i);
i += 5;
Sleep(AlphaCtrl->nTimer);
}
AlphaCtrl->bCanPaint = 1; //Make OnPaint Word
AfxEndThread(0);
return 0;
}
第四步,现在万事俱备,加上初始化函数:
BOOL LoadAlphaBitmap(UINT uID, int iTimer)
{
int i = Bmp.LoadBitmap(uID);
if(i)
{
AfxBeginThread(tdDisplay, this);
nBmpID = uID;
nTimer = iTimer;
return 1;
}
else
{
TRACE("Load Bitmap Failed/n");
return 0;
}
return 1;
}
最后,执行程序,打开你的对话框看一看那幅很酷的位图!
一、托盘简介
所谓的“托盘”,在Windows系统界面中,指的就是下面任务条右侧,有系统时间等等的标志的那一部分。在程序最小化或挂起时,但有不希望占据任务栏的时候,就可以把程序放到托盘区。其实,托盘区的编程很简单,下面简要阐述一下子喽
二、托盘编程相关函数
其实呢,把程序放到托盘上的本质就是先在托盘区绘制一个图标,然后把程序隐藏不见,再对托盘的图标进行消息处理,就可以了。
绘制图标以及确定图标所传送消息的函数只有一个,那就是——————
WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(
DWORD dwMessage,
PNOTIFYICONDATA pnid
);
这个函数呢,负责向系统传递消息,以添加、修改或删除托盘区的图标。她的返回值呢,是个布尔类型的。就是说,如果返回0,那就是成仁啦,非0才成功。
参数dwMessage 是表示这个函数的应用功能是哪一方面,是添加、删除,还是修改图标。如果是添加,则它的值为NIM_ADD;删除则是NIM_DELETE;而修改是NIM_MODIFY。参数pnid就是具体的和程序在托盘区的图标有关系的结构了。它的定义如下:
typedef struct _NOTIFYICONDATA {
DWORD cbSize;
HWND hWnd;
UINT uID;
UINT uFlags;
UINT uCallbackMessage;
HICON hIcon;
char szTip[64];
} NOTIFYICONDATA, *PNOTIFYICONDATA;
下面就对该结构各个参数进行刨析:
cbSize : 结构的长度,用“位”来做单位。一般在程序中,我们用(DWORD)sizeof(NOTIFYICONDATA) 给它赋值。
HWnd : 一个句柄,如果对托盘中的图标进行操作,相应的消息就传给这个句柄所代表的窗口。自然了,大多数情况下是this->m_hWnd喽。
uID : 在工程中定义的图标ID
uFlags : 这个成员标志着其他哪些成员的数据是有效的,分别为NIF_ICON, NIF_MESSAGE, NIF_TIP,分别代表着数据有效的成员是hIcon, uCallbackMessage, szTip。当然,三个值可以用“|”联系到一起。下面分别对涉及到的成员进行阐述
hIcon : 要增加,删除或修改的图标句柄。如果只知道个uID, 一般可能会用函数LoadIcon来得到句柄。例如LoadIcon ( AfxGetInstanceHandle() ,MAKEINTRESOURCE (IDR_MAINFRAME) )。
uCallbackMessage : 这在对托盘区的操作中,是比较重要的数据成员。这是个消息标志,当用鼠标对托盘区相应图标进行操作的时候,就会传递消息给Hwnd所代表的窗口。所以说,在uFlags中,一般都得标志它有效。这里一般都是自定义的消息。
szTip : 鼠标移动到托盘图标上时的提示文字。
三、托盘编程例子
有关托盘编程的基础知识呢,也就上面这些了。下面呢,我们就进入具体的实战演练阶段,举几个托盘编程的例子瞧瞧,加深理解。
1、将程序最小化到系统托盘区的函数toTray()。
void CTimeWakeDlg::toTray()
{
NOTIFYICONDATA nid;
nid.cbSize=(DWORD)sizeof(NOTIFYICONDATA);
nid.hWnd=this->m_hWnd;
nid.uID=IDR_MAINFRAME;
nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP ;
nid.uCallbackMessage=WM_SHOWTASK;//自定义的消息名称
nid.hIcon=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDR_MAINFRAME));
strcpy(nid.szTip,"计划任务提醒");//信息提示条为“计划任务提醒”
Shell_NotifyIcon(NIM_ADD,&nid);//在托盘区添加图标
ShowWindow(SW_HIDE);//隐藏主窗口
}
这是个很简单的函数,里面首先给NOTIFYICONDATA赋值,然后调用shell_NotifyIcon, 头一个参数是NIM_ADD,表示添加。然后用函数ShowWindow 隐藏主窗口,这样,就实现了将程序最小化到系统托盘区的任务了。
2、程序已经最小化到托盘区了,但是呢,对托盘图标的操作如何进行呢?这就体现了结构NOTIFYICONDATA的成员uCallbackMessage 的作用了。它所提供的作用就是,当用户用鼠标点击托盘区的图标的时候(无论是左键还是右键),会向hWnd所代表的窗口传送消息,如果是上例,消息的名称就是WM_SHOWTASK。根据VC的消息机制,对自定义消息增加消息响应函数。
在头文件的//{{AFX_MSG和//}}AFX_MSG之间声明消息响应函数:
afx_msg LRESULT onShowTask(WPARAM wParam,LPARAM lParam);
然后在CPP文件中添加消息映射。在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP 之间加入:
ON_MESSAGE(WM_SHOWTASK,onShowTask)将消息和消息响应函数映射起来。
然后就是在CPP文件中加入函数onShowTask的实现了:
LRESULT CTimeWakeDlg:nShowTask(WPARAM wParam,LPARAM lParam)
//wParam接收的是图标的ID,而lParam接收的是鼠标的行为
{
if(wParam!=IDR_MAINFRAME)
return 1;
switch(lParam)
{
case WM_RBUTTONUP://右键起来时弹出快捷菜单,这里只有一个“关闭”
{
LPPOINT lpoint=new tagPOINT;
::GetCursorPos(lpoint);//得到鼠标位置
CMenu menu;
menu.CreatePopupMenu();//声明一个弹出式菜单
//增加菜单项“关闭”,点击则发送消息WM_DESTROY给主窗口(已
//隐藏),将程序结束。
menu.AppendMenu(MF_STRING,WM_DESTROY,"关闭");
//确定弹
在windows中,提供了一整套视频采集函数,要做一个视频采集软件,先要创建一个采集窗口
hCapWnd = capCreateCaptureWindow((LPSTR)"Capture Window",
WS_CHILD | WS_VISIBLE,
15, 129, VideoW, VideoH,
m_hWnd, (int) 0) ;
采集窗口的大小一般与摄像头有关,然后指定回调函数,这个函数是用来接收采集的数据的,
FARPROC fpVideoCallback = MakeProcInstance((FARPROC)VideoCallbackProc, m_hInstance);
capSetCallbackOnVideoStream(hCapWnd, fpVideoCallback);
if(!capDriverConnect(hCapWnd, 0))
MessageBox("Error! Not found video card!");
设置视频格式。
ZeroMemory(&m_biInput, sizeof(BITMAPINFO));
m_biInput.bmiHeader.biSize = sizeof(BITMAPINFO);
m_biInput.bmiHeader.biWidth = VideoW;
m_biInput.bmiHeader.biHeight = VideoH;
m_biInput.bmiHeader.biPlanes = 1;
m_biInput.bmiHeader.biBitCount = 24;
m_biInput.bmiHeader.biCompression = BI_RGB;
m_biInput.bmiHeader.biSizeImage = VideoW * VideoH * m_biInput.bmiHeader.biBitCount / 8;
m_biInput.bmiHeader.biXPelsPerMeter = 0;
m_biInput.bmiHeader.biYPelsPerMeter = 0;
m_biInput.bmiHeader.biClrUsed = 0;
m_biInput.bmiHeader.biClrImportant = 0;
capSetVideoFormat(hCapWnd, &m_biInput, sizeof(m_biInput));
设置其它一些参数,如帧速率等。
CAPTUREPARMS CapParam;
capCaptureGetSetup(hCapWnd, &CapParam, sizeof(CapParam));
CapParam.fAbortLeftMouse = FALSE;
CapParam.fAbortRightMouse = FALSE;
CapParam.fYield = TRUE;
CapParam.dwRequestMicroSecPerFrame = 1000000/25; //25 FPS
capCaptureSetSetup(hCapWnd, &CapParam, sizeof(CapParam));
capOverlay(hCapWnd, TRUE);
开始视频采集
time1 = GetTickCount();
capCaptureSequenceNoFile(hCapWnd);
停止视频采集
capCaptureStop(hCapWnd);
视频采集回调函数
LRESULT FAR PASCAL VideoCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr)
{
//lpVHdr就是一帧图象的数据
char s[120];
wsprintf(s, "%ld", FrameNo++);
SetDlgItemText(hMainWnd, IDC_CAPFRAME, s);
if(fpCode)
{
memcpy(EncodeSrc, lpVHdr->lpData, lpVHdr->dwBytesUsed);
//MP4压缩
int length = EncEncode(EncodeSrc, EncodeDest);
//写AVI文件
fwrite(&length, sizeof(int), 1, fpCode);
fwrite(EncodeDest, length, 1, fpCode);
time2 = GetTickCount();
WORD dTime = WORD(time2-time1);
fwrite(&dTime, sizeof(WORD), 1, fpCode);
time1 = GetTickCount();
}
return (LRESULT)TRUE;
}
这是实现视频采集的框架,具体代码大家只有自己写了,因为MP4压缩算法现在美国禁止出口,在Windows中不含有该算法,
采用这种算法每帧图象的数据量只有800字节,如果每秒按15帧来处理,在互联网上传送应该可以实现,如果采用H263协议,数据量还会更小一些,但图象清晰度会下降很多。
而我们现在开发的网上视频系统就是采用这种压缩算法
在网络程序设计过程中,我们经常要与各种类型的代理服务器打交道,比如在企业内部网通过代理去访问Internet网上的服务器等等,一般代理服务器支持几种常见的代理协议标准,如Socks4,Socks5,Http代理,其中Socks5需要用户验证,代理相对复杂。我在查阅RFC文档和相关资料后,特总结一些TCP协议穿透代理服务器的程序片断,希望对大家有所帮助。
//使用到的结构
struct sock4req1
{
char VN;
char CD;
unsigned short Port;
unsigned long IPAddr;
char other[1];
};
struct sock4ans1
{
char VN;
char CD;
};
struct sock5req1
{
char Ver;
char nMethods;
char Methods[255];
};
struct sock5ans1
{
char Ver;
char Method;
};
struct sock5req2
{
char Ver;
char Cmd;
char Rsv;
char Atyp;
char other[1];
};
struct sock5ans2
{
char Ver;
char Rep;
char Rsv;
char Atyp;
char other[1];
};
struct authreq
{
char Ver;
char Ulen;
char Name[255];
char Plen;
char Pass[255];
};
struct authans
{
char Ver;
char Status;
};
//通过Socks4方式代理
if( !ClientSock.Connect( g_ProxyInfo.m_strProxyIP,g_ProxyInfo.m_nProxyPort) )
{
m_sError = _T("不能连接到代理服务器!");
ClientSock.Close();
return FALSE;
}
char buff[100];
memset(buff,0,100);
struct sock4req1 *m_proxyreq;
m_proxyreq = (struct sock4req1 *)buff;
m_proxyreq->VN = 4;
m_proxyreq->CD = 1;
m_proxyreq->Port = ntohs(GetPort());
m_proxyreq->IPAddr = inet_addr(GetServerHostName());
ClientSock.Send(buff,9);
struct sock4ans1 *m_proxyans;
m_proxyans = (struct sock4ans1 *)buff;
memset(buff,0,100);
ClientSock.Receive(buff,100);
if(m_proxyans->VN != 0 || m_proxyans->CD != 90)
{
m_sError = _T("通过代理连接主站不成功!");
ClientSock.Close();
return FALSE;
}
//通过Socks5方式代理
if( !ClientSock.Connect( g_ProxyInfo.m_strProxyIP,g_ProxyInfo.m_nProxyPort) )
{
m_sError = _T("不能连接到代理服务器!");
ClientSock.Close();
return FALSE;
}
char buff[600];
struct sock5req1 *m_proxyreq1;
m_proxyreq1 = (struct sock5req1 *)buff;
m_proxyreq1->Ver = 5;
m_proxyreq1->nMethods = 2;
m_proxyreq1->Methods[0] = 0;
m_proxyreq1->Methods[1] = 2;
ClientSock.Send(buff,4);
struct sock5ans1 *m_proxyans1;
m_proxyans1 = (struct sock5ans1 *)buff;
memset(buff,0,600);
ClientSock.Receive(buff,600);
if(m_proxyans1->Ver != 5 || (m_proxyans1->Method!=0 && m_proxyans1->Method!=2))
{
m_sError = _T("通过代理连接主站不成功!");
ClientSock.Close();
return FALSE;
}
if(m_proxyans1->Method == 2)
{
int nUserLen = strlen(g_ProxyInfo.m_strProxyUser);
int nPassLen = strlen(g_ProxyInfo.m_strProxyPass);
struct authreq *m_authreq;
m_authreq = (struct authreq *)buff;
m_authreq->Ver = 1;
m_authreq->Ulen = nUserLen;
strcpy(m_authreq->Name,g_ProxyInfo.m_strProxyUser);
m_authreq->Plen = nPassLen;
strcpy(m_authreq->Pass,g_ProxyInfo.m_strProxyPass);
ClientSock.Send(buff,513);
struct authans *m_authans;
m_authans = (struct authans *)buff;
memset(buff,0,600);
ClientSock.Receive(buff,600);
if(m_authans->Ver != 1 || m_authans->Status != 0)
{
m_sError = _T("代理服务器用户验证不成功!");
ClientSock.Close();
return FALSE;
}
}
struct sock5req2 *m_proxyreq2;
m_proxyreq2 = (struct sock5req2 *)buff;
m_proxyreq2->Ver = 5;
m_proxyreq2->Cmd = 1;
m_proxyreq2->Rsv = 0;
m_proxyreq2->Atyp = 1;
unsigned long tmpLong = inet_addr(GetServerHostName());
unsigned short port = ntohs(GetPort());
memcpy(m_proxyreq2->other,&tmpLong,4);
memcpy(m_proxyreq2->other+4,&port,2);
ClientSock.Send(buff,sizeof(struct sock5req2)+5);
struct sock5ans2 *m_proxyans2;
memset(buff,0,600);
m_proxyans2 = (struct sock5ans2 *)buff;
ClientSock.Receive(buff,600);
if(m_proxyans2->Ver != 5 || m_proxyans2->Rep != 0)
{
m_sError = _T("通过代理连接主站不成功!");
ClientSock.Close();
return FALSE;
}
//通过HTTP方式代理
if( !ClientSock.Connect( g_ProxyInfo.m_strProxyIP,g_ProxyInfo.m_nProxyPort) )
{
m_sError = _T("不能连接到代理服务器!");
ClientSock.Close();
return FALSE;
}
char buff[600];
sprintf( buff, "%s%s:%d%s","CONNECT ",GetServerHostName(),GetPort()," HTTP/1.1/r/nUser-Agent: MyApp/0.1/r/n/r/n");
ClientSock.Send(buff,strlen(buff)); //发送请求
memset(buff,0,600);
ClientSock.Receive(buff,600);
if(strstr(buff, "
对于Windows下ping命令相信大家已经再熟悉不过了,但是能把ping的功能发挥到最大的人却并不是很多,当然我也并不是说我可以让ping发挥最大的功能,我也只不过经常用ping这个工具,也总结了一些小经验,现在和大家分享一下。
现在我就参照ping命令的帮助说明来给大家说说我使用ping时会用到的技巧,ping只有在安装了TCP/IP协议以后才可以使用:
ping [-t] [-a] [-n count] [-l length] [-f] [-I ttl] [-v tos] [-r count] [-s count] [[-j computer-list] | [-k computer-list]] [-w timeout] destination-list
Options:
-t Ping the specified host until stopped.To see statistics and continue - type Control-Break;To stop - type Control-C.
不停的ping地方主机,直到你按下Control-C。
此功能没有什么特别的技巧,不过可以配合其他参数使用,将在下面提到。
-a Resolve addresses to hostnames.
解析计算机NetBios名。
示例:C:\>ping -a 192.168.1.21
Pinging iceblood.yofor.com [192.168.1.21] with 32 bytes of data:
Reply from 192.168.1.21: bytes=32 time<10ms TTL=254
Reply from 192.168.1.21: bytes=32 time<10ms TTL=254
Reply from 192.168.1.21: bytes=32 time<10ms TTL=254
Reply from 192.168.1.21: bytes=32 time<10ms TTL=254
Ping statistics for 192.168.1.21:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
从上面就可以知道IP为192.168.1.21的计算机NetBios名为iceblood.yofor.com。
-n count Number of echo requests to send.
发送count指定的Echo数据包数。
在默认情况下,一般都只发送四个数据包,通过这个命令可以自己定义发送的个数,对衡量网络速度很有帮助,比如我想测试发送50个数据包的返回的平均时间为多少,最快时间为多少,最慢时间为多少就可以通过以下获知:
C:\>ping -n 50 202.103.96.68
Pinging 202.103.96.68 with 32 bytes of data:
Reply from 202.103.96.68: bytes=32 time=50ms TTL=241
Reply from 202.103.96.68: bytes=32 time=50ms TTL=241
Reply from 202.103.96.68: bytes=32 time=50ms TTL=241
Request timed out.
………………
Reply from 202.103.96.68: bytes=32 time=50ms TTL=241
Reply from 202.103.96.68: bytes=32 time=50ms TTL=241
Ping statistics for 202.103.96.68:
Packets: Sent = 50, Received = 48, Lost = 2 (4% loss),Approximate round trip times in milli-seconds:
Minimum = 40ms, Maximum = 51ms, Average = 46ms
从以上我就可以知道在给202.103.96.68发送50个数据包的过程当中,返回了48个,其中有两个由于未知原因丢失,这48个数据包当中返回速度最快为40ms,最慢为51ms,平均速度为46ms。
-l size Send buffer size.
定义echo数据包大小。
在默认的情况下windows的ping发送的数据包大小为32byt,我们也可以自己定义它的大小,但有一个大小的限制,就是最大只能发送65500byt,也许有人会问为什么要限制到65500byt,因为Windows系列的系统都有一个安全漏洞(也许还包括其他系统)就是当向对方一次发送的数据包大于或等于65532时,对方就很有可能挡机,所以微软公司为了解决这一安全漏洞于是限制了ping的数据包大小。虽然微软公司已经做了此限制,但这个参数配合其他参数以后危害依然非常强大,比如我们就可以通过配合-t参数来实现一个带有攻击性的命令:(以下介绍带有危险性,仅用于试验,请勿轻易施于别人机器上,否则后果自负)
C:\>ping -l 65500 -t 192.168.1.21
Pinging 192.168.1.21 with 65500 bytes of data:
Reply from 192.168.1.21: bytes=65500 time<10ms TTL=254
Reply from 192.168.1.21: bytes=65500 time<10ms TTL=254
………………
这样它就会不停的向192.168.1.21计算机发送大小为65500byt的数据包,如果你只有一台计算机也许没有什么效果,但如果有很多计算机那么就可以使对方完全瘫痪,我曾经就做过这样的试验,当我同时使用10台以上计算机ping一台Win2000Pro系统的计算机时,不到5分钟对方的网络就已经完全瘫痪,网络严重堵塞,HTTP和FTP服务完全停止,由此可见威力非同小可。
-f Set Don’t Fragment flag in packet.
在数据包中发送“不要分段”标志。
在一般你所发送的数据包都会通过路由分段再发送给对方,加上此参数以后路由就不会再分段处理。
-I TTL Time To Live.
指定TTL值在对方的系统里停留的时间。
此参数同样是帮助你检查网络运转情况的。
-v TOS Type Of Service.
将“服务类型”字段设置为 tos 指定的值。
-r count Record route for count hops.
在“记录路由”字段中记录传出和返回数据包的路由。
在一般情况下你发送的数据包是通过一个个路由才到达对方的,但到底是经过了哪些路由呢?通过此参数就可以设定你想探测经过的路由的个数,
P2P 之 UDP穿透NAT的原理与实现(附源代码)
原创:shootingstars
参考:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
论坛上经常有对P2P原理的讨论,但是讨论归讨论,很少有实质的东西产生(源代码)。呵呵,在这里我就用自己实现的一个源代码来说明UDP穿越NAT的原理。
首先先介绍一些基本概念:
NAT(Network Address Translators),网络地址转换:网络地址转换是在IP地址日益缺乏的情况下产生的,它的主要目的就是为了能够地址重用。NAT分为两大类,基本的NAT和NAPT(Network Address/Port Translator)。
最开始NAT是运行在路由器上的一个功能模块。
最先提出的是基本的NAT,它的产生基于如下事实:一个私有网络(域)中的节点中只有很少的节点需要与外网连接(呵呵,这是在上世纪90年代中期提出的)。那么这个子网中其实只有少数的节点需要全球唯一的IP地址,其他的节点的IP地址应该是可以重用的。
因此,基本的NAT实现的功能很简单,在子网内使用一个保留的IP子网段,这些IP对外是不可见的。子网内只有少数一些IP地址可以对应到真正全球唯一的IP地址。如果这些节点需要访问外部网络,那么基本NAT就负责将这个节点的子网内IP转化为一个全球唯一的IP然后发送出去。(基本的NAT会改变IP包中的原IP地址,但是不会改变IP包中的端口)
关于基本的NAT可以参看RFC 1631
另外一种NAT叫做NAPT,从名称上我们也可以看得出,NAPT不但会改变经过这个NAT设备的IP数据报的IP地址,还会改变IP数据报的TCP/UDP端口。基本NAT的设备可能我们见的不多(呵呵,我没有见到过),NAPT才是我们真正讨论的主角。看下图:
Server S1
18.181.0.31:1235
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 155.99.25.11:62000 v |
|
NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 10.0.0.1:1234 v |
|
Client A
10.0.0.1:1234
有一个私有网络10.*.*.*,Client A是其中的一台计算机,这个网络的网关(一个NAT设备)的外网IP是155.99.25.11(应该还有一个内网的IP地址,比如10.0.0.10)。如果Client A中的某个进程(这个进程创建了一个UDP Socket,这个Socket绑定1234端口)想访问外网主机18.181.0.31的1235端口,那么当数据包通过NAT时会发生什么事情呢?
首先NAT会改变这个数据包的原IP地址,改为155.99.25.11。接着NAT会为这个传输创建一个Session(Session是一个抽象的概念,如果是TCP,也许Session是由一个SYN包开始,以一个FIN包结束。而UDP呢,以这个IP的这个端口的第一个UDP开始,结束呢,呵呵,也许是几分钟,也许是几小时,这要看具体的实现了)并且给这个Session分配一个端口,比如62000,然后改变这个数据包的源端口为62000。所以本来是(10.0.0.1:1234->18.181.0.31:1235)的数据包到了互联网上变为了(155.99.25.11:62000->18.181.0.31:1235)。
一旦NAT创建了一个Session后,NAT会记住62000端口对应的是10.0.0.1的1234端口,以后从18.181.0.31发送到62000端口的数据会被NAT自动的转发到10.0.0.1上。(注意:这里是说18.181.0.31发送到62000端口的数据会被转发,其他的IP发送到这个端口的数据将被NAT抛弃)这样Client A就与Server S1建立以了一个连接。
呵呵,上面的基础知识可能很多人都知道了,那么下面是关键的部分了。
看看下面的情况:
Server S1 Server S2
18.181.0.31:1235 138.76.29.7:1235
| |
| |
+----------------------+----------------------+
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 155.99.25.11:62000 v | v 155.99.25.11:62000 v
|
Cone NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 10.0.0.1:1234 v | v 10.0.0.1:1234 v
|
Client A
10.0.0.1:1234
接上面的例子,如果Client A的原来那个Socket(绑定了1234端口的那个UDP Socket)又接着向另外一个Server S2发送了一个UDP包,那么这个UDP包在通过NAT时会怎么样呢?
这时可能会有两种情况发生,一种是NAT再次创建一个Session,并且再次为这个Session分配一个端口号(比如:62001)。另外一种是NAT再次创建一个Session,但是不会新分配一个端口号,而是用原来分配的端口号62000。前一种NAT叫做Symmetric NAT,后一种叫做Cone NAT。我们期望我们的NAT是第二种,呵呵,如果你的NAT刚好是第一种,那么很可能会有很多P2P软件失灵。(特别是如果双方都是Symmetric NAT,或者一方是Symmetric NAT,另一方是Restricted Cone NAT,这种情况下,建立p2p的连接将会比较困难。关于Restricted Cone NAT,请参看http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt)
好了,我们看到,通过NAT,子网内的计算机向外连结是很容易的(NAT相当于透明的,子网内的和外网的计算机不用知道NAT的情况)。
但是如果外部的计算机想访问子网内的计算机就比较困难了(而这正是P2P所需要的)。
那么我们如果想从外部发送一
译者:徐景周(原作:VGirish)
Q:如何在对话框中加入工具条
在 OnInitDialog 中加入下面代码: BOOL CYourDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Create the toolbar. To understand the meaning of the styles used, you
// can take a look at the MSDN for the Create function of the CToolBar class.
ToolBar.Create(this, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_TOOLTIPS |CBRS_FLYBY | CBRS_BORDER_BOTTOM);
// I have assumed that you have named your toolbar’’s resource as IDR_TOOLBAR1.
// If you have given it a different name, change the line below to accomodate
// that by changing the parameter for the LoadToolBar function.
ToolBar.LoadToolBar(IDR_TOOLBAR1);
CRect rcClientStart;
CRect rcClientNow;
GetClientRect(rcClientStart);
// To reposition and resize the control bar
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST,0, reposQuery, rcClientNow);
CPoint ptOffset(rcClientNow.left - rcClientStart.left,rcClientNow.top-rcClientStart.top);
CRect rcChild;
CWnd* pwndChild = GetWindow(GW_CHILD);
while (pwndChild)
{
pwndChild->GetWindowRect(rcChild);
ScreenToClient(rcChild);
rcChild.OffsetRect(ptOffset);
pwndChild->MoveWindow(rcChild, FALSE);
pwndChild = pwndChild->GetNextWindow();
}
CRect rcWindow;
GetWindowRect(rcWindow);
rcWindow.right += rcClientStart.Width() - rcClientNow.Width();
rcWindow.bottom += rcClientStart.Height() - rcClientNow.Height();
MoveWindow(rcWindow, FALSE);
// And position the control bars
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
return TRUE; // return TRUE unless you set the focus to a control
}
Q:如何改变对话框的形状?
可用下面一些函数:
CreatePolygonRgn
CreateRectRgn
CreateRoundRectRgn 等. CRgn m_rgn; // Put this in your dialog’’s header file. i.e. a member variable
// This Gets the size of the Dialog: This piece of code is to be placed in the
// OnInitDialog Function of your dialog.
CRect rcDialog
GetClientRect(rcDialog);
// The following code Creates the area and assigns it to your Dialog
m_rgn.CreateEllipticRgn(0, 0, rcDialog.Width(), rcDialogHeight());
SetWindowRgn(GetSafeHwnd(), (HRGN) m_rgn, TRUE);
Q:如何实现非客户区移动?
可用下面二种方法// Handler for WM_LBUTTONDOWN message
void CYourDialog::OnLButtonDown(UINT nFlags, CPoint point)
{
CDialog::OnLButtonDown(nFlags, point);
PostMessage( WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM( point.x, point.y));
}
// Handler for WM_NCHITTEST message
LONG CYourDialog::OnNcHitTest( UINT uParam, LONG lParam )
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
UINT nHitTest = CDialog::OnNcHitTest(CSize(xPos, yPos));
return (nHitTest == HTCLIENT) ? HTCAPTION : nHitTest;
}
Q:如何使对话框初始为最小化状态?
在 OnInitDialog 中加入下面代码:SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE, NULL);
Q:如何限定对话框大小范围?
在 WM_SIZING中加入下面代码:void CYourDialog::OnSizing(UINT fwSide, LPRECT pRect)
{
if(pRect->right - pRect->left <=200)
pRect->right = pRect->left + 200;
if(pRect->bottom - pRect->top <=200)
pRect->bottom = pRect->top + 200;
CDialog::OnSizing(fwSide, pRect);
}
Q:如何在对话框中加入状态条?
定义 CStatusBar 变量:CStatusBar m_StatusBar;
定义状态条指定状态:static UINT BASED_CODE indicators[] =
{
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM
};
在 OnInitDialog 中加入下面代码: m_StatusBar.CreateEx(this,SBT_TOOLTIPS,WS_CHILD|WS_VISIBLE|CBRS_BOTTOM,AFX_IDW_STATUS_BAR);
// Set the indicators namely caps and nums lock status
m_StatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT));
CRect rect;
GetClientRect(&rect);
m_StatusBar.SetPaneInfo(0,ID_INDICATOR_CAPS,SBPS_NORMAL,rect.Width()/2);
m_StatusBar.SetPaneInfo(1,ID_INDICATOR_NUM,SBPS
使用多媒体API
(一)实验目的:
学习使用多媒体API
(二)实验内容
使用MCI可以很好的进行多媒体的播放和录制,但当你要做一些实际工作的时候,你会发现MCI的功能远远不够,因为它不能对多媒体设备和多媒体文件进行精细的控制。要做到这些,需要使用多媒体API。
多媒体API分为许多类,例如:
waveOutxxxx 类函数用于wave声音播放设备的控制
waveInxxxx 类函数用于wave声音录制设备的控制
midiStreamxxxx 类函数用于midi声音的播放控制
midiOutxxxx 类函数用于midi声音播放设备的控制
midiInxxxx 类函数用于midi声音录制设备的控制
AVIFilerxxxx 类函数用于AVI文件的操作
AVIStreamxxxx 类函数用于AVI流的播放控制
capxxxx 类函数用于AVI图象的录制控制
mmioxxxx 类函数用于RIFF类多媒体文件的操作
.wav文件、.avi文件、.mid文件都属于RIFF(resource information file format)类多媒体文件。
下面将以声音播放设备和录制设备的控制为例,学习多媒体API的使用。其他类多媒体API的使用,请参阅MSDN文档。
(三)音频多媒体API的使用:
使用多媒体API,源文件中需要包含头文件 Mmsystem.h,在Project->Settings->Link->Object/libray module中加入库 Winmm.lib。
1、音频设备句柄
在Windows中可以发现,许多时候都会用到句柄,例如最常用的窗口句柄HWND,绘图句柄HDC等。
句柄不是指针,可以认为句柄就是一个号码,一个代表。代表一块内存,一个硬件设备或一个软件结构体。
在多媒体应用中,每一个多媒体设备都可以申请一个句柄并通过这个句柄来操作这个设备。
注意:声卡并不是多媒体设备,一块声卡上至少有一个waveOut、一个waveIn和一个midiOut设备。每一个多媒体并不是只能有一个句柄,有的多媒体设备可以同时申请多个句柄。
打开waveOut设备:
MMRESULT waveOutOpen(
LPHWAVEOUT phwo, //句柄指针,用于存放返回的设备句柄
UINT uDeviceID, //设备ID,通常使用WAVE_MAPPER由系统来选择
LPWAVEFORMATEX pwfx, //设备属性设置
DWORD dwCallback, //回调句柄,通常是一个窗口句柄HWND
DWORD dwCallbackInstance, //回调Instance,通常设为NULL
DWORD fdwOpen //回调方式,通常设为CALLBACK_WINDOW
);
下面是WAVEFORMATEX结构的定义:
typedef struct {
WORD wFormatTag; //设备类型,通常为WAVE_FORMAT_PCM
WORD nChannels; //设备通道数,1或2
DWORD nSamplesPerSec; //声音频率,通常为8000、11025、22050、44100
DWORD nAvgBytesPerSec; //通常为 nSamplesPerSec * nBlockAlign
WORD nBlockAlign; //通常为 nChannels * wBitsPerSample/8
WORD wBitsPerSample; //声音采样位数,8或16
WORD cbSize; //附加字节数,通常为0
} WAVEFORMATEX;
可以理解,应首先设置WAVEFORMATEX结构值,然后再用此结构来打开一个waveOut设备或waveIn设备,获得设备句柄。
2、声音数据信息WAVEHDR
typedef struct {
LPSTR lpData; //声音数据指针
DWORD dwBufferLength; //声音数据长度
DWORD dwBytesRecorded; //已经录制的声音数据长度
DWORD dwUser; //可不用关心
DWORD dwFlags; //可不用关心
DWORD dwLoops; //播放循环次数
struct wavehdr_tag * lpNext; //可不用关心
DWORD reserved;
} WAVEHDR;
声音数据信息WAVEHDR用于存放声音数据的信息,主要信息为数据指针和数据长度。
获得音频设备句柄以后,即可在waveOutPrepareHeader函数或waveInPrepareHeader、waveInAddBuffer函数中准备WAVEHDR,之后可以调用waveOutWrite或waveInStart进行声音的播放或录制,使用完毕后,调用waveOutUnprepareHeader或waveInUnprepareHeader函数释放WAVEHDR。
3、实例
(1)建一个多文档的MFC应用程序。
(2)在View类的头文件中加入变量:
HWAVEIN hWaveIn;
HWAVEOUT hWaveOut;
WAVEFORMATEX hWaveFmt;
WAVEHDR hWaveHdr;
char buf[102400l];
(3)建立菜单项ID_RECORD和ID_PLAY。
(4)在View类中加入ID_PLAY的响应函数:
hWaveFmt.wFormatTag = WAVE_FORMAT_PCM;
hWaveFmt.nChannels = 2;
hWaveFmt.nSamplesPerSec = 22050;
hWaveFmt.nAvgBytesPerSec = 44100;
hWaveFmt.nBlockAlign = 4;