- 在线时间
- 1957 小时
- 最后登录
- 2024-6-29
- 注册时间
- 2004-4-26
- 听众数
- 49
- 收听数
- 0
- 能力
- 60 分
- 体力
- 40950 点
- 威望
- 6 点
- 阅读权限
- 255
- 积分
- 23860
- 相册
- 0
- 日志
- 0
- 记录
- 0
- 帖子
- 20501
- 主题
- 18182
- 精华
- 5
- 分享
- 0
- 好友
- 140
TA的每日心情 | 奋斗 2024-6-23 05:14 |
|---|
签到天数: 1043 天 [LV.10]以坛为家III
群组: 万里江山 群组: sas讨论小组 群组: 长盛证券理财有限公司 群组: C 语言讨论组 群组: Matlab讨论组 |
< >取得本地internet机器的名字及IP地址 </P>< >一、下面的例子使用 Winsock API 取得本地主机的名字及地址
6 T0 |1 N1 {3 f5 _void __fastcall TForm1::Button1Click(TObject *Sender)
4 H9 Z# m, @) J{
! l9 H) q$ _% X9 [hostent *p;
4 b) J% Y" F& l9 y5 @char s[128]; # C$ p* ^6 {- {
char *p2; </P>< >//Get the computer name ! F- P) U" Z+ g7 c
gethostname(s, 128); + L, f5 S9 s! G3 r
p = gethostbyname(s);
+ M b2 a2 |# P6 W6 a x2 ?4 uMemo1->Lines->Add(p->h_name); </P>< >//Get the IpAddress
8 h: i0 m( ]4 l/ yp2 = inet_ntoa(*((in_addr *)p->h_addr)); 7 e1 F% x! o' X. a) {" C" c
Memo1->Lines->Add(p2);
9 e% a* g, p5 z$ x} </P>< >void __fastcall TForm1::FormCreate(TObject *Sender)
& o* q5 v- n i, ]5 N D) U( z7 H{
3 w9 q! K2 l$ V' _# B% @% FWORD wVersionRequested;
4 ?5 [. o0 `/ f. ?; ~" TWSADATA wsaData; </P>< >//Start up WinSock ' z3 ^0 f, y9 W
wVersionRequested = MAKEWORD(1, 1); ' Q( T; R# o1 {8 w( }& n+ ~+ e6 f( F8 s
WSAStartup(wVersionRequested, &wsaData); 5 ]! v* {% v; H7 `+ l
} </P>< >void __fastcall TForm1::FormDestroy(TObject *Sender) : V7 b7 m- R2 i# c' @& R6 }1 k
{
) i7 ]7 S3 B1 r. ZWSACleanup();
4 n( a, a! ?0 F' S; D} </P>< >用C++Builder创建数字签名 </P>< >
5 m) k( g, f7 T 如果你在网络上传递一份数据,但却存在着种种不安全的因素,使你对数据能否原封不动地到达目的地而心存疑惑,这时,你就可以给数据加上数字签名,从而使对方可以通过验证签名来检查你所传过去的数据是否已被他人修改。 </P>< > 一、程序原理 </P>< > 数字签名的工作原理还是比较简单的,它是根据你所提供的原始数据,经过复杂的算法,产生特定的数据签名,对方通过同样的过程也产生签名,如果数据已被修改,那么就不可能得到两份一模一样的签名,从而就可判断数据已被他人修改。编程人员利用Windows的CAPI接口,就可以实现数据的加密、解密和数字签名。 </P>< > 二、程序清单 </P>< > 下面用C++ Builder的语句来看一下它的具体实现过程。 # [3 s% z7 n9 }( D
先来创建数字签名,假定其数据来自于一个文件。
1 C6 C8 B5 O _+ U; N: S //变量声明:
& W! s7 Z% a2 R9 W L$ d HCRYPTPROV hProv;
+ g! m1 o3 _& U // CSP的句柄
7 U' C, G; P1 j* q HCRYPTHASH hHash; % b' K8 E# I: w7 O' ^ P4 _# T
// 散列的句柄 + c5 V* A6 k) W2 s" L7 h" q
const int BUFFER=4096;
; @- F5 A" m* \) K# ^ // 缓冲区大小常数 $ |. I0 B% z) o
BYTE pBuffer[BUFFER]; ; c, x" `" p! D2 Y& x, i7 q
// 存放读文件内容的缓冲区
; t1 M: z1 N8 W BYTE pSignature[256]; - \& u1 G: b7 e9 ~3 A+ y
// 存放签名的缓冲区 - L ]7 [8 h" H2 x4 n9 }
DWORD dSignatureLen=256; - ~1 D* C' s) p6 l! S4 l" |1 z
// 签名的长度 0 v/ C# K" X0 K+ \3 F
TFileStream *sourceFile; 8 q1 @9 ]- D1 B9 Q
// 一个文件流 7 U+ J2 r, A- ]3 e; |
if(!CryptAcquireContext(&hProv,NULL,NULL,PROV-RSA-FULL,0)) 1 o, l/ u% J& [4 E- |8 r
// 连接默认的CSP,接受它的句柄放入hProv ( o$ C6 }. a) t5 v
{ , G8 | [$ V: p8 c6 U! r) h5 O
// 错误处理 + k3 d* y% z1 _ P( o0 p
} ( b, H* v5 \; N- @
if(!CryptCreateHash(hProv,CALG-MD5,0,0,&hHash))
~( U9 c- C! W) V4 H // 创建一个散列对象,得到它的句柄放入hHash 7 ~$ y( E1 U$ L7 \, \0 {
{
7 ^& A, D$ X/ h R9 \3 r // 错误处理
- `6 t: h4 P- H: x }
0 ~0 r: M" [: F do * W8 R/ J3 ]4 O9 t- S5 ^7 s
{ 9 g+ X. I# Q5 Y7 |
dReadLen=sourceFile->Read(pBuffer,BUFFER); $ n, x/ z7 I9 E
if(!CryptHashData(hHash,pBuffer,dReadLen,0))
' ^# x" Q+ D7 {0 |5 x // 根据文件的内容计算散列值 ( F, k' j6 _, [0 l% e
{
' t5 k8 A' C& O // 错误处理
5 r6 f8 z2 P( n6 Z. L! i% V$ ] }
1 r: }& s; f' e0 V& u }while(!(dReadLen<BUFFER));
& K- r/ Q3 Q; W0 h! p if(!CryptSignHash(hHash,AT-SIGNATURE,NULL,0,pSignature,&dSignatureLen))
) P! m/ q0 ~ j1 z; Z; F; j* G% n D //使用私人密钥对散列值进行数字签名 6 l1 Z9 B1 {: `
//签名数据放入pSignature,长度放入dSignatureLen 0 G$ x2 Z' L# I, {# k; q* ~+ c7 `
// 错误处理 / ^- h" F! w& d
} 5 v5 x; {4 I( e
对基于文件的数据签名进行检验。
+ x3 i+ d$ G$ G9 E( o //变量声明: 8 D, i; r3 L A8 n: {
HCRYPTPROV hProv; " l6 b3 b* K# m
// CSP的句柄 . c; ^' }. ^0 B
HCRYPTHASH hHash;
6 V# t# X3 o/ m# ~ // 散列的句柄 ( x0 Y* f% V+ A' ]1 L% h' G! P4 M
HCRYPTKEY hPublicKey;
3 T4 M& I, J0 ^2 {0 _6 e& X. a0 w( J+ ?% P // 公共密钥的句柄 7 j$ ~. l5 D! q+ ~& Z; }
const int BUFFER=4096; , v. l& f2 }4 F1 {
// 缓冲区大小常数 . F) M' w$ G! ~" }
BYTE pBuffer[BUFFER];
7 y% j0 c" j; ~; ?* Y3 i // 存放读文件内容的缓冲区 3 x- X" M# r v4 {
TFileStream *sourceFile; // 一个文件流 ' u# H F: ^8 j" S8 X: [
BYTE pSignature[256];
3 k% w+ A# e& I* \4 R // 上一段得到的签名的缓冲区
; F7 m" J! V' ~+ c DWORD dSignatureLen; % S# g/ h( r1 ?6 [% d* x! w! @
// 上一段得到的签名的长度
# y5 v/ g: q1 R. L if(!CryptAcquireContext(&hProv,NULL,NULL,PROV-RSA-FULL,0)) u! E1 X' y) u% i4 l- E' r8 p
// 连接默认的CSP,接受它的句柄放入hProv
) `8 |* ~- ^" ]: }- w. s9 y& V { 0 j3 D; O8 n( m" w
// 错误处理
( n) D& f7 T- X( x5 N* u4 r4 `. d. d }
# V3 m9 @9 o% y if(!CryptGetUserKey(hProv,AT_SIGNATURE,&hPublicKey); // 得到公共密钥的句柄 1 _) d+ {- q R5 F9 \
{
' V, ^' P2 N- E9 s1 V. u$ P7 \ K // 错误处理
7 v* J8 }' C E" G8 |1 @ }
; ~ p; L9 W; T; ? if(!CryptCreateHash(hProv,CALG-MD5,0,0,&hHash)) // 创建一个散列对象,得到它的句柄放入hHash
# _6 W' `9 s5 m) ^$ N E {
+ H& R7 ]. G4 V O // 错误处理 + N' M! c0 W# g1 B3 x( O
} ) n( E9 U- T5 J H
do / W8 N& Y+ n8 R# F) ^0 u, `
{
* B6 m; V" h6 b2 J7 [; ^% b6 h dReadLen=sourceFile->Read(pBuffer,BUFFER); # Y0 \. `$ ~- n
if(!CryptHashData(hHash,pBuffer,dReadLen,0)) 8 o! w4 i, X/ y/ z! ]9 d
// 根据文件的内容计算散列值 ' `, i& X3 C; v( M: f( R, F3 q
{
" x0 x' ]& n$ ~" N+ k A5 T: u // 错误处理 0 U: \& _: `5 O7 T) Y
}
5 k3 Y( e0 D, ?. H0 [1 r: g& N; ` }while(!(dReadLen<BUFFER)); & t9 j) o1 j; o9 |
if(!CryptVerifySignature(hHash,pSignature,dSignatureLen,hPublicKey,NULL,0))
, U* V. |% |; T" E2 @! S; m. u {
6 F$ }" ]3 ^! N3 W3 d* V; m R7 L if(GetLastError()==NTE-BAD-SIGNATURE) ShowMessage(″文件已被修改″); 3 v5 v" r7 O' P/ m7 g* t. T5 k4 |
}
% G: x: C- F5 U& w) m else , i0 V5 t7 H, U4 z
{ ; L0 D6 V3 V' G, R
ShowMessage(″文件没被修改″); , k1 Z: h A+ J' y
} </P>< > 以上是一个数字签名的简单实现,得到的签名数据可以单独保存,也可以分开保存。 </P>< >用Enter 键 控 制 焦 点 切 换 的 方 法 </P>< >在Windows 环 境 下, 要 使 一 个 控 件 取 得 焦 点, 可 在 该 控 件 上 用 鼠 标 单 击 一 下, 或 按Tab 键 将 焦 点 移 至 该 控 件 上。 这 种 控 制 焦 点 切 换 的 方 法 有 时 不 符 合 用 户 的 习 惯。 就 图 一 而 言, 用 户 就 希 望 用Enter 键, 控 制 焦 点 由Edit1 切 换 到 Edit2。 要 实 现 这 样 的 功 能 需 借 助WinAPI 函 数SendMessage 来 完 成。 方 法 是: 先 设Form1 的KeyPreview 属 性 为true, 然 后 在Form1 的OnKeyPress 事 件 中 加 入 如 下 的 代 码。 这 样, 用 户 就 可 以 通 过 按Enter, 键 控 制 焦 点 按 定 义 好 的Taborder 顺 序 来 移 动 了 !
$ s9 E$ Y: M( k3 ~1 {1 j& u0 I4 pvoid __fastcall TForm1::FormKeyPress(TObject *Sender, char &Key) 8 R6 ?; ]7 @! M% C
{ * n0 _% k& s- W2 h1 g" E
if(Key==VK_RETURN) </P>< >{ ! x% v7 \6 i3 K2 r
SendMessage(this- >Handle,WM_NEXTDLGCTL,0,0);
8 C8 D7 u8 t# e2 L# FKey=0;
' s/ Q/ B) S1 w8 v! ~! R$ I}
8 v4 m- z3 j. Z* k} </P>< >( q H I! ? U8 j% G6 Y+ ?. O# ]
拦 截 Windows 消 息 </P>< >& a5 ^) R0 b/ Q; F" ], u: n
- --Borland C++ Builder的API后门 </P>< >---- 引子 </P>< >---- C++ Builder不愧为Borland公司的优秀产品,用它来开发Windows程序非常快捷高效,但在编程过程中你也会发现它的一些限制性,让你无法实现自己的想法。比如你无法在修改表单的系统菜单;比如使用跟踪栏时,你找不到StartTrack和EndTrack事件,而偏偏你的程序需要这两个事件。Windows API编程中,你就不会有这些麻烦,只需处理一下WM_SYSCOMMAND和WM_HSCROLL(或WM_VSCROLL)消息,就能实现上述功能。Windows API的缺点是编程十分麻烦,太多的时间要耗在细节上面,但它的功能却是最强大的。C++ Builder的VCL在功能上只是它的一个子集,因为VCL是在API的基础上封装的,封装时舍弃了一些不常用到的功能。但是程序员的想象力没有被封装,他们总怀着更大的热情去实现别出心裁的想法,修改系统菜单和给跟踪栏增加StartTrack和ndTrack事件只是其中的小把戏而已。可是VCL并没有这些功能,怎么办? </P>< >---- 幸好,Borland公司没有把路堵死,而是留了个后门--允许程序员自己拦截并处理Windows消息,就象API编程一样。于是,办法有了... </P>< >---- 方法 </P>< >---- 拦截Windows消息需要以下几步:
b/ S8 a, j2 s* I q---- 在表单头文件内(如Unit1.h)
0 w' p' j8 U5 T3 x5 l: l7 H) E---- 1. 在类声明中建立消息映射表,把某条消息的处理权交给自定义的消息处理函数。 </P>< >BEGIN_MESSAGE_MAP 7 q0 s8 G, I$ }! W
MESSAGE_HANDLER(Windows消息名,TMessage,消息处理函数名)
) g/ G+ n- D% C0 O0 S `$ X iMESSAGE_HANDLER(...)
% U" }; S5 A( A7 UEND_MESSAGE_MAP(TForm) </P>< >
8 a9 _& ?& p- v/ F* S( y---- 2. 在类声明的private区内声明消息处理函数。 </P>< >private: // User declarations & f6 n O- g) h- K
void __fastcall 消息处理函数名(TMessage &Message);
$ \( L2 ]; G+ m Q8 p3 i$ _$ e在表单文件内(如Unit1.cpp) </P>< >
5 v2 s& g) G: L* ~" c# N---- 3. 写出消息处理函数,在这里实现你需要的功能。比如 % n, u; R9 H7 e% V
void __fastcall MainForm::OnWMHScroll (TMessage &Message) 7 @8 s" q0 j3 J
{
8 \* d1 i0 w. ?! {* {6 g" H# b7 N... // 在此加入你自己的代码
7 c3 y9 r6 h1 d: bTForm: ispatch(&Message); 6 w+ I- j8 X7 R1 d3 B3 o
} </P>< >
& {& L* v6 T8 {. j1 A4 O---- 解释 </P>< >---- 1. 关于TMessage </P><P>---- TMessage是VCL预定义的结构,定义如下:
+ p. D% N9 U" pstruct TMessage 0 x! q+ V3 \" s# J2 h) o
{
/ e$ t* r0 B A& {$ W) Y8 Vunsigned int Msg; //消息
{. P/ Y, y6 K0 G1 gint WParam; //字参数
- E, I% `: T3 o4 a* ~int LParam; //长字参数 , S6 z% C: \6 {2 {) }7 }- _- u
int Result; //消息结果 - b5 O0 i' i7 ^* h! d
}; </P><P>
* K0 P3 J- b: }/ v---- 2. 关于TForm: ispatch(&Message) </P><P>---- 自定义的消息处理函数末尾最好加一句TForm: ispatch(&Message),这一句的作用是让消息继续传递下去。如果没有这一句,消息将被完全拦截,VCL类可能由于得不到消息而无法实现正常功能。 </P><P>---- 实例一:修改系统菜单 </P><P>---- 有一些程序,主窗口很小,菜单也没有,如果想加入关于或设置对话框,最好的办法是拿系统菜单开刀。Windows API编程中,修改系统菜单与实现其他功能一样,不太容易,也不会太难。但在C++ Builder中,表单类(TForm)没有提供有关系统菜单的任何属性与方法,实现其他功能易如反掌,而修改系统菜单似乎难于上青天。 </P><P>---- 还好,Borland公司允许程序员自已处理Window消息,于是机会来了! </P><P>一、用Window API函数修改系统菜单 </P><P>假定表单名为MainForm,设置MainForm::OnCreate()函数: </P><P>1. 用GetSystemMenu(MainForm->Handle,false)取得系统菜单句柄; </P><P>2. 用AppendMenu,DeleteMenu,ModifyMenu函数修改系统菜单,把新的ID号赋于自定义的菜单项。
* A m+ h; Y- P8 l5 ?) @这时运行程序,可以看到系统菜单也被修改,但自定义的菜单项却不能被响应。 </P><P>二、拦截WM_SYSCOMMAND消息以响应自定义的菜单项 3 R _! q' }: n4 z
在表单头文件内(如Unit1.h) </P><P>1. 在表单类定义末尾加入消息响应表,取得WM_SYSCOMMAND消息的处理权
* _$ P; Y0 J/ [8 aBEGIN_MESSAGE_MAP 0 D/ S) t) }% j; k% H& U" A! }
MESSAGE_HANDLER(WM_SYSCOMMAND,TMessage,OnWMSysCommand)
2 A* ]' n' E- t# s; B, VEND_MESSAGE_MAP(TForm) </P><P>2. 在表单类定义的private区内加入消息处理函数声明 9 t4 @% w* D8 s7 m+ g
private: // User declarations 8 X8 J6 e M+ g0 @( X5 H( d
void __fastcall OnWMSysCommand(TMessage& Message); </P><P>在表单文件内(如Unit1.h) </P><P>3. 写出消息响应函数
- P; {1 f) x: v& l8 [void __fastcall TForm1::OnWMSysCommand(TMessage& Message)
; L8 Y1 T+ v0 `/ J1 ]" H$ V! q/ m{
" _/ O; ^& g9 }5 W8 q! _) z" W2 \if(Message.WParam==ID_SysMenu_MyItem)
s& p& V1 A; x% c M1 I+ L" Z" [{ & l4 _8 ?9 o* c% \2 R- i
// Your Code Here, Do Something " E9 a: d# [) @% K8 t! C
} ( Q5 k" K8 ~! r
TForm: ispatch(&Message); ! d( z |' n" Y7 w$ I5 g
} </P><P>
5 N3 @/ z9 W0 o2 k三、完整程序示例 </P><P>实例二:给跟踪栏增加OnStartTrack和OnEndTrack事件 </P><P>当跟踪栏用于进度控制时,OnStartTrack和OnEndTrack很可能是你需要的事件。比如在控制多媒体播放进度的场合,当用户移动滑块时,你需要OnStartTrack事件让播放停止,需要OnEndTrack事件定位新的播放位置。但Borland公司没有提供这两个事件,我等编程爱好者只好自力更生,打拦截Windows消息的主意了。 </P><P>一、拦截WM_HSCROLL消息,给跟踪栏增加OnStartTrack和OnEndTrack事件 </P><P>在表单头文件内(如Unit.h) </P><P>1. 在表单类定义末尾加入消息响应表,把WM_HSCROLL消息处理权交给OnWMHScroll函数。
" c5 q8 @6 J8 ~' t: aBEGIN_MESSAGE_MAP
6 I# p# t- U# k" v7 g2 [MESSAGE_HANDLER(WM_HSCROLL,TMessage,OnWMHScroll)
% }4 e7 ^/ n6 REND_MESSAGE_MAP(TForm) </P><P>2. 在表单类定义的private区内加入OnWMHScroll函数声明。 1 ~) B$ J: P, h
private: // User declarations , M3 }4 k; s+ l0 U
void __fastcall OnWMHScroll(TMessage &Message); </P><P>
( L, n% t- {* T9 \3. 在表单类定义的private区内加入StartTrack和EndTrack函数声明。
5 ^* S- J- Q: R. t& _private: // User declarations 6 ^7 k w/ ?3 _& p4 [
void __fastcall TrackBar1StartTrack(TObject *Sender); 1 ?3 k+ c4 z& N1 ]) q# }& f" w( P( s
void __fastcall TrackBar1EndTrack(TObject *Sender); </P><P>在表单文件内(如Unit.cpp) </P><P>4. 写出OnWMHScroll函数,使它能根据消息参数调用StartTrack和EndTrack函数,在实际意义上产生OnStartTrack和OnEndTrack事件。 </P><P>5. 写出StartTrack和EndTrack函数。 </P><P>如果是垂直跟踪栏,把上面的WM_HSCROLL改为WM_VSCROLL即可。 </P><P>二、完整程序示例 </P><P>尾声 </P><P>Borland C++ Builder编程中,拦截Windows消息是一项高级编程技术,能让你尽量挖掘Windows的潜力,尤其让曾用API编程的程序员感到心慰。拦截Windows消息是API尽情发挥的舞台,当VCL不能为你做什么时,请想起底层的API。 </P><P>使用CommaText </P><P>有时需要一个方便的方法存放一个StringList,它只有简单的一行。例如,当你想使用一个INI文件,如何向一个INI文件中写入一行呢,使用CommaText 就能完成这个工作。 </P><P>这里有个例子,功能是创建一个blah.ini文件,并写入一个如下形式的值: </P><P>[My Section]
O6 B J+ [1 ^, I; G% u2 zMemo1=(你在Memo1中输入的文字) </P><P>1.在Form1上有两个按钮btnLoad and btnSave和一个Memo1 </P><P>2.还要加入:
$ p4 d0 r( T% V! k F+ Z#include <inifiles.hpp> </P><P>3.定义变量: " n( B2 R5 {& m6 O3 z* [# q
const String iniFile="blah.ini",iniSection="My Section",iniValue="Memo1"; </P><P>4.保存按钮代码: ( T% L! e& T: F, ^- Z* q( A
void __fastcall TForm1::btnSaveClick(TObject *Sender)
9 N+ Q5 ], F# r% T{ " V; o' E8 h) z0 Y
TIniFile *ini=new IniFile(ExtractFilePath(Application->ExeName)+iniFile); 6 s# K% |* P3 W/ F
ini->WriteString(iniSection,iniValue,Memo1->Lines->CommaText); 6 Y9 F% l' s; t9 J6 ?, Q3 S5 r! ? @
delete ini; ) q% g( E' e7 V) O2 U7 b
} </P><P>5.装载按钮代码:
/ N3 D6 u' e; v' Q# |( Vvoid __fastcall TForm1::btnLoadClick(TObject *Sender)
/ {; N2 a- X, K; P5 o{ * u* \7 X1 \7 d0 T9 T/ m% O
TIniFile *ini=new TIniFile(ExtractFilePath(Application->ExeName)+iniFile); ! H8 N. h& F' z* `
Memo1->Lines->CommaText=ini->ReadString(iniSection,iniValue,"");
6 L9 s; Z* w8 T) J" [! ndelete ini;
! D1 A0 l( U7 s2 L" X u} </P><P>6.以下代码支持加载后对内容进行排序,到实际存储不变:
/ @& R/ L/ H) m: E; ^: P) pvoid __fastcall TForm1::btnSortLoadClick(TObject *Sender) 4 p) P _) p6 z8 I2 R. e7 p3 N5 X1 m
{ : E$ q+ L& Q8 k, Y6 p
TStringList *sl=new TStringList;
0 A0 [$ a' l* f. v* ^. r/ OTIniFile *ini=new TIniFile(ExtractFilePath(Application->ExeName)+iniFile);
( J( r( u- C Y) v- nsl->CommaText=ini->ReadString(iniSection,iniValue,"");
; j' q' m% c$ ~6 M: esl->Sort(); + E# E" r2 d. _. {) i @+ p; p
Memo1->Lines=sl;
5 V$ m/ `2 i! g$ S$ Edelete ini; % G5 n% Z+ {8 y% b
delete sl;
i3 B5 x% f- q) ~5 Y5 r6 |4 E} </P><P>程序开始时先显示信息框 </P><P>一、软件进入主窗口前,先显示一个信息框,告诉用户一些有关该软件的信息,比如软件名称,版本号等。该信息框在显示1~2秒后自动消失。 2 g P3 p: c" q1 [
1.建立New Application,这时系统自动生成一个Form1,这作为主Form. </P><P>2.File->New Form 建立一个新Form为Form2,这个作为信息框。 </P><P>3.在Form2上添加组件TTimer(System控件条上),用于设定信息框的显示时间。 $ K1 F! l, n4 X2 d3 k/ L+ g
4.TTimer的事件OnTimer中加入:Form2->Close(); </P><P>5.在WinMain()函数中加入:
1 W1 `' O* z3 \Application->CreateForm(__classid(TForm2), &Form2);
3 y! @, n9 z; I$ m7 w, CForm2->ShowModal( ); //这句要自己加入 - _ Q% E: q, d. M
Application->Run(); - y; U& p* G. ^1 u$ D
并且要把Form2的头文件Unit2.h包括到WinMain()所在的Project1.cpp中。 </P><P>6.运行程序,将先显示Form2,显示时间由TTimer的Interval属性决定,1000是一秒。 </P><P>二、软 件 封 面 的 实 现 9 r7 o, ?" U- L
现 代 软 件 设 计 的 流 行 做 法 是, 在 程 序 运 行 完 成 初 始 化 之 前, 先 调 用 一 幅 画 面 做 为 封 面, 通 常 是1/4 屏 幕 大 小, 显 示 一 下 软 件 的 名 称、 作 者、 版 本 等 信 息。 要 用C++ Builder 实 现 这 样 的 功 能, 方 法 很 简 单: </P><P>① 自 定 义 一 窗 体 类 TSplashForm, 将 其 设 置 成" 透 明 窗 口", 即 BorderIcons 下 的 所 有 选 项 均 置 成false, BorderStyle=bsNone,FormStyle=fsStayOnTop, Position=poScreenCenter; </P><P>② 在TSplashForm 窗 体 上 放 置 一TPanel( 相 当 于 图 形 的 镜 框); </P><P>③ 在TPanel 上 放 置 一TImage 控 件, 调 入 所 需 要 的 图 形; </P><P>④ 对WinMain 函 数 稍 加 修 改, 加 入 如 下 所 示 代 码 即 可。 需 要 指 出 的 是, 这 段 代 码 通 过 函 数 FindWindow, 搜 索 内 存 中 是 否 有 窗 口 标 题 为 "Demo" 应 用 程 序 存 在, 若 存 在, 则 退 出 程 序 的 运 行。 该 功 能 可 防 止 程 序 的 再 次 运 行。 在 某 些 场 合 这 样 设 计 是 必 须 的。 </P><P>WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
0 w: C. a9 N$ s/ o0 Y{
' @( _6 N( K! y6 @3 x5 R1 ptry
! b3 e% i+ c4 B( l5 r. R{
3 [( p, V7 Z+ b# v! C7 Z' d& oif(FindWindow(NULL,"Demo")!=0) ! k$ V5 j/ c" u N" ^9 B5 T) u
{
2 u! t1 `0 ]4 t2 }Application- >MessageBox (" 程 序 已 经 运 行!"," 警 0 P& Z |1 j' H# M+ g
告",MB_ICONSTOP); # P \# A- Q1 t' }" [: h
return 0; 4 J1 F2 K0 i) @' g
} </P><P>TSplashForm *splash=new TSplashForm(Application); </P><P>splash- >Show(); </P><P>splash- >Update(); </P><P>Application- >Initialize(); </P><P>Application- >CreateForm(__classid(TForm1), &Form1); </P><P>splash- >Close(); </P><P>delete splash; </P><P>Application- >Run(); </P><P>} </P><P>catch (Exception &exception) </P><P>{ </P><P>Application- >ShowException(&exception); </P><P>} </P><P>return 0; </P><P>} </P><P>怎样获取程序的命令行参数? </P><P>
2 P/ l6 [. w2 l% @* ?! V你可以用下面的两种不同的技巧来解决这个问题。 </P><P>技巧1:首先,也许是最简单的方法是调用VCL ParaStr()函数。你可使用ParamCount()函数来确定到底有多少个命令行参数传递给了应用程序。 </P><P>ParamStr需要一个整数参数并且返回一个AnsiString对象。若参数为0,ParamStr 将返回可执行文件的全称路径。若参数为1,将返回程序名及第一个命令行参数。若参数为2,将返回第二个参数,等等。 </P><P>作为一个实践,开启一个新的项目,在主窗口上放置5个Label,将下面的代码添加到窗口的构造函数中: </P><P>Label1->Caption = ParamStr(0);
: e& t; f2 R {% a& `5 bLabel2->Caption = ParamStr(1);
: s2 \% w2 F9 R) M( P( mLabel3->Caption = ParamStr(2);
' t* p0 ?, s7 YLabel4->Caption = ParamStr(3);
. K7 d* n8 T, JLabel5->Caption = ParamStr(4); . V2 p9 p% o- \8 w5 S. Z0 p: L
再运行程序。一般应能看到类似字符串: </P><P>E:\CBUILDER\PROJECTS\PROJECT1.EXE 5 s! M0 u C- q
如果没传递参数到程序,那么Label2到Label5是空字符串。关闭程序,从C++Builder菜单中选择 Run | Parameters。输入几个参数(-debug -testing -param)再次运行程序。你将看到: </P><P>E:\CBUILDER\PROJECTS\PROJECT1.EXE
# x( H; d' X1 Y" J, ]-debug
5 U" A1 L- x3 C7 b X( F. Y-testing
4 k8 I/ V( H! v( ?% h2 v-param
. N$ l R$ e: P* ^1 [9 Z提示: ParamStr 对目录中的空格能智能判断。为证实这点,把生成的EXE文件拷贝到Program Files目录下再运行它,你将会看到ParamStr(0)返回全路径,并包含空格。 </P><P>技巧2:第二个方法就是调用GetCommandLine API函数。GetCommandLine不需要参数,并且返回一个C风格的char *,包含全部的命令行参数。你将不得不分解字符串以取得相关参数。 </P><P>Label5->Caption = AnsiString(GetCommandLine()); & h$ c/ y1 u# b, f/ Q
运行后,Label5将为: </P><P>"E:\CBuilder\Projects\Project1.exe" -debug -testing -param </P><P>2 O: Z6 W2 f4 H1 L1 T Q; F7 Y
如何监视剪贴板 </P><P>在Form1的.h的private加上:
& U0 p/ s5 ?( B) I0 l! }void __fastcall ClipboardChanged(TMessage& Msg); </P><P>在Form1的.h的public加上: / t, q q& D) L/ ]
BEGIN_MESSAGE_MAP
% H: l$ `! M* W/ v' D8 s7 w% j MESSAGE_HANDLER(WM_DRAWCLIPBOARD,TMessage,ClipboardChanged) % k7 M4 H; J: m6 |# y
END_MESSAGE_MAP(TForm) </P><P>在Form1的.cpp内加上:
& `: A5 y6 t. D# ]" h) o$ Fvoid __fastcall TForm1::ClipboardChanged(TMessage& Msg) 4 B# |8 U- U. V* J# I" ]7 g
{ + |; g8 E( u( L3 S' j3 L' c6 s( A6 i
POINT MousePos; # a8 Z+ F# X6 [) V6 h3 r
GetCursorPos(&MousePos);
% p h" C0 I3 r( F. g- f PopupMenu4->PopupComponent=Form1; 5 L) z5 a* |' g
PopupMenu4->Popup(MousePos.x,MousePos.y); //一有变化,就弹出一个菜单,复制,剪切或清除都能引发此函数
1 {/ I, a- W; l1 q& r5 i) R} </P><P>在Form1的.cpp内有一个ToolButton 6 } g2 A( U' E/ x& U" X
void __fastcall TForm1::ToolButton9Click(TObject *Sender)
- u2 V, N4 f5 i{
( j2 I4 u4 n8 C static HWND LastHandle; ' X: M1 ~, i& V7 X, [/ q
static bool clip=false;
- B. \4 e% P7 c if(clip==true) 1 j+ w; v7 n& o/ [3 D
{ 8 J* W! ^' b5 H
ToolButton9->Down=false;
' v2 N% S" p# b1 H ChangeClipboardChain(Form1->Handle,LastHandle); //结束监视
- o7 |, g" S" a V* p } 9 L0 J8 v. K7 O; z3 B
else
( v/ b* ^' \+ X; {8 d* n/ |' R {
4 A/ h& h1 O: S+ ~ ToolButton9->Down=true; p0 _& X9 I+ F' l: t5 C
Clipboard()->Clear();
2 d7 q4 W/ x2 w+ p2 c6 m+ Z Application->Minimize(); 6 ?9 b9 w: o# B! V+ L
LastHandle=SetClipboardViewer(Form1->Handle); //启动监视
: _' X6 Z. y0 M( e7 P } 6 z* A+ O1 y4 t1 b% f3 Z, ~1 P ~- y
clip=!clip; # D; A+ n' _. o" C- X9 L g: w
} </P><P>
/ Z/ ~4 N K( @& w! |% c' s! m如何使用OnIdle事件 </P><P>使用OnIdle事件随时监视剪贴板内容以改变弹出菜单的可执行项。 </P><P>在Form1的.h的private加上:
9 v: I; F8 C& t& u9 G& s* hvoid __fastcall OnIdle(TObject* Sender,bool& Done); </P><P>在Form1的.cpp内加上: " a; ^: k; [$ c* j" V* U0 Y- v
void __fastcall TForm1::OnIdle(TObject* Sender,bool& Done) - P4 P. B3 |9 q4 S! T4 k/ G
{ 8 c# F9 ]+ f( ~6 N ?
bool TextSelected=DBRichEdit1->SelLength>0;
2 A0 y: @6 u& a: c+ h x5 _, ` N17->Enabled=TextSelected;//剪切,复制,清除
\; u- g3 w6 }8 \1 U: V/ R' Q N18->Enabled=TextSelected;
6 A% x& j2 ~* A N20->Enabled=TextSelected; ( [9 ?: j" z! o, ?6 P
bool CBHasText=Clipboard()->HasFormat(CF_TEXT);// 需加入#include<Clipbrd.h> ) m5 n/ U+ U1 U2 N+ ~. e* V# ^
N19->Enabled=CBHasText;//粘贴
9 H6 Z" G7 A* r2 v% \. a0 G bool HasText=RichEdit1->Lines->Count>0;
$ _8 H' V$ I- q' w N21->Enabled=HasText;//全选
/ N( T3 R& f$ j) R bool HasChanged=RichEdit1->Modified; / ?* c5 ~- Q; r: W: q. E
ToolButton2->Enabled=HasChanged;
6 H( T2 p- D/ w ToolButton4->Enabled=HasChanged;
; o" D( r6 m" z" N) K8 l} </P><P>在Form1的OnCreate内加上: 5 H, j# Y' j6 z r- p8 W
Application->OnIdle=OnIdle; </P><P>5 _5 o3 p; s7 ?2 y6 r" I, Z& X
v) s% b- G: Q5 ?: K用C++Builder4.0编写Win 95下的串行异步通信程序 </P><P> ·串口操纵的基本方法· </P><P>
& t: t4 h' ]% m+ ? 在Win32下,对串口的操作就如同对文件一样打开或关闭,对串行数据的读写可在用户定义的读写缓冲区中进行。具体使用的函数为: </P><P> 首先用CreateFile( )打开通信串口,其中参数lpFileName指向串口逻辑名,如“COM1”或“COM2”等,参数dwDesiredAccess定义文件的读写权限,一般设为GENERIC-READ|GENERIC-WRITE;参数dwShareMode定义资源共享方式,此处必须设为0,为独占方式;lpSecurityAttributes定义安全属性,Win 95下为NULL;dwCreationDistribution定义文件创建方式;dwFlagsAndAttributes定义文件属性和标记,应设为FILE-FLAG-OVERLAPPED,表示异步通信方式;hTemplateFile 指向一个模板文件的句柄,在 Windows 95下为NULL。 </P><P> 然后用BuildCommDCB( )和SetCommState( )函数通过通信设备控制块DCB(Device Control Block)设置串口通信参数(如波特率、停止位、数据位、校验位等),其中BuildCommDCB( )中的字符串参数lpDef 定义同DOS命令中MODE的参数格式,关于DCB更具体的设置需要根据用户对数据流定义、握手信号及通信控制要求具体定义,参见有关Windows技术资料。用GetCommState()可以得到当前的DCB参数值。如果需要还可通过SetCommTimeouts()和GetCommTomeouts()重新设置读写的超时参数;读写缓冲区的设置使用SetupComm(),参数dwInQueue和 dwOutQueue分别定义为输入和输出缓冲区的大小。 </P><P> 在串口初始化完毕后,还要建立与通信有关的事件对象。一般使用CreateEvent()函数,它返回一事件句柄,其中参数lpEventAttributes指向安全属性结构地址,在Win 95(无安全属性)中为NULL;布尔参数bManualReset 定义事件重置方式,true 表示手工重置,false表示自动重置(相关函数为SetEvent()和ResetEvent());参数bInitialState定义事件初始状态,true表示发信号,否则为不发信号;lpName是为多进程设置的事件名,对于单进程定义为NULL。然后用SetCommMask()定义用户程序可监视的通信事件类别。 </P><P> 以上设置完成后,用户程序就可以等待通信事件的产生,一般调用函数WaitCommEvent()监视通信事件,其中参数lpEvtMask指向产生事件的掩码地址,用于判断事件产生的性质,lpOverlapped指向重叠结构地址,可简单定义为NULL。对于串口事件的响应一般有四种方式:查询、同步I/O、异步I/O和事件驱动I/O,需要根据用户不同控制要求而定。查询方式占用较长的计算机时间,同步I/O方式直到读取完指定的字节数或超时时才返回,容易造成线程阻塞,异步I/O用于后台处理,事件驱动是由系统通知用户程序发生的事件并进行串口操作。 比较而言事件驱动I/O方式较灵活。 </P><P> 当有通信事件产生时,就可用函数ReadFile()和WriteFile()直接对串口缓冲区进行读写操作了。其中lpBuffer 指向读写缓冲区,nNumberOfBytes为要读写的字节数,lpNumberOfBytes为实际读写的字节数,lpOverlapped指定同步或异步操作。通信结束后,调用函数CloseHandle()将串口关闭。 </P><P> ·应用实例说明· </P><P>3 ^! Q" f- W8 u, `* |5 R5 R; d. ~3 b
使用以上的API函数,笔者给出了简化后的串口初始化的实例。图1为使用C++ Builder 组件生成的串口通信基本参数设置的界面实例。 </P><P> HANDLE hcom; //定义句柄 </P><P> DCB dcb; </P><P> OVERLAPPED e; //定义重叠结构 </P><P> void -fastcall TForm1::OkBtnClick(TObject?Sender) </P><P> { hcom=CreateFile("COM2",GENERIC-READ|GENERIC-WRITE,0,NULL,OPEN-EXISTING, ! E/ M0 R: v$ W/ q$ f4 r& s0 j* C
FILE-ATTRIBUTE-NORMAL|FILE-FLAG-OVERLAPPED,NULL); //打开通讯口 </P><P> BuildCommDCB("9600,O,8,1",&dcb); </P><P>//第一个字符串参数实际使用时由图1选择后组合,这里仅简单说明其格式 </P><P> SetCommState(hcom,&dcb); </P><P> SetupComm(hcom,512,512);//设置读写缓冲区 </P><P> e.hEvent=CreateEvent(NULL,false,false,NULL); //设置事件 </P><P> SetCommMask(hcom,EV-RXCHAR| EV-TXEMPTY); //设置事件掩码 </P><P> OkBtn-〉Enabled=false;} </P><P>C++BUILDER非可视组件的消息处理技巧 </P><P> 一个非可视的组件必须对Windows操作系统或用户定义的消息作出响应。然而,由于一个非可视组件没有窗口,因此它也没有窗口句柄,自然它也不能接收到消息,为了解决这一问题,我们的思路是创建一个隐藏的窗口,使非可视组件能够接收到消息。 </P><P> 为了给你的非可视组件创建一个隐藏的窗口,需要有以下: </P><P> 1.一个私有变量型(Private Variable)的HWnd来取得窗口句柄。 </P><P> 2.一个用来捕捉窗口发送给组件的函数(a WndProc)。 </P><P> 3.对AllcolateHwnd的调用使之创建窗口句柄并设置WndProc。 </P><P> 为了清楚的解释上述思路和展示创建过程,下面我们将以一个具体的实例来说明。 </P><P> 首先我们先创建一个新的组件,在C++Builder中,选择FILE|NEW...双击组件图标显示一个新的组件对话框改变Ancestor Type为Tcomponent和Class name为TTest并设置完毕。 </P><P> 然后,切换到新组件的头文件,在类的私有部分(private section)加入以下声明: </P><P> HWnd FHandle; </P><P> void-fastcall WndProc </P><P>(TMessage& Msg); </P><P> 第一行声明了一个调用Fhandle的HWnd变量,这个变量将用于窗口创建后捕获窗口句柄。第二行声明了一个用于接收消息的WndProc函数。这个函数的声明必须加以标识,以便限定它是一个WndProc,然后在类声明Public(公有)部分构造以下声明: </P><P> Viod DoIt( ); </P><P> 这个公有函数将被我们用来测试组件,类声明应如下: </P><P> class PACKAGE TTest : public </P><P>TComponent </P><P> { </P><P> private: </P><P> HWnd FHandle; </P><P> void-fastcall WndProc </P><P>(TMessage& Msg); </P><P> protected: </P><P> public: </P><P> -fastcall TTest </P><P>(TComponent* Owner); </P><P> void DoIt( ); </P><P> -published: </P><P> }; </P><P> 现在切换到组件的代码单元,将下面一行加入到单元的顶部(在函数上也许是不错的地方) </P><P> #define MY-Message.WM_USER+1 </P><P> 这一行声明了一个在DoIt函数被调用时,组件将发送给它自己的用户自定义消息。此时我们必须为组件分配一个窗口句柄。这个句柄将提供一个隐藏的窗口使我们可以捕捉组件中的消息。找到组件构造代码,加入下面代码: </P><P> -fastcall Test::Test </P><P>(TComponent* Owner) </P><P>: TComponent(Owner) </P><P> { </P><P> FHandle=AllocateHWnd </P><P>(WndProc); </P><P> } </P><P> 好,重要的一步已完成,AllocateHWnd函数创建了一个隐藏窗口并且返回它的句柄,注意这里我们为了使Windows知道哪里发来了消息,传递WndProc的地址; </P><P> 现在我们来创建WndProc的函数部分。在源文件中加入: </P><P> void-fastcall TTest::WndProc </P><P>(TMessage& Msg) </P><P> { </P><P> if (Msg.Msg == MY_MESSAGE) </P><P> MessageBox(0, ″Got here!″, ″Message″, 0); </P><P> try { </P><P> Dispatch(&Msg); </P><P> } </P><P> catch (...) { </P><P> Application-〉HandleException(this); </P><P> } </P><P> } </P><P> 无论何时Windows发送消息给组件,Windows都会调用这个函数。这部分代码完成了两件事。首先,它检查被接收的消息是否是我们用户自定义的消息。如果是,一个消息框将被显示,你可以看到实际上我们接收到的消息。其次,这段代码传送了系统(或VCL)处理过程中的消息,try/catch块用来保证,如果异常出现,它将成为缺省风格下的句柄。 </P><P> 概括地说,WndProc函数在为缺省句柄传递所有其他消息,监控了所有客户消息。现在我们创建DoIt函数,完成我们的组件,加入我们创建DoIt函数,完成我们的组件,加入代码: </P><P> void TTest: oIt() </P><P> { </P><P> PostMessage(FHandle, </P><P>MY-MESSAGE, 0, 0); </P><P> } 这个函数发送一个消息组件的窗口句柄(记住,这个窗口句柄是以前存入到Fhandle数据成品中的)。现在我们已经完成了创建组件选择,用SelectFile|ColseAll来保存我们的工作测试组件。 </P><P> 下一步将测试组件。如果你使用BCB3,那么你必须把组件加入到“包”(Packege)中,然后用Componet|install(可以使用DCLSTD35 Packege来快速测试)。再选择你刚存的TestBCB.Cpp,一旦你安装完成组件后,它将出现在组件板上。双击按钮,为按钮的OnClick事件创建以下代码: </P><P> Test1-〉 DoIt( ); </P><P> 现在运行程序,当你点击按钮时,将看到一个消息框显示“Got here". </P><P> ListingA和B包含了头文件和源代码以下列出。 </P><P> 总结:一个可以响应Windows消息的非可视组件有许多用途。最显而易见的就是用来封装某些方面的WindowsAPI。例如:TAPI和WinSock发送消息给事件的指定用户。如果你写的组件封装了一个这样的API。你将需要捕捉Windows发送的消息。而在你的组件中加入隐藏窗口将很好的帮你做到这一点。 </P><P> 以上程序在C++ BUILDER 3.0中调试通过。 </P><P>用C++Builder 建立数据库VCL使用经验 </P><P> 随着数据库的广泛应用,数据库编程已经成为程序设计中发展迅猛的一支。C++ Builder在数据库开发方面具有的强大功能是无可比拟的,你甚至可以不写一行程序就生成漂亮的数据库程序。 </P><P> 下面对C++Builder中的几个数据库VCL的使用技巧做一下介绍: </P><P> 一、DBGrid控件 </P><P> 1.设置DBGrid的字段显示宽度属性 </P><P> 为了在DBGrid中建立较小的列,你必须建立一个显示标题,它等于或小于字段值。例如,你希望建立一个只有三个字符宽的列,你的列标题显示必须只有三个字符或更少。 </P><P> 2.改变DBGrid的显示字段及日期显示格式 </P><P> (1)双击DBGrid对应的Table1,进入字段编辑器。 </P><P> (2)点右键出现选单选“Add Fields…" ,出现添加字段对话框,选择要添加的字段(该字段将在运行时由DBGrid显示)然后点OK按钮。 </P><P> (3)假设添加了“日期”字段,点该字段,在属性表中的:DisplayLabel中填入你希望DBGrid显示的字段名。如果原来字段名是英文的,这里用中文名后DBGrid将显示中文名。在DisplayFormat中填入:yyyy-mm-dd,以后日期将按1999-05-28格式显示。 </P><P> 二、Tquery控件 </P><P> Tquery 控件是数据库编程中非常重要的一个控件,它负责通过BDE与数据库建立联系,通过SQL语句方便的建立查询。Query必须建立相应的SQL才能生效。 </P><P> Tquery的参数设置如下: </P><P> (1)在SQL属性中:Select * from 表名 where 字段名=:变量名 </P><P> 跟在“ : "后面的是变量。这样写后,在参数属性中就可以修改该变量的数据类型等。 </P><P> (2)对变量的赋值: </P><P> Query1-〉Active=false; </P><P> Query1-〉Params-〉Items[0]-〉AsString=Edit1-〉Text; </P><P> Query1-〉Active=true;//查找符合变量的记录 </P><P> (3)用DBGrid显示结果 </P><P> DBGrid的DataSource与DataSource1连接,而DataSource1的DataSet与Tquery1 连接。 </P><P> 三、应用示例 </P><P> 通过Query控件嵌入SQL语句建立的查询比Table更简单、更高效。 </P><P> 用一个简单的代码来说明如何建立查询程序: </P><P> 例如,要建立一个检索表1中书名为book1的程序则在表单上放置DBGrid,DataSource,Query三个控件加入以下代码: </P><P> DBGrid1-〉DataSource=DataSource1; </P><P> DataSource1-〉DataSet=Tqery1; </P><P> Query1-〉Close(); </P><P> Query1-〉SQL-〉Clear(); </P><P> Query1-〉SQL-〉Add(″Select * From 表 Where (书名=′book1′ ″); </P><P> Query1-〉ExecSQL(); </P><P> Query-〉Active=true; </P><P> 你就可以在生成的表格中看到所有名称为book1的记录。 </P><P>用C++ Builder创建基于Internet的点对点Chat </P><P>---- 创建基于Internet的应用程序,你也许会想到复杂的WinSock编程。不过,C++ Builder3提供了新的WebBroker的Internet套件,其中的TClientSocket和TServerSocket组件封装了Windows的有关API,大大简化了WinSock编程。要通过Internet传输数据,至少需要一对Socket,一个Socket在客户端,另一个Socket在服务器端。其实TClientSocket、TServerSocket组件并不是Socket对象,其属性Socket将返回各自的Socket对象。TClientSocket用来处理客户端到服务器端之间的socket连接,TServerSocket用来处理由客户端发来的socket连接,一旦客户端和服务器端都接通了socket,客户端和服务器端就可以相互通信了。 </P><P>---- 建立一新项目,创建应用程序的用户界面: </P><P>---- 1.将组件页切换到Internet页,放一个TServerSocket组件和一个TClientSocket组件到窗体上,这样应用程序既可以是TCP/IP服务器,也可以是TCP/IP客户。将Port属性都设为同一个值(如1000),确定Socket之间的连接类型为NonBlocking(非阻塞方式)。 </P><P>---- 2.放两个TMemo组件到窗体上,用来分别显示双方的谈话内容,将Memo2的ReadOnly属性设为True。 </P><P>---- 3.在窗体的顶部放上一个Panel组件,在其上放三个按钮:监听(btnlisten)、连接(btnconnect)、断开(btndisconnect),用来启动相应的操作。 </P><P>---- 4.在窗体底部放一个StatusBar组件,将其SimplePanel属性设为True,在相应的事件处理程序中改变状态条信息,让用户随时了解连接状态。 </P><P>---- 打开头文件,在窗体类的Private段添加两个私有成员: bool IsServer;String Server。双方通信时需同时运行Chat程序,IsServer用来确定哪个Chat程序处于服务器端,Server用来存放服务器的主机名。建立窗体类的构造器如下: </P><P>__fastcall TForm1::TForm1(TComponent* Owner)
0 }1 k8 i* v/ \& E7 e9 ~: TForm(Owner)
% J& h6 \$ n! ]$ u, O{ + [$ O. L& ?3 d* X# o
IsServer=false;
* |9 h. x( }- G% EServer="localhost";
6 t. Q& ^3 l" V, b- P% v} </P><P>$ }( z& G0 j6 z6 ~. t( t }
---- 这里Server被缺省设为localhost,这样程序可以在没有连入Internet的单机上进行调试。在Windows子目录下你可以找到hosts.sam文件中,在该文件中已经将本机IP地址127.0.0.1定义了主机名:localhost。
. |) a7 t$ \( x1 Cvoid __fastcall TForm1::FormCreate(TObject *Sender) ; ^' L2 P Z. Q# i
{
, L7 f3 f+ t" M8 ~* E' mbtndisconnect- >Enabled=false;
+ a+ U u# a+ S' |} </P><P>---- 程序运行后,如果用户按下"监听"钮,则将该程序设为服务器端,这时应将TServerSocket的Active属性设为True,使服务器自动进入监听状态。
% k" e) Q3 t- A s5 tvoid __fastcall TForm1::btnlistenClick(TObject *Sender) 9 v5 b& D7 X+ c
{
, V1 I% f$ T; w6 P% Q# cClientSocket1- >Active=false;
# n0 P1 C7 G1 s: @: J1 M+ _ServerSocket1- >Active=true; 2 w0 ?! F8 E) T& ^
StatusBar1- >SimpleText="正在监听...";
4 ?0 ^8 X# x* M5 E% @# Sbtnlisten- >Enabled=false; : y$ y* H R8 `- ?' z k9 M. i
btnconnect- >Enabled=false; , U) l9 H% C7 b
} </P><P>---- 当用户按下"连接"钮后,程序会弹出一个询问框,要求用户输入要连接的服务器的主机名,然后建立连接。 ! |& `' Q: T+ i$ }2 z
void __fastcall TForm1::btnconnectClick(TObject *Sender)
. B7 {6 J* I1 K{
* M; ?1 I" X" S A* pif(InputQuery("连接到服务器","输入服务器地址:",Server)){
& d6 x) K& |# K1 r+ Z5 jif(Server.Length() >0){
$ g$ y4 ]. c' [$ c n2 J! X7 R2 sClientSocket1- >Host=Server; # |% {3 r# t8 R3 G5 _# J' Z
ClientSocket1- >Active=true; , `# J; s @ q+ J Y, Y
btnlisten- >Enabled=false;
6 \7 i& @) n* C- Nbtnconnect- >Enabled=false; B' E4 n# H6 \4 g0 p8 z0 ]
btndisconnect- >Enabled=true;
% V q0 r I7 K- ]) }/ b9 {4 T} ) F% I( Q! v* K) M1 `, W
} + Y) X+ ?: k1 ^& ~, F
} </P><P>8 c1 V% o) ~9 A
---- 当用户提出连接请求后,客户端会触发OnCreate事件,程序先在状态条中显示连接信息,然后将显示对方谈话内容的Memo2清空,准备开始交谈。
5 y0 w- X$ n4 i. _void __fastcall TForm1::ClientSocket1Connect(TObject *Sender,
A5 D# l* G$ X' |% a) ~TCustomWinSocket *Socket) ! W5 T z. Z! A. Q/ e6 r7 u1 f7 s
{ * k: U. m7 P& _; ^% j) ]
StatusBar1- >SimpleText="连接到:"+Server; 9 ~9 Q2 {8 `# e& ?( J! n
Memo2- >Lines- >Clear(); 9 f' R: H$ K8 i, W4 N* [
} </P><P>. t8 l5 _8 k: ~2 S3 E6 c- O
---- 在服务器接受了客户的请求后会触发OnAccept事件,在这个事件处理程序中将标志服务器端的变量IsServer设为True,并准备开始交谈。
* X2 D) _" s# }8 B$ X' U; [void __fastcall TForm1::ServerSocket1Accept(
6 ?& F% K# P9 ITObject *Sender,
( j( L* |# }1 m9 H, _# j) LTCustomWinSocket *Socket) 7 W4 H [, T* E# v2 x
{
2 ~$ T: h$ r& C( |% m4 ]( LMemo2- >Lines- >Clear(); 5 y' h/ ^# W+ ]. d* W
IsServer=true;
) b" v9 O0 C# Q( KStatusBar1- >SimpleText="连接到:"
8 u: l* g5 t7 L5 z+Socket- >RemoteAddress; ' e; R: q( s6 |' _
} </P><P>- n- d( H: M, N" J+ K
---- 在建立连接后,双方就可以在Memo1中输入谈话内容开始进行交谈了,按下Enter键后,将所在行的文本发送出去。服务器端的Socket的Connections属性返回一个数组,该数组由服务器当前活动的连接组成。 ( ?5 D% U% r$ M
void __fastcall TForm1::Memo1KeyDown( . e* |6 E* |0 A( t7 b+ `6 @
TObject *Sender, WORD &Key, $ J) ?' F& Y3 E2 q3 b4 K% S" n& y; ~
TShiftState Shift)
3 ?$ b" L0 U4 b- B! |{ 8 ~( u, ]2 L' y. H& h
if(Key==VK_RETURN){ ! `" m0 ?. `, \
if(IsServer) & G5 E4 }, |# R. V, p0 B
ServerSocket1- >Socket- >Connections[0]- >SendText(
% p( a. Q ?1 _4 M) Q0 A/ m# zMemo1- >Lines- >Strings[Memo1- >Lines- >Count-1]); 5 V$ M0 C; w& j- X4 N
else
) w& T' T3 F- c6 ]7 J) ^- XClientSocket1- >Socket- >SendText( o" T% R# V. D5 Q
Memo1- >Lines- >Strings[Memo1- >Lines- >Count-1]);
' I# N' m2 }2 Z2 J3 J} + V" [1 c" d' [/ D
} </P><P>
9 t; q0 P" n: o3 e3 S---- 在本例中我们采用非阻塞传输方式,当其中的一方进行写操作时,另一方会触发OnRead事件(客户端)或OnClientRead事件(服务器端),这两个事件的处理程序只是将接收到的内容添加到Memo2的后面。 5 S' j: d u, ~5 C# z& {
Memo2- >Lines- >Add(Socket- >ReceiveText()); </P><P>---- 如果在用户建立连接后单击"断开"钮,将断开客户端与服务器的连接,服务器端将触发OnClientDisconnect事件,而客户端则会触发OnDisconnect事件,这时服务器端应回到监听状态,等待用户的连接;而客户端将返回到连接前的状态,等待用户再次建立连接,如果有不止一个服务器的话,可以选择连接到其他的服务器上。
) m& C; w( K6 H1 Qvoid __fastcall TForm1::btndisconnectClick( . ]. g2 g+ B6 v
TObject *Sender) . B3 G# `5 F3 ^( S; ^6 f
{ + O! ~; ^* w2 y9 P3 W
ClientSocket1- >Close(); * x# D6 a: M! @9 a% i# G
} 9 g, ]" t. A1 D4 K
void __fastcall TForm1::ServerSocket1ClientDisconnect(
9 ? \$ t- R$ `/ wTObject *Sender, ) }! m' z0 \6 k/ o4 I
TCustomWinSocket *Socket) ' i0 I! ?$ Z/ {7 I5 k- U
{
7 B5 R& t' c+ eStatusBar1- >SimpleText="正在监听...";
S: ^ e3 H1 f6 i. X; r' Z}
! f" N C! Y& @" S; r. Pvoid __fastcall TForm1::ClientSocket1Disconnect(
4 S* G) G% }# \- [TObject *Sender, TCustomWinSocket *Socket) 7 i! j" B1 j. k9 K* e
{
& S2 v% m& \3 S1 Rbtnlisten- >Enabled=true;
8 @& y6 G3 l, ebtnconnect- >Enabled=true;
; i0 x# `; O4 l# L8 Kbtndisconnect- >Enabled=false;
% ?7 u2 Q0 M4 y( {' u4 XStatusBar1- >SimpleText="";
1 M) ^: N h, M) X( a} </P><P>8 W5 L+ ` V* G. k1 X E
---- 此外在客户端还应该增加错误捕获机制,当用户输入无效的服务器名或服务器端没有处于监听状态时能够及时给用户反馈信息。 , f" {* n" _, c, z( t. ?. L% G
void __fastcall TForm1::ClientSocke 5 p' S7 ^" s4 ^# f/ L0 D
t1Error(TObject *Sender,
" _( ?) G% x% O, XTCustomWinSocket *Socket,
. ]& x* Z; i# i, P4 a8 r# c* NTErrorEvent ErrorEvent, int &ErrorCode) ( E& a# f: b" z
{ ) X. d, k: L& I7 t* x, o
StatusBar1- >SimpleText="无法连接到: 2 G/ j# ]" M! Y+ L7 @# E
"+Socket- >RemoteHost; 5 v6 _; W' X3 O( g2 P' n+ H7 y
ErrorCode=0; 3 M8 O% D _7 {& Q6 z
} </P><P>用C++Builder获取应用程序图标 </P><P>现在,网上有大量的有关图标的共享软件或免费软件,而且很多也很好用,也方便。但是那毕竟是别人的,用起来总有些哪个,况且自己又喜欢编程,何不自己动手呢!说干就干,而且手头上有可视化编成的利器――C++Builder4.0,想来应该是很简单的一件事。 </P><P>首先启动C++Builder,新建一工程,在窗体上放置一个Button控件,一个Image控件,和一个OpenDialog控件,它们的名称均不必改动。双击Button控件,写如下代码: if(OpenDialog1->Execute()) </P><P>{ </P><P>FileName = OpenDialog1->FileName; </P><P>HICON hIcon; </P><P>// Total =(int) ExtractIcon( Form1->Handle, FileName.c_str(), -1); </P><P> </P><P>Icon = new TIcon(); </P><P>hIcon = ExtractIcon( Form1->Handle, FileName.c_str(), 0); </P><P>Icon->Handle=hIcon; </P><P> </P><P>Icon->SaveToFile(TempFile); </P><P>Image1->Picture->LoadFromFile(TempFile); </P><P>} </P><P>其中:FileName,TempFile,Icon在其头文件中定义:AnsiString TempFile, FileName ; Ticon *Icon; </P><P>这样,你所选定的程序的第一个图标就在Image控件中显示了出来。本程序所用的是Windows API ExtractIcon来获取图表的,因此它只能获取可执行文件的图标,如果想获取任意文件的图标,那末你可以调用Windows API 的SHGetFileInfo函数来完成,SHGetFileInfo所能完成的任务有很多,具体用法可参见Win32的帮助文件。 </P><P>" l* ~1 h) c: y0 S' o7 K J
BIG5到GB的转换技术 </P><P>中文因为数量太多,所以与英文用ASCII码一个字节表示不同,它使用两个字节来表示。通过计算这两个字节,我们可以得到其表示的汉字在中文字库中的位置。读取该位置的若干字节,以获得表示这个汉字的点阵信息。有了这些信息,就可以分别在DOS或WINDOWS中显示该汉字。事实上,在文本文件中保存的就是每个汉字对应的两个字节编码,而显示问题由中文操作系统自动解决。
' H. c1 w" I6 _9 |汉字编码并不统一,我们使用的是GB码,而台湾地区使用的是BIG5码。BIG5码文件中保存的是汉字相应的BIG5编码,GB码文件中保存的是汉字相应的GB编码(这也就是“乱码现象”的来由)。所以转换工作的关键是有一个记录每个BIG5编码对应GB编码的码表文件。 2 [0 ]5 K! _* W; p) N" n( F
第一步 制作码表文件 8 W8 y! k$ a4 a4 H* y- M9 J
BIG5码编码规则是这样的:每个汉字由两个字节构成,第一个字节的范围从0X81-0XFE,共126种。第二个字节的范围分别为0X40-0X7E,0XA1-0XFE,共157种。也就是说,利用这两个字节共可定义出 126 * 157=19782种汉字。这些汉字的一部分是我们常用到的,如一、丁,这些字我们称为常用字,其BIG5码的范围为0XA440-0XC671,共5401个。较不常用的字,如滥、调,我们称为次常用字,范围为 0XC940-0XF9FE,共7652个,剩下的便是一些特殊字符。 3 v4 M4 G; p$ y7 {: D
制作码表文件的原理是这样的:首先将所有的BIG5编码写入一个文件,然后,使用具有BIG5码到GB码转换功能的软件,如地球村、东方快车、四通利方,将文件转换为GB码文件,即得到码表文件。
9 n" Q7 j3 G h& F+ I1 e下面的源程序将所有可能的BIG5编码(0XA100-0XFEFF)写入文件“Table.TXT”。 3 G/ @ h# Q. s
//TURBO C++ 3.0
4 e7 f$ N- z' B#include <Stdio.h>
1 M# Y7 @" _$ Z#include <stdlib.h>
. f& N7 ]8 Q+ s0 S7 r3 P7 g Kvoid main(){ . z- ~& M& y& G# M5 R- N
FILE * codefile; ; g6 x8 T. h) U4 ] q1 Y" r v: ?
int i,j,k; & b D9 ?$ G0 S6 b9 h& Z
codefile=fopen("table.txt","w+b"); 8 h9 R6 o, v5 v5 p% G
for (i=0xa1;i<=0xfe;I++){ 6 q1 s, h0 ^& a# u
for(j=0x00;j<=0xff;j++){
( F1 b) X( E# o4 f) a! R: Zfwrite(& i,1,1,codefile); . A/ _; _* n: v6 P; r {
fwrite(& j,1,1,codefile);}
7 d; |* c" I) f& G) \" o l} ( l9 }& Q- B+ [; |$ O
fclose(codefile);
; z* K% i! }' u! F8 K) m" Oreturn;
, I$ h' M3 @, B) q. b3 c}
" d. \( K( t* e3 `9 L运行地球村、东方快车或四通利方,将“Table.txt”从BIG5码转换为GB码,即获得码表文件。 & g5 r3 W. ^ c" c' f& e7 T4 b
第二步 转换 " C" n! }, u, @# K& a
下面的源程序,将BIG5码文件转换为GB码文件。
+ z! ^! t9 Q+ }1 ]8 f: {+ l//TURBO C++3.0 & X' U0 Q8 H% G
#include <stdio.h> ! G$ Y: x9 G9 R: ?' p* b9 p
#include <stdlib.h> ' w: _/ v+ x' I9 }: _% V1 V
void main(){ & K7 b& D- I% T0 X& m% l
int que, wei;
" l; F) Z4 ^: ~/ z2 aFILE * sourcefile; ( U- |$ B4 ?7 o
FILE * tabfile;
[, t7 v2 ~% y1 ?4 mFILE * destfile; 4 l! j7 d8 b4 f: M
sourcefile = fopen("big.txt', "r+b"); ! @' Q; o! O9 h8 j
//BIG5 码文件
& C: T# n; m9 K/ j, otabfile = fopen("table.txt", 'r+b");
/ A" M0 Z/ t' z; ]0 A. n* a//码表文件 / k, f V9 K2 C7 i# S' A
destfile = fopen("gb.txt","w+b");
. w+ m- Z' J9 n+ `//转换生成的GB码文件 7 }3 z+ e- Q7 S R( c
while (!feof(sourcefile)){
. T4 @: r% k# C$ w: Cfread(& que,1,1,sourcefile);
5 z$ ^5 [' u+ k" n. B- L: c) Eif (feof(sourcefile)){
; C% Z& e( v) H* o7 K; Ybreak; } : {9 h7 S9 x9 O, {
if (que> =0xa1 && que <=0xfe) 7 r; x% {9 i9 p
//叛断是否汉字(BIG5编码)
! k; P6 b, R2 U. {/ E{fread(& wei,1,1,sourcefile);
1 e( E. `& w; `. i; |: i$ ]4 o3 o/ V* oif (wei<0xa1) wei = wei - 0x40; ( e! F8 X) _( d* |/ G
if (wei>=0xa1) wei = wei - 0xa1 + 0x7e - 0x40 + 1; 8 ]" _, G& N2 h
fseek(tabfile, 2 * ((que -0xa1) * (0xfe - 0xa1 + 1 + 0x7e - 0x40 + 1 ) + wei), SEEK_SET); 7 C! ?/ ]0 P4 {, y
fread(& que,1,1,tabfile); . l- k7 s8 e8 B! @
fread(& wei,1,1,tabfile); ; W0 F& ?0 }) R
fwrite(& que,1,1,destfile); $ o& S& ?, \: B V' U
fwrite(& wei,1,1,destfile);
& ?6 x5 z+ h$ f3 m}
4 f2 C2 E$ r9 a" ?8 p( l# S/ melse
) h9 E$ O$ a, Afwrite(& que,1,1,destfile); //处理英文 5 v% G- A, J5 P, Z7 \! u4 q i( f
} 8 @' P( \0 ]6 ^- h" ~! R1 c( L3 }9 _
fclose(sourcefile);
n5 Q. P, _ n) ?fclose(tabfile); " e0 f/ \2 O" X+ u; o3 C
fclose(destfile);
! v9 Q$ @- N$ B/ }; Freturn;
' q6 N, ?5 k( d+ E}
2 o9 K9 K/ S% m) W% n以上程序在Win95/97,TC3.0 通过。稍加修改,也可用于VC或VB程序中。用同样的方法,我们也可以将GB码转换为BIG5码。 </P><P>C++BUILDER让你的任务栏图标动起来 </P><P>---- 在windows环境下上网时,你有没有注意到在屏幕的右下脚的任务栏上有一个动画图标呢?它一闪一闪的,形象的表示出网络此时正在传输数据。关于任务栏图标编程的文章有不少,可是如何才能编制出动态图标呢?在C++Builder中可以比较方便的实现。 </P><P>---- 其基本编程思路是:通过设置Timer时钟控件使应用程序在规定的时间间隔内发送特定的消息,使任务栏图标不断更改,从而形成动画效果。实现方法为在应用程序的表单中加载几个Image控件,使他们装载相应的图画,几幅图画按顺序连续的被显示,就形成了动画。 </P><P>---- 在这里,我们用一个门的开关动画来做例子,在表单上放置一个Timer控件,两个Image,分别装载“开门”和“关门”两幅图。开始加入代码。 </P><P>---- 应用程序必须用发送消息的办法通知任务栏增加,删除,和修改图标。发送消息必须调用Shell_NotifyIcon。它的原形为: </P><P>WINSHELLAPI BOLL WINAPI Shell_NotifyIcon( / E% W- @' q$ ?- e3 c" F) y2 X
DWORD dwMessage, POINTIFYCONDATA pnid); </P><P>第一个参数 dwMessage是发送消息的标志,可以选
/ Y. ^8 x: r6 F9 l+ q/ SNIM_ADD // 往任务栏通知区添加图标 / }; n/ H; n+ f# j9 \
NIM_DELETE //往任务栏通知区删除图标 : p( _2 k2 ]; M) o _5 N: A( o0 o
NIM_MODIFY //通知任务栏通知区修改图标 </P><P>编制消息发送函数TrayMessage 6 b3 M/ T0 V* s5 N' W8 z$ T
bool __fastcall TForm1::TrayMessage(DWORD dwMessage)
0 }. r) J" M( D# R{
7 z% b/ N/ S9 WNOTIFYICONDATA tnd; 1 e- x0 }3 G# @
PSTR pszTip; . n' w; K# {2 l$ A6 @8 o
pszTip = TipText();
/ m. U6 r+ G% T$ Gtnd.cbSize= sizeof(NOTIFYICONDATA); y3 p+ o" L1 t+ s
//结构的大小 & L" h$ N8 x- x
tnd.uCallbackMessage = MYWM_NOTIFY; . C- t2 m& ^+ b8 \
//自定义回调消息,在头文件中声明
: s5 m" J% }( `1 S$ i( Vtnd.hWnd= Handle; $ V: g$ ~" ?: ?9 g; h( ~
//接受回调消息的窗口句柄 % K' N$ u i5 Y
tnd.uID = IDC_MYICON; ; \) W" ^3 Q5 t$ W) J( `
//图标标志号 $ u: f* {* d+ \$ w2 W
tnd.uFlags= NIF_MESSAGE | NIF_ICON | NIF_TIP;
/ Y0 U: \! z3 l' i& C9 G//指定以下三个参数哪个包含有效数据
' y6 }4 G u9 ^" R i6 R- bif (dwMessage == NIM_MODIFY) 7 {$ n) e# V6 P% \1 q+ D
{ . _5 \7 y: v, ]9 {5 \6 N' C G
tnd.hIcon = " ]& |+ {- K B# K: P
(HICON)IconHandle(); //取得图标句柄
% P# w+ J0 I3 |$ hif (pszTip) & n$ U2 C0 x# W- n1 _' i
lstrcpyn(tnd.szTip, pszTip, 5 g; f. i3 Z. f" v+ Q* l6 R
sizeof(tnd.szTip)); 9 H) s& ]2 B5 P3 g& I
else
4 l, ^) i1 v7 E* K% t ~tnd.szTip[0] = '\0';
% s- I0 b, L8 C3 r1 h8 E} 5 ?3 F8 w: r+ [, d. Q$ C- ?
else
\% Z) N/ }4 A* d7 H4 A5 Y{
+ u& c, x8 G7 O5 N' Otnd.hIcon = NULL; . f t/ c% I; _0 Q
tnd.szTip[0] = '\0'; + N$ p/ W! a9 Q6 v" |5 h
}
% x8 O+ I3 r3 sreturn (Shell_NotifyIcon(dwMessage, &tnd));
1 D. `/ J- z. ~ g% ~* S7 |# |/ D} 0 u5 F- L$ _* B( I
编制取得图标句柄的函数
7 }) h6 f( M; i; C% j0 Z D( e8 cHICON __fastcall TForm1::IconHandle(void) 5 G) z9 |1 W/ f- g# S# Y
{ 9 n1 I5 C0 n' a9 C5 Z% l
if (n==1)
/ T! o" c) x; I+ S{ return (Image1- >Picture->Icon- >Handle); 7 T# _& u; v- v [9 F9 F, ]
//n是全局变量,1为显示Image1,0为Image2
, z. i: A6 U7 t+ C( j/ l}
: }% x1 ]/ s( D" m2 V# }else ' l' t" h: ~" J
{ return (Image2- >Picture- >Icon- >Handle);
9 n Z2 h4 |# j W/ J. \}
: ]- R P/ W. }) a: a p* l$ \} 4 L9 x. f: `( E2 p5 P* r2 ~
编制图标状态转换函数 </P><P>void __fastcall TForm1::ToggleState(void) ) } }, A" v, j5 G
{
1 W5 f9 I: T! q2 w6 \6 Tif (n==1) //n为图标句柄锁,是全局变量, / t: c/ Q3 I E3 H1 `
1为显示Image1,0为Image2 4 S/ u# O, l2 ?% z6 D+ s4 l
{
I) ]' R; p( f8 O, t# Cn=n-1; ) e z; d& n: a. G
}
: \8 G( s+ A" L! W( n- uelse v/ z& Z' ]! S' f; `" b/ K C
{ ; E3 s! [& z! l& N/ s/ G' C
n=n+1; 2 C$ y. G$ y. k( l5 Y/ G1 _' ^
}
! G2 N' S& d. Q( ?TrayMessage(NIM_MODIFY); ' _8 d6 g2 [! f6 Y3 r( c
//发送图标变换消息 9 T" S% C2 A) d
} </P><P>
" T9 w- Y; S/ y6 w对Timer控件编制代码,设它的Interval - L5 v$ S, g: j9 q0 @: w
属性为1000,即定时器每一秒响应一次。为 Ontimer : i) F' t, R* q- ~, ~3 D& z$ [
事件键入代码: </P><P>void __fastcall TForm1::Timer1Timer(TObject *Sender)
: w+ q4 t$ A: r k{ ToggleState( ); 3 W5 Y1 C5 ^" f* j
} </P><P>---- 由于篇幅有限,以上只列出了基本部分的代码,其他功能的实现,如关闭程序,打开窗口等,比较简单,不在赘述。程序运行时,你将看到在屏幕的右下角任务栏有一扇门打开又关闭的动画图标。是不是很有趣,快编一个你喜欢的吧。 </P><P>TFORM </P><P>
8 }" F4 `6 X. y% r1 S一、让窗口总是在最前面
& q: X. x' o! d/ Q8 KForm 的FormStyle属性设置为fsStayOnTop值。 </P><P>二、 动 态 调 用 窗 体Form 5 u0 A9 [% q) ^, U
在 缺 省 情 况 下, 由File/New Form 生 成 添 加 入 项 目 文 件 中 的 窗 体 都 具 有"Auto Create"( 自动 创 建) 的 特 性。 即 只 要 程 序 运 行, 该 窗 体 就 存 在 于 内 存中 了, 不 管 当 前 它 是 否 被 调 用。 具 有 这 种 特 性 的 窗 体 一 般适 用 于 窗 体 属 性 比 较 固 定、 经 常 被 调 用 的 情 况。 其 优点 是 速 度 快, 缺 点 是 占 用 内 存。 在 实 际 程 序 设 计 中, 会 遇见 大 量 类 似 对 话 框 功 能 的 窗 体, 它 们 用 于 显 示 状 态 或 输入 信 息, 仅 须 在 程 序 中 调 用 一 下, 完 成 其 功 能 就 行 了, 无需 常 驻 内 存。 这 时 可 以 通 过 选 择Project/Options/Forms, 将"Auto--Create forms " 栏 中 相 应 的 窗 体, 如Form1, 用" >" 键 移 动 到 "Available forms" 栏 中, 并 在 程 序 需 调 用 该 窗 体 处, 加 入 下 列 语 句: </P><P>TForm1 *myform=new TForm1(this); </P><P>myform- >ShowModal(); </P><P>delete myform; </P><P>窗 体Form1 仅 是 在 需要 调 用 时 才 调 入 内 存,调 用 完 成 后, 即 用delete 清 除 出 内 存。这 样 可 减 少 程 序 对 内 存 资 源 的 占 用。 </P><P>三、遍 历 窗 体 控 件 的 方 法 O8 m G& h: [0 p
要 访 问 或 修 改 窗 体 上的 控 件, 方 法 很 简 单, 以TEdit 为 例 子: </P><P>Edit1- >Text=""; </P><P>Edit2- >Text=""; </P><P>但 如 果 窗 体 上 有 十 来个 像Edit1 这 样 的 控 件, 需 要 进 行 相 同 的 初 始 化, 用 上 面 的方 法 一 个 一 个 地 进 行, 岂 不 麻 烦 ! 所 以 有 必 要 掌 握 遍 历窗 体 控 件 的 方 法。 在 介 绍 该 方 法 之 前, 让 我 们 先了 解 一 下 窗 体Form 的Components 和Controls 属 性。 参 见 表 一。 * T' @4 i# S' ?" y
表 一 </P><P>属性 类型 说明 </P><P>ComponentCount Int 目前Form上各类 1 V8 z1 x4 U3 y2 j4 l
控件的总数 </P><P>Components TCompont* 目前Form上指向
/ g) M* {4 d. i$ E' b0 k9 J. \; @所有控件的数组 </P><P>目前Form上指向
4 _ o; v: q; ~( J+ Q* U g所有控件的数组 ' m# B: q7 G; |$ N6 F( C7 t
ControlCount + o0 ]% P0 ?+ v8 ?6 j- m" u' J
Int
1 y+ p* b5 U7 k# E( c2 ~9 j目前Form上某一子 $ G. S' D* T- Q) [
区域上各类控件的总数
+ i9 P* O) A" v( }Controls TControl* </P><P>目前Form上指向某一子
, W% r4 N3 T8 F4 v4 q+ c2 ]) }区域上所有控件的数组 </P><P>) `' r; Y8 r" ~5 K& I
以 图 一 为 例(图 略) 说 明,Form1 的ComponentCount=6, 而Panel1 的ControlCount=4., </P><P>其 中: 数 组 对象 </P><P>Components[0] Panel1 </P><P>Components[1] Label1 </P><P>Components[2] Edit1 </P><P>Components[3] Label2 </P><P>Components[4] Edit2 </P><P>Components[5] Button1 </P><P>
" C1 q( z6 q. n- [6 i7 U1 `2 Q数 组 对 象 </P><P>Controls[0] Label1 </P><P>Controls[1] Edit1 </P><P>Controls[2] Label2 </P><P>Controls[3] Edit2 </P><P>- H- U( Y p* q! g7 M4 r9 D
下 面 这 段 代 码 完 成 了 对Panel1 上 所 有TEdit 控 件 的 遍 历 初 始 化。 读 者 稍 加 修 改, 即 可 对 其它 控 件 进 行 遍 历。 这 里 有 一 个 小 技 巧, 我 们 把 需 要 进 行 初始 化 的 控 件 放 置 在 了 一Panel1 上, 与 不 需 要 初 始 化 的 控 件区 分 开 来, 这 样 便 于 编 程。
- }) R. E' u; lAnsiString namestring="TEdit";
0 w% X# v! K/ k+ k9 h0 Yfor(int i=1;i< Panel1- > ControlCount;i++)
2 {7 ~$ [$ h( d' T* N{ - f, j# ~4 m7 m! J. n; O( M+ |0 h
if(Panel1- > Controls- > ClassNameIs(namestring)) </P><P>{ </P><P>TEdit *p=dynamic_cast < TEdit* > (Panel1- >Controls); </P><P>P- >Text=""; </P><P>}
" u% t+ G' C7 {* K q% S; [} </P><P>四、不规则窗口 </P><P>1.在窗口定义中,加入HRGN hWndRgn;
$ B7 Q" S. L7 h* D, E# X4 w2.在TForm::OnCreate()消息函数最后,加入下面的代码: 3 L! m4 {; o7 G& l$ V& m
hWndRgn=::CreateEllipticRgn(0,0,Width,Height);
" V$ n8 D* I" I3 q4 }6 z1 W( v6 p::SetWindowRgn(hWndRgn,TRUE);
/ u7 }, ?4 A9 `3.设置TForm的属性为无标题,无边框。
1 g( s' C7 b# v/ V9 M3 a4.编译连接应用程序,就可以看到一个椭圆形窗口。 </P><P>五、MDI Form </P><P>1.Application->CreateForm(__classid(Tjjcginput), &jjcginput); </P><P>后不用在使用显示Form的语句就可以显示出来了。 </P><P>2.form 的onclose 事件必须用下面语句释放空间: 9 }- I9 h( Z# ^8 q( y$ j# b
void __fastcall TMDIChild::FormClose(TObject *Sender, TCloseAction &Action) " H0 U8 A+ o. K
{
( e! ^7 {/ r7 z9 h. W% }0 dAction = caFree;
7 ?- M ?- }" ?6 }4 d& ? m} </P><P>用BCB在windows桌面创建快捷方式 </P><P>API提供了一个叫做IShellLink的COM接口允许我们创建快捷方式。为在桌面创建快捷方式,我们创建一个IShellLink对象,设置它的属性,然后把这个link保存到desktop目录。 </P><P>下面的例子代码演示了怎样创建一个快捷方式。在这个例子里,这个快捷方式保存在C:\Drive目录下。 </P><P>//---------------------------------------------------------------------- " U; N2 c+ R! y+ I2 i* |& A0 c
include <shlobj.h> </P><P>void __fastcall TForm1::Button1Click(TObject *Sender)
( B" z' m6 j" B0 o) U{ 3 b* T* z, Y: R) ~$ H8 ]
if(OpenDialog1->Execute())
) l/ B% t3 v% O' c1 SCreateShortCut(OpenDialog1->FileName); % }$ m6 v5 x+ ^1 C1 e9 G8 d' B
}
7 S% u5 e$ s/ ~: C//---------------------------------------------------------------------- 9 X0 |: x% w, R
void TForm1::CreateShortCut(const AnsiString &file) ' g0 b3 d' f# y) a( Y- h: @
{ 3 }: K- |; @: n+ K& p! {9 y
IShellLink* pLink;
3 j& Y3 z9 X# p& X8 eIPersistFile* pPersistFile;
- w' ~/ ]1 v% Z" I! C* q. Uif(SUCCEEDED(CoInitialize(NULL))) 8 Z7 S" H" B& \" O
{ * c, D3 w4 Q8 ]
if(SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL,
! ~% Z7 y. h, Q8 mCLSCTX_INPROC_SERVER,
' J9 n/ g: }. e( c' U: wIID_IShellLink, (void **) &pLink)))
+ f6 G1 |& `8 K/ L3 f{
* w, P& U9 X5 TpLink->SetPath(file.c_str()); , w t, z- F" E9 g2 C: L( Z$ U
pLink->SetDescription("Woo hoo, look at Homer's shortcut"); ) X f# } e3 ?' |+ O
pLink->SetShowCmd(SW_SHOW); 4 m5 S/ q4 \9 [) I; c. N
if(SUCCEEDED(pLink->QueryInterface(IID_IPersistFile,
/ B2 k* ]5 ^/ ^9 N1 E(void **)&pPersistFile))) 2 s6 d: ?# ~4 P/ ?3 L
{ 6 ^# @5 u2 x. E* v" N7 ~
WideString strShortCutLocation("C:\\bcbshortcut.lnk"); 6 q8 k" z8 n* _# T
pPersistFile->Save(strShortCutLocation.c_bstr(), TRUE); ; T5 v* }( [$ O, @8 W
pPersistFile->Release();
a8 {$ F: M9 W0 ?0 N6 y} 4 H7 H5 i6 p; a/ W. x6 ?
pLink->Release();
1 N( _& r6 [; R) G$ h7 P} </P><P>CoUninitialize();
) B8 H& G: J; a. q}
1 @6 j3 T( }4 a$ P4 \}
4 X- x8 N/ T9 M: b4 ]0 |//----------------------------------------------------------------------
7 r1 _( x) Q8 ] a* x0 y上面的例子只是把快捷方式文件保存到了c:\drive目录下,但没保存到desktop目录下。 </P><P>要让快捷方式出现在桌面上,只须把快捷方式文件保存到desktop目录下。首先我们要找到windows的desktop目录,请参阅判断windows的Desktop及相关目录这一节。一旦我们知道了desktop所在的目录,我们就能将快捷方式文件保存到desktop目录下。然后windows就能将快捷方式图标显示到桌面上。下面是经过改进了的例子: </P><P>//----------------------------------------------------------------------
0 J8 W D }" l+ z* rvoid TForm1::CreateShortCut(const AnsiString &file)
$ T0 M2 D) x% [# h* a( c{
$ }3 b$ I$ Y! s& ~1 W8 gIShellLink* pLink; $ D1 o: \1 V/ s8 p
IPersistFile* pPersistFile; W- \$ v, t5 q
LPMALLOC ShellMalloc;
5 Z7 k3 \0 e7 c" [LPITEMIDLIST DesktopPidl; 6 Q/ I R9 U7 w6 l8 N: F8 m
char DesktopDir[MAX_PATH]; </P><P>if(FAILED(SHGetMalloc(&ShellMalloc)))
; v; k8 A9 @6 Q: freturn; </P><P>if(FAILED(SHGetSpecialFolderLocation(NULL, 5 C$ s) w3 f, ]
CSIDL_DESKTOPDIRECTORY, 4 _1 i- r3 `) P- [9 K, b' s& [
&DesktopPidl))) - @9 L. I. x) @! Q, ]0 x
return; </P><P>if(!SHGetPathFromIDList(DesktopPidl, DesktopDir)) & _- V. m4 s9 g9 L& d9 }
{ ) P# W/ @% c1 X0 y
ShellMalloc->Free(DesktopPidl);
# k3 f. c) g' p6 F! q0 q( lShellMalloc->Release(); + A3 q$ A* C) e
return;
( t" t. |9 z7 S* t6 N9 d( _} </P><P>ShellMalloc->Free(DesktopPidl);
, E; T& F+ j8 A% q2 rShellMalloc->Release(); </P><P>if(SUCCEEDED(CoInitialize(NULL)))
9 [4 o* B% @2 r! i{ 0 p$ i x3 t1 k2 \1 M
if(SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL,
) h" m0 b$ D& {: L( W. j) Q% F VCLSCTX_INPROC_SERVER,
& O! u- F' x) Q+ S0 _$ c% j* xIID_IShellLink, (void **) &pLink))) : C& U" N/ Y8 m$ H' k9 T0 V9 i
{ 7 T# c3 J3 n0 Q0 h0 c5 K; p
pLink->SetPath(file.c_str()); c& ~# G: }/ a3 y! N' R" j
pLink->SetDescription("Woo hoo, look at Homer's shortcut"); # r3 p: }& A5 M0 f+ h0 P9 y: C
pLink->SetShowCmd(SW_SHOW); </P><P>if(SUCCEEDED(pLink->QueryInterface(IID_IPersistFile,
( F/ t! k' O {$ Z9 G(void **)&pPersistFile)))
3 @% L, L' u! p2 d6 |5 Q{ </P><P>WideString strShortCutLocation(DesktopDir); ( Z) }! h2 q: w# B" n: c
strShortCutLocation += "<a>\\bcbshortcut.lnk</A>";
: z, B' ?) Q5 S, v; ?" A+ s6 upPersistFile->Save(strShortCutLocation.c_bstr(), TRUE);
s% C% v! d0 m( {" u/ \5 ?8 ZpPersistFile->Release(); " {1 V+ W/ v- H5 F
}
( c$ m! \6 ~0 e" B' D1 V9 ppLink->Release();
0 h8 {# R$ A- C2 D}
" f5 v3 H1 q* l+ {CoUninitialize();
3 Y0 x/ S8 n. P `$ O& W} 9 y: `* ~6 j$ Q
} 3 u6 |; c7 ~3 n I8 o4 @% |) |
//---------------------------------------------------------------------- ( M' e d5 [- L2 H3 M
不要陷于COM的泥沼之中
, J8 ?! A' k9 e& I9 P; f% J创建快捷方式包括一些对COM的使用。不要让你陷入到COM的复杂之中。COM只是创建和使用对象的一种方法。在这个例子里我们可以考虑不使用COM而是用等价的C++技术。 </P><P>COM code C++ psuedo-equivalent
: M: G0 F B @" I6 ]IShellLink* pLink; TShellLink *Link;
/ ^, {( U$ W# w' s, W5 v" d. jIPersistFile* pPersistFile; TPersistFile *PersistFile; </P><P>CoInitialize(); </P><P>
8 b& u l+ C1 | M5 NCoCreateInstance(CLSID_ShellLink, Link = new TShellLink;
+ J; o9 n' `; ^/ p# A' D- ~3 ^NULL,
, e: R' G# e% \9 N9 f- KCLSCTX_INPROC_SERVER,
7 R2 g/ r9 ~/ q4 \6 c: N' X1 IIID_IShellLink, 6 {7 q. [ M, P1 i/ u8 P
(void **) &pLink) </P><P>
3 I% t9 N/ s/ w% m, O7 r/ d8 ipLink->SetPath(file.c_str()); Link->SetPath(file.c_str());
; S( l# A. b8 QpLink->SetShowCmd(SW_SHOW); Link->SetShowCmd(SW_SHOW); </P><P>
1 H( L7 T8 X" l( S |pLink->QueryInterface(IID_IPersistFile PersistFile =
& _/ f' ?. F, J5 B(void **)&pPersistFile))) dynamic_cast<TPersistFile*>(Link); </P><P>pPersistFile->Save("C:\\", TRUE); PersistFile->Save("C:\\"); </P><P>* J. k5 @- G$ {
pPersistFile->Release(); delete PersistFile
; O6 K9 N% c# V9 K NpLink->Release(); delete Link; </P><P>CoUninitialize(); </P><P>
5 t. L! C# p6 k, I读磁片磁区 </P><P>
# o0 U* M& j# ^ Z3 @- i一、以前的DOS版要读、写、格式化第0轨的第1个磁区,程式大致如下: </P><P>char buffer[512]; </P><P>reg.x.dx=0 ; /* for drive A *
1 ?- G6 P4 U1 l( N" Nreg.x.cx=0x0001 /* for boot sector */
9 c6 R1 ?: T. k! m# `" u2 ^, Freg.x.bx=FP_OFF(buffer); ( P- f V; l5 o$ p) I$ v
sreg.es=FP_SEG(buffer);
) e! }: X8 F% O6 }resg.x.ax=0x0201; /* 02 for Read, 03 for Write ,05 for Format */ " `% x, N0 J+ P w8 S! r. c8 e
int86x(0x13,?,?,&sreg); </P><P>那麽在windows 下转换为呼叫 DeviceIoControl 以便格式化、读取、写入该磁轨,DIOC_REGISTERS 这struct 在套上 DOS 下 Int21对HDD或FDD 的各项参数如要格式化是Int21也是有, 但Windows下也另有提供。 </P><P>l#pragma pack(push, 1) / [+ u7 _ J2 d9 |& g
struct DIOC_REGISTERS { : g4 n6 X' l, w5 [( }+ Y
DWORD reg_EBX;
- s; n* H- v# |) mDWORD reg_EDX;
8 _ C4 ~ N+ _0 R8 nDWORD reg_ECX;
. V% W9 j4 Y3 K% QDWORD reg_EAX; 3 J$ V, \7 |6 M5 k6 J* B9 Y$ ~ o
DWORD reg_EDI; R. A2 `" x0 _5 Q! W; J3 G
DWORD reg_ESI;
6 |7 ? R- I7 QDWORD reg_Flags; " {* Z! G7 a, o
};
1 Z% _# ^4 R* i& l( H$ e#pragma pack(pop) </P><P>sDiskImageInfo->hDevice = ::CreateFile("<A>\\\\.\\vwin32</A>", 0, 0, NULL, 0, 1 T+ z. J% l) L6 r: e+ k1 a- y
FILE_FLAG_DELETE_ON_CLOSE, NULL); 1 P$ c# ?8 Y$ N) ?9 z
if( sDiskImageInfo->hDevice == INVALID_HANDLE_VALUE)
6 P+ r$ C {3 o8 I/ LbRunNext = false; </P><P>// Reset Floppy Disk - Y& y8 Q/ s: @
reg.reg_EBX = 0; ( G' h7 M5 A! i2 V
reg.reg_EAX = 0x0000; // IOCTL for block devices 7 Q8 O& p5 i4 C/ Y+ A+ c: |
reg.reg_EDX = sDiskImageInfo->Driver; ; S- b5 _ a k" j
reg.reg_EDI = 0; reg.reg_ESI= 0; ; K- L, R2 f9 m6 U0 z b
reg.reg_Flags = 0x0001; // assume error (carry flag is set)
" @' j& q; t% p, g, sdwResult = : eviceIoControl( sDiskImageInfo->hDevice,
8 b( k, {6 P, g$ wVWIN32_DIOC_DOS_INT13, , ]9 B6 f! V( K4 G% c
?, sizeof(DIOC_REGISTERS), ?,
, u' L6 B8 T0 I3 ssizeof(DIOC_REGISTERS), &cb, 0); </P><P>// Seek Floppy + \" N9 P# q7 Y4 u( e( e
reg.reg_EBX = 0;
# T0 Q* u; ?! Zreg.reg_EAX = 0x0C00; // IOCTL for block devices 0 } D) H2 c( @) p. ?
reg.reg_ECX = ( sDiskImageInfo->nC << 8) | sDiskImageInfo->nS; 1 ?0 v! ~& D! b0 E2 Y0 O
reg.reg_EDX = ( sDiskImageInfo->nH << 8) | sDiskImageInfo->Driver; " D8 M" n4 E1 \3 o( w7 }
reg.reg_EDI = 0; 6 i+ W: ^3 R; {+ Y+ l$ } f
reg.reg_ESI= 0; - M7 S1 f, J& h/ O* o
reg.reg_Flags = 0x0001; // assume error (carry flag is set)
1 I* t6 K1 z! h, NdwResult = : eviceIoControl( sDiskImageInfo->hDevice,
7 d8 V& V6 `1 |1 SVWIN32_DIOC_DOS_INT13, ; v+ i5 T2 }/ s) {! z
?, sizeof(DIOC_REGISTERS), ?,
" q- U) t. d5 q( c$ Lsizeof(DIOC_REGISTERS), &cb, 0); </P><P>// Read Floppy
! J7 R( V9 F5 j2 aR_CreateDiskImageFile: 2 }" m S1 Z) B N2 ^, H# w9 D- q
reg.reg_EBX = 0; 8 [. p* `7 n: E6 U
reg.reg_EAX = 0x0200 | 0x01; // IOCTL for block devices
7 H0 t6 a- o% Freg.reg_ECX = ( sDiskImageInfo->nC << 8) | sDiskImageInfo->nS;
: I1 [: C9 }& Zreg.reg_EDX = ( sDiskImageInfo->nH << 8) | sDiskImageInfo->Driver; 4 T6 Z. e" H9 [( y) H( L5 A/ X
reg.reg_EBX = (DWORD) &m_Buf; 4 d6 u4 q* ]! M; e9 F' q
reg.reg_EDI = 0; : h8 U% @. {: e9 K
reg.reg_ESI= 0; * h5 t# J. t# E, c
reg.reg_Flags = 0x0001; // assume error (carry flag is set)
% {+ T! e( y5 d9 \dwResult = : eviceIoControl( hDevice, VWIN32_DIOC_DOS_INT13, 2 Z N7 P S/ @3 w0 x2 U( O. i3 Y n& m
?, sizeof(DIOC_REGISTERS), ?,
) o, \- r4 ? e4 w, Isizeof(DIOC_REGISTERS), &cb, 0); + D: @# B! i% c( S! Y
if (!dwResult || (reg.reg_Flags & 0x0001)) " [4 Q8 k' t# x' ^
{
% v7 Z* g. `5 S, y. \& Z# z# I} </P><P>I/O 端 口 读 写 的 实 现 </P><P>3 a6 M- U& o4 O: V( E! V. @: q$ h2 R
细 心 的 读 者 会 发现,C++ Builder 不 再 支 持 如inportb()、outportb() 一 类I/O 端 口 读 写指 令 了。 准 确 地 说, 在Windows 环 境 下,Borland C++ 仅 支 持16 位应 用 程 序 的 端 口 操 作, 对32 位 应 用 程 序 的 端 口 操 作 不 再 支持, 而C++ Builder 开 发 出 来 的 程 序 是32 位 的。 我 个 人 以 为, 这是C++ Builder 设 计 者 的 败 笔。 因 为PC 机 中,I/O 地 址 空 间 与 内存 地 址 空 间 从 来 都 是 各 自 独 立 的。 看 看Delphi, 不 就 通 过Port 数 组 实 现 了 对I/O 端 口 的 访 问 了 吗? 搞 不 清 楚 为 什 么C++ Builder 就 没 有 提 供 类 似 的 机 制 ? 下 面 这 几 个 函 数 是 笔 者 从 网 上淘 下 来 的, 经 过 验 证, 在Windows95 环 境 下, 的 确 可 实 现 对I/O 端 口 的 读 写。 读 者 可 以 借 鉴 使 用。
8 @9 e" {! {2 O. d' d& Jvoid outportb(unsigned short int port, unsigned char value) </P><P>{ </P><P>// mov edx, *(&port); </P><P>__emit__(0x8b, 0x95, &port); </P><P>// mov al, *(&value); </P><P>__emit__(0x8a, 0x85, &value); </P><P>// out dx, al; </P><P>__emit__(0x66, 0xee); </P><P>} </P><P>void outportw(unsigned short int port, unsigned short int value) </P><P>{ </P><P>// mov edx, *(&port); </P><P>__emit__(0x8b, 0x95, &port); </P><P>// mov ax, *(&value); </P><P>__emit__(0x66, 0x8b, 0x85, &value); </P><P>// out dx, ax; </P><P>__emit__(0xef); </P><P>} </P><P>unsigned char inportb(unsigned short int port) </P><P>{ </P><P>unsigned char value; </P><P>// mov edx, *(&port); </P><P>__emit__(0x8b, 0x95, &port); </P><P>// in al, dx; </P><P>__emit__(0x66, 0xec); </P><P>// mov *(&value), al; </P><P>__emit__(0x88, 0x85, &value); </P><P>return value; </P><P>} </P><P>unsigned short int inportw(unsigned short int port) </P><P>{ </P><P>unsigned short int value; </P><P>// mov edx, *(&port); </P><P>__emit__(0x8b, 0x95, &port); </P><P>// in ax, dx </P><P>__emit__(0xed); </P><P>// mov *(&value), ax </P><P>__emit__(0x66, 0x89, 0x85, &value); </P><P>return value; </P><P>} </P><P>
( E7 T5 k# v1 G5 O3 O检 测 鼠 标 位 置 </P><P>例如,通过一个定时器Timer1的触发事件源来检测鼠标位置 </P><P>void __fastcall TForm1::Timer1Timer(TObject *Sender) </P><P>{ </P><P>TPoint pt; </P><P>GetCursorPos(&pt); </P><P>Label1->Caption = "(" +IntToStr(pt.x) +")(" +IntToStr(pt.y) +")"; </P><P>} </P><P>令Win32 应 用 程 序 跳 入 系 统 零 层 </P><P>
- p: a& |+ k" Y- `众 所 周 知, 在Windows95/98 的Win32 on Intel x86 体 系 中 利 用 了 处 理 器 的 三 环 保 护 模 型 中 的 零 环(Ring0, 最 高 权 限 级 别) 和 三 环(Ring3, 最 低 权 限 级 别)。 一 般 应 用 程 序 都 运 行 在Ring3 下, 受 到 严 格 的" 保 护", 只 能 规 矩 地 使 用Win32API。 如 果 我 们 想 进 行 一 些 系 统 级 的 操 作, 例 如 在 嵌 入 汇 编 中 使 用 诸 如"Mov EAX,CR0", 或 像 在DOS 下 那 样 调 用 一 些 必 不 可 少 的 系 统 服 务( 如BIOS,DPMI 服 务) 而 用"Int xx", 都 会 导 致" 非 法 操 作"。 但 这 种 能 力 有 时 是 必 不 可 少 的, 一 到 这 种 时 候Microsoft 就 " 建 议 编 写 一 个VxD"。VxD 大 家 早 有 所 闻 了, 在VxD 里, 不 但 可 以 执 行CPU 的 所 有 指 令, 而 且 可 以 调 用VMM( 虚 拟 机 管 理 器) 和 其 他VxD 提 供 的 上 千 个 系 统 级 服 务。 获 得 这 一 能 力 的 最 本 质 原 因 在 于 它 运 行 在Ring0, 与 系 统 内 核 同 一 级 别。 但 是 它 体 系 的 复 杂 性、 开 发 工 具 的 不 易 获 得、 帮 助 文 档 的 不 完 备, 使Microsoft 排 除 了 一 大 批 程 序 员 和 竞 争 对 手。 而 将 在Windows2000(Windows98 也 开 始 支 持) 中 取 代VxD 的WDM 对Win95 程 序 员 也 是 个 噩 梦, 它 需 要 了 解Windows NT 核 心 驱 动 模 型。 </P><P>----有 没 有 简 单 一 些 的 办 法 呢 ? 我 们 可 以 令 一 个 普 通Win32 应 用 程 序 运 行 在Ring0 下, 从 而 获 得VxD 的 能 力 吗 ? 答 案 是 肯 定 的。 下 面 我 们 就 简 述 一 下 这 一 技 巧, 有 关Intel x86 保 护 模 式 的 基 础 知 识 请 大 家 看 有 关 书 籍。 </P><P>----首 先 此 技 巧 基 于 以 下 理 论 根 据: </P><P>----一、SIDT 指 令( 将 中 断 描 述 符 表 寄 存 器 IDTR - -64 位 宽,16 ~47Bit 存 有 中 断 描 述 符 表IDT 基 地 址 - - 的 内 容 存 入 指 定 地 址 单 元) 不 是 特 权 指 令, 就 是 说 我 们 可 以 在Ring3 下 执 行 该 指 令, 获 得IDT 的 基 地 址, 从 而 修 改IDT, 增 加 一 个 中 断 门 安 置 我 们 的 中 断 服 务, 一 旦Ring3 程 序 中 产 生 此 中 断,VMM 就 会 调 用 此 中 断 服 务 程 序, 而 此 中 断 服 务 程 序 就 运 行 在Ring0 下 了。 这 一 点 与 在DOS 下 非 常 相 似。 </P><P>----二、Windows95 Win32 应 用 程 序 运 行 一 个 映 射 到 全 部4G 内 存 的 段 中, 选 择 子 为0137h,Ring0 中 的VxD 运 行 在 另 一 个 映 射 到 全 部4G 内 存 的 段 中, 选 择 子028h, 这 两 个 段 除 了 选 择 子 决 定 的 访 问 权 限 不 同 外, 没 什 么 不 同, 各 自 段 中 相 同 的 偏 移 量 对 应 了 相 同 的 线 性 地 址。 所 以 我 们 放 在Win32 应 用 程 序 中 的 中 断 服 务 程 序 可 以 以Ring3 的 段 偏 移 量 被Ring0 中 的VMM 寻 址。 </P><P>----下 面 我 们 以 具 体 例 子 进 一 步 说 明, 程 序 中 有 详 细 注 释。 </P><P>----这 是 一 个Win32 Console Program( 控 制 台 应 用 程 序), 虽 然 运 行 中 看 起 来 很 像DOS 筐 中 运 行 的 实 模 式DOS 程 序, 但 它 是 货 真 价 实 的 运 行 在Ring3 下 的Win32 程 序。 用Visual C + + 5.0 AppWizard 创 建 一 个Win32 Console Program 项 目, 添 加 以 下.CPP 文 件, 编 译 即 可。 </P><P>#include
5 G0 K3 ~) [; Q; y#include & f2 M g" ]8 v3 l
#include ! j0 _+ F' |( a9 j+ B: A
#include 0 m0 j* R% J. q# n2 ^+ ?2 _
// 若 无DDK 带 下 划 线 的 可 略 去,
5 l( y& r7 Z+ W! ?这 些 语 句 演 示 了 调 用VMM/VXD 服 务
/ J" W$ J, N2 L5 mDWORDLONG IDTR,SavedGate;
0 f* U, Z( ?8 P( r" [WORD OurGate[4]={0,0x0028,0xee00,0x0000}; $ k8 {2 E. K! e% f) E1 D% i; o7 w% M, _
// 中 断 门 描 述 符 格 式 如 下: </P><P>2 I, F* S9 |1 n. g
DWORD _eax,_ecx,_cr0;
) g& y( {. c! J: H1 g3 QWORD vmmver;
0 n" X- l& |- g7 f2 L9 l5 xHVM sysvm; </P><P>void nothing() " W2 S# K. r. N0 G9 }7 S
{
" Y3 `+ k8 d* l//Used to test call in Ring0 - N7 J% p2 @( D( [$ @% o
sysvm=Get_Sys_VM_Handle(); 7 a6 `3 k% H/ n2 [' W/ C* ?
} </P><P>void __declspec( naked ) Ring0Proc(void)
( M0 t+ x# ?) |( t8 G k// 中 断 例 程, 运 行 在Ring0 - {/ D) Z% _! S
{
f8 D/ W/ U5 t: i* `_asm{
/ L6 R0 U4 k5 y8 qmov _eax,eax // 0 t8 V, D" j' U) h) G, j5 N% A
mov _ecx,ecx //
1 [6 I0 C% h8 b7 g' k/ v5 o* o9 P8 G# Ymov eax, CR0 % Z) P' M9 D0 a! r+ v0 P# f
// 测 试Ring3 中 不 能 执 行 的 特 权 指 令
5 Y# m7 W9 y2 _: t+ {, r2 c7 _mov _cr0,eax // / ]7 k* p: X1 C) ^' F
} 4 ]$ X& n/ l d2 ~3 U1 Y' M
VMMCall(Get_VMM_Version); ^, R: d1 f- ]
// 调 用VMM 服 务
; Y! W# c' H4 a7 O* w- o0 }_asm{ ; o" Q8 N& x" Z3 s8 t
mov vmmver,ax
' r4 X/ x( E1 _) f% t} ; q* q% D- Z+ ~, ^' V
nothing(); # b6 R' a' M! c6 {+ C y( R5 X2 G2 m! H
// 测 试 在 运 行 于Ring0 的
2 L3 b+ P, z p中 断 例 程 中 调 用 子 * e( `# w( k/ V8 e3 S
_asm iretd 8 B- O: h1 f' C' E: [/ p
// 中 断 返 回, 与 在 实 模 式 ' k, @% I& ^* y3 X' l- b4 X5 `
编 程 无 本 质 区 别 2 n S) s& J6 j
} ' P/ \9 P$ {& {2 U4 Z$ ]! \
void main() // 主 程 序
3 b* c g4 {' r! e7 M' H, i{ : O( t G0 M% \1 n" }9 {! r
_asm{
# m4 F% D! E; c/ Dmov eax, offset Ring0Proc
) ~9 } k" N1 V6 |) x' Jmov [OurGate], ax // 将 中 断 函 数 的 地 址 - c/ b4 p+ D" t/ b
shr eax, 16 // 填 入 新 造 的 中 断 门
( C4 M$ w9 D% P% h' {4 G0 t* Gmov [OurGate +6], ax // 描 述 符 </P><P>sidt fword ptr IDTR
+ D, h& F7 E- c/ C/ S// 将 中 断 描 述 符 表 寄 存 器(IDTR)
( [9 e4 U8 |/ O3 }0 U的 内 容 取 出 ! }7 V5 k- T9 K8 y. I
mov ebx, dword ptr [IDTR +2]
8 V' s4 o( }3 L6 o8 i$ y// 取 出 中 断 描 述 符 表(IDT) 基 地 址 4 a& E% n E5 V7 j6 O
add ebx, 8 *9
. R# ^: T; e3 q0 J, N, q+ c// 计 算Int 9 的 描 述 符 应 放 置 的 地 址 选 用
9 _& X: D; w* A5 V/ T( Q7 aInt9 是 因 为 它 在Win32 保 护 模 式 下 未 占 用 </P><P>mov edi, offset SavedGate
5 `% n9 C- z4 }% gmov esi, ebx
; k* R8 ]& m, j* z5 F4 y3 Lmovsd // 保 存 原 来 的Int 9 描 述 符 到 4 o# {3 v0 U2 _! ~6 |* E0 L
movsd //SavedGate 以 便 恢 复 </P><P>mov edi, ebx
/ U A9 `1 m) N; cmov esi, offset OurGate " r; O+ M+ P. w2 p; `) Y
movsd // 替 换 原 来 的 中 断 门 描 述 符
2 j( P( r+ G$ v" G. I8 vmovsd // 以 安 装 中 断 服 务 例 程 </P><P>mov eax,0x6200
4 a" X5 i% d! ?1 U; X// 用 以 测 试 放 在EAX 中 的 数 据
# y- f" N4 h$ q" s1 A. S; O5 s能 否 正 确 传 到Ring0 中 断
( j4 O5 f$ k! u. ] n3 ymov ecx,0 % b! L3 c: y$ w6 [0 O: U
// 用 以 测 试 放 在ECX 中 的 数 据 6 A/ s( |/ f: ]6 c+ n
能 否 正 确 传 到Ring0 中 断 ) Z5 ~* R+ X, ^7 ~7 S
mov ecx,0
+ T9 U: r! A9 F: s2 L v// 用 以 测 试 放 在ECX 中 的 数 据
* e. Z/ T7 R0 B能 否 正 确 传 到Ring0 中 断 ) {6 x+ F9 d' W" B0 p- Z
// 因 为 很 多VxD 服 务 都 用
+ c' B9 j1 @' D) r! M1 `此 二 寄 存 器 传 递 参 数 : N7 k+ R2 p2 ~& [7 Z
int 9h
5 R/ ` e2 f. Y. {2 H// 人 为 触 发 中 断, 平 时 会 出 现
D: {6 K2 p! b H/ `保 护 错 误 蓝 屏 或 非 法 操 ! i3 b6 b5 p; I/ Z& G* E. S$ e% k
// 作 对 话 框, 现 在 安 装 了 2 O- C% Y9 C9 G; d
// 中 断 服 务 例 程 后, 就 会 通 过 5 T6 a; ~0 P' Q4 P5 r4 x
//VMM 在Ring0 调 用 中 断 服 务 例 程 * k0 [0 c4 m# }% ^
- -Ring0Proc </P><P>mov edi, ebx
) q- [% L8 ~. Q6 F, kmov esi, offset SavedGate : O0 X" j( X& t0 c- y7 E2 g
movsd // 恢 复 原 来 的 中 断 门 描 述 符 0 D- _5 i5 X! E& v- M
movsd
! M o& ?8 s* ^, ]& m c3 C4 I}
5 Q* G* j! M3 ~5 _cout<<"CR0="<<_cr0< } _getch(); if(0="=_getch())" while(_kbhit()="=0);" do{} continue.?< </P><P>( e) z# @1 i' }& V+ c5 R
如何取得Memo的行和列 </P><P>
% S7 n2 A/ C$ Y) q# H* P2 M 新建一个应用,在窗体Form1上添加两个TLabel组件名为Label1,Label2; + s* D. \& i4 t5 ?. r7 u0 x1 R
添加两个TButton组件名为Button1,Button2;添加一个TMemo组件名为Memo1。
, S. a5 a* V- i1 x2 Y; b* X然后在代码编辑器中添加以下代码。
7 F0 |3 U" }. Y) H, \$ d) q/ M8 R void __fastcall TForm1::Button1Click(TObject *Sender)
" E* X8 ^4 A! d, P; ^9 d, a; s {
4 O) O. f8 y3 A1 L, | Label1→Caption=SendMessage(Memo1→Handle,EM_LINEFROMCHAR,-1,0)+1; 9 |( V# u+ S J$ _- V
} : x, _ k3 i! E- R8 G) G+ }
" T7 v+ O4 `" x* ?& [7 G
void __fastcall TForm1::Button2Click(TObject *Sender)
' F; j! J, d' j {
9 f& `7 h6 P9 O3 L* G Label2→Caption=Memo1→SelStart-SendMessage(Memo1→Handle,EM_LINEINDEX,-1,0)+1;
: U3 e9 y7 ^7 O( i6 \ } 1 B- |) `# B$ G7 ?* f& p( A
这种方法同样适用于RichEdit。 </P><P>使用Sockets </P><P>% ]1 H( G1 l5 U' @3 b
使用sockets Socket控件让你建立一个利用TCP/IP和有关的协议与其他系统进行通信的应用。使用Sockets,你能够读和写通过它连接的其他机器,而不用担心实际的网络软件的相关细节。Sockets提供基于TCP/IP协议的连接。除此以外还能很好的工作,在其他相关的协议,例如Xerox Network System (XNS), Digital's DEC net, or Novell's IPX/SPX 家族。
+ m1 B7 z! S* x r( z( _C++ Builder提供你写网络服务器或客户应用程序去读和写其他的系统。一个服务或客户程序通常专注于一个单一的服务如超文本传送协议(HTTP)或文件传输协议(FTP)。使用server sockets,一个应用程序可以提供这些服务中的一个去连接一个希望使用服务的客户程序。Client sockets允许一个应用使用这些服务中的一个去连接提供这个服务的服务应用。 4 ]* ^8 C* b, J6 P( N/ e1 z
使用sockets去写应用程序,你必须理解下面这些知识: 9 x- \$ F" o2 |. C$ |
一、服务工具
4 \' u1 W% G$ q) Q' R# r当你需要写网络服务或客户应用时,Sockets提供一种接合。对于许多服务,象 - |& A5 d3 a% X b% P9 ?
HTTP 或 FTP,第三方服务商提供这些服务已经相当有效。有些甚至随着操作系统捆绑而来,以便不用你自己写。然而,当你想更多的控制服务的实现,如想让你的应用程序与网络通信更加紧密,或当没有一个服务能提供你特殊需要的服务时,你可能想建立你自己的服务或客户应用。例如,工作在分布式data sets时,你可能想为数据库写一层与其他系统通信的应用。想使用Sockets实现一个服务,你必须理解: </P><P>1.服务协议 3 J5 C0 {& e/ R2 J) d
在你写一个网络服务或客户程序前,你必须明白你的应用将提供或使用什么服务。你的网络应用必须支持许多服务的标准协议。如果你为标准的服务例如HTTP,FTP写网络应用,或even finger or time,你必须先理解与其他系统通信所使用的协议。特殊服务细节你必须看提供的或使用的文档。 : J. \; V) ^+ n% B& P; k
如果你的应用程序提供一个新的服务与其他系统通信,第一步是为这个服务的
8 r, V( m# s. Y% D服务端和客户端设计通信协议。什么信息将发送?如何整理这些信息?如何对这些信息进行编码? </P><P>应用程序通信
% z$ F. ?# P9 r2 K, a% N T4 q' s经常的,你的网络服务端或客户端应用程序要提供一层在网络软件和一个应用之间使用的服务。例如,一个HTTP服务站点在INternet与一个Web 服务应用之间为HTTP请求信息提供内容和应答。
, A3 H3 M6 o4 p% d( T5 a. O: Q在你的网络应用(或客户应用)和网络软件之间Sockets 提供一个接口。你必须提供一个接口,在你的应用程序与应用间使用。你可以拷贝第三方服务商提供的标准API(例如ISAPI),或你可以设计和发布你自己的API. </P><P>2.理解服务和端口
7 Z6 a. @3 h) i, f许多标准服务都有关联的、指定的端口号。当 执行服务时,你可以为服务考虑一个端口号。如果你实现一个标准服务, Windows socket objects 提供一些方法让你为服务寻找端口号。如果提供一个新的服务,在基于Windows 95 或 NT机器上,你能够在文件Services中为你的服务指定一个相关联的端口号。设置Services文件的更多信息请看微软 的Windows Sockets文档。 </P><P>二、Socket连接的类型
1 x3 v! G8 U8 k l/ HSocket连接可以分成三个基本的类型,它们反映了如何开始连接和本地Socket 连接是什么。这三个类型是: </P><P>1.客户端连接 7 o; [) m% @) `7 |0 q4 Z
客户端连接是一个本地系统的客户端socket与一个远程系统上的服务端Socket连接。客户端连接由客户端Socket开始。首先,客户端Socket必须描述它想连接到的服务端Socket. 接着客户端socket查找服务端socket,当找到服务器时,就要求连接。服务端socket可能不能完成正确的连接。服务器sockets维持一个客户端请求队列,在他们有时间时完成连接。当服务端socket接受客户端连接,服务端socket 8 P2 ]6 A, Y! h1 C: h
将向它想连接的客户socket发送一个完整的描述,客户端的连接完成。 </P><P>2.倾听连接 % q; m9 f( h# J3 w. P" {
服务器 socket不会去定位客户端,代替的,他们形成被动的,"半连接"状态,倾听来自客户端的请求。服务器 sockets形成一个队列,存放 它们听到的连接请求。这个队列记录着客户端连接请求就象他们已连接进来一样。当服务器sockets同意客户连接请求时,它形成一个新的socket去连接客户端,因此这个倾听连接能保持开放状态允许其他客户端请求。 </P><P>3.服务端连接 - j0 X6 U" \ o- i. B( D' E
当倾听socket同意一个客户端请求时,服务器端socket形成一个服务器连接。当服务器端同意连接时,向客户端发送一个服务端socket描述以完成连接,当客户端socket收到这个描述时这个连接得到确认,连接完成。一但连接到客户端的Socket完成,服务端连接就不能识别从一个客户端来的连接。末端双方有同样的能力去接收同样的事件类型。只有倾听(listening)连接是根本不同的,它只有一个单一的末端。 </P><P>三、sockets描述
" w; x0 w# r' w5 d8 aSockets让你的网络应用软件通过网络与其他系统进行通信。在网络连接中每个socket可以看成一个终端点。它有一个指定的地址。 </P><P>*这个系统正在运行
4 C: `$ W6 k& V5 S*它理解的接口类型 % n7 ^! P9 I% o/ ?/ ]5 i2 K) B- q# }
*用来连接的端口 ' U5 P6 N( G- U1 `7 S
一个完整的socket连接描述,你必须提供sockets 在连接两端的地址。在你开始一个socket连接前,你必须完整的描述你想得到的连接。有些信息可以从你的应用
- f/ E3 ]6 ?/ h2 M' [软件运行的系统平台上得到。例如,你不需要描述一个客户端socket的本地IP地址--这个信息可以从操作系统上获得。你必须提供你工作所依靠的socket的类型的信息。客户端socket必须描述他们想连接的服务器。侦听服务器sockets必须描述他们提供反应的服务器的端口。一个socket 连接终端的完整描述包括两部分: </P><P>1.IP地址
) ^2 {/ L1 E7 k- e; B主机是这样一个系统,它运行着包含有socket的应用程序。你必须描述主机给socket,通过给出主机的IP地址来完成这个描述。IP地址是一个有四个数字(byte)值的,在标准internet点付内的字符串。
3 U) |8 j6 f# b ~! v例如123.197.1.2 " \' n+ p& ~1 f
一个简单的系统可以支持多于一个的IP地址。IP地址通常难于记忆并且容易打错。一个可供选择的方法是使用主机名。主机名就是IP地址的别名,它就是你常看到的统一资源定位(URLs)。它是一个字符串,包括了域名和服务。
! P3 ]: h h8 [* q# w例如 <a href="http://www.wsite.com" target="_blank" >http://www.wsite.com</A> 4 W* g7 x3 Y% s8 L5 w4 ^0 N6 x+ [
许多内部网提供给主机的名字对应的系统IP地址是internetIP地址。在windows95 和NT机器上,如果一个主机名不能用,你可以在HOSTS文件中为你的本地IP地址(这个本地IP地址应该是指你想连接的主机IP地址--zyqsj)建立一个进入的名字。 ! L) r5 I6 {1 j1 `5 v
关于HOSTS文件的更多信息请看WINDOWS SOCKETS的文档。
* ]! m& T* H6 ?. G服务器sockets不需要指定主机。本地IP地址可以从系统中读到。如果本地系统支持多于一个的IP地址,服务器sockets将同时在所有的IP地址上侦听客户端请求。当一个服务器socket同意一个连接,客户端提供一个远程IP地址。客户sockets必须指定远程主机通过提供主机名或者IP地址。 </P><P>在主机名和IP地址间作一个选择
5 {: S/ u/ K9 m: w许多应用软件使用一个主机名去指定一个系统。主机名容易记住和容易检查排版错误。进一步讲,服务器能改变系统或与IP地址关联的特殊的主机名。使用一个主机名,能够允许客户端通过主机名描述找到抽象的站点,即使主机使用一个新的IP地址。 # {$ z7 _% w. |
如果主机名是未知的,客户socket必须指定服务器系统使用的IP地址。通过给一个IP地址来指定服务器将更快。当你提供主机名时,socket在定位服务器系统前,必须搜寻与这个主机名相关的IP地址。 </P><P>2.端口号 1 F# q& X% ~, f8 @
虽然IP得地址提供了足够的信息去找到socket连接中位于另一端的系统,你通常还需要指定那个系统的端口号。没有端口号,一个系统在同一时间只能进行一个单一的连接。端口号是唯一标识那允许一个独立系统连接到支持同时多个连接的主机,每个连接都必须指定一个端口号。 ! ]5 S5 T2 V0 g9 {1 B+ ~& E
在网络应用中,对于服务器工具来说端口号是一个数字代码。有一个习惯就是侦听服务连接到他们自己固定的端口号上,以便他们能找到客户端sockets.服务器socket监听为他们提供服务的相关端口号。当他们允许给予一个客户端socket连接时,他们创建一个独立的socket连接,使用不同的专用的端口号。通过这个方法,能持续的监听相关服务的端口号。
+ A- g& b, }' Z客户端socket使用一个专用的本地端口号,就不用其他的socket去寻找它们。他们指定他们想连接的服务器端socket的端口号,这样他们就能找到服务器应用程序。常常的,这个端口号是通过命名想连接的服务来间接指定的。 </P><P>四、使用socket控件
- t( S T y- R9 B% {( a/ PC++Builder提供两个socket控件,客户端sockets和服务器sockets.他们允许你的网络应用构成连接其他的机器和允许你通过这个连接来读写信息。与每个socket控件相关联的是windows socket对象,它们在终端的的作用是一个实际的socket连接。socket控件使用windows socket对象去封装windows socket API 调用,所以你的应用不用去关心连接建立的细节或管理socket信息。
* ^0 P% A# I Y2 J如果你想利用windows socket API调用或自定义连接细节,socket控件提供了便利,你可以使用windows socket对象的properies,events和方法。 </P><P>1.使用客户端sockets
) G5 J9 V: \; w/ d/ W添加一个客户端socket控件(TClientSocket)到你的form或data module 使你的应用成为一个TCP/IP客户。客户sockets允许你指定你想连接的服务器socket和你希望服务器提供的服务。一但你描述你想得到的连接,你可以使用客户socket控件去完成连接服务。 7 Y) y8 B8 a/ Z% C) r
每个客户socket控件使用独立的客户windows socket对象(TClientWinSocket)去应答连接中的客户终端。使用客户sockets去: </P><P>A.指定想得到的服务
4 j) O- {1 l! f4 u I. B客户socket控件有一个数字properties,允许你指定想连接的服务器系统和端口。你可以通过主机名来指定服务器系统,使用Host property。
, u% e+ u0 b2 w; x3 P! U如果你不知道主机名,或者你关心找到服务器的速度,你可以指定服务器系统的IP地址,通过使用 Address property。你必须指定IP地址和主机名中的一个。
5 t0 N0 S$ M1 d如果你两个都指定,客户socket控件将使用主机名。除服务器系统外,你必须指定你的客户socket将连接的在服务器系统上的端口。你能够直接使用Port property来指定服务端口号。或者直接在Service property使用想得到的服务的名字。如果你指定端口号和服务名,客户socket控件将使用服务名。 </P><P>B.建立连接 ; I! n6 K1 q$ A/ ~
一旦你在客户socket控件中完成了设置描述你想连接的服务器的属性,你就可以进行连接,通过调用Open方法。如果你想你的应用启动时自动建立连接,在设计时设置Active property为true,通过使用Object Inspector来设置。 </P><P>C.取得关于连接的信息
% n9 u. \! F% l/ D) I: J" x完成连接到服务器socket后,你可以使用与你的客户socket控件相关的客户windows socket object去取得关于连接的信息。使用Socket property去访问client windows socket object。windows socket object 有一个properties,它能让你确定在连接的两端客户和服务器使用的地址和端口号。 5 S* G( j2 C% a2 m
当使用一个windows socket API调用时,你可以使用SocketHandle property区获得socket连接使用的handle。你可以使用Handle property去访问windows,以便接收来自socket连接的信息。AsyncStyles property决定哪种信息类型是windows handle要接收的。 </P><P>D.关闭连接
+ L6 z0 E: I) h6 Q% k1 i* D当你完成通讯想关闭socket 连接时,你能够通过调用Close方法来关闭连接。连接可能要由服务器端来关闭。如果是这种情况,你将收到一个OnDisconnect 事件的通知。 </P><P>2.使用服务器sockets 8 n8 [; l5 q' ~7 L
添加一个服务端socket控件(TServerSocket)到你的form或data module使你的应用成为一个TCP/IP服务器。服务器sockets允许你指定你想提供的服务或你想用来监听客户请求时使用的端口。你可以使用服务器socket控件去监听和允许客户连接请求。每个服务器socket控件使用一个单一的服务器windows socket Object(TServerWinSocket)去应答在服务器端监听到的连接。它通常使用一个服务器客户winodws socket Object(TServerClientWinSocket)应答在服务器端每个活动的,连接着得到允许服务的客户socket。使用服务器sockets去: </P><P>A.指定端口
t$ Q8 Z* n" q2 r$ S5 z8 S Y在你的服务器socket能够监听客户请求之前,你必须指定一个端口给你的监听服务。你可以使用Port property来指定这个端口。如果你的服务器应用提供一个标准的服务,这个服务使用一个习惯使用的相关联的端口。你能够使用Service property直接指定端口号。使用Service property是一个好的主意,能够减少设置端口号时的错误。如果你既指定了Port property,又指定了Service property,服务socket将使用服务名。 </P><P>B.监听客户请求
! {- q7 z- Y" U( y1 T6 e+ @0 o: p一旦你在server socket控件上设置好你的端口号,你就能够通过在运行时通过调用Open方法来监听一个连接。如果你希望你的应用程序能够在启动的时候自动监听连接,在设计的时候通过使用Object Inspector设置Active 属性为true。 </P><P>C.连接到客户端。
9 G- t' Q. V& |& p" Q& c. Q当监听服务socket控件接收到一个客户端连接请求时他们将自动接受这个请求。当你没次收到通知时,OnClientConnetc事件将发生。 </P><P>D.取得关于连接的信息 : S( j7 }7 o3 L$ r) v: O
一但你的服务器socket打开了监听连接,你能够使用与你服务器socket控件相关联的服务器windows socket object来取得关于连接的信息。使用Socket property去访问server windows socket object.windows socket object有一个属性能够让你找到关于所有活动的客户socket连接这些客户socket是你通过服务器socket控件允许连接的。使用Handle属性去存取windows通过socket连接收到的信息。
% N0 t# g: n8 x2 } j: |每个活动的,连接到客户应用是通过服务、客户windows socket bject (TServerClientWinSocket)封装的。你能够通过server windows socket object的连接属性来访问所有的这些。这些server client windows socket object有些属性让你能够决定哪些地址和端口号给连接的两端--客户和服务器socket使用。当你使用windows socket API调用时,可以使用SocketHandle属性去获得socket连接使用的handle。你能够使用Handle属性去访问windows从socket连接处得来的信息。AsyncStyles属性决定windows handle将接收哪种类型的信息。 </P><P>E.关闭连接
+ o6 ?( G' N, X. `当你决定关闭监听连接时,调用Close方法。这将关闭所有打开着的,连接到客户应用的连接,取消任何尚未同意的连接,接着关闭监听连接以便你的服务socket控件不在接受任何新的连接。当客户端关闭他们自己独立的连接到你的server socket的连接时,你可以在OnClientDisconnect事件中得到讯息。 </P><P>五、socket事件的应答 * H& v' m" h5 n) h6 s5 h# B
当使用sockets写应用程序时,大多数工作发生在socket控件的handler事件中.当通过socket连接开始读或写时,OnRead和OnWrite事件在non-blocking client sockets中发生从而通知sockets.同样的,服务器sockets(blocking or non-blocking)收到OnClientRead和OnClientWrite事件. ; q9 b) Y! z* W
当服务器结束一个连接时,客户scokets收到一个OnDisconnect事件.当客户端结束一个连接时,服务器socket收到一个OnClientDisconnect事件. # J& i7 u( j4 Y0 A" Y! z: Q
另外,客户端Sockets和服务器端socket从连接中收到一个错误信息时,都将产生有个错误事件. </P><P>错误事件:客户sockets和服务器sockets通常会产生一个OnError事件,当他们从连接中收到一个错误信息的时候.你能够写一个OnError事件处理去响应这些错误信息.这个OnError事件处理提供传送关于socket试图做什么的时候这个错误发生的信息,以及错误信息提供的错误代码.你可以在OnError事件处理中对这个错误作出响应,并且把错误代码改为0,以避免socket产生一个例外. </P><P>当开始和完成发生时,socket控件通常会收到一个事件号(number of events).如果你的应用程序需要改变socket开始操作的处理过程或通过连接开始读或写操作时,你将写事件handlers去应答这些client events和server events. </P><P>A.client events
% G6 Y# R. Q! {. G当一个客户socket打开一个连接时,以下事件发生:
# e0 {: c2 v* \6 J! O6 F, l2 D1.一个OnLookup事件最先发生,它试图去定位server socket.在这里你不能改变Host,Address,Port,Service属性去改变你想定位的服务器.你能够使用Socket属性去访问client windows socket object,并且使用它的SocketHandle属性去调用windows API,以便改变socket的客户属性.例如,如果你想在客户应用软件中设置端口号,你必须在server client连接前做这件事.
. n" L1 Q$ F9 J4 W0 G0 H1 G: j9 U2.windows socket设置和初始化事件通知. 4 c- ~5 P( V8 d# `1 w
3.当找到server socket时一个OnConnecting事件发生.在这事件中,windows Socket object可以利用的是通过socket属性提供关于连接的另一端的服务socket的一些信息.这是获得实际使用来连接的端口和IP地址的第一个机会,它可能不同于从监听socket处同意连接时得到的端口或IP地址. 5 q! T! @1 l% p
4.服务器同意连接请求,客户端socket完成连接.
: Q* {9 l7 ? r5.当一个连接得到确定后,一个OnConnect事件发生.如果你的socket立即开始通过连接读或写,就应写一个OnConnect事件Handler去作这件事. </P><P>B.服务器端事件(server events) , H$ i* a/ L( S( c3 K3 y; e% ~
服务器socket控件通过两中方式连接:监听连接和连接到客户应用.服务器socket收到这两个连接的所有事件. </P><P>监听时事件 $ ]0 S B8 ?/ f8 T+ C1 f8 c
当构成监听连接前,OnListen事件发生.在这个时候你能够通过socket属性获得server windows socket object.你能够使用它的SocketHandle属性去改变socket,在socket打开监听之前.例如,如果你想限定监听服务使用的IP地址,你可以在这个OnListen事件Handler中做. </P><P>与客户端连接的事件
2 v) M/ W- I" w7 ~/ d2 W当一个服务器socket同意一个客户连接请求时,接下来的事件发生: / b3 ^" a9 F9 }/ Z8 c. I3 G
1.服务器socket产生一个OnGetSocket事件,通过windows socket handle传送给连接的另一端的socket.如果你想提供自己定义的TServerClientWinSocket of descendant,你可以在OnGetSocket 事件 handler中建立,将被用来替代TServerClientWinSocket.
3 Z0 f$ ~8 G8 l/ g: }2.一个OnAccept事件发生,传送新的TServerClientWinSocket对象给事件句柄.这是第一个要点,当你使用TServerClientWinSocket的属性去获得被连接中服务的那端的客户的信息时. ' C, Q2 D* t7 N1 h4 Q8 M8 u
3.如果服务类型是stThreadBlocking,一个OnGetThread事件发生.如果你想提供自己定义的TServerClientThread子类,你可以在OnGetThread事件句柄中建立一个,它将替代TServerClientThread. * f! {. W/ L9 T4 N/ T
4.如果服务类型是stThreadBlocking,一个ONThreadStart事件发生当这个线程(thread)开始执行时.如果你想执行任何初始化这个线程,或调用一些windows socket API在这线程开始通过连接读和写之前,应该使用OnThreadStart事件句柄. : s4 y5 o0 Y7 }" C# ~
5.当客户端完成一个连接时,一个OnClientConnect事件发生.如果是non-blocking服务,你可能想开始通过socket连接在这端进行读或写操作. </P><P>六、通过socket连接进行读和写
: O# F- k5 \3 p6 l3 y通过socket连接到其他机器的原因是想通过这些连接来读和写信息.什么信息是你要读和写的,或者当你想读和写时是依靠哪些socket连接的相关服务的. 7 c- Q+ D; p& {" F' h0 k) r
通过sockets进行读和写可以是异步的,所以在你的网络应用中不需要阻塞其他代码的执行.这是调用non-blocking connection.你也同样可以通过blocking connection,这时你的下一行代码的执行必须等到读或写操作完成. </P><P>A.Non-blocking连接,读和写是异步的, 所以在你的网络应用中不需要阻塞其他代码的执行.建立一个Non-blocking连接: * e' T3 S. e- R' B% ?
1.在客户socket中设置ClientType属性为ctNonBlocking. + G* s b) V5 i$ E0 R6 W" i
2.在服务器socket中设置ServerType属性为stNonBlocking. ! ]5 L9 ?0 @4 w* F/ c3 V
当连接是non-blocking时,连接的另一端企图读或写时读和写事件将把这个信息通知你的socket. </P><P>读和写操作事件 , ~) U! S6 i3 m0 {# r# Z
Non-blocking sockets想通过连接读或写时,它会产生一个读和写操作事件通知你的socket.在客户端sockets,你可以在OnRead或OnWrite事件句柄中对这些事件做出反应.在服务器端Scokets,可以在OnClientRead或OnClientWrite事件句柄中对这些事件做出反应.与socket连接相关联的windows socket object在事件句柄的读或写中被当作一个参数.Windows socket object提供一个方法号(number of methods)以允许你通过连接读或写.
* e3 s8 i4 I1 |& _通过socket连接读,使用ReceiveBuf或ReceiveText方法.在使用ReceiveBuf方法前,使用Receivelength方法去确定在连接的另一端socket准备发送的字节数(number of bytes).
3 y3 X# l" R- Q* n通过socket连接写,使用SendBuf,SendStream,或SendText方法.如果你通过socket发送信息后不在需要socket连接,你可以使用SendStreamThenDrop方法. SendStreamThenDrop在写完所有的信息后将关闭Socket连接,它能够从stream读信息.如果你使用SendStream或SendStreamThenDrop方法,不要释放Stream object, socket在连接结束后会自动释放这个Stream.
9 i( B; J& G6 v. s- ]$ R/ b4 C注意:SendStreamThenDrop将关闭一个独立的客户连接服务,而不是监听连接. </P><P>B.Blocking connections
, M/ \2 t$ u1 e1 [0 T+ C: ]当你使用的连接是Blocking时,你的Socket必须通过连接发起读或写操作,胜过被动的等待从socket连接发来的通知. 当你的连接末端的读和写操作发生改变时使用Blocking socket.对于客户端sockets,设置ClientType属性为ctBlocking 以便构成一个blocing connection.根据你的客户端应用想完成什么,你可能想建立一个执行线程去完成读或写操作,以便你的应用能够继续执行其他的线程,当它在等待通过连接读或写操作的完成.
! J. R( d# ]" h, \对于服务器sockets,设置ServerType属性为stThreadBlocking以便构成一个blocking connection.因为blocking connections在等待通过连接读或写信息完成时挂起了其他代码的执行,服务器socket控件通常产生一个新的执行线程给每一个客户连接,当ServerType设置为stThreadBlocking时.许多使用Blocking连接的应用都写使用线程(using threads.甚至如果你不使用线程,你可能也想使用(using) TWinSocketStream去读和写.
/ F5 c4 M( z( n+ P+ f( P4 S1)using threads . p" X- y# `/ d7 F8 r$ W' O ?5 f
当使用一个blocking connection进行读或写操作时,客户sockets不会自动产生一个新线程.如果你的客户应用程序没有什么事做,直到读或写信息完成,那么这正是你想要的.如果你的应用包括了一个用户界面,它还需要响应用户的操作,那么,你可能想产生一个独立的线程去读写.当服务器sockets形成一个blocking连接时,他们常常产生独立的线程给每一个客户连接,所以没有客户需要等待直到其他客户完成通过连接读或写操作.在默认情况下,服务器sockets使用TServerClientThread对象去实现为每个连接执行不同的线程.
8 w* J/ F" c( ~! `* |9 QTServerClientThread对象模拟发生在non-blocking连接中的OnClientRead和OnClientWrite事件.可是,这些事件发生在监听socket上时,不是本地线程(thread-local).如果客户请求频繁,你将想建立你自己的TServerClientThread子类去提供一个安全线程(Thread-Safe)去完成读和写操作.
, f% g& a) ]% X( X当写客户线程或写服务器线程时,你能够使用TwinSocketStream去做实际的读写操作. 2 S( g7 j. Q2 u6 e9 w# |2 H0 ]! ^+ [
A)写客户端线程 % {4 l! [( x) t# G1 [
为客户端连接写一个线程,定义一个新线程对象,使用新线程对象对话框.你的新线程对象Execute方法的句柄的通过线程连接进行读写操作的细节,可以建立一个TWinSocketStream对象,然后使用它来读或写.
& f( {7 a9 l3 S8 F! M使用你自己的线程,在OnConnect事件句柄中建立它.关于建立和运行线程的更多信息,请看Executing thread objects.
0 D" Q6 j0 t7 g例子:这个例子显示一个应用的客户线程在连接确定后向服务器发出写请求.
- a6 o$ Y/ x" e% gvoid __fastcall TMyClientThread::Execute() % t0 K- ?: ?0 K* j6 l# l3 @9 j
{
# Q/ `. W( m1 N3 I4 a6 b5 \while (!Terminated && ClientSocket1->Active) m1 e, ]( {6 C) L
// make sure connection is active
5 F0 f# V! f a% x" q7 X4 w{
) u1 ^6 g! u* ~0 ctry
* q7 [+ s& Y$ g7 L3 D{ ! L- ]# x$ l! @
TWinSocketStream *pStream = new TWinSocketStream(ClientSocket1.Socket,60000); ( [. e1 ~1 z& k- [
try
: ^% s% Z, E5 V! }$ A8 R{
' P0 c* p3 c; r( Y" ichar buffer[10]; M% S1 K% C) V
GetNextRequest(buffer); & L& ~& T8 {5 T, u1 B/ Q
// GetNextRequest must be a thread-safe method
4 s4 m- k, o' _: L+ ]// write a request to the server
7 o" q4 Q+ p* f' `0 O+ G1 VpStream->Write(buffer,strlen(buffer) + 1); 2 r2 a( y% c; B# c/ B7 w+ j
// continue the communication (eg read a response) </P><P>} ' |4 s" |0 C5 E+ k
__finally </P><P>{ : M+ ~, V9 r5 Z2 v0 C! ]9 z, h
delete pStream;
& ` U+ g8 E5 h: T}
8 K! ]( R. k2 _1 ^( d. R} % F. W" f$ d i ~: K7 L
catch (Exception &E) # n' D4 q4 ]1 d0 o4 K' ~- m
{
$ \7 a% q8 c7 L0 M2 u M7 Mif (!E.ClassNameIs("EAbort"))
+ \- i. y; e7 cSynchronize(HandleThreadException());
; |) V; r1 f ]// you must write HandleThreadException $ {. i- h. j, @/ |0 u
} 8 d2 W F$ x" L
} ( [3 x: y8 W. k9 n/ E
} </P><P>B)写服务器线程 1 O( O3 Y6 N. b( D2 u* O
服务器连接线程由TServerClientThread派生.因为这个,不能使用新线程对象对话框.替代的,手动声明你的线程如下: </P><P>class PACKAGE TMyServerThread : & e0 [( E" Y/ q% a+ E& u1 V
public ScktComp::TServerClientThread
7 D; C+ S8 u) Z{
8 j; u `- j; s1 M: x2 r; ^public . V- {. E5 i. i7 ?6 ]+ y) H
void __fastcall ClientExecute(void);
5 L: A# _- F, D4 q. H) g. T3 x9 t4 I} </P><P>注意你将用重载ClientExcute方法替代Execute方法.执行ClientExecute方法必须为客户端连接写一个同样的Execute方法线程.然而,当你从控件栏上放一个客户socket控件到你的应用上时来替代这个方法时.监听服务socket同意一个连接时,服务客户线程必须使用TServerClientWinSocket对象来建立.这可以利用共公共的CientSocket属性.另外,你能够使用HandleException这个protected性的方法,胜过
6 z, _! N. I* |! V( Z7 L n" Y你自己写你的thread-safe例外操作. </P><P>警告:Server sockets会缓存他们使用到的线程.确信ClientExecute方法执行一些必要的初始化操作,以便它们在最后执行时不致于产生不利的结果. </P><P>当你使用你的线程时,在OnGetThread事件句柄中建立它.当建立线程,设置CreateSuspended参数为false.
1 g- w4 o4 g* }4 ]& ^ [( u' \# z- m例子:这个例子显示一个为一个应用服务的线程,这个应用是在连接确定后由客户端来的读请求. </P><P>void __fastcall TMyServerThread::ClientExecute()
, u4 e ]1 A6 `/ w. j! _{
a4 c: r8 Z. D' Mwhile (!Terminated && ClientSocket->Connected) 1 H1 ?- Y) y; D! N8 K# y4 Q7 b0 X
// make sure connection is active
$ _0 I5 t( B$ Z0 _- l; V6 h{
# w* w: s, @& A$ O; _: {7 \# gtry + j2 z. `" x# H6 m& D/ |" L
{
# t9 ~. t3 {) T" vTWinSocketStream *pStream = new TWinSocketStream(ClientSocket,
1 S- C; i5 F( G$ ^8 d60000);
: ?# }* ?( c* V8 ntry
' l/ ?6 U' m9 X8 j{ # Y& ~- w" a2 J) n0 y/ w
char buffer[10]; : I" |% A* x1 l" l. u! M; h4 p) m
memset(buffer, 0, sizeof(buffer));
. a7 t3 H0 t9 P, kif (pStream->WaitForData(60000))
7 o8 H2 o- l! ]: U- u' v// give the client 60 seconds to start writing , j) H( \# h9 a4 \
{
( E# s5 y) Q. H0 w* qif (pStream->Read(buffer, sizeof(buffer) == 0) </P><P>ClientSocket->Close();
% p7 S/ B# S: y8 s7 O% K// if can't read in 60 seconds, close the connection 2 c* t! l2 g, S, _* E
// now process the request - x. i( w' e; j* q' A2 V
}
" ^ D) o; `3 L7 ]( c/ e4 d# i: Pelse % I) z' L& `9 y6 y8 w- t# k' b6 ^
ClientSocket->Close();
- N. j6 {! }9 a2 T# l6 r) r1 s} : C" K9 m- m( I' V# d/ o/ ?7 `
__finally </P><P>{
( `2 y) _" f( v- f9 ydelete pStream;
3 A1 S- I0 ?2 p- @* y- I}
) O! Q! i- u0 g F7 {2 G3 l- H8 T} 6 p" c3 H: q. l8 C$ v6 I, A
catch (...)
$ Z U b* N2 A; u7 _{
; H F- Z0 J% B; l1 qHandleException(); 6 j0 j/ L- ^) A
} " b+ S6 R9 ]. Y
} - [$ D6 ^% [7 p4 V) A) E; z/ M
} </P><P>C.使用TwinSocketStream
( _5 f, s7 S2 D5 a! g0 n& h当为一个blocking连接实现一个线程时,你必须确定在连接的另一端的socket是准备写还是读.Blocking连接不会通知socket当它准备好写或读操作的时候.想看看连接是否准备好,使用TWinSocketStream对象.TWinSocketStream提供一个方法去帮助调整读或写操作时间的选择.调用WaitForData方法去等待,直到socket另一端的
8 Q, h: P1 w3 y! n: F& ~准备好写操作.当读写操作使用TWinSocketStream时,如果读或写操作在指定的时间期限内未能完成,Stream将发生超时.这个超时被当作一个结果,socket应用不会暂停,而是不断的通过一个dropped connection试图读或写. </P><P>注意:你不能在non-blocking连接中使用TWinSocketStream </P> |
|