PersonalCorpus 版 (精华区)
发信人: wildwolf (桐子), 信区: C_and_CPP
标 题: C++ Builder技巧
发信站: 哈工大紫丁香 (2003年09月21日20:42:33 星期天), 站内信件
http://yynets.51.net/cbuilder.htm
怎样在C++Builder中创建使用DLL
自从C++Builder从去年浪漫情人节上市以来,吸引了大量的Delphi、VC、Vb的程序
员到它的怀抱,大量的C、C++程序员感叹道:总算有了C的可视化开发工具,对我也是一
样,从BC、Delphi到C++Builder。
动态链接库(DLL)是Windows编程常遇到的编程方法,下面我就介绍一下在BCB (C++B
uilder下简称BCB) 中如何创建使用DLL和一些技巧。
一、创建:
使用BCB File|NEW建立一个新的DLL工程,并保存好文件BCB,生成一个DLL的程序框架
。
1.DllEntryPoint函数为一个入口方法,如果使用者在DLL被系统初始化或者注销时被
调用,用来写入对DLL的初始化程序和卸载程序;参数:hinst用来指示DLL的基地址;reas
on用来指示DLL的调用方式,用于区别多线程单线程对DLL的调用、创建、卸载DLL;
2.在程序中加入自己所要创建的DLL过程、函数;
3.用dllimport描述出口;
例程序如下:
#include
#pragma hdrstop
extern 揅?__declspec(dllexport) int test();
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
return 1;
}
int test()
{
return 3;
}
注意:动态链接库中调用过程、函数时有不同的CALL方式 __cdecl、 __pascal,
__fastcall、__stdcall,BCB中默认的方式为__cdecl(可不写),如果考虑兼容性可用时__
stdcall声明方法为:
extern 揅?__declspec(dllexport) int __stdcall test();
对于其中过程、函数也改为:
int __stdcall test()
二、使用DLL
在BCB中使用DLL有两种方法:
1.用静态调用法
首先需要在BCB的项目中加入输入接口库(import library),打开工程项目,使用BCB
View|Project Manager打开项目列表,向项目中加入接口库(*.lib)。
其次在头文件中加入接口声明。
例程序如下:
//define in include file
extern 揅?__declspec(dllimport) int __cdecl test();
//use function in main program
int I;
I=test();
注意:
(1)动态链接库调用过程、函数时CALL方式 与创建时方式一样不写为__cdecl,其它需要声
明。
(2)BCB创建的DLL有对应的输入接口库(import library),如只有DLL而无库时,可用BCB的
implib工具产生:implib xxx.lib xxx.dll;另外可用:tlib xxx.lib,xxx.lst 产生DLL
的内部函数列表,许多Windows的未公开技术就是用这种方法发现的。
2.动态调用法
动态调用法要用Windows API 中的LoadLibrary()和GetProcAddress()来调入DLL库,
指出库中函数位置,这种方法较常见。
例程序如下:
HINSTANCE dd;
int _stdcall (*ddd)(void);
dd=LoadLibrary(搙xx.dll?;
ddd=GetProcAddress(dd,搕est?;
Caption=IntToStr(ddd());
FreeLibrary(dd);
三、注意:
创建DLL时编译链接时注意设置Project Options。
Packages标签:去除Builder with runtime packages检查框。
Linker标签:去除Use dynamic RTL检查框。
否则创建的DLL需要Runtime packages or Runtime library。
用C++Bulider在WIN.INI中保存信息
现在许多软件把程序中需要的数据保存在注册表中,这样当用户装的软件越来越多时
,致使注册表越来越庞大,容易使系统出错。当然,微软也建议在注册表中保存数据,但
当我们需要保存的数据不多时完全可以把数据保存在WIN.INI中,这样可以很方便地维护,
实现方法相对来说比较简单。下面我以Borland C++ Builder为例来说说如何实现。
原理其实很简单,只需调用API的 WriteProfileString和GetProfileInt函数就可以了
。这两个函数的原型是:BOOL WriteProfileString(LPCTSTR lpAppName,LPCTSTR
lpKeyName,LPCTSTR lpString );
UINT GetProfileInt(LPCTSTR lpAppName,LPCTSTR lpKeyName,INT nDefault);
其中lpAppName指在WIN.INI中段的名字,即用[]括起来的字符串,lpKeyName指在这个
段中每一个项目的名字,lpString指这个项目的值,即“=”后的数, nDefault为当GetPro
fileInt没有找到lpAppName和lpKeyName时返回的值,即缺省值,前者返回为布尔值(true
或 false),后者返回为无符号整形值。当在WriteProfileString函数中 lpKeyName 为空(
NULL)时,则清除这个段的全部内容,lpString 为空时,则清除这一项目的内容,即这一
行将清除掉。
下面举一例子来说明这两个函数的用法。新建一个应用程序,在Form1上放两个Edit和
三个Button,其中Edit的Text为空,三个Button的Caption分别为“添加”、“查看”、“
清除”。双击“添加”按钮加入下面代码:
WriteProfileString(“例子程序”,“项目”,Edit1→Text.c_str());
双击“查看”按钮加入如下代码:
unsigned int Temp;
Temp=GetProfileInt(“例子程序”,“项目”,100);
Edit2→Text=IntToStr(Temp);
双击“清除”按钮加入如下代码:
WriteProfileString(“例子程序”,NULL,NULL);
然后按F9键运行程序。
下来可以检验一下程序的正确性。在Edit1中输入数字,如“3265”,按“添加”按钮
,这时运行“sysedit”来查看“WIN.INI”文件的最后面,可以看到加入了如下内容:
[例子程序]
项目=3265
其中“[]”和“=”是函数自动加上的。按下“查看”按钮,在Edit2中出现“3265”
,当按下“清除”按钮可清除添加的部分。经过查看可知程序已达到预期的目的。
喜爱编程的朋友可以把上述方法应用到自己的程序中去,来达到保存数据信息的作用
。当确实要把信息保存到注册表中,可以在C++ Builder中定义一个TRegistry类的对象
来进行相关的操作,或者直接调用Windows的API函数,具体如何编程大家可以参阅相关资
料或者同我联系。
如何在C++Builder中检测硬件
在我们编写的程序中常常要和硬件打交道,那么如何在程序中确定系统中是否有该设备
,它的运行状态又是怎样的呢?对于初学者来说,这个问题常常不好解决,其实只需简单地
利用几个API函数,硬件的问题并不神秘。下面就让我们一起看看在C++ Builder中是如
何检测硬件的。
1. 检测CPU的型号
先让我们从最简单的做起,看一看自己的CPU型号。首先,在C++ Builder中画出图1
所示的窗体,在下面的几个例子中我们将一直使用这个窗体作示范,它包括一个用来激活
测试的Button和一个用来显示结果的Memo。我们可以用GetSystemInfo这个API获得CPU的型
号。将下列代码添加到Button的Click事件里就可以了:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
//获得CPU型号
SYSTEM_INFO systeminfo;
GetSystemInfo (&systeminfo);
Memo1→Lines→Add(撃腃PU类型是:敚玈tring( systeminfo.dwProcessorType ));
}
运行它,点击Test试试,CPU型号出来了吧!
2.检测内存状态
获得内存状态的方法和CPU型号差不多,只是他用到的是另外一个API:GlobalMemoryS
tatus。
其中,成员dwTotalPhys用来获得物理内存总量,而dwAvailPhys顾名思义是有效物理
内存的意思。我们只要把下面几行代码加到上面程序的后面就可以了(不用重做,下同)
:
//获得内存状态
MEMORYSTATUS memory;
memory.dwLength =sizeof(memory); //初始化
GlobalMemoryStatus(&memory);
Memo1→Lines→Add(撃奈锢砟诖媸?Mb):敚玈tring(int(memory.dwTotalPhys
/1024/1024)));
Memo1→Lines→Add(撈渲锌捎媚诖媸?Kb):敚玈tring(int( memory. /1024)));
怎么样,看出点门道了么?两段程序的格式几乎一模一样,其实,GetSystemInfoGlob
alMemoryStatus还可以获得许多其他有关CPU和内存的信息,就按照上面的格式去套就行了
,更详细的资料可以去看C++ Builder4的Help。
3. 检测可用硬盘空间
好了,经过前面两个简单问题的热身,我们来处理一个稍微复杂的问题:我们知道安装程
序大都有一个检测硬盘空间的过程,那么这是怎么实现的呢?他用到的是API函数GetDiskFr
eeSpace,这个函数输入一个参数:目标盘的路径;返回四个参数,依次是每簇的扇区数、
每扇区的字节数、空闲的簇数、总簇数。假如我们需要检测C盘的总容量和可用容量,那么
可以把以下代码加到上面的程序中:
//获得C盘可用空间
DWORD sector,byte,cluster,free;
long int freespace,totalspace;
GetDiskFreeSpace(揅:?&sector,&byte,&free,&cluster); //获得返回参数
totalspace=int(cluster)*int(byte)*int(sector)/1024/1024; //计算总容量
freespace=int(free)*int(byte)*int(sector)/1024/1024; //计算可用空间
Memo1→Lines→Add(揅盘总空间(Mb):敚玈tring(totalspace));
Memo1→Lines→Add(揅盘可用空间(Mb):敚玈tring(freespace));
怎么样?现在可以自己做安装程序了吧!
C++Builder如何响应消息及自定义消息
Inprise(Borland) C++Builder中,可以象在Delphi中一样响应消息,只是看起来要稍
复杂一点。
对于系统已定义的消息,可以直接响应:
#define WM_MY_OPEN_CMDLINE_FILE (WM_USER+1) //进程间通讯的自定义消息
#define WM_MY_SEARCH_NODE (WM_USER+2) //查找命令的自定义消息
class TSomeForm : public TForm
{
//...类中的其它代码
protected:
//消息的响应过程
void __fastcall OpenCmdLineFile(TMessage Message);
void __fastcall SearchDocumentNode(TMessage Message);
void __fastcall GetWindowMinMaxInfo(TWMGetMinMaxInfo Message);
//以下通过宏定义实现消息的正确响应
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_MY_OPEN_CMDLINE_FILE, TMessage, OpenCmdLineFile)
MESSAGE_HANDLER(WM_MY_SEARCH_NODE, TMessage, SearchDocumentNode)
MESSAGE_HANDLER(WM_GETMINMAXINFO , TWMGetMinMaxInfo, GetWindowMinMaxIn fo)
END_MESSAGE_MAP(TForm)
};//end class
//以下为实现代码
void __fastcall TSomeForm::OpenCmdLineFile(TMessage Message)
{//直接通过消息结构传递参数
LPSTR lpCmdLine=(LPSTR)Message.LParam;//从Message中取得参数
this->HandleCmdLineFile(lpCmdLine);//处理命令行的参数
return;
}
void __fastcall TSomeForm::SearchDocumentNode(TMessage Message)
{//响应查找消息
//Message中的参数在此处不需要。
this->SearchNode();
return;
}
void __fastcall TSomeForm::GetWindowMinMaxInfo(TWMGetMinMaxInfo Messag
e)
{//设置主窗口的最小尺寸
MINMAXINFO *MinMaxInfo=Message.MinMaxInfo;
MinMaxInfo->ptMinTrackSize.x=400;
MinMaxInfo->ptMinTrackSize.y=300;
return;
}
其中:TMessage和TWMGetMinMaxInfo类型的定义可参见:
C:\Program Files\Borland\CBuilder\inlucde\vcl\Messages.hpp;其它的消息
响应方法与此相同。
另外,可以为自定义的消息也定义一个对应的消息结构(如:TSearchNode_Mes
sage),至于如何定义消息结构, 可以参考:
C:\Program Files\Borland\CBuilder\inlucde\vcl\Messages.hpp
利用C++ Builder开发动画DLL
我们在Windows98环境下执行拷贝文件、查找文件或计算机等耗时比较长的操作时,Wi
ndows会显示一个小小的动画,指示正在进行的操作,与死板的静止图像相比增色不少。那
么我们自己开发软件时,能否也显示一个这样的动画提示呢?我在开发一个外贸应用软件
系统时,遇到的数据量很大,当通过复合条件查找时,因为不是数据库表的每个项目都有
索引,所以很费时,系统也会表现出长时间停顿,用户感觉极为不爽。我经过一段时间的
探索,开发了一个能够在采用的开发环境PowerBuilder下调用的动画DLL,由于采用多线程
编程,PB调用的DLL函数能够及时将控制权交还为PB,不影响应用系统的运转。用户能够看
到一个东西在动,也就不会想到系统是不是停止响应了,感觉时间也似乎没那么久了。
代码与编译选项
(1) 在C++Builder的File菜单下选择New,在New Item对话框的New属性中选择DLL,C+
+Builder就会创建一个空白的DLL项目。
(2) 在File菜单下选择New Form,C++Builder创建一个空白的Form,修改它的属性为
BorderStyle=bsDialog
BorderIcons的子属性均为False
FormStyle=fsStayOnTop
Position= poScreenCenter
Name=StatusForm
(3) 在Form上添加一个Win32下的Animate控件Animate1,修改它的属性为
Align=alTop
(4) 在Form上添加一个Standard下的Button控件Button_Cancel,再添加System下的Ti
mer控件Timer1,设置定时Interval时间位250,以较快的响应用户的取消请求。
因为PB应用系统与动画窗体代码分别属于两个线程,不能采用PB线程直接关闭动画窗
体线程的窗口,否则会引起系统运行不正常,因此采用PB线程设置关闭标志,而动画线程
采用Timer控件定时检查标志,一旦检测到关闭标志,就关闭窗口,清除线程标志,结束动
画线程。
下面给出编码及编码原理:
1.DLL DLL主体代码:
/**********************************
* DLL主体代码
* 定义DLL公用变量
* g_CommonAVI
对Animate控件动画类型索引
* gi_Canceled
Button_Cancel按钮是否被选择过
* gi_AVIType
要显示的动画类型,由DLL输出函数做为参数输入
* gi_RequestClose
请求动画线程关闭标志
* gi_WindowActive
动画窗口所处的状态
* lpsWinTitle
动画窗体的标题,由DLL输出函数做为参数输入
*/
TCommonAVI g_CommonAVI[]={
aviNone, aviFindFolder,
aviFindFile, aviFindComputer,
aviCopyFiles, aviCopyFile,
aviRecycleFile, aviEmptyRecycle,
aviDeleteFile
};
int gi_Canceled=0,gi_AVIType=0;
int gi_RequestClose=0,gi_WindowActive=0;
char lpsWinTitle[256];
HWND hWndParent=NULL;
/* 定义DLL 输出函数 */
extern "C" __declspec(dllexport)
int pascal DllEntryPoint(HINSTANCE hinst,
unsigned long reason, void*);
extern "C" __declspec(dllexport) int
pascal ShowStatusWindow(int AVIType,
LPSTR WinTitle,long hWnd);
extern "C" __declspec(dllexport) int
pascal GetStatus(int ai_CloseWin);
extern "C" __declspec(dllexport) int
pascal CloseStatusWindow();
/*定义线程TformThread:*/
class TFormThread : public TThread{
public:// User declarations
__fastcall TFormThread(bool CreateSuspended);
void __fastcall Execute(void);
};
__fastcall TFormThread::
TFormThread(bool CreateSuspended):
TThread(CreateSuspended){
}
/* 动画线程执行代码,
动画窗体的定时器控件会关闭它,
清除窗体存在标志后结束线程的运行
*/
void __fastcall TFormThread::Execute(void){
gi_WindowActive=1;
StatusForm=new TStatusForm(NULL);
StatusForm- >Caption=lpsWinTitle;
StatusForm- >ShowModal();
gi_WindowActive=0;
delete StatusForm;
gi_RequestClose=0;
}
/* 定义一个线程实例指针 */
TFormThread *FormThread;
/**********************************************
* 输出函数代码实现部分
* DllEntryPoint 32位DLL入口
* ShowStatusWindow 显示动画窗口,
它通过创建一个线程来创建窗口,避免由于窗口
的MODAL属性而使控制权不能及时的返还给调用者
* GetStatus
取得撊∠麛状态,即用户有没有选择撊∠麛按钮
* CloseStatusWindow 关闭动画窗口,
*/
__declspec(dllexport) int WINAPI DllEntryPoint
(HINSTANCE hinst, unsigned long reason, void*)
{
return 1;
}
__declspec(dllexport) int pascal
ShowStatusWindow(int AVIType,LPSTR
WinTitle,long hWnd){
hWndParent=(HWND)hWnd;
memset(lpsWinTitle,0,sizeof(lpsWinTitle));
strncpy(lpsWinTitle,WinTitle,sizeof(lpsWinTitle)-1);
if (AVIType >0 && AVIType< =8)
gi_AVIType=AVIType;
FormThread=new TFormThread(true);
FormThread- >Priority = tpNormal;
FormThread- >Resume();
}
__declspec(dllexport) int pascal
GetStatus(int ai_CloseWin){
if (gi_Canceled)
if (gi_WindowActive){
gi_RequestClose=1;
while(gi_RequestClose);
}
return gi_Canceled;
}
__declspec(dllexport) int pascal
CloseStatusWindow(){
if (gi_WindowActive){
gi_RequestClose=1;
while(gi_RequestClose);
}
return gi_Canceled;
}
2.窗体StatusForm的代码:
TStatusForm *StatusForm;
//-----------------------------------
extern int gi_Canceled;
extern int gi_AVIType;
extern TCommonAVI g_CommonAVI[];
__fastcall TStatusForm::
TStatusForm(HWND ParentWindow)
: TForm(ParentWindow)
{
gi_Canceled=0;
}
//-----------------------------------
//取消按钮并不直接关闭窗体,
而指示设置取消标志,供调用者查看
void __fastcall TStatusForm::
Button_CancelClick(TObject *Sender)
{
gi_Canceled=1;
// ModalResult=mrCancel;
}
//-----------------------------------
// 激活动画,在FORMCREATE事件中
void __fastcall TStatusForm::
FormCreate(TObject *Sender)
{
Animate1- >CommonAVI=g_CommonAVI[gi_AVIType];
Animate1- >Active = true;
}
//-----------------------------------
extern int gi_RequestClose;
// 定时器事件检测到结束标志关闭窗体
void __fastcall TStatusForm::
Timer1Timer(TObject *Sender)
{
if (gi_RequestClose){
ModalResult=mrOk;
}
}
//-----------------------------------
(5) 设置编译选项:Project->Options打开Project Options对话框,清除Linker属性
页中的Use Dynamic RTL标志,清除Packages属性页中的Build with runtime packages。
这样只要单个DLL就可以运行了,而不必安装一些动态连接运行时间库。使用动画DLL
上面编译出DLL可以由其它任何开发语言调用,下面给出在PB中的使用方法。
(1) 定义:
//Declare - > Global External Functions
FUNCTION Long ShowStatusWindow(Long
AVIType,String WinTitle,long hWnd) &
LIBRARY "STATWIN.DLL" ALIAS FOR "ShowStatusWindow"
FUNCTION Long GetCancelStatus(Long CloseWindow) &
LIBRARY "STATWIN.DLL" ALIAS FOR "GetStatus"
FUNCTION Long CloseStatusWindow() &
LIBRARY "STATWIN.DLL" ALIAS FOR "CloseStatusWindow"
(2) 调用:
long ll_EndTime
//显示查找文件夹动画
ShowStatusWindow(2)
setpointer(HourGlass!)
ll_EndTime = Cpu() + 10 * 1000
DO
if GetCancelStatus(0)=1 then
exit
end if
// 做想做的事情
LOOP UNTIL cpu() > ll_EndTime
CloseStatusWindow()
用C++ Builder 3制作屏幕保护程序
屏幕保护程序是以scr为扩展名的标准Windows可执行程序,在激活控制面板的显示器
属性的"屏幕保护程序"页时,该模块会自动在Windows启动目录(Windows目录和系统目录
)下查找扩展名是scr的基于Windows的可执行文件。使用屏幕保护程序,不仅可以延长显
示器的使用寿命,还可以保护私人信息。
编制屏幕保护程序不仅要涉及消息的处理,还要涉及命令行参数的处理。在WIN32SDK
文档中描述了编制基于WIN32的标准的屏幕保护程序所必须遵守的严格标准。按照这些标准
,屏幕保护程序必须要输出两个函数:ScreenSaverProc和ScreenSaverConfigureDialog,
但是,在Windows系统中的很多屏幕保护程序并没有遵循这些标准(使用impdef或者tdump实
用工具查看即可)。并且使用该文档中介绍的方法编写屏幕保护程序,不仅要使用资源编辑
器,并且在链接时还要利用Scrsaver.lib文件(在C++Builder3环境下,不能成功连接)。
不仅要涉及消息的处理,还要涉及命令行参数的处理。
C++Builder3是一种快速的应用程序开发工具,提供了许多类型的应用程序开发模板,
但没有提供开发屏幕保护程序的模板,并且在其在线帮助中也没有提及如何开发这类应用
程序。经过本人的研究,找到了用C++Builder3编制屏幕保护程序的方法。
在控制面板的"显示器属性"项的"屏幕保护程序"页中进行设置时,要遇到三种类型的
命令行参数,并且,各种情况下的屏幕保护程序的显示结果也各不相同,一般来讲,就需
要三种类型的窗体(或两种,在随后的内容中讨论)。下面将分四步来具体地说明如何编
制屏幕保护程序。
一、屏幕保护程序的选择
如果在标题为"屏幕保护程序"的下拉列表框中选中了某个保护程序时,系统会自动启
动该程序,这个程序的显示范围是在这个页面上的显示器图形的屏幕范围,同时,会将两
个命令行参数:一个是"/p";另一个是显示窗口的句柄,传递给这个被选中的程序。因此
,这类程序首先应该能够处理命令行参数。在C++Builder3中,与命令行参数处理有关的函
数是:ParamCount()和ParamStr(),具体的申明方式如下:
1.externPACKAGEint__fastcallParamCount(void);
该函数返回命令行参数的个数,但不包含应用程序本身。
2.externPACKAGEAnsiString__fastcallParamStr(intIndex);
该函数返回指定索引值的命令行参数。ParamStr(0)返回的是应用程序本身。
所以,在这以步骤中的参数判断的语句如下:
if(UpperCase(ParamStr(1))==
"-p"||UpperCase(ParamStr(i))=="/p")
{
//addthecodeinhere
}
在完成了参数判断后,就应该对显示窗口的处理,为能够使程序在显示器图形的屏幕
区域内显示,就要重新设置程序的父窗口和显示区域。这要涉及到父窗口句柄的获得及父
窗口的设置,以及API函数的调用。这种环境下的父窗口句柄就是传递过来的第二个命令行
参数;要设置父窗口,只需设置窗体的ParentWindow属性即可。这段程序如下:
RECTrc;//Line1
HWNDhWnd=(HWND)
(atol(ParamStr(2).c_str()));//Line2
::GetClientRect(hWnd,&rc);//Line3
ParentWindow=hWnd;//Line4
Left=rc.left;//Line5
Top=rc.top;//Line6
Width=rc.right-rc.left;//Line7
Height=rc.bottom-rc.top;//Line8
在上面的程序片段中,第2行语句是将传递过来的第2个参数转换成窗口句柄;然后,
第3行语句利用这个窗口句柄,调用API函数以获得该窗口的客户区域;第4行语句将选中的
屏幕保护程序的父窗口设置为指定的窗口;余下的语句是将该程序的窗口大小设置成副窗
口的客户区大小。这一程序片段的位置应该是在窗体的OnCreate事件处理中。
需要说明的是,这种类型(包括第三步介绍的窗体)的窗体样式应是:
FormStyle=fsStayOnTop;
窗体边界的样式应为:
BorderStyle=bsNone;
当然,这时也不需要鼠标图形,因此,可以将鼠标的形状设为crNone:
Cursor=crNone;
二、初始化参数的设置
单击"显示器属性"模块的"屏幕保护程序"页面中的"设置"按钮时,系统会启动指定的保护
程序的初始值设置对话框,这时传递过来的命令行参数是:"/c"或"-c"(参数的处理与前
面介绍的相同)。通过该对话框,可以设置保护程序的一些初始参数,比如图形的变化快
慢等。在这段程序中,还要涉及到初始化文件或注册表的读写,用以记录初始化参数,便
于保护程序启动时使用。
三、预览及运行
预览的效果就是屏幕保护程序被激活后的显示。单击单击"显示器属性"模块的"屏幕保
护程序"页面中的"预览"按钮,就可以观察保护程序运行的实际效果。这时,系统启动该程
序时传递过来的命令行参数是:"/s"或"-s"。对于命令行参数的处理与前面的步骤相同,
但在这一步中,还要对几个消息进行处理,这些消息是:WM_MOUSEMOVE,WM_LBUTTONDOWN
,WM_MBUTTONDOWN,WM_RBUTTONDOWN,WM_KEYDOWN,WM_ACTIVATE。对WM_MOUSEMOVE和WM_A
CTIVATE消息的处理形式如下:
void__fastcallHandleSomeMessage(TMessage&Msg)
{
switch(Msg.Msg)
{//......
caseWM_ACTIVATE:if(Msg.WParamLo==WA_INACTIVE)
Close();
break;
caseWM_MOUSEMOVE:if(OldMouseX==-1&&OldMouseY==-1)
//Intheconstructor,OldMouseXand
OldMouseYmustbeinitializedby-1.
{OldMouseX=Msg.LParamLo;
OldMouseY=Msg.LParamHi;
}
elseif(OldMouseX!=Msg.LParamLo
||OldMouse!=Msg.LParamHi)
Close();
break;
......
}
}
对于其他的消息仅仅是调用Close()函数来关闭应用程序即可。应用这种消息处理方式
时,必须要类定义时进行消息映射,不然的话,就要在相应的消息响应中进行处理(使用
一定的布尔变量,就可以与第一步合用一个窗体)。
与第一步类似,在该步骤中,也不需要具体的鼠标指针的形状,因此,将鼠标指针设
为crNone:
Cursor=crNone;
四、修改项目源文件
在C++Builder3中,一个窗体也就是一个类,换句话说,具有某些特性的类也就是一个
窗体,因此,编制屏幕保护程序时,也不需要什么主窗体,同时,也不用自动创建某些窗
体了,这时就要修改项目源文件,下面所列出的程序就是笔者在编制某屏幕保护程序时使
用的项目源文件,供读者参考。
WINAPIWinMain(HINSTANCE,HINSTANCE,LPSTR,int)
{
CreateMutex(NULL,true,"ScreenSaver");
if(GetLastError()!=ERROR_ALREADY_EXISTS)
{
try
{
Application->Initialize();
Application->Title="屏幕保护程序测试";
if(UpperCase(ParamStr(1))==
"/C"||UpperCase(ParamStr(1))=="-C"
||ParamCount()==0)
{TScrSaverConfiguerF*ScrCfg=
newTScrSaverConfiguerF(NULL);
ScrCfg->ShowModal();
deleteScrCfg;
return0;
}//单击"设置"按钮
elseif(UpperCase(ParamStr(1))==
"/P"||UpperCase(ParamStr(1))=="-P")
{TScrForP*ScrFP=newTScrForP(NULL);
ScrFP->ShowModal();
deleteScrFP;
return0;
}//在"屏幕保护程序"下拉列表框中选择一个程序
elseif(UpperCase(ParamStr(1))==
"/S"||UpperCase(ParamStr(1))=="-S")
{TScreenSaveF*ScreenSave=newTScreenSaveF(NULL);
ScreenSave->ShowModal();
deleteScreenSave;
return0;
}//单击"预览"按钮,及运行屏幕保护程序
else
return1;
}
catch(Exception&exception)
{
Application->ShowException(&exception);
}
}
return0;
}//theWinMainFunctionend
前面介绍了在C++Builder3下编制屏幕保护程序的方法.对于C++Builder3这种RAD工具
来讲,开发这类程序也是相当方便的,按照前述的方法,可以在极短的时间开发出屏幕保
护程序。对于屏幕保护程序,在本文中没有说明的就是如何设置口令的问题,这部分就由
读者自己摸索吧。
TCP/IP头格式
一、先是常用的IP头格式。
IP头格式:
版本号 (4位)
IP头长度 (4位)
服务类型 (8位)
数据包长度 (16位)
标识段 (16位)
标志段 (16位)
生存时间 (8位)
传输协议 (8位)
头校验和 (16位)
发送地址 (16位)
目标地址 (16位)
选项
填充
简单说明
============
1. IP头长度计算所用单位为32位字, 常用来计算数据开始偏移量
2. 数据包长度用字节表示, 包括头的长度, 因此最大长度为65535字节
3. 生存时间表示数据被丢失前保存在网络上的时间, 以秒计.
4. 头校验和的算法为取所有16位字的16位和的补码.
5. 选项长度是可变的, 填充区域随选项长度变化, 用于确保长度为整字节的倍数.
描述
============
struct iphdr {
BYTE versionihl;
BYTE tos;
WORD tot_len;
WORD id;
WORD frag_off;
BYTE ttl;
BYTE protocol;
WORD check;
DWORD saddr;
DWORD daddr;
/* Put options here. */
};
二、TCP头格式
TCP头格式:
源端口 (16位)
目的端口 (16位)
序号 (32位)
确认号 (32位)
数据偏移 (4位)
保留 (6位)
标志 (6位)
窗口 (16位)
校验和 (16位)
紧急指针 (16位)
选项
填充
简单说明
============
1. 数据偏移用于标识数据段的开始
2. 保留段6位必须为0
3. 标志包括紧急标志、确认标志、入栈标志、重置标志、同步标志等。
4. 校验和计算方式为将头与16位二进制反码和中的16位二进制反码加在一起。
5. 选项长度是可变的, 填充区域随选项长度变化, 用于确保长度为整字节的倍数.
6. 更详细的说明请参阅有关资料。
描述
============
struct tcphdr {
WORD SourPort;
WORD DestPort;
DWORD SeqNo;
DWORD AckNo;
BYTE HLen;
BYTE Flag;
WORD Window;
WORD ChkSum;
WORD UrgPtr;
/* Put options here. */
};
UDP
一、说明
使用UDP时,直接使用API代替控件。
第一个程序(ReadBufferUdp)使用来接收到缓存中。
"Destino"变量非常重要,如果你从其他地方接收数据到Buffer,你必须设置Destino =
0 并且在以后执行的时候赋值你将要发送的包的地址给它(after the execution it
will have the address which send you the packet.)。
如果你只想从一个指定的地址接收数据,你必须设置变量Destino = <address>.
"gvEncerrar" 用来中止处理过程。(gvEncerrar被设置为全局变量。)
超时时间设置。"Inicio + 12" = 12 sec of timeout.
第三个程序是用来准备WinSock程序。
二、代码
int ReadBufferUdp(unsigned long *Destino,void *T,int Size)
{
char Buffer[128];
SOCKADDR_IN SockAddr;
int LenSockAddr=sizeof(SOCKADDR_IN);
fd_set FdRead;
struct timeval t_val;
int Ret;
time_t Inicio = time(NULL);
Application->ProcessMessages();
if(gvEncerrar)
return false;
FD_ZERO(&FdRead);
FD_SET(gvSocket,&FdRead);
t_val.tv_sec=0;
t_val.tv_usec=0;
while((Ret=select(0,&FdRead,NULL,NULL,&t_val))!=1 && (Inicio + 12) >
time(NULL) && !gvEncerrar)
{
FD_ZERO(&FdRead);
FD_SET(gvSocket,&FdRead);
t_val.tv_sec=0;
t_val.tv_usec=0;
Application->ProcessMessages();
}
if(Ret != 1)
return false;
if(recvfrom(gvSocket,Buffer,Size,0,(LPSOCKADDR)&SockAddr,&LenSockAddr)!=Size)
return false;
if(*Destino == 0)
{
*Destino = SockAddr.sin_addr.s_addr;
}
else
if(*Destino != SockAddr.sin_addr.s_addr)
return false;
memcpy(T,Buffer,Size);
return true;
}
int WriteBufferUdp(unsigned long Destino,void *T,int Size)
{
SOCKADDR_IN SockAddr;
int Sent;
Application->ProcessMessages();
SockAddr.sin_family = AF_INET;
SockAddr.sin_port = gvPortUdp;
SockAddr.sin_addr.s_addr = Destino;
Sent = sendto(gvSocket,(char
*)T,Size,0,(LPSOCKADDR)&SockAddr,sizeof(SockAddr));
if(Sent != Size)
return false;
else
return true;
}
void InicializaTCPIP()
{
WORD wVersionRequested;
WSADATA wsaData;
IN_ADDR In;
PSERVENT PServent;
SOCKADDR_IN SockAddrIn;
wVersionRequested = MAKEWORD( 1, 1 );
if(WSAStartup( wVersionRequested, &wsaData ))
{
ShowMessage("Erro na inicializao do TCP/IP");
Application->Terminate();
return;
}
// Get the port on service file
if((PServent=getservbyname("your_service_name","udp"))==NULL)
{
ShowMessage("Erro obtendo port do servi transurb/udp");
Application->Terminate();
return;
}
gvPortUdp = PServent->s_port;
sprintf(StrAux,"Servi transurb/udp port:%d",ntohs(gvPortUdp));
Log(StrAux);
// Open de Socket
if((gvSocket = socket(AF_INET,SOCK_DGRAM,0))==INVALID_SOCKET)
{
ShowMessage("Erro na criao do socket");
Application->Terminate();
return;
}
Log("Socket criado com sucesso");
// Do the bind
SockAddrIn.sin_family = AF_INET;
SockAddrIn.sin_port = gvPortUdp;
SockAddrIn.sin_addr.s_addr = NULL;
if(bind(gvSocket,(LPSOCKADDR)&SockAddrIn,sizeof(SockAddrIn))==SOCKET_ERROR)
{
ShowMessage("Erro no bind do socket");
Application->Terminate();
return;
}
Log("Bind do socket com sucesso");
}
判断windows的Desktop及其它目录
使用API函数SHGetSpecialFolder。shlobj.h里有SHGetSpecialFolder的原型声明。这个函
数可以帮我们找到windows的Desktop目录、启动目录、我的文档目录等。
SHGetSpecialFolder需要三个参数。 第一个参数是HWND,它指定了"所有者窗口":在调用
这个函数时可能出现的对话框或消息框。第二个参数是一个整数id,决定哪个目录是待查
找目录,它的取值可能是:
CSIDL_BITBUCKET 回收站
CSIDL_CONTROLS 控制面板
CSIDL_DESKTOP Windows 桌面desktop
CSIDL_DESKTOPDIRECTORY desktop的目录
CSIDL_DRIVES 我的电脑
CSIDL_FONTS 字体目录
CSIDL_NETHOOD 网上邻居
CSIDL_NETWORK 网上邻居virtual folder
CSIDL_PERSONAL 我的文档
CSIDL_PRINTERS 打印机
CSIDL_PROGRAMS 程序组
CSIDL_RECENT 大多数最近打开的文档列一
CSIDL_SENDTO “发送到”菜单项
CSIDL_STARTMENU 任务条启动菜单项
CSIDL_STARTUP 启动目录
CSIDL_TEMPLATES 临时文档
最后一个参数是pidl地址。SHGetSpecialFolderLocation把地址写到pidl。
下面的代码演示了怎样使用SHGetSpecialFolderLocation:
//----------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
LPITEMIDLIST pidl;
LPMALLOC pShellMalloc;
char szDir[MAX_PATH];
if(SUCCEEDED(SHGetMalloc(&pShellMalloc)))
{
if(SUCCEEDED(SHGetSpecialFolderLocation(NULL,
CSIDL_DESKTOPDIRECTORY,
&pidl)))
{
// 如果成功返回true
if(SHGetPathFromIDList(pidl, szDir))
{
Label1->Caption = szDir;
}
pShellMalloc->Free(pidl);
}
pShellMalloc->Release();
}
}
//----------------------------------------------------------------------
注意: 有些目录是空的。有些特定的目录在这个文件系统上并没有一个相应的目录。
取得本地internet机器的名字及IP地址
一、下面的例子使用 Winsock API 取得本地主机的名字及地址
void __fastcall TForm1::Button1Click(TObject *Sender)
{
hostent *p;
char s[128];
char *p2;
//Get the computer name
gethostname(s, 128);
p = gethostbyname(s);
Memo1->Lines->Add(p->h_name);
//Get the IpAddress
p2 = inet_ntoa(*((in_addr *)p->h_addr));
Memo1->Lines->Add(p2);
}
void __fastcall TForm1::FormCreate(TObject *Sender)
{
WORD wVersionRequested;
WSADATA wsaData;
//Start up WinSock
wVersionRequested = MAKEWORD(1, 1);
WSAStartup(wVersionRequested, &wsaData);
}
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
WSACleanup();
}
用C++Builder创建数字签名
如果你在网络上传递一份数据,但却存在着种种不安全的因素,使你对数据能否原封
不动地到达目的地而心存疑惑,这时,你就可以给数据加上数字签名,从而使对方可以通
过验证签名来检查你所传过去的数据是否已被他人修改。
一、程序原理
数字签名的工作原理还是比较简单的,它是根据你所提供的原始数据,经过复杂的算
法,产生特定的数据签名,对方通过同样的过程也产生签名,如果数据已被修改,那么就
不可能得到两份一模一样的签名,从而就可判断数据已被他人修改。编程人员利用Windows
的CAPI接口,就可以实现数据的加密、解密和数字签名。
二、程序清单
下面用C++ Builder的语句来看一下它的具体实现过程。
先来创建数字签名,假定其数据来自于一个文件。
//变量声明:
HCRYPTPROV hProv;
// CSP的句柄
HCRYPTHASH hHash;
// 散列的句柄
const int BUFFER=4096;
// 缓冲区大小常数
BYTE pBuffer[BUFFER];
// 存放读文件内容的缓冲区
BYTE pSignature[256];
// 存放签名的缓冲区
DWORD dSignatureLen=256;
// 签名的长度
TFileStream *sourceFile;
// 一个文件流
if(!CryptAcquireContext(&hProv,NULL,NULL,PROV—RSA—FULL,0))
// 连接默认的CSP,接受它的句柄放入hProv
{
// 错误处理
}
if(!CryptCreateHash(hProv,CALG—MD5,0,0,&hHash))
// 创建一个散列对象,得到它的句柄放入hHash
{
// 错误处理
}
do
{
dReadLen=sourceFile->Read(pBuffer,BUFFER);
if(!CryptHashData(hHash,pBuffer,dReadLen,0))
// 根据文件的内容计算散列值
{
// 错误处理
}
}while(!(dReadLen<BUFFER));
if(!CryptSignHash(hHash,AT—SIGNATURE,NULL,0,pSignature,&dSignatureLen))
//使用私人密钥对散列值进行数字签名
//签名数据放入pSignature,长度放入dSignatureLen
// 错误处理
}
对基于文件的数据签名进行检验。
//变量声明:
HCRYPTPROV hProv;
// CSP的句柄
HCRYPTHASH hHash;
// 散列的句柄
HCRYPTKEY hPublicKey;
// 公共密钥的句柄
const int BUFFER=4096;
// 缓冲区大小常数
BYTE pBuffer[BUFFER];
// 存放读文件内容的缓冲区
TFileStream *sourceFile; // 一个文件流
BYTE pSignature[256];
// 上一段得到的签名的缓冲区
DWORD dSignatureLen;
// 上一段得到的签名的长度
if(!CryptAcquireContext(&hProv,NULL,NULL,PROV—RSA—FULL,0))
// 连接默认的CSP,接受它的句柄放入hProv
{
// 错误处理
}
if(!CryptGetUserKey(hProv,AT_SIGNATURE,&hPublicKey); // 得到公共密钥的句柄
{
// 错误处理
}
if(!CryptCreateHash(hProv,CALG—MD5,0,0,&hHash)) // 创建一个散列对象,得到
它的句柄放入hHash
{
// 错误处理
}
do
{
dReadLen=sourceFile->Read(pBuffer,BUFFER);
if(!CryptHashData(hHash,pBuffer,dReadLen,0))
// 根据文件的内容计算散列值
{
// 错误处理
}
}while(!(dReadLen<BUFFER));
if(!CryptVerifySignature(hHash,pSignature,dSignatureLen,hPublicKey,NULL,0))
{
if(GetLastError()==NTE—BAD—SIGNATURE) ShowMessage(″文件已被修改″);
}
else
{
ShowMessage(″文件没被修改″);
}
以上是一个数字签名的简单实现,得到的签名数据可以单独保存,也可以分开保存。
用Enter 键 控 制 焦 点 切 换 的 方 法
在Windows 环 境 下, 要 使 一 个 控 件 取 得 焦 点, 可 在 该 控 件 上 用 鼠 标
单 击 一 下, 或 按Tab 键 将 焦 点 移 至 该 控 件 上。 这 种 控 制 焦 点 切 换
的 方 法 有 时 不 符 合 用 户 的 习 惯。 就 图 一 而 言, 用 户 就 希 望 用Ent
er 键, 控 制 焦 点 由Edit1 切 换 到 Edit2。 要 实 现 这 样 的 功 能 需 借 助Wi
nAPI 函 数SendMessage 来 完 成。 方 法 是: 先 设Form1 的KeyPreview 属 性 为tru
e, 然 后 在Form1 的OnKeyPress 事 件 中 加 入 如 下 的 代 码。 这 样, 用 户 就
可 以 通 过 按Enter, 键 控 制 焦 点 按 定 义 好 的Taborder 顺 序 来 移 动 了
!
void __fastcall TForm1::FormKeyPress(TObject *Sender, char &Key)
{
if(Key==VK_RETURN)
{
SendMessage(this- >Handle,WM_NEXTDLGCTL,0,0);
Key=0;
}
}
拦 截 Windows 消 息
- --Borland C++ Builder的API后门
---- 引子
---- C++ Builder不愧为Borland公司的优秀产品,用它来开发Windows程序非常快捷高效
,但在编程过程中你也会发现它的一些限制性,让你无法实现自己的想法。比如你无法在
修改表单的系统菜单;比如使用跟踪栏时,你找不到StartTrack和EndTrack事件,而偏偏
你的程序需要这两个事件。Windows API编程中,你就不会有这些麻烦,只需处理一下WM_S
YSCOMMAND和WM_HSCROLL(或WM_VSCROLL)消息,就能实现上述功能。Windows API的缺点是
编程十分麻烦,太多的时间要耗在细节上面,但它的功能却是最强大的。C++ Builder的VC
L在功能上只是它的一个子集,因为VCL是在API的基础上封装的,封装时舍弃了一些不常用
到的功能。但是程序员的想象力没有被封装,他们总怀着更大的热情去实现别出心裁的想
法,修改系统菜单和给跟踪栏增加StartTrack和ndTrack事件只是其中的小把戏而已。可是
VCL并没有这些功能,怎么办?
---- 幸好,Borland公司没有把路堵死,而是留了个后门——允许程序员自己拦截并处理W
indows消息,就象API编程一样。于是,办法有了...
---- 方法
---- 拦截Windows消息需要以下几步:
---- 在表单头文件内(如Unit1.h)
---- 1. 在类声明中建立消息映射表,把某条消息的处理权交给自定义的消息处理函数。
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(Windows消息名,TMessage,消息处理函数名)
MESSAGE_HANDLER(...)
END_MESSAGE_MAP(TForm)
---- 2. 在类声明的private区内声明消息处理函数。
private: // User declarations
void __fastcall 消息处理函数名(TMessage &Message);
在表单文件内(如Unit1.cpp)
---- 3. 写出消息处理函数,在这里实现你需要的功能。比如
void __fastcall MainForm::OnWMHScroll (TMessage &Message)
{
... // 在此加入你自己的代码
TForm::Dispatch(&Message);
}
---- 解释
---- 1. 关于TMessage
---- TMessage是VCL预定义的结构,定义如下:
struct TMessage
{
unsigned int Msg; //消息
int WParam; //字参数
int LParam; //长字参数
int Result; //消息结果
};
---- 2. 关于TForm::Dispatch(&Message)
---- 自定义的消息处理函数末尾最好加一句TForm::Dispatch(&Message),这一句的作用
是让消息继续传递下去。如果没有这一句,消息将被完全拦截,VCL类可能由于得不到消息
而无法实现正常功能。
---- 实例一:修改系统菜单
---- 有一些程序,主窗口很小,菜单也没有,如果想加入关于或设置对话框,最好的办法
是拿系统菜单开刀。Windows API编程中,修改系统菜单与实现其他功能一样,不太容易,
也不会太难。但在C++ Builder中,表单类(TForm)没有提供有关系统菜单的任何属性与方
法,实现其他功能易如反掌,而修改系统菜单似乎难于上青天。
---- 还好,Borland公司允许程序员自已处理Window消息,于是机会来了!
一、用Window API函数修改系统菜单
假定表单名为MainForm,设置MainForm::OnCreate()函数:
1. 用GetSystemMenu(MainForm->Handle,false)取得系统菜单句柄;
2. 用AppendMenu,DeleteMenu,ModifyMenu函数修改系统菜单,把新的ID号赋于自定义的菜
单项。
这时运行程序,可以看到系统菜单也被修改,但自定义的菜单项却不能被响应。
二、拦截WM_SYSCOMMAND消息以响应自定义的菜单项
在表单头文件内(如Unit1.h)
1. 在表单类定义末尾加入消息响应表,取得WM_SYSCOMMAND消息的处理权
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_SYSCOMMAND,TMessage,OnWMSysCommand)
END_MESSAGE_MAP(TForm)
2. 在表单类定义的private区内加入消息处理函数声明
private: // User declarations
void __fastcall OnWMSysCommand(TMessage& Message);
在表单文件内(如Unit1.h)
3. 写出消息响应函数
void __fastcall TForm1::OnWMSysCommand(TMessage& Message)
{
if(Message.WParam==ID_SysMenu_MyItem)
{
// Your Code Here, Do Something
}
TForm::Dispatch(&Message);
}
三、完整程序示例
实例二:给跟踪栏增加OnStartTrack和OnEndTrack事件
当跟踪栏用于进度控制时,OnStartTrack和OnEndTrack很可能是你需要的事件。比如在控
制多媒体播放进度的场合,当用户移动滑块时,你需要OnStartTrack事件让播放停止,需
要OnEndTrack事件定位新的播放位置。但Borland公司没有提供这两个事件,我等编程爱好
者只好自力更生,打拦截Windows消息的主意了。
一、拦截WM_HSCROLL消息,给跟踪栏增加OnStartTrack和OnEndTrack事件
在表单头文件内(如Unit.h)
1. 在表单类定义末尾加入消息响应表,把WM_HSCROLL消息处理权交给OnWMHScroll函数。
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_HSCROLL,TMessage,OnWMHScroll)
END_MESSAGE_MAP(TForm)
2. 在表单类定义的private区内加入OnWMHScroll函数声明。
private: // User declarations
void __fastcall OnWMHScroll(TMessage &Message);
3. 在表单类定义的private区内加入StartTrack和EndTrack函数声明。
private: // User declarations
void __fastcall TrackBar1StartTrack(TObject *Sender);
void __fastcall TrackBar1EndTrack(TObject *Sender);
在表单文件内(如Unit.cpp)
4. 写出OnWMHScroll函数,使它能根据消息参数调用StartTrack和EndTrack函数,在实际
意义上产生OnStartTrack和OnEndTrack事件。
5. 写出StartTrack和EndTrack函数。
如果是垂直跟踪栏,把上面的WM_HSCROLL改为WM_VSCROLL即可。
二、完整程序示例
尾声
Borland C++ Builder编程中,拦截Windows消息是一项高级编程技术,能让你尽量挖掘Win
dows的潜力,尤其让曾用API编程的程序员感到心慰。拦截Windows消息是API尽情发挥的舞
台,当VCL不能为你做什么时,请想起底层的API。
使用CommaText
有时需要一个方便的方法存放一个StringList,它只有简单的一行。例如,当你想使用一
个INI文件,如何向一个INI文件中写入一行呢,使用CommaText 就能完成这个工作。
这里有个例子,功能是创建一个blah.ini文件,并写入一个如下形式的值:
[My Section]
Memo1=(你在Memo1中输入的文字)
1.在Form1上有两个按钮btnLoad and btnSave和一个Memo1
2.还要加入:
#include <inifiles.hpp>
3.定义变量:
const String iniFile="blah.ini",iniSection="My Section",iniValue="Memo1";
4.保存按钮代码:
void __fastcall TForm1::btnSaveClick(TObject *Sender)
{
TIniFile *ini=new IniFile(ExtractFilePath(Application->ExeName)+iniFile);
ini->WriteString(iniSection,iniValue,Memo1->Lines->CommaText);
delete ini;
}
5.装载按钮代码:
void __fastcall TForm1::btnLoadClick(TObject *Sender)
{
TIniFile *ini=new TIniFile(ExtractFilePath(Application->ExeName)+iniFile);
Memo1->Lines->CommaText=ini->ReadString(iniSection,iniValue,"");
delete ini;
}
6.以下代码支持加载后对内容进行排序,到实际存储不变:
void __fastcall TForm1::btnSortLoadClick(TObject *Sender)
{
TStringList *sl=new TStringList;
TIniFile *ini=new TIniFile(ExtractFilePath(Application->ExeName)+iniFile);
sl->CommaText=ini->ReadString(iniSection,iniValue,"");
sl->Sort();
Memo1->Lines=sl;
delete ini;
delete sl;
}
程序开始时先显示信息框
一、软件进入主窗口前,先显示一个信息框,告诉用户一些有关该软件的信息,比如软件
名称,版本号等。该信息框在显示1~2秒后自动消失。
1.建立New Application,这时系统自动生成一个Form1,这作为主Form.
2.File->New Form 建立一个新Form为Form2,这个作为信息框。
3.在Form2上添加组件TTimer(System控件条上),用于设定信息框的显示时间。
4.TTimer的事件OnTimer中加入:Form2->Close();
5.在WinMain()函数中加入:
Application->CreateForm(__classid(TForm2), &Form2);
Form2->ShowModal( ); //这句要自己加入
Application->Run();
并且要把Form2的头文件Unit2.h包括到WinMain()所在的Project1.cpp中。
6.运行程序,将先显示Form2,显示时间由TTimer的Interval属性决定,1000是一秒。
二、软 件 封 面 的 实 现
现 代 软 件 设 计 的 流 行 做 法 是, 在 程 序 运 行 完 成 初 始 化 之 前, 先
调 用 一 幅 画 面 做 为 封 面, 通 常 是1/4 屏 幕 大 小, 显 示 一 下 软 件 的
名 称、 作 者、 版 本 等 信 息。 要 用C++ Builder 实 现 这 样 的 功 能, 方 法
很 简 单:
① 自 定 义 一 窗 体 类 TSplashForm, 将 其 设 置 成" 透 明 窗 口", 即
BorderIcons 下 的 所 有 选 项 均 置 成false, BorderStyle=bsNone,FormStyle=fsS
tayOnTop, Position=poScreenCenter;
② 在TSplashForm 窗 体 上 放 置 一TPanel( 相 当 于 图 形 的 镜 框);
③ 在TPanel 上 放 置 一TImage 控 件, 调 入 所 需 要 的 图 形;
④ 对WinMain 函 数 稍 加 修 改, 加 入 如 下 所 示 代 码 即 可。 需 要 指 出 的
是, 这 段 代 码 通 过 函 数 FindWindow, 搜 索 内 存 中 是 否 有 窗 口 标 题
为 "Demo" 应 用 程 序 存 在, 若 存 在, 则 退 出 程 序 的 运 行。 该 功 能 可
防 止 程 序 的 再 次 运 行。 在 某 些 场 合 这 样 设 计 是 必 须 的。
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
if(FindWindow(NULL,"Demo")!=0)
{
Application- >MessageBox (" 程 序 已 经 运 行!"," 警
告",MB_ICONSTOP);
return 0;
}
TSplashForm *splash=new TSplashForm(Application);
splash- >Show();
splash- >Update();
Application- >Initialize();
Application- >CreateForm(__classid(TForm1), &Form1);
splash- >Close();
delete splash;
Application- >Run();
}
catch (Exception &exception)
{
Application- >ShowException(&exception);
}
return 0;
}
怎样获取程序的命令行参数?
你可以用下面的两种不同的技巧来解决这个问题。
技巧1:首先,也许是最简单的方法是调用VCL ParaStr()函数。你可使用ParamCount()函
数来确定到底有多少个命令行参数传递给了应用程序。
ParamStr需要一个整数参数并且返回一个AnsiString对象。若参数为0,ParamStr 将返回
可执行文件的全称路径。若参数为1,将返回程序名及第一个命令行参数。若参数为2,将
返回第二个参数,等等。
作为一个实践,开启一个新的项目,在主窗口上放置5个Label,将下面的代码添加到窗口
的构造函数中:
Label1->Caption = ParamStr(0);
Label2->Caption = ParamStr(1);
Label3->Caption = ParamStr(2);
Label4->Caption = ParamStr(3);
Label5->Caption = ParamStr(4);
再运行程序。一般应能看到类似字符串:
E:\CBUILDER\PROJECTS\PROJECT1.EXE
如果没传递参数到程序,那么Label2到Label5是空字符串。关闭程序,从C++Builder菜单
中选择 Run | Parameters。输入几个参数(-debug -testing -param)再次运行程序。你将
看到:
E:\CBUILDER\PROJECTS\PROJECT1.EXE
-debug
-testing
-param
提示: ParamStr 对目录中的空格能智能判断。为证实这点,把生成的EXE文件拷贝到Prog
ram Files目录下再运行它,你将会看到ParamStr(0)返回全路径,并包含空格。
技巧2:第二个方法就是调用GetCommandLine API函数。GetCommandLine不需要参数,并且
返回一个C风格的char *,包含全部的命令行参数。你将不得不分解字符串以取得相关参数
。
Label5->Caption = AnsiString(GetCommandLine());
运行后,Label5将为:
"E:\CBuilder\Projects\Project1.exe" -debug -testing -param
如何监视剪贴板
在Form1的.h的private加上:
void __fastcall ClipboardChanged(TMessage& Msg);
在Form1的.h的public加上:
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_DRAWCLIPBOARD,TMessage,ClipboardChanged)
END_MESSAGE_MAP(TForm)
在Form1的.cpp内加上:
void __fastcall TForm1::ClipboardChanged(TMessage& Msg)
{
POINT MousePos;
GetCursorPos(&MousePos);
PopupMenu4->PopupComponent=Form1;
PopupMenu4->Popup(MousePos.x,MousePos.y); //一有变化,就弹出一个菜单,复制,剪
切或清除都能引发此函数
}
在Form1的.cpp内有一个ToolButton
void __fastcall TForm1::ToolButton9Click(TObject *Sender)
{
static HWND LastHandle;
static bool clip=false;
if(clip==true)
{
ToolButton9->Down=false;
ChangeClipboardChain(Form1->Handle,LastHandle); //结束监视
}
else
{
ToolButton9->Down=true;
Clipboard()->Clear();
Application->Minimize();
LastHandle=SetClipboardViewer(Form1->Handle); //启动监视
}
clip=!clip;
}
如何使用OnIdle事件
使用OnIdle事件随时监视剪贴板内容以改变弹出菜单的可执行项。
在Form1的.h的private加上:
void __fastcall OnIdle(TObject* Sender,bool& Done);
在Form1的.cpp内加上:
void __fastcall TForm1::OnIdle(TObject* Sender,bool& Done)
{
bool TextSelected=DBRichEdit1->SelLength>0;
N17->Enabled=TextSelected;//剪切,复制,清除
N18->Enabled=TextSelected;
N20->Enabled=TextSelected;
bool CBHasText=Clipboard()->HasFormat(CF_TEXT);// 需加入#include<Clipbrd.h>
N19->Enabled=CBHasText;//粘贴
bool HasText=RichEdit1->Lines->Count>0;
N21->Enabled=HasText;//全选
bool HasChanged=RichEdit1->Modified;
ToolButton2->Enabled=HasChanged;
ToolButton4->Enabled=HasChanged;
}
在Form1的OnCreate内加上:
Application->OnIdle=OnIdle;
用C++Builder4.0编写Win 95下的串行异步通信程序
·串口操纵的基本方法·
在Win32下,对串口的操作就如同对文件一样打开或关闭,对串行数据的读写可在用户
定义的读写缓冲区中进行。具体使用的函数为:
首先用CreateFile( )打开通信串口,其中参数lpFileName指向串口逻辑名,如“COM1
”或“COM2”等,参数dwDesiredAccess定义文件的读写权限,一般设为GENERIC—READ|GE
NERIC—WRITE;参数dwShareMode定义资源共享方式,此处必须设为0,为独占方式;lpSec
urityAttributes定义安全属性,Win 95下为NULL;dwCreationDistribution定义文件创建
方式;dwFlagsAndAttributes定义文件属性和标记,应设为FILE—FLAG—OVERLAPPED,表
示异步通信方式;hTemplateFile 指向一个模板文件的句柄,在 Windows 95下为NULL。
然后用BuildCommDCB( )和SetCommState( )函数通过通信设备控制块DCB(Device
Control Block)设置串口通信参数(如波特率、停止位、数据位、校验位等),其中BuildC
ommDCB( )中的字符串参数lpDef 定义同DOS命令中MODE的参数格式,关于DCB更具体的设置
需要根据用户对数据流定义、握手信号及通信控制要求具体定义,参见有关Windows技术资
料。用GetCommState()可以得到当前的DCB参数值。如果需要还可通过SetCommTimeouts()
和GetCommTomeouts()重新设置读写的超时参数;读写缓冲区的设置使用SetupComm(),参
数dwInQueue和 dwOutQueue分别定义为输入和输出缓冲区的大小。
在串口初始化完毕后,还要建立与通信有关的事件对象。一般使用CreateEvent()函数
,它返回一事件句柄,其中参数lpEventAttributes指向安全属性结构地址,在Win 95(无
安全属性)中为NULL;布尔参数bManualReset 定义事件重置方式,true 表示手工重置,f
alse表示自动重置(相关函数为SetEvent()和ResetEvent());参数bInitialState定义事
件初始状态,true表示发信号,否则为不发信号;lpName是为多进程设置的事件名,对于
单进程定义为NULL。然后用SetCommMask()定义用户程序可监视的通信事件类别。
以上设置完成后,用户程序就可以等待通信事件的产生,一般调用函数WaitCommEvent
()监视通信事件,其中参数lpEvtMask指向产生事件的掩码地址,用于判断事件产生的性质
,lpOverlapped指向重叠结构地址,可简单定义为NULL。对于串口事件的响应一般有四种
方式:查询、同步I/O、异步I/O和事件驱动I/O,需要根据用户不同控制要求而定。查询方
式占用较长的计算机时间,同步I/O方式直到读取完指定的字节数或超时时才返回,容易造
成线程阻塞,异步I/O用于后台处理,事件驱动是由系统通知用户程序发生的事件并进行串
口操作。 比较而言事件驱动I/O方式较灵活。
当有通信事件产生时,就可用函数ReadFile()和WriteFile()直接对串口缓冲区进行读
写操作了。其中lpBuffer 指向读写缓冲区,nNumberOfBytes为要读写的字节数,lpNumber
OfBytes为实际读写的字节数,lpOverlapped指定同步或异步操作。通信结束后,调用函数
CloseHandle()将串口关闭。
·应用实例说明·
使用以上的API函数,笔者给出了简化后的串口初始化的实例。图1为使用C++
Builder 组件生成的串口通信基本参数设置的界面实例。
HANDLE hcom; //定义句柄
DCB dcb;
OVERLAPPED e; //定义重叠结构
void —fastcall TForm1::OkBtnClick(TObjectSender)
{ hcom=CreateFile("COM2",GENERIC—READ|GENERIC—WRITE,0,NULL,OPEN—EXISTING
,
FILE—ATTRIBUTE—NORMAL|FILE—FLAG—OVERLAPPED,NULL); //打开通讯口
BuildCommDCB("9600,O,8,1",&dcb);
//第一个字符串参数实际使用时由图1选择后组合,这里仅简单说明其格式
SetCommState(hcom,&dcb);
SetupComm(hcom,512,512);//设置读写缓冲区
e.hEvent=CreateEvent(NULL,false,false,NULL); //设置事件
SetCommMask(hcom,EV—RXCHAR| EV—TXEMPTY); //设置事件掩码
OkBtn-〉Enabled=false;}
C++BUILDER非可视组件的消息处理技巧
一个非可视的组件必须对Windows操作系统或用户定义的消息作出响应。然而,由于一
个非可视组件没有窗口,因此它也没有窗口句柄,自然它也不能接收到消息,为了解决这一
问题,我们的思路是创建一个隐藏的窗口,使非可视组件能够接收到消息。
为了给你的非可视组件创建一个隐藏的窗口,需要有以下:
1.一个私有变量型(Private Variable)的HWnd来取得窗口句柄。
2.一个用来捕捉窗口发送给组件的函数(a WndProc)。
3.对AllcolateHwnd的调用使之创建窗口句柄并设置WndProc。
为了清楚的解释上述思路和展示创建过程,下面我们将以一个具体的实例来说明。
首先我们先创建一个新的组件,在C++Builder中,选择FILE|NEW...双击组件图标显
示一个新的组件对话框改变Ancestor Type为Tcomponent和Class name为TTest并设置完毕
。
然后,切换到新组件的头文件,在类的私有部分(private section)加入以下声明:
HWnd FHandle;
void—fastcall WndProc
(TMessage& Msg);
第一行声明了一个调用Fhandle的HWnd变量,这个变量将用于窗口创建后捕获窗口句柄
。第二行声明了一个用于接收消息的WndProc函数。这个函数的声明必须加以标识,以便限
定它是一个WndProc,然后在类声明Public(公有)部分构造以下声明:
Viod DoIt( );
这个公有函数将被我们用来测试组件,类声明应如下:
class PACKAGE TTest : public
TComponent
{
private:
HWnd FHandle;
void—fastcall WndProc
(TMessage& Msg);
protected:
public:
—fastcall TTest
(TComponent* Owner);
void DoIt( );
—published:
};
现在切换到组件的代码单元,将下面一行加入到单元的顶部(在函数上也许是不错的
地方)
#define MY—Message.WM_USER+1
这一行声明了一个在DoIt函数被调用时,组件将发送给它自己的用户自定义消息。此
时我们必须为组件分配一个窗口句柄。这个句柄将提供一个隐藏的窗口使我们可以捕捉组
件中的消息。找到组件构造代码,加入下面代码:
—fastcall Test::Test
(TComponent* Owner)
: TComponent(Owner)
{
FHandle=AllocateHWnd
(WndProc);
}
好,重要的一步已完成,AllocateHWnd函数创建了一个隐藏窗口并且返回它的句柄,
注意这里我们为了使Windows知道哪里发来了消息,传递WndProc的地址;
现在我们来创建WndProc的函数部分。在源文件中加入:
void—fastcall TTest::WndProc
(TMessage& Msg)
{
if (Msg.Msg == MY_MESSAGE)
MessageBox(0, ″Got here!″, ″Message″, 0);
try {
Dispatch(&Msg);
}
catch (...) {
Application-〉HandleException(this);
}
}
无论何时Windows发送消息给组件,Windows都会调用这个函数。这部分代码完成了两
件事。首先,它检查被接收的消息是否是我们用户自定义的消息。如果是,一个消息框将
被显示,你可以看到实际上我们接收到的消息。其次,这段代码传送了系统(或VCL)处理
过程中的消息,try/catch块用来保证,如果异常出现,它将成为缺省风格下的句柄。
概括地说,WndProc函数在为缺省句柄传递所有其他消息,监控了所有客户消息。现在
我们创建DoIt函数,完成我们的组件,加入我们创建DoIt函数,完成我们的组件,加入代
码:
void TTest::DoIt()
{
PostMessage(FHandle,
MY—MESSAGE, 0, 0);
} 这个函数发送一个消息组件的窗口句柄(记住,这个窗口句柄是以前存入到Fha
ndle数据成品中的)。现在我们已经完成了创建组件选择,用SelectFile|ColseAll来保存
我们的工作测试组件。
下一步将测试组件。如果你使用BCB3,那么你必须把组件加入到“包”(Packege)中
,然后用Componet|install(可以使用DCLSTD35 Packege来快速测试)。再选择你刚存的Tes
tBCB.Cpp,一旦你安装完成组件后,它将出现在组件板上。双击按钮,为按钮的OnClick事
件创建以下代码:
Test1-〉 DoIt( );
现在运行程序,当你点击按钮时,将看到一个消息框显示“Got here".
ListingA和B包含了头文件和源代码以下列出。
总结:一个可以响应Windows消息的非可视组件有许多用途。最显而易见的就是用来封
装某些方面的WindowsAPI。例如:TAPI和WinSock发送消息给事件的指定用户。如果你写的
组件封装了一个这样的API。你将需要捕捉Windows发送的消息。而在你的组件中加入隐藏
窗口将很好的帮你做到这一点。
以上程序在C++ BUILDER 3.0中调试通过。
用C++Builder 建立数据库VCL使用经验
随着数据库的广泛应用,数据库编程已经成为程序设计中发展迅猛的一支。C++
Builder在数据库开发方面具有的强大功能是无可比拟的,你甚至可以不写一行程序就生成
漂亮的数据库程序。
下面对C++Builder中的几个数据库VCL的使用技巧做一下介绍:
一、DBGrid控件
1.设置DBGrid的字段显示宽度属性
为了在DBGrid中建立较小的列,你必须建立一个显示标题,它等于或小于字段值。例
如,你希望建立一个只有三个字符宽的列,你的列标题显示必须只有三个字符或更少。
2.改变DBGrid的显示字段及日期显示格式
(1)双击DBGrid对应的Table1,进入字段编辑器。
(2)点右键出现选单选“Add Fields…" ,出现添加字段对话框,选择要添加的字段(
该字段将在运行时由DBGrid显示)然后点OK按钮。
(3)假设添加了“日期”字段,点该字段,在属性表中的:DisplayLabel中填入你希望
DBGrid显示的字段名。如果原来字段名是英文的,这里用中文名后DBGrid将显示中文名。
在DisplayFormat中填入:yyyy-mm-dd,以后日期将按1999-05-28格式显示。
二、Tquery控件
Tquery 控件是数据库编程中非常重要的一个控件,它负责通过BDE与数据库建立联系
,通过SQL语句方便的建立查询。Query必须建立相应的SQL才能生效。
Tquery的参数设置如下:
(1)在SQL属性中:Select * from 表名 where 字段名=:变量名
跟在“ : "后面的是变量。这样写后,在参数属性中就可以修改该变量的数据类型等
。
(2)对变量的赋值:
Query1-〉Active=false;
Query1-〉Params-〉Items[0]-〉AsString=Edit1-〉Text;
Query1-〉Active=true;//查找符合变量的记录
(3)用DBGrid显示结果
DBGrid的DataSource与DataSource1连接,而DataSource1的DataSet与Tquery1 连接。
三、应用示例
通过Query控件嵌入SQL语句建立的查询比Table更简单、更高效。
用一个简单的代码来说明如何建立查询程序:
例如,要建立一个检索表1中书名为book1的程序则在表单上放置DBGrid,DataSource,Q
uery三个控件加入以下代码:
DBGrid1-〉DataSource=DataSource1;
DataSource1-〉DataSet=Tqery1;
Query1-〉Close();
Query1-〉SQL-〉Clear();
Query1-〉SQL-〉Add(″Select * From 表 Where (书名=′book1′ ″);
Query1-〉ExecSQL();
Query-〉Active=true;
你就可以在生成的表格中看到所有名称为book1的记录。
用C++ Builder创建基于Internet的点对点Chat
---- 创建基于Internet的应用程序,你也许会想到复杂的WinSock编程。不过,C++
Builder3提供了新的WebBroker的Internet套件,其中的TClientSocket和TServerSocket组
件封装了Windows的有关API,大大简化了WinSock编程。要通过Internet传输数据,至少需
要一对Socket,一个Socket在客户端,另一个Socket在服务器端。其实TClientSocket、TS
erverSocket组件并不是Socket对象,其属性Socket将返回各自的Socket对象。TClientSoc
ket用来处理客户端到服务器端之间的socket连接,TServerSocket用来处理由客户端发来
的socket连接,一旦客户端和服务器端都接通了socket,客户端和服务器端就可以相互通
信了。
---- 建立一新项目,创建应用程序的用户界面:
---- 1.将组件页切换到Internet页,放一个TServerSocket组件和一个TClientSocket组件
到窗体上,这样应用程序既可以是TCP/IP服务器,也可以是TCP/IP客户。将Port属性都设
为同一个值(如1000),确定Socket之间的连接类型为NonBlocking(非阻塞方式)。
---- 2.放两个TMemo组件到窗体上,用来分别显示双方的谈话内容,将Memo2的ReadOnly属
性设为True。
---- 3.在窗体的顶部放上一个Panel组件,在其上放三个按钮:监听(btnlisten)、连接(b
tnconnect)、断开(btndisconnect),用来启动相应的操作。
---- 4.在窗体底部放一个StatusBar组件,将其SimplePanel属性设为True,在相应的事件
处理程序中改变状态条信息,让用户随时了解连接状态。
---- 打开头文件,在窗体类的Private段添加两个私有成员: bool IsServer;String
Server。双方通信时需同时运行Chat程序,IsServer用来确定哪个Chat程序处于服务器端
,Server用来存放服务器的主机名。建立窗体类的构造器如下:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
IsServer=false;
Server="localhost";
}
---- 这里Server被缺省设为localhost,这样程序可以在没有连入Internet的单机上进行
调试。在Windows子目录下你可以找到hosts.sam文件中,在该文件中已经将本机IP地址127
.0.0.1定义了主机名:localhost。
void __fastcall TForm1::FormCreate(TObject *Sender)
{
btndisconnect- >Enabled=false;
}
---- 程序运行后,如果用户按下"监听"钮,则将该程序设为服务器端,这时应将TServe
rSocket的Active属性设为True,使服务器自动进入监听状态。
void __fastcall TForm1::btnlistenClick(TObject *Sender)
{
ClientSocket1- >Active=false;
ServerSocket1- >Active=true;
StatusBar1- >SimpleText="正在监听...";
btnlisten- >Enabled=false;
btnconnect- >Enabled=false;
}
---- 当用户按下"连接"钮后,程序会弹出一个询问框,要求用户输入要连接的服务器的
主机名,然后建立连接。
void __fastcall TForm1::btnconnectClick(TObject *Sender)
{
if(InputQuery("连接到服务器","输入服务器地址:",Server)){
if(Server.Length() >0){
ClientSocket1- >Host=Server;
ClientSocket1- >Active=true;
btnlisten- >Enabled=false;
btnconnect- >Enabled=false;
btndisconnect- >Enabled=true;
}
}
}
---- 当用户提出连接请求后,客户端会触发OnCreate事件,程序先在状态条中显示连接信
息,然后将显示对方谈话内容的Memo2清空,准备开始交谈。
void __fastcall TForm1::ClientSocket1Connect(TObject *Sender,
TCustomWinSocket *Socket)
{
StatusBar1- >SimpleText="连接到:"+Server;
Memo2- >Lines- >Clear();
}
---- 在服务器接受了客户的请求后会触发OnAccept事件,在这个事件处理程序中将标志服
务器端的变量IsServer设为True,并准备开始交谈。
void __fastcall TForm1::ServerSocket1Accept(
TObject *Sender,
TCustomWinSocket *Socket)
{
Memo2- >Lines- >Clear();
IsServer=true;
StatusBar1- >SimpleText="连接到:"
+Socket- >RemoteAddress;
}
---- 在建立连接后,双方就可以在Memo1中输入谈话内容开始进行交谈了,按下Enter键后
,将所在行的文本发送出去。服务器端的Socket的Connections属性返回一个数组,该数组
由服务器当前活动的连接组成。
void __fastcall TForm1::Memo1KeyDown(
TObject *Sender, WORD &Key,
TShiftState Shift)
{
if(Key==VK_RETURN){
if(IsServer)
ServerSocket1- >Socket- >Connections[0]- >SendText(
Memo1- >Lines- >Strings[Memo1- >Lines- >Count-1]);
else
ClientSocket1- >Socket- >SendText(
Memo1- >Lines- >Strings[Memo1- >Lines- >Count-1]);
}
}
---- 在本例中我们采用非阻塞传输方式,当其中的一方进行写操作时,另一方会触发OnRe
ad事件(客户端)或OnClientRead事件(服务器端),这两个事件的处理程序只是将接收到的
内容添加到Memo2的后面。
Memo2- >Lines- >Add(Socket- >ReceiveText());
---- 如果在用户建立连接后单击"断开"钮,将断开客户端与服务器的连接,服务器端将
触发OnClientDisconnect事件,而客户端则会触发OnDisconnect事件,这时服务器端应回
到监听状态,等待用户的连接;而客户端将返回到连接前的状态,等待用户再次建立连接
,如果有不止一个服务器的话,可以选择连接到其他的服务器上。
void __fastcall TForm1::btndisconnectClick(
TObject *Sender)
{
ClientSocket1- >Close();
}
void __fastcall TForm1::ServerSocket1ClientDisconnect(
TObject *Sender,
TCustomWinSocket *Socket)
{
StatusBar1- >SimpleText="正在监听...";
}
void __fastcall TForm1::ClientSocket1Disconnect(
TObject *Sender, TCustomWinSocket *Socket)
{
btnlisten- >Enabled=true;
btnconnect- >Enabled=true;
btndisconnect- >Enabled=false;
StatusBar1- >SimpleText="";
}
---- 此外在客户端还应该增加错误捕获机制,当用户输入无效的服务器名或服务器端没有
处于监听状态时能够及时给用户反馈信息。
void __fastcall TForm1::ClientSocke
t1Error(TObject *Sender,
TCustomWinSocket *Socket,
TErrorEvent ErrorEvent, int &ErrorCode)
{
StatusBar1- >SimpleText="无法连接到:
"+Socket- >RemoteHost;
ErrorCode=0;
}
用C++Builder获取应用程序图标
现在,网上有大量的有关图标的共享软件或免费软件,而且很多也很好用,也方便。但是
那毕竟是别人的,用起来总有些哪个,况且自己又喜欢编程,何不自己动手呢!说干就干
,而且手头上有可视化编成的利器――C++Builder4.0,想来应该是很简单的一件事。
首先启动C++Builder,新建一工程,在窗体上放置一个Button控件,一个Image控件,和一
个OpenDialog控件,它们的名称均不必改动。双击Button控件,写如下代码:
if(OpenDialog1->Execute())
{
FileName = OpenDialog1->FileName;
HICON hIcon;
// Total =(int) ExtractIcon( Form1->Handle, FileName.c_str(), -1);
Icon = new TIcon();
hIcon = ExtractIcon( Form1->Handle, FileName.c_str(), 0);
Icon->Handle=hIcon;
Icon->SaveToFile(TempFile);
Image1->Picture->LoadFromFile(TempFile);
}
其中:FileName,TempFile,Icon在其头文件中定义:AnsiString TempFile,
FileName ; Ticon *Icon;
这样,你所选定的程序的第一个图标就在Image控件中显示了出来。本程序所用的是Window
s API ExtractIcon来获取图表的,因此它只能获取可执行文件的图标,如果想获取任意文
件的图标,那末你可以调用Windows API 的SHGetFileInfo函数来完成,SHGetFileInfo所
能完成的任务有很多,具体用法可参见Win32的帮助文件。
BIG5到GB的转换技术
中文因为数量太多,所以与英文用ASCII码一个字节表示不同,它使用两个字节来表示。通
过计算这两个字节,我们可以得到其表示的汉字在中文字库中的位置。读取该位置的若干
字节,以获得表示这个汉字的点阵信息。有了这些信息,就可以分别在DOS或WINDOWS中显
示该汉字。事实上,在文本文件中保存的就是每个汉字对应的两个字节编码,而显示问题
由中文操作系统自动解决。
汉字编码并不统一,我们使用的是GB码,而台湾地区使用的是BIG5码。BIG5码文件中保存
的是汉字相应的BIG5编码,GB码文件中保存的是汉字相应的GB编码(这也就是“乱码现象
”的来由)。所以转换工作的关键是有一个记录每个BIG5编码对应GB编码的码表文件。
第一步 制作码表文件
BIG5码编码规则是这样的:每个汉字由两个字节构成,第一个字节的范围从0X81-0XFE,
共126种。第二个字节的范围分别为0X40-0X7E,0XA1-0XFE,共157种。也就是说,利用
这两个字节共可定义出 126 * 157=19782种汉字。这些汉字的一部分是我们常用到的,如
一、丁,这些字我们称为常用字,其BIG5码的范围为0XA440-0XC671,共5401个。较不常
用的字,如滥、调,我们称为次常用字,范围为 0XC940-0XF9FE,共7652个,剩下的便是
一些特殊字符。
制作码表文件的原理是这样的:首先将所有的BIG5编码写入一个文件,然后,使用具有BIG
5码到GB码转换功能的软件,如地球村、东方快车、四通利方,将文件转换为GB码文件,即
得到码表文件。
下面的源程序将所有可能的BIG5编码(0XA100-0XFEFF)写入文件“Table.TXT”。
//TURBO C++ 3.0
#include <Stdio.h>
#include <stdlib.h>
void main(){
FILE * codefile;
int i,j,k;
codefile=fopen("table.txt","w+b");
for (i=0xa1;i<=0xfe;I++){
for(j=0x00;j<=0xff;j++){
fwrite(& i,1,1,codefile);
fwrite(& j,1,1,codefile);}
}
fclose(codefile);
return;
}
运行地球村、东方快车或四通利方,将“Table.txt”从BIG5码转换为GB码,即获得码表文
件。
第二步 转换
下面的源程序,将BIG5码文件转换为GB码文件。
//TURBO C++3.0
#include <stdio.h>
#include <stdlib.h>
void main(){
int que, wei;
FILE * sourcefile;
FILE * tabfile;
FILE * destfile;
sourcefile = fopen("big.txt', "r+b");
//BIG5 码文件
tabfile = fopen("table.txt", 'r+b");
//码表文件
destfile = fopen("gb.txt","w+b");
//转换生成的GB码文件
while (!feof(sourcefile)){
fread(& que,1,1,sourcefile);
if (feof(sourcefile)){
break; }
if (que> =0xa1 && que <=0xfe)
//叛断是否汉字(BIG5编码)
{fread(& wei,1,1,sourcefile);
if (wei<0xa1) wei = wei - 0x40;
if (wei>=0xa1) wei = wei - 0xa1 + 0x7e - 0x40 + 1;
fseek(tabfile, 2 * ((que -0xa1) * (0xfe - 0xa1 + 1 + 0x7e - 0x40 + 1 ) +
wei), SEEK_SET);
fread(& que,1,1,tabfile);
fread(& wei,1,1,tabfile);
fwrite(& que,1,1,destfile);
fwrite(& wei,1,1,destfile);
}
else
fwrite(& que,1,1,destfile); //处理英文
}
fclose(sourcefile);
fclose(tabfile);
fclose(destfile);
return;
}
以上程序在Win95/97,TC3.0 通过。稍加修改,也可用于VC或VB程序中。用同样的方法,
我们也可以将GB码转换为BIG5码。
C++BUILDER让你的任务栏图标动起来
---- 在windows环境下上网时,你有没有注意到在屏幕的右下脚的任务栏上有一个动画图
标呢?它一闪一闪的,形象的表示出网络此时正在传输数据。关于任务栏图标编程的文章
有不少,可是如何才能编制出动态图标呢?在C++Builder中可以比较方便的实现。
---- 其基本编程思路是:通过设置Timer时钟控件使应用程序在规定的时间间隔内发送特
定的消息,使任务栏图标不断更改,从而形成动画效果。实现方法为在应用程序的表单中
加载几个Image控件,使他们装载相应的图画,几幅图画按顺序连续的被显示,就形成了动
画。
---- 在这里,我们用一个门的开关动画来做例子,在表单上放置一个Timer控件,两个Ima
ge,分别装载“开门”和“关门”两幅图。开始加入代码。
---- 应用程序必须用发送消息的办法通知任务栏增加,删除,和修改图标。发送消息必须
调用Shell_NotifyIcon。它的原形为:
WINSHELLAPI BOLL WINAPI Shell_NotifyIcon(
DWORD dwMessage, POINTIFYCONDATA pnid);
第一个参数 dwMessage是发送消息的标志,可以选
NIM_ADD // 往任务栏通知区添加图标
NIM_DELETE //往任务栏通知区删除图标
NIM_MODIFY //通知任务栏通知区修改图标
编制消息发送函数TrayMessage
bool __fastcall TForm1::TrayMessage(DWORD dwMessage)
{
NOTIFYICONDATA tnd;
PSTR pszTip;
pszTip = TipText();
tnd.cbSize= sizeof(NOTIFYICONDATA);
//结构的大小
tnd.uCallbackMessage = MYWM_NOTIFY;
//自定义回调消息,在头文件中声明
tnd.hWnd= Handle;
//接受回调消息的窗口句柄
tnd.uID = IDC_MYICON;
//图标标志号
tnd.uFlags= NIF_MESSAGE | NIF_ICON | NIF_TIP;
//指定以下三个参数哪个包含有效数据
if (dwMessage == NIM_MODIFY)
{
tnd.hIcon =
(HICON)IconHandle(); //取得图标句柄
if (pszTip)
lstrcpyn(tnd.szTip, pszTip,
sizeof(tnd.szTip));
else
tnd.szTip[0] = '\0';
}
else
{
tnd.hIcon = NULL;
tnd.szTip[0] = '\0';
}
return (Shell_NotifyIcon(dwMessage, &tnd));
}
编制取得图标句柄的函数
HICON __fastcall TForm1::IconHandle(void)
{
if (n==1)
{ return (Image1- >Picture->Icon- >Handle);
//n是全局变量,1为显示Image1,0为Image2
}
else
{ return (Image2- >Picture- >Icon- >Handle);
}
}
编制图标状态转换函数
void __fastcall TForm1::ToggleState(void)
{
if (n==1) //n为图标句柄锁,是全局变量,
1为显示Image1,0为Image2
{
n=n-1;
}
else
{
n=n+1;
}
TrayMessage(NIM_MODIFY);
//发送图标变换消息
}
对Timer控件编制代码,设它的Interval
属性为1000,即定时器每一秒响应一次。为 Ontimer
事件键入代码:
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{ ToggleState( );
}
---- 由于篇幅有限,以上只列出了基本部分的代码,其他功能的实现,如关闭程序,打开
窗口等,比较简单,不在赘述。程序运行时,你将看到在屏幕的右下角任务栏有一扇门打
开又关闭的动画图标。是不是很有趣,快编一个你喜欢的吧。
TFORM
一、让窗口总是在最前面
Form 的FormStyle属性设置为fsStayOnTop值。
二、 动 态 调 用 窗 体Form
在 缺 省 情 况 下, 由File/New Form 生 成 添 加 入 项 目 文 件 中 的 窗 体 都
具 有"Auto Create"( 自动 创 建) 的 特 性。 即 只 要 程 序 运 行, 该 窗 体 就
存 在 于 内 存中 了, 不 管 当 前 它 是 否 被 调 用。 具 有 这 种 特 性 的 窗
体 一 般适 用 于 窗 体 属 性 比 较 固 定、 经 常 被 调 用 的 情 况。 其 优点 是
速 度 快, 缺 点 是 占 用 内 存。 在 实 际 程 序 设 计 中, 会 遇见 大 量 类
似 对 话 框 功 能 的 窗 体, 它 们 用 于 显 示 状 态 或 输入 信 息, 仅 须 在
程 序 中 调 用 一 下, 完 成 其 功 能 就 行 了, 无需 常 驻 内 存。 这 时 可 以
通 过 选 择Project/Options/Forms, 将"Auto--Create forms " 栏 中 相 应 的 窗
体, 如Form1, 用" >" 键 移 动 到 "Available forms" 栏 中, 并 在 程 序 需 调 用
该 窗 体 处, 加 入 下 列 语 句:
TForm1 *myform=new TForm1(this);
myform- >ShowModal();
delete myform;
窗 体Form1 仅 是 在 需要 调 用 时 才 调 入 内 存,调 用 完 成 后, 即 用delete
清 除 出 内 存。这 样 可 减 少 程 序 对 内 存 资 源 的 占 用。
三、遍 历 窗 体 控 件 的 方 法
要 访 问 或 修 改 窗 体 上的 控 件, 方 法 很 简 单, 以TEdit 为 例 子:
Edit1- >Text="";
Edit2- >Text="";
但 如 果 窗 体 上 有 十 来个 像Edit1 这 样 的 控 件, 需 要 进 行 相 同 的 初
始 化, 用 上 面 的方 法 一 个 一 个 地 进 行, 岂 不 麻 烦 ! 所 以 有 必 要
掌 握 遍 历窗 体 控 件 的 方 法。 在 介 绍 该 方 法 之 前, 让 我 们 先了 解 一
下 窗 体Form 的Components 和Controls 属 性。 参 见 表 一。
表 一
属性 类型 说明
ComponentCount Int 目前Form上各类
控件的总数
Components TCompont* 目前Form上指向
所有控件的数组
目前Form上指向
所有控件的数组
ControlCount
Int
目前Form上某一子
区域上各类控件的总数
Controls TControl*
目前Form上指向某一子
区域上所有控件的数组
以 图 一 为 例(图 略) 说 明,Form1 的ComponentCount=6, 而Panel1 的ControlCoun
t=4.,
其 中: 数 组 对象
Components[0] Panel1
Components[1] Label1
Components[2] Edit1
Components[3] Label2
Components[4] Edit2
Components[5] Button1
数 组 对 象
Controls[0] Label1
Controls[1] Edit1
Controls[2] Label2
Controls[3] Edit2
下 面 这 段 代 码 完 成 了 对Panel1 上 所 有TEdit 控 件 的 遍 历 初 始 化。 读
者 稍 加 修 改, 即 可 对 其它 控 件 进 行 遍 历。 这 里 有 一 个 小 技 巧, 我
们 把 需 要 进 行 初始 化 的 控 件 放 置 在 了 一Panel1 上, 与 不 需 要 初 始
化 的 控 件区 分 开 来, 这 样 便 于 编 程。
AnsiString namestring="TEdit";
for(int i=1;i< Panel1- > ControlCount;i++)
{
if(Panel1- > Controls[i]- > ClassNameIs(namestring))
{
TEdit *p=dynamic_cast < TEdit* > (Panel1- >Controls[i]);
P- >Text="";
}
}
四、不规则窗口
1.在窗口定义中,加入HRGN hWndRgn;
2.在TForm::OnCreate()消息函数最后,加入下面的代码:
hWndRgn=::CreateEllipticRgn(0,0,Width,Height);
::SetWindowRgn(hWndRgn,TRUE);
3.设置TForm的属性为无标题,无边框。
4.编译连接应用程序,就可以看到一个椭圆形窗口。
五、MDI Form
1.Application->CreateForm(__classid(Tjjcginput), &jjcginput);
后不用在使用显示Form的语句就可以显示出来了。
2.form 的onclose 事件必须用下面语句释放空间:
void __fastcall TMDIChild::FormClose(TObject *Sender, TCloseAction &Action)
{
Action = caFree;
}
用BCB在windows桌面创建快捷方式
API提供了一个叫做IShellLink的COM接口允许我们创建快捷方式。为在桌面创建快捷方式
,我们创建一个IShellLink对象,设置它的属性,然后把这个link保存到desktop目录。
下面的例子代码演示了怎样创建一个快捷方式。在这个例子里,这个快捷方式保存在C:\Dr
ive目录下。
//----------------------------------------------------------------------
include <shlobj.h>
void __fastcall TForm1::Button1Click(TObject *Sender)
{
if(OpenDialog1->Execute())
CreateShortCut(OpenDialog1->FileName);
}
//----------------------------------------------------------------------
void TForm1::CreateShortCut(const AnsiString &file)
{
IShellLink* pLink;
IPersistFile* pPersistFile;
if(SUCCEEDED(CoInitialize(NULL)))
{
if(SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink, (void **) &pLink)))
{
pLink->SetPath(file.c_str());
pLink->SetDescription("Woo hoo, look at Homer's shortcut");
pLink->SetShowCmd(SW_SHOW);
if(SUCCEEDED(pLink->QueryInterface(IID_IPersistFile,
(void **)&pPersistFile)))
{
WideString strShortCutLocation("C:\\bcbshortcut.lnk");
pPersistFile->Save(strShortCutLocation.c_bstr(), TRUE);
pPersistFile->Release();
}
pLink->Release();
}
CoUninitialize();
}
}
//----------------------------------------------------------------------
上面的例子只是把快捷方式文件保存到了c:\drive目录下,但没保存到desktop目录下。
要让快捷方式出现在桌面上,只须把快捷方式文件保存到desktop目录下。首先我们要找到
windows的desktop目录,请参阅判断windows的Desktop及相关目录这一节。一旦我们知道
了desktop所在的目录,我们就能将快捷方式文件保存到desktop目录下。然后windows就能
将快捷方式图标显示到桌面上。下面是经过改进了的例子:
//----------------------------------------------------------------------
void TForm1::CreateShortCut(const AnsiString &file)
{
IShellLink* pLink;
IPersistFile* pPersistFile;
LPMALLOC ShellMalloc;
LPITEMIDLIST DesktopPidl;
char DesktopDir[MAX_PATH];
if(FAILED(SHGetMalloc(&ShellMalloc)))
return;
if(FAILED(SHGetSpecialFolderLocation(NULL,
CSIDL_DESKTOPDIRECTORY,
&DesktopPidl)))
return;
if(!SHGetPathFromIDList(DesktopPidl, DesktopDir))
{
ShellMalloc->Free(DesktopPidl);
ShellMalloc->Release();
return;
}
ShellMalloc->Free(DesktopPidl);
ShellMalloc->Release();
if(SUCCEEDED(CoInitialize(NULL)))
{
if(SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink, (void **) &pLink)))
{
pLink->SetPath(file.c_str());
pLink->SetDescription("Woo hoo, look at Homer's shortcut");
pLink->SetShowCmd(SW_SHOW);
if(SUCCEEDED(pLink->QueryInterface(IID_IPersistFile,
(void **)&pPersistFile)))
{
WideString strShortCutLocation(DesktopDir);
strShortCutLocation += "\\bcbshortcut.lnk";
pPersistFile->Save(strShortCutLocation.c_bstr(), TRUE);
pPersistFile->Release();
}
pLink->Release();
}
CoUninitialize();
}
}
//----------------------------------------------------------------------
不要陷于COM的泥沼之中
创建快捷方式包括一些对COM的使用。不要让你陷入到COM的复杂之中。COM只是创建和使用
对象的一种方法。在这个例子里我们可以考虑不使用COM而是用等价的C++技术。
COM code C++ psuedo-equivalent
IShellLink* pLink; TShellLink *Link;
IPersistFile* pPersistFile; TPersistFile *PersistFile;
CoInitialize();
CoCreateInstance(CLSID_ShellLink, Link = new TShellLink;
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void **) &pLink)
pLink->SetPath(file.c_str()); Link->SetPath(file.c_str());
pLink->SetShowCmd(SW_SHOW); Link->SetShowCmd(SW_SHOW);
pLink->QueryInterface(IID_IPersistFile PersistFile =
(void **)&pPersistFile))) dynamic_cast<TPersistFile*>(Link);
pPersistFile->Save("C:\\", TRUE); PersistFile->Save("C:\\");
pPersistFile->Release(); delete PersistFile
pLink->Release(); delete Link;
CoUninitialize();
读磁片磁区
一、以前的DOS版要读、写、格式化第0轨的第1个磁区,程式大致如下:
char buffer[512];
reg.x.dx=0 ; /* for drive A *
reg.x.cx=0x0001 /* for boot sector */
reg.x.bx=FP_OFF(buffer);
sreg.es=FP_SEG(buffer);
resg.x.ax=0x0201; /* 02 for Read, 03 for Write ,05 for Format */
int86x(0x13,®,®,&sreg);
那麽在windows 下转换为呼叫 DeviceIoControl 以便格式化、读取、写入该磁轨,DIOC_R
EGISTERS 这struct 在套上 DOS 下 Int21对HDD或FDD 的各项参数如要格式化是Int21也是
有, 但Windows下也另有提供。
l#pragma pack(push, 1)
struct DIOC_REGISTERS {
DWORD reg_EBX;
DWORD reg_EDX;
DWORD reg_ECX;
DWORD reg_EAX;
DWORD reg_EDI;
DWORD reg_ESI;
DWORD reg_Flags;
};
#pragma pack(pop)
sDiskImageInfo->hDevice = ::CreateFile("\\\\.\\vwin32", 0, 0, NULL, 0,
FILE_FLAG_DELETE_ON_CLOSE, NULL);
if( sDiskImageInfo->hDevice == INVALID_HANDLE_VALUE)
bRunNext = false;
// Reset Floppy Disk
reg.reg_EBX = 0;
reg.reg_EAX = 0x0000; // IOCTL for block devices
reg.reg_EDX = sDiskImageInfo->Driver;
reg.reg_EDI = 0; reg.reg_ESI= 0;
reg.reg_Flags = 0x0001; // assume error (carry flag is set)
dwResult = ::DeviceIoControl( sDiskImageInfo->hDevice,
VWIN32_DIOC_DOS_INT13,
®, sizeof(DIOC_REGISTERS), ®,
sizeof(DIOC_REGISTERS), &cb, 0);
// Seek Floppy
reg.reg_EBX = 0;
reg.reg_EAX = 0x0C00; // IOCTL for block devices
reg.reg_ECX = ( sDiskImageInfo->nC << 8) | sDiskImageInfo->nS;
reg.reg_EDX = ( sDiskImageInfo->nH << 8) | sDiskImageInfo->Driver;
reg.reg_EDI = 0;
reg.reg_ESI= 0;
reg.reg_Flags = 0x0001; // assume error (carry flag is set)
dwResult = ::DeviceIoControl( sDiskImageInfo->hDevice,
VWIN32_DIOC_DOS_INT13,
®, sizeof(DIOC_REGISTERS), ®,
sizeof(DIOC_REGISTERS), &cb, 0);
// Read Floppy
R_CreateDiskImageFile:
reg.reg_EBX = 0;
reg.reg_EAX = 0x0200 | 0x01; // IOCTL for block devices
reg.reg_ECX = ( sDiskImageInfo->nC << 8) | sDiskImageInfo->nS;
reg.reg_EDX = ( sDiskImageInfo->nH << 8) | sDiskImageInfo->Driver;
reg.reg_EBX = (DWORD) &m_Buf;
reg.reg_EDI = 0;
reg.reg_ESI= 0;
reg.reg_Flags = 0x0001; // assume error (carry flag is set)
dwResult = ::DeviceIoControl( hDevice, VWIN32_DIOC_DOS_INT13,
®, sizeof(DIOC_REGISTERS), ®,
sizeof(DIOC_REGISTERS), &cb, 0);
if (!dwResult || (reg.reg_Flags & 0x0001))
{
}
I/O 端 口 读 写 的 实 现
细 心 的 读 者 会 发现,C++ Builder 不 再 支 持 如inportb()、outportb() 一 类I/
O 端 口 读 写指 令 了。 准 确 地 说, 在Windows 环 境 下,Borland C++ 仅 支 持1
6 位应 用 程 序 的 端 口 操 作, 对32 位 应 用 程 序 的 端 口 操 作 不 再 支持,
而C++ Builder 开 发 出 来 的 程 序 是32 位 的。 我 个 人 以 为, 这是C++
Builder 设 计 者 的 败 笔。 因 为PC 机 中,I/O 地 址 空 间 与 内存 地 址 空 间
从 来 都 是 各 自 独 立 的。 看 看Delphi, 不 就 通 过Port 数 组 实 现 了 对I/O
端 口 的 访 问 了 吗? 搞 不 清 楚 为 什 么C++ Builder 就 没 有 提 供 类 似 的
机 制 ? 下 面 这 几 个 函 数 是 笔 者 从 网 上淘 下 来 的, 经 过 验 证, 在W
indows95 环 境 下, 的 确 可 实 现 对I/O 端 口 的 读 写。 读 者 可 以 借 鉴 使
用。
void outportb(unsigned short int port, unsigned char value)
{
// mov edx, *(&port);
__emit__(0x8b, 0x95, &port);
// mov al, *(&value);
__emit__(0x8a, 0x85, &value);
// out dx, al;
__emit__(0x66, 0xee);
}
void outportw(unsigned short int port, unsigned short int value)
{
// mov edx, *(&port);
__emit__(0x8b, 0x95, &port);
// mov ax, *(&value);
__emit__(0x66, 0x8b, 0x85, &value);
// out dx, ax;
__emit__(0xef);
}
unsigned char inportb(unsigned short int port)
{
unsigned char value;
// mov edx, *(&port);
__emit__(0x8b, 0x95, &port);
// in al, dx;
__emit__(0x66, 0xec);
// mov *(&value), al;
__emit__(0x88, 0x85, &value);
return value;
}
unsigned short int inportw(unsigned short int port)
{
unsigned short int value;
// mov edx, *(&port);
__emit__(0x8b, 0x95, &port);
// in ax, dx
__emit__(0xed);
// mov *(&value), ax
__emit__(0x66, 0x89, 0x85, &value);
return value;
}
检 测 鼠 标 位 置
例如,通过一个定时器Timer1的触发事件源来检测鼠标位置
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
TPoint pt;
GetCursorPos(&pt);
Label1->Caption = "(" +IntToStr(pt.x) +")(" +IntToStr(pt.y) +")";
}
令Win32 应 用 程 序 跳 入 系 统 零 层
众 所 周 知, 在Windows95/98 的Win32 on Intel x86 体 系 中 利 用 了 处 理 器 的
三 环 保 护 模 型 中 的 零 环(Ring0, 最 高 权 限 级 别) 和 三 环(Ring3,
最 低 权 限 级 别)。 一 般 应 用 程 序 都 运 行 在Ring3 下, 受 到 严 格 的"
保 护", 只 能 规 矩 地 使 用Win32API。 如 果 我 们 想 进 行 一 些 系 统 级 的
操 作, 例 如 在 嵌 入 汇 编 中 使 用 诸 如"Mov EAX,CR0", 或 像 在DOS 下 那
样 调 用 一 些 必 不 可 少 的 系 统 服 务( 如BIOS,DPMI 服 务) 而 用"Int xx"
, 都 会 导 致" 非 法 操 作"。 但 这 种 能 力 有 时 是 必 不 可 少 的, 一 到
这 种 时 候Microsoft 就 " 建 议 编 写 一 个VxD"。VxD 大 家 早 有 所 闻 了, 在V
xD 里, 不 但 可 以 执 行CPU 的 所 有 指 令, 而 且 可 以 调 用VMM( 虚 拟 机 管
理 器) 和 其 他VxD 提 供 的 上 千 个 系 统 级 服 务。 获 得 这 一 能 力 的 最
本 质 原 因 在 于 它 运 行 在Ring0, 与 系 统 内 核 同 一 级 别。 但 是 它 体
系 的 复 杂 性、 开 发 工 具 的 不 易 获 得、 帮 助 文 档 的 不 完 备, 使Micro
soft 排 除 了 一 大 批 程 序 员 和 竞 争 对 手。 而 将 在Windows2000(Windows98
也 开 始 支 持) 中 取 代VxD 的WDM 对Win95 程 序 员 也 是 个 噩 梦, 它 需 要
了 解Windows NT 核 心 驱 动 模 型。
----有 没 有 简 单 一 些 的 办 法 呢 ? 我 们 可 以 令 一 个 普 通Win32 应 用
程 序 运 行 在Ring0 下, 从 而 获 得VxD 的 能 力 吗 ? 答 案 是 肯 定 的。 下
面 我 们 就 简 述 一 下 这 一 技 巧, 有 关Intel x86 保 护 模 式 的 基 础 知 识
请 大 家 看 有 关 书 籍。
----首 先 此 技 巧 基 于 以 下 理 论 根 据:
----一、SIDT 指 令( 将 中 断 描 述 符 表 寄 存 器 IDTR - -64 位 宽,16 ~47B
it 存 有 中 断 描 述 符 表IDT 基 地 址 - - 的 内 容 存 入 指 定 地 址 单 元)
不 是 特 权 指 令, 就 是 说 我 们 可 以 在Ring3 下 执 行 该 指 令, 获 得IDT
的 基 地 址, 从 而 修 改IDT, 增 加 一 个 中 断 门 安 置 我 们 的 中 断 服 务
, 一 旦Ring3 程 序 中 产 生 此 中 断,VMM 就 会 调 用 此 中 断 服 务 程 序,
而 此 中 断 服 务 程 序 就 运 行 在Ring0 下 了。 这 一 点 与 在DOS 下 非 常 相
似。
----二、Windows95 Win32 应 用 程 序 运 行 一 个 映 射 到 全 部4G 内 存 的 段 中
, 选 择 子 为0137h,Ring0 中 的VxD 运 行 在 另 一 个 映 射 到 全 部4G 内 存 的
段 中, 选 择 子028h, 这 两 个 段 除 了 选 择 子 决 定 的 访 问 权 限 不 同
外, 没 什 么 不 同, 各 自 段 中 相 同 的 偏 移 量 对 应 了 相 同 的 线 性 地
址。 所 以 我 们 放 在Win32 应 用 程 序 中 的 中 断 服 务 程 序 可 以 以Ring3
的 段 偏 移 量 被Ring0 中 的VMM 寻 址。
----下 面 我 们 以 具 体 例 子 进 一 步 说 明, 程 序 中 有 详 细 注 释。
----这 是 一 个Win32 Console Program( 控 制 台 应 用 程 序), 虽 然 运 行 中
看 起 来 很 像DOS 筐 中 运 行 的 实 模 式DOS 程 序, 但 它 是 货 真 价 实 的 运
行 在Ring3 下 的Win32 程 序。 用Visual C + + 5.0 AppWizard 创 建 一 个Win32
Console Program 项 目, 添 加 以 下.CPP 文 件, 编 译 即 可。
#include
#include
#include
#include
// 若 无DDK 带 下 划 线 的 可 略 去,
这 些 语 句 演 示 了 调 用VMM/VXD 服 务
DWORDLONG IDTR,SavedGate;
WORD OurGate[4]={0,0x0028,0xee00,0x0000};
// 中 断 门 描 述 符 格 式 如 下:
DWORD _eax,_ecx,_cr0;
WORD vmmver;
HVM sysvm;
void nothing()
{
//Used to test call in Ring0
sysvm=Get_Sys_VM_Handle();
}
void __declspec( naked ) Ring0Proc(void)
// 中 断 例 程, 运 行 在Ring0
{
_asm{
mov _eax,eax //
mov _ecx,ecx //
mov eax, CR0
// 测 试Ring3 中 不 能 执 行 的 特 权 指 令
mov _cr0,eax //
}
VMMCall(Get_VMM_Version);
// 调 用VMM 服 务
_asm{
mov vmmver,ax
}
nothing();
// 测 试 在 运 行 于Ring0 的
中 断 例 程 中 调 用 子
_asm iretd
// 中 断 返 回, 与 在 实 模 式
编 程 无 本 质 区 别
}
void main() // 主 程 序
{
_asm{
mov eax, offset Ring0Proc
mov [OurGate], ax // 将 中 断 函 数 的 地 址
shr eax, 16 // 填 入 新 造 的 中 断 门
mov [OurGate +6], ax // 描 述 符
sidt fword ptr IDTR
// 将 中 断 描 述 符 表 寄 存 器(IDTR)
的 内 容 取 出
mov ebx, dword ptr [IDTR +2]
// 取 出 中 断 描 述 符 表(IDT) 基 地 址
add ebx, 8 *9
// 计 算Int 9 的 描 述 符 应 放 置 的 地 址 选 用
Int9 是 因 为 它 在Win32 保 护 模 式 下 未 占 用
mov edi, offset SavedGate
mov esi, ebx
movsd // 保 存 原 来 的Int 9 描 述 符 到
movsd //SavedGate 以 便 恢 复
mov edi, ebx
mov esi, offset OurGate
movsd // 替 换 原 来 的 中 断 门 描 述 符
movsd // 以 安 装 中 断 服 务 例 程
mov eax,0x6200
// 用 以 测 试 放 在EAX 中 的 数 据
能 否 正 确 传 到Ring0 中 断
mov ecx,0
// 用 以 测 试 放 在ECX 中 的 数 据
能 否 正 确 传 到Ring0 中 断
mov ecx,0
// 用 以 测 试 放 在ECX 中 的 数 据
能 否 正 确 传 到Ring0 中 断
// 因 为 很 多VxD 服 务 都 用
此 二 寄 存 器 传 递 参 数
int 9h
// 人 为 触 发 中 断, 平 时 会 出 现
保 护 错 误 蓝 屏 或 非 法 操
// 作 对 话 框, 现 在 安 装 了
// 中 断 服 务 例 程 后, 就 会 通 过
//VMM 在Ring0 调 用 中 断 服 务 例 程
- -Ring0Proc
mov edi, ebx
mov esi, offset SavedGate
movsd // 恢 复 原 来 的 中 断 门 描 述 符
movsd
}
cout<<"CR0="<<_cr0< } _getch(); if(0="=_getch())" while(_kbhit()="=0);" do{}
continue.?<
如何取得Memo的行和列
新建一个应用,在窗体Form1上添加两个TLabel组件名为Label1,Label2;
添加两个TButton组件名为Button1,Button2;添加一个TMemo组件名为Memo1。
然后在代码编辑器中添加以下代码。
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Label1→Caption=SendMessage(Memo1→Handle,EM_LINEFROMCHAR,-1,0)+1;
}
void __fastcall TForm1::Button2Click(TObject *Sender)
{
Label2→Caption=Memo1→SelStart-SendMessage(Memo1→Handle,EM_LINEINDEX,-1
,0)+1;
}
这种方法同样适用于RichEdit。
使用Sockets
使用sockets Socket控件让你建立一个利用TCP/IP和有关的协议与其他系统进行通信的应
用。使用Sockets,你能够读和写通过它连接的其他机器,而不用担心实际的网络软件的相
关细节。Sockets提供基于TCP/IP协议的连接。除此以外还能很好的工作,在其他相关的协
议,例如Xerox Network System (XNS), Digital's DEC net, or Novell's IPX/SPX 家族
。
C++ Builder提供你写网络服务器或客户应用程序去读和写其他的系统。一个服务或客户程
序通常专注于一个单一的服务如超文本传送协议(HTTP)或文件传输协议(FTP)。使用serve
r sockets,一个应用程序可以提供这些服务中的一个去连接一个希望使用服务的客户程序
。Client sockets允许一个应用使用这些服务中的一个去连接提供这个服务的服务应用。
使用sockets去写应用程序,你必须理解下面这些知识:
一、服务工具
当你需要写网络服务或客户应用时,Sockets提供一种接合。对于许多服务,象
HTTP 或 FTP,第三方服务商提供这些服务已经相当有效。有些甚至随着操作系统捆绑而来
,以便不用你自己写。然而,当你想更多的控制服务的实现,如想让你的应用程序与网络
通信更加紧密,或当没有一个服务能提供你特殊需要的服务时,你可能想建立你自己的服
务或客户应用。例如,工作在分布式data sets时,你可能想为数据库写一层与其他系统通
信的应用。想使用Sockets实现一个服务,你必须理解:
1.服务协议
在你写一个网络服务或客户程序前,你必须明白你的应用将提供或使用什么服务。你的网
络应用必须支持许多服务的标准协议。如果你为标准的服务例如HTTP,FTP写网络应用,或e
ven finger or time,你必须先理解与其他系统通信所使用的协议。特殊服务细节你必须看
提供的或使用的文档。
如果你的应用程序提供一个新的服务与其他系统通信,第一步是为这个服务的
服务端和客户端设计通信协议。什么信息将发送?如何整理这些信息?如何对这些信息进
行编码?
应用程序通信
经常的,你的网络服务端或客户端应用程序要提供一层在网络软件和一个应用之间使用的
服务。例如,一个HTTP服务站点在INternet与一个Web 服务应用之间为HTTP请求信息提供
内容和应答。
在你的网络应用(或客户应用)和网络软件之间Sockets 提供一个接口。你必须提供一个
接口,在你的应用程序与应用间使用。你可以拷贝第三方服务商提供的标准API(例如ISAPI
),或你可以设计和发布你自己的API.
2.理解服务和端口
许多标准服务都有关联的、指定的端口号。当 执行服务时,你可以为服务考虑一个端口号
。如果你实现一个标准服务, Windows socket objects 提供一些方法让你为服务寻找端
口号。如果提供一个新的服务,在基于Windows 95 或 NT机器上,你能够在文件Services
中为你的服务指定一个相关联的端口号。设置Services文件的更多信息请看微软 的Window
s Sockets文档。
二、Socket连接的类型
Socket连接可以分成三个基本的类型,它们反映了如何开始连接和本地Socket 连接是什么
。这三个类型是:
1.客户端连接
客户端连接是一个本地系统的客户端socket与一个远程系统上的服务端Socket连接。客户
端连接由客户端Socket开始。首先,客户端Socket必须描述它想连接到的服务端Socket.
接着客户端socket查找服务端socket,当找到服务器时,就要求连接。服务端socket可能
不能完成正确的连接。服务器sockets维持一个客户端请求队列,在他们有时间时完成连接
。当服务端socket接受客户端连接,服务端socket
将向它想连接的客户socket发送一个完整的描述,客户端的连接完成。
2.倾听连接
服务器 socket不会去定位客户端,代替的,他们形成被动的,"半连接"状态,倾听来自客
户端的请求。服务器 sockets形成一个队列,存放 它们听到的连接请求。这个队列记录着
客户端连接请求就象他们已连接进来一样。当服务器sockets同意客户连接请求时,它形成
一个新的socket去连接客户端,因此这个倾听连接能保持开放状态允许其他客户端请求。
3.服务端连接
当倾听socket同意一个客户端请求时,服务器端socket形成一个服务器连接。当服务器端
同意连接时,向客户端发送一个服务端socket描述以完成连接,当客户端socket收到这个
描述时这个连接得到确认,连接完成。一但连接到客户端的Socket完成,服务端连接就不
能识别从一个客户端来的连接。末端双方有同样的能力去接收同样的事件类型。只有倾听
(listening)连接是根本不同的,它只有一个单一的末端。
三、sockets描述
Sockets让你的网络应用软件通过网络与其他系统进行通信。在网络连接中每个socket可以
看成一个终端点。它有一个指定的地址。
*这个系统正在运行
*它理解的接口类型
*用来连接的端口
一个完整的socket连接描述,你必须提供sockets 在连接两端的地址。在你开始一个socke
t连接前,你必须完整的描述你想得到的连接。有些信息可以从你的应用
软件运行的系统平台上得到。例如,你不需要描述一个客户端socket的本地IP地址--这个
信息可以从操作系统上获得。你必须提供你工作所依靠的socket的类型的信息。客户端soc
ket必须描述他们想连接的服务器。侦听服务器sockets必须描述他们提供反应的服务器的
端口。一个socket 连接终端的完整描述包括两部分:
1.IP地址
主机是这样一个系统,它运行着包含有socket的应用程序。你必须描述主机给socket,通
过给出主机的IP地址来完成这个描述。IP地址是一个有四个数字(byte)值的,在标准inte
rnet点付内的字符串。
例如123.197.1.2
一个简单的系统可以支持多于一个的IP地址。IP地址通常难于记忆并且容易打错。一个可
供选择的方法是使用主机名。主机名就是IP地址的别名,它就是你常看到的统一资源定位
(URLs)。它是一个字符串,包括了域名和服务。
例如 http://www.wsite.com
许多内部网提供给主机的名字对应的系统IP地址是internetIP地址。在windows95 和NT机
器上,如果一个主机名不能用,你可以在HOSTS文件中为你的本地IP地址(这个本地IP地址
应该是指你想连接的主机IP地址--zyqsj)建立一个进入的名字。
关于HOSTS文件的更多信息请看WINDOWS SOCKETS的文档。
服务器sockets不需要指定主机。本地IP地址可以从系统中读到。如果本地系统支持多于一
个的IP地址,服务器sockets将同时在所有的IP地址上侦听客户端请求。当一个服务器sock
et同意一个连接,客户端提供一个远程IP地址。客户sockets必须指定远程主机通过提供主
机名或者IP地址。
在主机名和IP地址间作一个选择
许多应用软件使用一个主机名去指定一个系统。主机名容易记住和容易检查排版错误。进
一步讲,服务器能改变系统或与IP地址关联的特殊的主机名。使用一个主机名,能够允许
客户端通过主机名描述找到抽象的站点,即使主机使用一个新的IP地址。
如果主机名是未知的,客户socket必须指定服务器系统使用的IP地址。通过给一个IP地址
来指定服务器将更快。当你提供主机名时,socket在定位服务器系统前,必须搜寻与这个
主机名相关的IP地址。
2.端口号
虽然IP得地址提供了足够的信息去找到socket连接中位于另一端的系统,你通常还需要指
定那个系统的端口号。没有端口号,一个系统在同一时间只能进行一个单一的连接。端口
号是唯一标识那允许一个独立系统连接到支持同时多个连接的主机,每个连接都必须指定
一个端口号。
在网络应用中,对于服务器工具来说端口号是一个数字代码。有一个习惯就是侦听服务连
接到他们自己固定的端口号上,以便他们能找到客户端sockets.服务器socket监听为他们
提供服务的相关端口号。当他们允许给予一个客户端socket连接时,他们创建一个独立的s
ocket连接,使用不同的专用的端口号。通过这个方法,能持续的监听相关服务的端口号。
客户端socket使用一个专用的本地端口号,就不用其他的socket去寻找它们。他们指定他
们想连接的服务器端socket的端口号,这样他们就能找到服务器应用程序。常常的,这个
端口号是通过命名想连接的服务来间接指定的。
四、使用socket控件
C++Builder提供两个socket控件,客户端sockets和服务器sockets.他们允许你的网络应用
构成连接其他的机器和允许你通过这个连接来读写信息。与每个socket控件相关联的是win
dows socket对象,它们在终端的的作用是一个实际的socket连接。socket控件使用window
s socket对象去封装windows socket API 调用,所以你的应用不用去关心连接建立的细节
或管理socket信息。
如果你想利用windows socket API调用或自定义连接细节,socket控件提供了便利,你可
以使用windows socket对象的properies,events和方法。
1.使用客户端sockets
添加一个客户端socket控件(TClientSocket)到你的form或data module 使你的应用成为
一个TCP/IP客户。客户sockets允许你指定你想连接的服务器socket和你希望服务器提供的
服务。一但你描述你想得到的连接,你可以使用客户socket控件去完成连接服务。
每个客户socket控件使用独立的客户windows socket对象(TClientWinSocket)去应答连接
中的客户终端。使用客户sockets去:
A.指定想得到的服务
客户socket控件有一个数字properties,允许你指定想连接的服务器系统和端口。你可以通
过主机名来指定服务器系统,使用Host property。
如果你不知道主机名,或者你关心找到服务器的速度,你可以指定服务器系统的IP地址,
通过使用 Address property。你必须指定IP地址和主机名中的一个。
如果你两个都指定,客户socket控件将使用主机名。除服务器系统外,你必须指定你的客
户socket将连接的在服务器系统上的端口。你能够直接使用Port property来指定服务端口
号。或者直接在Service property使用想得到的服务的名字。如果你指定端口号和服务名
,客户socket控件将使用服务名。
B.建立连接
一旦你在客户socket控件中完成了设置描述你想连接的服务器的属性,你就可以进行连接
,通过调用Open方法。如果你想你的应用启动时自动建立连接,在设计时设置Active
property为true,通过使用Object Inspector来设置。
C.取得关于连接的信息
完成连接到服务器socket后,你可以使用与你的客户socket控件相关的客户windows
socket object去取得关于连接的信息。使用Socket property去访问client windows
socket object。windows socket object 有一个properties,它能让你确定在连接的两端
客户和服务器使用的地址和端口号。
当使用一个windows socket API调用时,你可以使用SocketHandle property区获得socket
连接使用的handle。你可以使用Handle property去访问windows,以便接收来自socket连接
的信息。AsyncStyles property决定哪种信息类型是windows handle要接收的。
D.关闭连接
当你完成通讯想关闭socket 连接时,你能够通过调用Close方法来关闭连接。连接可能要
由服务器端来关闭。如果是这种情况,你将收到一个OnDisconnect 事件的通知。
2.使用服务器sockets
添加一个服务端socket控件(TServerSocket)到你的form或data module使你的应用成为一
个TCP/IP服务器。服务器sockets允许你指定你想提供的服务或你想用来监听客户请求时使
用的端口。你可以使用服务器socket控件去监听和允许客户连接请求。每个服务器socket
控件使用一个单一的服务器windows socket Object(TServerWinSocket)去应答在服务器端
监听到的连接。它通常使用一个服务器客户winodws socket
Object(TServerClientWinSocket)应答在服务器端每个活动的,连接着得到允许服务的客
户socket。使用服务器sockets去:
A.指定端口
在你的服务器socket能够监听客户请求之前,你必须指定一个端口给你的监听服务。你可
以使用Port property来指定这个端口。如果你的服务器应用提供一个标准的服务,这个服
务使用一个习惯使用的相关联的端口。你能够使用Service property直接指定端口号。使
用Service property是一个好的主意,能够减少设置端口号时的错误。如果你既指定了Por
t property,又指定了Service property,服务socket将使用服务名。
B.监听客户请求
一旦你在server socket控件上设置好你的端口号,你就能够通过在运行时通过调用Open方
法来监听一个连接。如果你希望你的应用程序能够在启动的时候自动监听连接,在设计的
时候通过使用Object Inspector设置Active 属性为true。
C.连接到客户端。
当监听服务socket控件接收到一个客户端连接请求时他们将自动接受这个请求。当你没次
收到通知时,OnClientConnetc事件将发生。
D.取得关于连接的信息
一但你的服务器socket打开了监听连接,你能够使用与你服务器socket控件相关联的服务
器windows socket object来取得关于连接的信息。使用Socket property去访问server
windows socket object.windows socket object有一个属性能够让你找到关于所有活动的
客户socket连接这些客户socket是你通过服务器socket控件允许连接的。使用Handle属性
去存取windows通过socket连接收到的信息。
每个活动的,连接到客户应用是通过服务、客户windows socket bject
(TServerClientWinSocket)封装的。你能够通过server windows socket object的连接属
性来访问所有的这些。这些server client windows socket object有些属性让你能够决定
哪些地址和端口号给连接的两端--客户和服务器socket使用。当你使用windows socket
API调用时,可以使用SocketHandle属性去获得socket连接使用的handle。你能够使用Hand
le属性去访问windows从socket连接处得来的信息。AsyncStyles属性决定windows handle
将接收哪种类型的信息。
E.关闭连接
当你决定关闭监听连接时,调用Close方法。这将关闭所有打开着的,连接到客户应用的连
接,取消任何尚未同意的连接,接着关闭监听连接以便你的服务socket控件不在接受任何
新的连接。当客户端关闭他们自己独立的连接到你的server socket的连接时,你可以在On
ClientDisconnect事件中得到讯息。
五、socket事件的应答
当使用sockets写应用程序时,大多数工作发生在socket控件的handler事件中.当通过sock
et连接开始读或写时,OnRead和OnWrite事件在non-blocking client sockets中发生从而通
知sockets.同样的,服务器sockets(blocking or non-blocking)收到OnClientRead和OnCli
entWrite事件.
当服务器结束一个连接时,客户scokets收到一个OnDisconnect事件.当客户端结束一个连接
时,服务器socket收到一个OnClientDisconnect事件.
另外,客户端Sockets和服务器端socket从连接中收到一个错误信息时,都将产生有个错误事
件.
错误事件:客户sockets和服务器sockets通常会产生一个OnError事件,当他们从连接中收到
一个错误信息的时候.你能够写一个OnError事件处理去响应这些错误信息.这个OnError事
件处理提供传送关于socket试图做什么的时候这个错误发生的信息,以及错误信息提供的错
误代码.你可以在OnError事件处理中对这个错误作出响应,并且把错误代码改为0,以避免so
cket产生一个例外.
当开始和完成发生时,socket控件通常会收到一个事件号(number of events).如果你的应
用程序需要改变socket开始操作的处理过程或通过连接开始读或写操作时,你将写事件hand
lers去应答这些client events和server events.
A.client events
当一个客户socket打开一个连接时,以下事件发生:
1.一个OnLookup事件最先发生,它试图去定位server socket.在这里你不能改变Host,Addre
ss,Port,Service属性去改变你想定位的服务器.你能够使用Socket属性去访问client
windows socket object,并且使用它的SocketHandle属性去调用windows API,以便改变soc
ket的客户属性.例如,如果你想在客户应用软件中设置端口号,你必须在server client连接
前做这件事.
2.windows socket设置和初始化事件通知.
3.当找到server socket时一个OnConnecting事件发生.在这事件中,windows Socket
object可以利用的是通过socket属性提供关于连接的另一端的服务socket的一些信息.这是
获得实际使用来连接的端口和IP地址的第一个机会,它可能不同于从监听socket处同意连接
时得到的端口或IP地址.
4.服务器同意连接请求,客户端socket完成连接.
5.当一个连接得到确定后,一个OnConnect事件发生.如果你的socket立即开始通过连接读或
写,就应写一个OnConnect事件Handler去作这件事.
B.服务器端事件(server events)
服务器socket控件通过两中方式连接:监听连接和连接到客户应用.服务器socket收到这两
个连接的所有事件.
监听时事件
当构成监听连接前,OnListen事件发生.在这个时候你能够通过socket属性获得server
windows socket object.你能够使用它的SocketHandle属性去改变socket,在socket打开监
听之前.例如,如果你想限定监听服务使用的IP地址,你可以在这个OnListen事件Handler中
做.
与客户端连接的事件
当一个服务器socket同意一个客户连接请求时,接下来的事件发生:
1.服务器socket产生一个OnGetSocket事件,通过windows socket handle传送给连接的另一
端的socket.如果你想提供自己定义的TServerClientWinSocket of descendant,你可以在O
nGetSocket 事件 handler中建立,将被用来替代TServerClientWinSocket.
2.一个OnAccept事件发生,传送新的TServerClientWinSocket对象给事件句柄.这是第一个
要点,当你使用TServerClientWinSocket的属性去获得被连接中服务的那端的客户的信息时
.
3.如果服务类型是stThreadBlocking,一个OnGetThread事件发生.如果你想提供自己定义的
TServerClientThread子类,你可以在OnGetThread事件句柄中建立一个,它将替代TServerCl
ientThread.
4.如果服务类型是stThreadBlocking,一个ONThreadStart事件发生当这个线程(thread)开
始执行时.如果你想执行任何初始化这个线程,或调用一些windows socket API在这线程开
始通过连接读和写之前,应该使用OnThreadStart事件句柄.
5.当客户端完成一个连接时,一个OnClientConnect事件发生.如果是non-blocking服务,你
可能想开始通过socket连接在这端进行读或写操作.
六、通过socket连接进行读和写
通过socket连接到其他机器的原因是想通过这些连接来读和写信息.什么信息是你要读和写
的,或者当你想读和写时是依靠哪些socket连接的相关服务的.
通过sockets进行读和写可以是异步的,所以在你的网络应用中不需要阻塞其他代码的执行.
这是调用non-blocking connection.你也同样可以通过blocking connection,这时你的下
一行代码的执行必须等到读或写操作完成.
A.Non-blocking连接,读和写是异步的, 所以在你的网络应用中不需要阻塞其他代码的执行
.建立一个Non-blocking连接:
1.在客户socket中设置ClientType属性为ctNonBlocking.
2.在服务器socket中设置ServerType属性为stNonBlocking.
当连接是non-blocking时,连接的另一端企图读或写时读和写事件将把这个信息通知你的so
cket.
读和写操作事件
Non-blocking sockets想通过连接读或写时,它会产生一个读和写操作事件通知你的socket
.在客户端sockets,你可以在OnRead或OnWrite事件句柄中对这些事件做出反应.在服务器端
Scokets,可以在OnClientRead或OnClientWrite事件句柄中对这些事件做出反应.与socket
连接相关联的windows socket object在事件句柄的读或写中被当作一个参数.Windows
socket object提供一个方法号(number of methods)以允许你通过连接读或写.
通过socket连接读,使用ReceiveBuf或ReceiveText方法.在使用ReceiveBuf方法前,使用Rec
eivelength方法去确定在连接的另一端socket准备发送的字节数(number of bytes).
通过socket连接写,使用SendBuf,SendStream,或SendText方法.如果你通过socket发送信息
后不在需要socket连接,你可以使用SendStreamThenDrop方法. SendStreamThenDrop在写完
所有的信息后将关闭Socket连接,它能够从stream读信息.如果你使用SendStream或SendStr
eamThenDrop方法,不要释放Stream object, socket在连接结束后会自动释放这个Stream.
注意:SendStreamThenDrop将关闭一个独立的客户连接服务,而不是监听连接.
B.Blocking connections
当你使用的连接是Blocking时,你的Socket必须通过连接发起读或写操作,胜过被动的等待
从socket连接发来的通知. 当你的连接末端的读和写操作发生改变时使用Blocking
socket.对于客户端sockets,设置ClientType属性为ctBlocking 以便构成一个blocing
connection.根据你的客户端应用想完成什么,你可能想建立一个执行线程去完成读或写操
作,以便你的应用能够继续执行其他的线程,当它在等待通过连接读或写操作的完成.
对于服务器sockets,设置ServerType属性为stThreadBlocking以便构成一个blocking
connection.因为blocking connections在等待通过连接读或写信息完成时挂起了其他代码
的执行,服务器socket控件通常产生一个新的执行线程给每一个客户连接,当ServerType设
置为stThreadBlocking时.许多使用Blocking连接的应用都写使用线程(using threads.甚
至如果你不使用线程,你可能也想使用(using) TWinSocketStream去读和写.
1)using threads
当使用一个blocking connection进行读或写操作时,客户sockets不会自动产生一个新线程
.如果你的客户应用程序没有什么事做,直到读或写信息完成,那么这正是你想要的.如果你
的应用包括了一个用户界面,它还需要响应用户的操作,那么,你可能想产生一个独立的线程
去读写.当服务器sockets形成一个blocking连接时,他们常常产生独立的线程给每一个客户
连接,所以没有客户需要等待直到其他客户完成通过连接读或写操作.在默认情况下,服务器
sockets使用TServerClientThread对象去实现为每个连接执行不同的线程.
TServerClientThread对象模拟发生在non-blocking连接中的OnClientRead和OnClientWrit
e事件.可是,这些事件发生在监听socket上时,不是本地线程(thread-local).如果客户请求
频繁,你将想建立你自己的TServerClientThread子类去提供一个安全线程(Thread-Safe)去
完成读和写操作.
当写客户线程或写服务器线程时,你能够使用TwinSocketStream去做实际的读写操作.
A)写客户端线程
为客户端连接写一个线程,定义一个新线程对象,使用新线程对象对话框.你的新线程对象Ex
ecute方法的句柄的通过线程连接进行读写操作的细节,可以建立一个TWinSocketStream对
象,然后使用它来读或写.
使用你自己的线程,在OnConnect事件句柄中建立它.关于建立和运行线程的更多信息,请看E
xecuting thread objects.
例子:这个例子显示一个应用的客户线程在连接确定后向服务器发出写请求.
void __fastcall TMyClientThread::Execute()
{
while (!Terminated && ClientSocket1->Active)
// make sure connection is active
{
try
{
TWinSocketStream *pStream = new TWinSocketStream(ClientSocket1.Socket,60000);
try
{
char buffer[10];
GetNextRequest(buffer);
// GetNextRequest must be a thread-safe method
// write a request to the server
pStream->Write(buffer,strlen(buffer) + 1);
// continue the communication (eg read a response)
}
__finally
{
delete pStream;
}
}
catch (Exception &E)
{
if (!E.ClassNameIs("EAbort"))
Synchronize(HandleThreadException());
// you must write HandleThreadException
}
}
}
B)写服务器线程
服务器连接线程由TServerClientThread派生.因为这个,不能使用新线程对象对话框.替代
的,手动声明你的线程如下:
class PACKAGE TMyServerThread :
public ScktComp::TServerClientThread
{
public
void __fastcall ClientExecute(void);
}
注意你将用重载ClientExcute方法替代Execute方法.执行ClientExecute方法必须为客户端
连接写一个同样的Execute方法线程.然而,当你从控件栏上放一个客户socket控件到你的应
用上时来替代这个方法时.监听服务socket同意一个连接时,服务客户线程必须使用TServer
ClientWinSocket对象来建立.这可以利用共公共的CientSocket属性.另外,你能够使用Hand
leException这个protected性的方法,胜过
你自己写你的thread-safe例外操作.
警告:Server sockets会缓存他们使用到的线程.确信ClientExecute方法执行一些必要的初
始化操作,以便它们在最后执行时不致于产生不利的结果.
当你使用你的线程时,在OnGetThread事件句柄中建立它.当建立线程,设置CreateSuspended
参数为false.
例子:这个例子显示一个为一个应用服务的线程,这个应用是在连接确定后由客户端来的读
请求.
void __fastcall TMyServerThread::ClientExecute()
{
while (!Terminated && ClientSocket->Connected)
// make sure connection is active
{
try
{
TWinSocketStream *pStream = new TWinSocketStream(ClientSocket,
60000);
try
{
char buffer[10];
memset(buffer, 0, sizeof(buffer));
if (pStream->WaitForData(60000))
// give the client 60 seconds to start writing
{
if (pStream->Read(buffer, sizeof(buffer) == 0)
ClientSocket->Close();
// if can't read in 60 seconds, close the connection
// now process the request
}
else
ClientSocket->Close();
}
__finally
{
delete pStream;
}
}
catch (...)
{
HandleException();
}
}
}
C.使用TwinSocketStream
当为一个blocking连接实现一个线程时,你必须确定在连接的另一端的socket是准备写还是
读.Blocking连接不会通知socket当它准备好写或读操作的时候.想看看连接是否准备好,使
用TWinSocketStream对象.TWinSocketStream提供一个方法去帮助调整读或写操作时间的选
择.调用WaitForData方法去等待,直到socket另一端的
准备好写操作.当读写操作使用TWinSocketStream时,如果读或写操作在指定的时间期限内
未能完成,Stream将发生超时.这个超时被当作一个结果,socket应用不会暂停,而是不断的
通过一个dropped connection试图读或写.
注意:你不能在non-blocking连接中使用TWinSocketStream
Windows95/98下怎样隐藏应用程序不让它出现在CTRL-ALT-DEL对话框中?
把你的应用程序从CTRL-ALT-DEL对话框中隐藏的一个简单办法是去应用程序的标题。如果
一个程序的主窗口没以标题,Windows95不把它放到CTRL-ALT-DEL对话框中。清除标题属性
的最好地方是在WinMain函数里。
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Title = "";
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
另一种方法是:调用RegisterServiceProcess API 函数将程序注册成为一个服务模式程序
。 RegisterServiceProcess是一个在Kernel32.dll里相关但无正式文件的函数。在MS
SDK头文件里没有该函数的原型说明,但在Borland import libraries for C++ Builder里
能找到。很显然,这个函数的主要目的是创建一个服务模式程序。之所以说很显然,是因
为MSDN里实质上对这个函数没有说什么。
下面的例子代码演示了在Windows95/98下怎样通过使用RegisterServiceProcess来把你的
程序从CTRL-ALT-DEL对话框中隐藏起来。
//------------Header file------------------------------
typedef DWORD (__stdcall *pRegFunction)(DWORD, DWORD);
class TForm1 : public TForm
{
__published:
TButton *Button1;
private:
HINSTANCE hKernelLib;
pRegFunction RegisterServiceProcess;
public:
__fastcall TForm1(TComponent* Owner);
__fastcall ~TForm1();
};
//-----------CPP file------------------------------
#include "Unit1.h"
#define RSP_SIMPLE_SERVICE 1
#define RSP_UNREGISTER_SERVICE 0
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
hKernelLib = LoadLibrary("kernel32.dll");
if(hKernelLib)
{
RegisterServiceProcess =
(pRegFunction)GetProcAddress(hKernelLib,
"RegisterServiceProcess");
if(RegisterServiceProcess)
RegisterServiceProcess(GetCurrentProcessId(),
RSP_SIMPLE_SERVICE);
}
}
__fastcall TForm1::~TForm1()
{
if(hKernelLib)
{
if(RegisterServiceProcess)
RegisterServiceProcess(GetCurrentProcessId(),
RSP_UNREGISTER_SERVICE);
FreeLibrary(hKernelLib);
}
}
//-------------------------------------------------
注: windows NT下没有RegisterServiceProcess函数。
怎样隐藏应用程序的任务条图标
首先,请看看这些术语。系统托盘是一个在任务条右角的小方框,在托盘了应用程序可以
显示小图标。任务条是可以在屏幕上伸展的工具栏。它就是程序图标所在的位置。想隐藏
程序的任务条图标,你可以应用ShowWindow函数并传给它Application->Handle窗口句柄。
ShowWindow(Application->Handle, SW_HIDE);
若想让任务条图标再出现,只需将SW_HIDE改为SW_SHOW。
ShowWindow(Application->Handle, SW_SHOW);
注: 你可以设置主窗口的Visible属性为false来隐藏它。
注: 通过ShowWindow来隐藏窗口的任务条图标是不持久的。某些动作会使任务条图标重现
。你可以将隐藏的应用程序窗口设为Tool Window来移走程序的任务条图标而避免它再次出
现。Tool windows永远不会有任务条图标。 使应用程序窗口成为一个Tool Window有一个
副作用:当用户按下Alt-TAB时它将不在程序列表中出现。你可以调用API函数GetWindowLo
ng和SetWindowLong来使应用程序窗口成为一个Tool Window。
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
DWORD dwExStyle = GetWindowLong(Application->Handle, GWL_EXSTYLE);
dwExStyle |= WS_EX_TOOLWINDOW;
SetWindowLong(Application->Handle, GWL_EXSTYLE, dwExStyle);
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
编写自己的Ping.exe程序
在Windows系统中,我们经常用Ping.exe来测试网络的连通性。
Ping的实现过程很简单,该命令将引发IP层发送一个简单的IP包,一般是32字节。而
目的方收到这个包后,将源地址和目的地址变换一下,重新发送这个包即可,当然还要加
一些超时机制。
其实,我们也可用C++ Builder NetMaster中的NMEcho控件来实现网络连接检测功能。
首先定义以下控件:
三个Edit控件:一个用于接收远程主机的IP地址或域名,一个用于接收用户设置的超
时机制的时间,一个用于设置端口号。
两个RichEdit控件:一个用于给远程主机发送信息,一个用于接收来自远程主机的信
息。
两个CheckBox控件:用于用户是否自己设定端口号。
一个Button控件:用于执行测试。
一个StatusBar控件:用于显示应用程序的状态。
程序实现代码如下:
void __fastcall TForm1::Button1Click(TObject Sender)
{ //设置NMEcho控件的标准TCP/IP属性
NMEcho1-〉Host=Edit1-〉Text ;
NMEcho1-〉TimeOut=StrToInt(Edit2-〉Text) ;
if(CheckBox1-〉Checked)
NMEcho1-〉Port=StrToInt(Edit3-〉Text);
else
NMEcho1-〉Port=7;
//TCP/IP中Echo的默认端口号
NMEcho1-〉ReportLevel=Status_Basic;
NMEcho1-〉Connect(); //建立连接
RichEdit2-〉Clear ();
for(int i=0;i
//RichEdit1用于给远程主机发送信息
RichEdit2-〉Text=RichEdit2-〉Text +NMEcho1-〉Echo(RichEdit1-〉Lines-〉
Strings[i]);
NMEcho1-〉Disconnect ();
}
注意:在调用NMEcho控件的Connect()方法时,应该确保在接收数据之前连接已经建
立。
当调用Connect()方法后,如果用户输入的是域地址而不是IP地址,且域名服务器成
功地解析了这个域名,将触发控件的OnHostResoved事件,在此事件的处理中,我们将解析
成功的消息在状态栏中显示给用户。具体实现代码如下: void __fastcall
TForm1::NMEcho1HostResolved(TComponent Sender)
{
StatusBar1-〉Panels-〉Items[0]-〉Text="Host Resolved!";
}
如果用户输入的远程主机不正确,将触发控件的OnInvalidHost事件,在此事件的处理
中,弹出对话框要求用户重新输入远程主机的IP地址或域名地址,然后试图与服务器重建
连接。具体代码如下:
void __fastcall TForm1::NMEcho1InvalidHost(bool &&Handled)
{
AnsiString s;
if(InputQuery("Invailid host!","Specify a new host:",s))
{
NMEcho1-〉Host=s;
Handled=true;
}
}
建立连接后,将触发控件的OnConnect事件,在此事件的处理中,我们将连接成功的消
息在状态栏中显示给用户。具体实现代码如下:
void __fastcall TForm1::NMEcho1Connect(TObject Sender)
{
StatusBar1-〉Panels-〉Items[0]-〉Text="Echo has connected host!";
}
如果在调用Connect()方法后,在超时时间仍然没有与服务器连接,将触发控件的On
ConnectFailed事件,在此事件的处理中,我们将连接失败的消息显示给用户。具体实现代
码如下:
void __fastcall TForm1::NMEcho1ConnectionFailed(TObject Sender)
{
ShowMessage("Connection failed!");
}
除了NMEcho控件可以实现以上功能外,NetMaster的NMDayTime、NMTime这两个控件也
能实现。方法与NMEcho控件一样,区别是NMDayTime和NMTime这两个控件不用首先调用Conn
ect()方法,它们与服务器的连接是在使用DayTimeStr、TimeStr属性时自动进行的。
用C++Builder在WINNT下编制一个Service
---- Windows NT与Windows 9x有一个非常重要的区别,即Windows NT提供了很多功能强大
的Service(服务)。这些Service可以随着NT的启动而自启动,也可以让用户通过控制面板
启动,还可以被Win32应用程序起停。甚至在没有用户登录系统的情况下,这些Service也
能执行。许多FTP、WWW服务器和数据库就是以Service的形式存在于NT上,从而实现了无人
值守。就连最新版的“黑客”程序Back Orifice 2000也是以Service形式在NT上藏身的。
由于Service的编程较复杂,许多开发者想开发自己的Service但往往都望而却步。鉴于此
,下面我们就从头到尾来构造一个全新的Service,读者只要在程序中注明的地方加上自己
的代码,那么就可以轻松拥有一个自己的Service。在编写Service之前,先介绍一下几个
重要的函数:
---- 1. SC_HANDLE OpenSCManager( LPCTSTR lpMachineName, LPCTSTR
lpDatabaseName, DWORD dwDesiredAccess)
---- OpenSCManager 函数打开指定计算机上的service control manager database。其中
参数lpMachineName指定计算机名,若为空则指定为本机。LpDatabaseName为指定要打开的
service control manager database名, 默认为空。dwDesiredAccess指定操作的权限, 可
以为下面取值之一:
---- SC_MANAGER_ALL_ACCESS //所有权限
---- SC_MANAGER_CONNECT //允许连接到service control manager database
---- SC_MANAGER_CREATE_SERVICE //允许创建服务对象并把它加入database
---- SC_MANAGER_ENUMERATE_SERVICE //允许枚举database 中的Service
---- SC_MANAGER_LOCK //允许锁住database
---- SC_MANAGER_QUERY_LOCK_STATUS //允许查询database的封锁信息
---- 函数执行成功则返回一个指向service control manager database的句柄,失败则返
回NULL。注意:WINNT通过一个名为service control manager database的数据库来管理所
有的Service,因此对Service的任何操作都应打开此数据库。
---- 2. SC_HANDLE CreateService(SC_HANDLE hSCManager,
LPCTSTR lpServiceName,
LPCTSTR lpDisplayName,
DWORD dwDesiredAccess,
DWORD dwServiceType,
DWORD dwStartType,
DWORD dwErrorControl,
LPCTSTR lpBinaryPathName,
LPCTSTR lpLoadOrderGroup,
LPDWORD lpdwTagId,
LPCTSTR lpDependencies,
LPCTSTR lpServiceStartName,
LPCTSTR lpPassword)
---- CreatService函数产生一个新的SERVICE。其中参数hSCManager为指向service
control manager database 的句柄,由OpenSCManager返回。LpServiceName为SERVICE的
名字,lpDisplayName为Service显示用名,dwDesiredAccess是访问权限,本程序中用SERV
ICE_ALL_ACCESS。wServiceType,指明SERVICE类型,本程序中用SERVICE_WIN32_OWN_PROCE
SS| SERVICE_INTERACTIVE_PROCESS。dwStartType为Service启动方式,本程序采用自启动
,即dwStartType等于SERVICE_AUTO_START。 dwErrorControl说明当Service在启动中出错
时采取什么动作,本程序采用SERVICE_ERROR_IGNORE即忽约错误,读者可以改为其他的。L
pBinaryPathName指明Service本体程序的路径名。剩下的五个参数一般可设为NULL。如函
数调用成功则返回这个新Service的句柄,失败则返回NULL。与此函数对应的是DeleteServ
ice( hService),它删除指定的Service。
---- 3. SC_HANDLE OpenService(SC_HANDLE hSCManager,LPCTSTR lpServiceName,
DWORD dwDesiredAccess )
---- OpenService函数打开指定的Service。其中参数hSCManager为指向service
control manager database 的句柄,由OpenSCManager返回。LpServiceName为Service的
名字,dwDesiredAccess是访问权限,其可选值比较多,读者可以参看SDK Help. 函数调用
成功则返回打开的Service句柄,失败则返回NULL。
---- 4. BOOL StartService( SC_HANDLE hService, DWORD dwNumServiceArgs,LPCTSTR
*lpServiceArgVectors )
---- StartService函数启动指定的Service。其中参数hService 为指向Service的句柄,
由OpenService返回。dwNumServiceAr为启动服务所需的参数的个数。lpszServiceArgs 为
启 动 服务所需的参数。函数执行成功则返回True, 失败则返回False。
---- 5. BOOL ControlService(SC_HANDLE hService DWORD
dwControl,LPSERVICE_STATUS lpServiceStatus )
---- Service程序没有专门的停止函数,而是用ControlService函数来控制Service的暂停
、继续、停止等操作。参数dwControl指定发出的控制命令,可以为以下几个值:
SERVICE_CONTROL_STOP //停止Service
SERVICE_CONTROL_PAUSE //暂停Service
SERVICE_CONTROL_CONTINUE //继续Service
SERVICE_CONTROL_INTERROGATE //查询Service的状态
SERVICE_CONTROL_SHUTDOWN //让ControlService调用失效
---- 参数lpServiceStatus是一个指向SERVICE_STATUS的指针。SERVICE_STATUS是一个比
较重要的结构,它包含了Service的各种信息,如当前状态、可接受何种控制命令等等。
---- 6. BOOL QueryServiceStatus( SC_HANDLE hService,LPSERVICE_STATUS
lpServiceStatus )
---- QueryServiceStatus函数比较简单,它查询并返回当前Service的状态。
---- 编制一个Service一般需要两个程序,一个是Service本体,一个是用于对Service进
行控制的控制程序。通常Service本体是一个console程序,而控制程序则是一个普通的Win
32应用程序(当然,用户不用控制程序而通过控制面板也可对Service进行启、停,但不能
进行添加、删除操作。)
---- 首先,我们来编写Service本体。对于Service本体来说,它一般又由以下三部分组成
:main()、ServiceMain()、Handler(),下面是main()的源代码:(注:由于篇幅的
关系,大部分程序都没进行错误处理,读者可以自己添上)
int main(int argc, char **argv)
{
SERVICE_TABLE_ENTRY ste[2];
//一个Service进程可以有多个线程,这是每个
//线程的入口表
ste[0].lpServiceName="W.Z.SERVICE"; //线程名字
ste[0].lpServiceProc=ServiceMain;
//线程入口地址
ste[1].lpServiceName=NULL;
//最后一个必须为NULL
ste[1].lpServiceProc=NULL;
StartServiceCtrlDispatcher(ste);
return 0;
}
---- main()是Service的主线程。当servie control manager开始一个Service进程时,它
总是等待这个Service去调用StartServiceCtrlDispatcher()函数。main( )作为这个进
程的主线程应该在程序开始后尽快调用StartServiceCtrlDispatcher()。StartServiceC
trlDispatcher()在被调用后并不立即返回,它把本Service的主线程连接到service
control manager,从而让service control manager通过这个连接发送开始、停止等控制
命令给主线程。主线程在这时就扮演了一个命令的转发器的角色,它或者调用Handle( )去
处理停止、继续等控制要求,或者产生一个新线程去执行ServiceMain。StartServiceCtrl
Dispatcher()在整个Service结束时才返回。
---- ServiceMain()是Service真正的入口点,必须在main()中进行了正确的定义。Serv
iceMain( )的两个参数是由StartService()传递过来的。下面是ServiceMain()的源代码
:
void WINAPI ServiceMain(DWORD dwArgc,LPTSTR *lpszArgv)
{
ssh=RegisterServiceCtrlHandler
("W.Z.SERVICE",Handler);
ss.dwServiceType=SERVICE_WIN32_OWN
_PROCESS|SERVICE_INTERACTIVE_PROCESS;
ss.dwCurrentState=SERVICE_START_PENDING;
//如用户程序的代码比较多
(执行时间超过1秒),这儿要设成SERVICE_
START_PENDING,待用户程序完成后再设为SERVICE_RUNNING。
ss.dwControlsAccepted=SERVICE_ACCEPT_
STOP;//表明Service目前能接受的命令是停止命令。
ss.dwWin32ExitCode=NO_ERROR;
ss.dwCheckPoint=0;
ss.dwWaitHint=0;
SetServiceStatus(ssh, &ss);
//必须随时更新数据库中Service的状态。
Mycode(); //这儿可放入用户自己的代码
ss.dwServiceType=SERVICE_WIN32_OWN_
PROCESS|SERVICE_INTERACTIVE_PROCESS;
ss.dwCurrentState=SERVICE_RUNNING;
ss.dwControlsAccepted=SERVICE_ACCEPT_STOP;
ss.dwWin32ExitCode=NO_ERROR;
ss.dwCheckPoint=0;
ss.dwWaitHint=0;
SetServiceStatus(ssh,&ss);
Mycode();// 这儿也可放入用户自己的代码
}
在ServiceMain()中应该立即调用
RegisterServiceCtrlHandler()注册一个Handler
去处理控制程序或控制面板对Service的控制要求。
Handler()被转发器调用去处理要求,
下面是Handler()的源代码:
void WINAPI Handler(DWORD Opcode)
{
switch(Opcode)
{
case SERVICE_CONTROL_STOP: //停止Service
Mycode();//这儿可放入用户自己的相关代码
ss.dwWin32ExitCode = 0;
ss.dwCurrentState =SERVICE_STOPPED;
//把Service的当前状态置为STOP
ss.dwCheckPoint = 0;
ss.dwWaitHint = 0;
SetServiceStatus (ssh,&ss);
/必须随时更新数据库中Service的状态
break;
case SERVICE_CONTROL_INTERROGATE:
SetServiceStatus (ssh,&ss);
/必须随时更新数据库中Service的状态
break;
}
}
---- 好了,Service本体程序已基本完成,我们接着来看一下Service的控制程序:
---- 控制程序是一个标准的window程序,上面主要有四个按纽:Create Service、Delete
Service、start、stop,分别用来产生、删除、开始和停止Service。下面是它们的部分
源代码:
1. 产生Service
void __fastcall TForm1::CreateBtnClick
(TObject *Sender)
{
scm=OpenSCManager(NULL,NULL,
SC_MANAGER_CREATE_SERVICE);
if (scm!=NULL){
svc=CreateService(scm,
"W.Z.SERVICE","W.Z.SERVICE",//Service名字
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS
|SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START,
//以自动方式开始
SERVICE_ERROR_IGNORE,
"C:\\ntservice.exe", //Service本体程序路径,
必须与具体位置相符
NULL,NULL,NULL,NULL,NULL);
if (svc!=NULL)
CloseServiceHandle(svc);
CloseServiceHandle(scm);
}
}
2. 删除Service
void __fastcall TForm1::DeleteBtnClick
(TObject *Sender)
{
scm=OpenSCManager(NULL,NULL,
SC_MANAGER_CONNECT);
if (scm!=NULL){
svc=OpenService(scm,"W.Z.SERVICE",
SERVICE_ALL_ACCESS);
if (svc!=NULL){
QueryServiceStatus(svc,&ServiceStatus);
if (ServiceStatus.dwCurrentState==
SERVICE_RUNNING)//删除前,先停止此Service.
ControlService(svc,
SERVICE_CONTROL_STOP,&ServiceStatus);
DeleteService(svc);
CloseServiceHandle(svc);
//删除Service后,最好再调用CloseServiceHandle
}
//以便立即从数据库中移走此条目。
CloseServiceHandle(scm);
}
}
3. 开始Service
void __fastcall TForm1::StartBtnClick(TObject *Sender)
{
scm=OpenSCManager(NULL,NULL,SC_MANAGER_CONNECT);
if (scm!=NULL){
svc=OpenService(scm,"W.Z.SERVICE",SERVICE_START);
if (svc!=NULL){
StartService(svc,0,NULL);//开始Service
CloseServiceHandle(svc);
}
CloseServiceHandle(scm);
}
}
4.停止Service
void __fastcall TForm1::StopBtnClick
(TObject *Sender)
{
scm=OpenSCManager(NULL,NULL,
SC_MANAGER_ALL_ACCESS);
if (scm!=NULL){
svc=OpenService(scm,"W.Z.SERVICE",
SERVICE_STOP|SERVICE_QUERY_STATUS);
if (svc!=NULL){
QueryServiceStatus(svc,&ServiceStatus);
if (ServiceStatus.dwCurrentState==
SERVICE_RUNNING)
ControlService(svc,
SERVICE_CONTROL_STOP,&ServiceStatus);
CloseServiceHandle(svc);
}
CloseServiceHandle(scm);
}
}
如何在C++ BUILDER中自动关闭WINDOWS屏幕保护
---- 在实际编程应用中,当程序需要用较长的时间来处理某些计算时,这段时间有可能使
WINDOWS启动屏幕保护,这样程序的处理会相对变得更长。那么如何在运行程序时自动关闭
屏幕保护呢?
---- WINDOWS在启动屏幕保护前会向已激活的程序发送一个WM_SYSCOMMAND消息,并将该消
息的WPARAM参数设置为SC_SCREENSAVE。我们可利用C++ BUILDER中的TApplication类的OnM
essage事件来处理WINDOWS发来的这条消息,如果在接收到的消息后将handled参数设为tru
e,这个响应的消息值就可以阻止屏幕保护运行。
---- 在C++ BUILDER 4.0的过程如下:
---- 1、从主菜单中选择File | New APPlication 来创建一个新的空工程文件。然后在Fo
rn 上加上一个Label对象,设置其Caption为"此程序将关闭WINDOWS屏幕保护"。
---- 2、在程序头文件unit1.h中对成员函数ProcessMessage的声明加到TForm1的定义中。
class TForm1 : public TForm
{
__published: // IDE-managed Components
TLabel *Label1;
private: // User declarations
void __fastcall ProcessMessage
(TMsg &message,bool &handled);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
---- 3、在unit1.cpp中,在程序中增加ProcessMessage函数语句:
void __fastcall TForm1::ProcessMessage
(TMsg &message,bool &handled)
{
if(message.message==WM_SYSCOMMAND
&&message.wParam==SC_SCREENSAVE)
{
handled=true;
}
}
---- 4、在TForm1的构造函数增加以下代码:
__fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner)
{
Application->OnMessage=ProcessMessage;
}
显示/隐藏任务栏图标
---- 标准的Windows应用程序运行时一般都会在任务栏上显示任务图标,用户可直接用鼠
标点击任务栏图标进行任务切换,但有些应用程序不使用任务栏图标,如典型的Office工
具条,也有些程序可由用户定制显示方式显示或隐藏任务栏图标,如Winamp。我们的程序
中也可以做到,只要调用Windows API函数SetWindowLong即可,如下:
// 隐藏任务栏图标:
SetWindowLong(Application->Handle,
GWL_EXSTYLE, WS_EX_TOOLWINDOW);
// 显示任务栏图标:
SetWindowLong(Application->Handle,
GWL_EXSTYLE, WS_EX_APPWINDOW);
信箱监视程序
----本 文 将 向 大 家 介 绍 怎 样 编 写 自 己 的 信 箱 监 视 程 序, 程 序 将
直 接 调 用WinSock 函 数 来 进 行 网 络 通 信。 除 了 具 备WinSock 编 程 知 识
之 外, 还 必 须 了 解POP3 协 议。 下 面 是 对POP3 的 一 个 粗 略 的 介 绍, 读
者 可 以 参 看RFC 1225 更 为 详 细 地 了 解 该 协 议。
一、 关 于POP3 协 议
----POP3 服 务 器 程 序 通 常 在TCP 端 口110 提 供 服 务。 当 客 户 想 要 使 用
服 务 时, 它 便 与 服 务 器 建 立 一 个TCP 连 接。 一 旦 连 接 建 立,POP3 服
务 器 就 向 客 户 发 送 一 条 欢 迎 消 息。 然 后 客 户 开 始 给 服 务 器 发
送 命 令, 服 务 器 则 给 出 相 应 的 回 答。POP3 的 命 令 由 一 个 关 键 词 或
者 关 键 词 加 参 数 组 成。 每 个 命 令 以 回 车 换 行(0xD0xA) 作 为 结 束
标 志。 对 于 所 有 的 命 令,POP3 服 务 器 都 会 提 供 一 个 回 答。 服 务 器
的 回 答 由 一 个 状 态 标 志 加 一 些 附 加 信 息 组 成。 目 前 使 用 的 两 个
标 志 是“ +OK” 和“ -ERR”, 分 别 表 示 客 户 的 命 令 是 否 合 法。 所
有 的 回 答 也 是 以 回 车 换 行 结 束。
----与 本 文 讨 论 的 话 题 相 关 的 四 个POP3 命 令 是USER、PASS、LIST 和QUIT
。
USER 命 令
格 式USER name
----其 中name 是 用 户 在 该POP3 服 务 器 上 的 用 户 标 识。 客 户 应 该 在 接
到 服 务 器 的 欢 迎 消 息 后 或 者 在 上 一 个 USER 或 者PASS 失 败 之 后 可
以 发 送 此 命 令。
PASS 命 令
格 式PASS string
----其 中string 为 该 用 户 的 密 码。 客 户 在 发 送 了USER 命 令 并 且 收 到
了 +OK 的 回 答 之 后 方 可 发 送 此 命 令。 如 果 用 户 名 和 密 码 都 正 确
, 服 务 器 回 答 +OK, 否 则 -ERR。
LIST 命 令
格 式LIST
----如 果 该 用 户 有 邮 件, 则LIST 命 令 会 回 答 +OK, 并 列 出 所 有 邮 件
的 标 识 符 和 大 小( 每 个 邮 件 一 行), 最 后 一 个 仅 包 含 一 个 句 点
的 行(0xD0xA0x2E) 表 示 整 个 回 答 的 结 束。 如 果 该 用 户 没 有 邮 件,
有 些 服 务 器 会 返 回 -ERR, 有 些 在 可 能 返 回 一 个 +OK 和 一 个 仅 包
含 一 个 句 点 的 行。 当 然, 客 户 必 须 在PASS 命 令 通 过 之 后 客 户 程 序
才 能 给 服 务 器 发 送LIST 命 令。
QUIT 命 令
----从POP3 服 务 器 上 退 出 登 录。
二、 实 现 相 关 函 数
----接 下 来 我 们 按 照POP3 协 议 所 定 义 的 通 信 规 则 来 实 现 一 个 名 叫
POP3CheckMail 的 函 数, 只 要 调 用 此 函 数, 我 们 就 可 以 检 测 信 箱 了。
----下 面 的 代 码 是 用 与Delphi 4 兼 容 的Pascal 语 言 实 现 的, 我 们 必 须
包 含WinSock 单 元, 并 且 在 调 用 下 列 函 数 之 前 初 始 化 好WinSock 动 态
连 接 库。 初 始 化WinSock 动 态 连 接 库 的 代 码 如 下:
----if WSAStartup( $002, wsadata)<>0 then Halt;
----POP3CheckMail 的 原 型 如 下:
----function POP3CheckMail(Email,Password:String;var MailList:TStringList;var
ErrorMsg:String):Bool;
----参 数 说 明:
----Email 和Password 分 别 为 用 户 的email 信 箱 名 和 口 令。
----变 量 参 数MailList 用 于 返 回 邮 件 的 标 识 和 大 小,MailList.Count 表
示 邮 件 的 封 数。
----变 量 参 数ErrorMsg 返 回 出 错 消 息。
----以 下 是POP3CheckMail 及 其 它 所 用 到 的 函 数 的 实 现 代 码。
Connect_Server 函 数
----功 能: 与 指 定 的 主 机 建 立 一 个TCP 连 接, 返 回 一 个Socket 描 述 符
。 参 数host 指 定 主 机 的 名 字,Port 指 定 端 口 号。
function Connect_Server(host:string;Port:integer):integer;
var i:integer;
p:^LongInt;
phe:pHostEnt;
sin:sockaddr_in;
begin
sin.sin_family:=AF_INET;
sin.sin_port:=htons(Port);
//Get the IP for host, allowing for dotted decimal
phe:=gethostbyname(pchar(host));
if phe<>nil
then begin
p:=Pointer(phe^.h_addr_list^);
sin.sin_addr.s_addr:=p^;
end
else begin
i:=inet_addr(PChar(Host));
if i<> -1 then sin.sin_addr.S_addr:=i
end;
//create a socket
Result:=socket(PF_INET,SOCK_STREAM,0);
if (Result=INVALID_SOCKET) then Exit;
//connect to server
if Connect(Result,sin,sizeof(sin))=SOCKET_ERROR
then begin {Error handling} end;
end;
Write_Socket 函 数
----功 能: 向Socket 写 入 一 个 字 符 串。
function Write_Socket(sockfd:Integer; const s:string):Integer;
begin
result:=Winsock.Send(sockfd,pointer(s)^,Length(s),0)
end;
Socket_Readline 函 数
----功 能: 从Socket 上 读 取 一 行。
function Socket_Readline(sockfd:Integer):String;
//Read until #10
var S:String; buf:array[0..1]of Char;
n:Cardinal;
begin
buf[0]:= #0;buf[1]:= #0; S:=‘';
n:=recv(sockfd,Buf,1,0);
while n>0 do begin
buf[1]:= #0;
S:=S +buf;
if (buf[0]= #10) then Break;
n:=recv(sockfd, buf, 1, 0);
end;
Result:=Trim(S);
end;
Pop3Response 函 数
----功 能: 读 取POP3 服 务 器 的 一 行 返 回 信 息, 如 果 是“ +OK” 则 函
数 返 回TURE, 如 果 是“ -ERR” 则 返 回FALSE。
function Pop3Response(Sockfd:Integer):Bool;
var S: string;
begin
S:=socket_readline(sockfd);
if copy(s,1,3)=‘ +OK' then Result:=True
else {if copy(s,1,4)=‘ -ERR' then }Result:=False;
end;
POP3CheckMail 函 数
----功 能: 检 测 名 字 为email 的 信 箱, 如 果 有 新 邮 件, 则 通 过 变 量
参 数MailList 将 每 一 封 邮 件 的 大 小 返 回。
function POP3CheckMail
(Email,Password:String;var MailList:
TStringList;var ErrorMsg:String):Bool;
var sockfd,i:integer;
S, Host, User:String;
begin
Result:=False; ErrorMsg:=‘';
if MailList=nil then Exit;
S:=Trim(Email);
i:=Pos(‘@',Email);
User:=Trim(Copy(S,1,i -1));
Host:=Trim(Copy(S,i +1,Length(Email) -i));
MailList.Clear;
if (user=‘')or(host=‘') then begin
ErrorMsg:=‘Invalid email address.';exit; end;
if (Host[1]=‘[')and (Host[Length(host)]=‘]')
then begin Host[1]:=‘ ';Host[Length(host)]:= #0;end;
Host:=Trim(host);
sockfd:=Connect_Server(Host,110);
if not Pop3Response(sockfd)then begin ErrorMsg:=
‘Cannot connect to server';exit; end;
Write_Socket(sockfd,‘USER ' +User + #13 #10);
IF NOT POP3Response(sockfd) then begin ErrorMsg:=
‘USER failed'; Exit;end;
Write_Socket(sockfd,‘PASS ' +Password + #13 #10);
IF NOT POP3Response(sockfd) then begin ErrorMsg:=
‘PASS failed'; Exit;end;
Write_Socket(sockfd,‘LIST' #13 #10);
POP3Response(sockfd);
while true do begin
s:=Socket_readline(sockfd);
if s=‘.' then BREAK;
MailList.Add(S);
end;
Write_Socket(sockfd,‘QUIT' #13 #10);
Closesocket(sockfd);
Result:=True;
end;
三、 邮 件 的 检 测
----下 面 我 们 来 看 一 个 使 用POP3CheckMail 函 数 的 简 单 示 例。
var MailList:TstringList;
ErrorMsg:String;
...
MailList:=TstringList.Create;
POP3CheckMail(‘simon_liu@263.net',
‘mypassword', MailList, ErrorMsg);
If MailList.Count>0 then
MessageBox(0, Pchar(‘You have ' +IntToStr (MailList.Count) + ‘ new
messages!'),
‘New Message!', MB_ICONINFORMATION)
Else if ErrorMsg=‘' then MessageBox (0, ‘No message!', ‘',0)
Else MessageBox(0, Pchar(ErrorMsg), ‘Error', 0);
MailList.Free;
----如 果 你 仔 细 阅 读 了POP3CheckMail 函 数 的 实 现 代 码, 你 会 发 现 此
函 数 除 了 可 以 获 取 邮 件 的 封 数 之 外, 还 可 以 获 得 每 一 封 邮 件 的
大 小。 你 可 以 通 过POP3CheckMail 函 数 的 变 量 参 数MailList 的Strings 数
组 来 获 取 邮 件 的 大 小。
----实 现 了POP3CheckMail 函 数, 再 在 此 基 础 上 编 写 一 个POP3 信 箱 的 监
视 程 序 就 变 得 很 简 单 了。 你 可 以 通 过 一 个 定 时 器 来 定 期 地 调
用POP3CheckMail 函 数, 这 样 你 就 可 以 监 视 某 个email 信 箱 了。 假 若 你
想 要 同 时 监 视 多 个email 信 箱, 只 要 为 每 一 个 信 箱 创 建 一 个 线 程
并 且 在 线 程 中 定 期 调 用POP3CheckMail 函 数 即 可。 你 的 程 序 中 如 果
没 有 使 用Delphi 的 控 件, 那 么 一 个 完 整 的 信 箱 监 视 程 序 可 能 只 有
60K 左 右。
C++Building制作闹钟
---- 大凡热恋中的网虫都曾经陷入下列的困境:约好女/男朋友晚七点半在老地方等,却
在计算机面前一直爬行到深夜,等反映过来,朋友早已拂尘而去,又得几天的功夫去陪礼
道歉。朋友何不按以下步骤做一简单的闹钟,让你安安心心上网,大大方方约会。你只要
在上网的时候打开此应用程序,设置好约会时间(当然也可以是默认好的)即可。时间一
到,音乐响起,快去约会吧。
---- 本闹钟程序有以下组件组成:
序号 组件类型 组件名称 功能
1 Tlabel l_Clock_1 显示“输入日期”
2 TdateTimePicker dtp_Clock_1 选择日期
3 Tlabel l_Clock_2 显示“输入时间”
4 TdateTimePacker tdp_Clock_2 选择时间
5 TmediaPlayer mp_Clock 演奏音乐
6 Tbutton b_Clock_Open 重新打开
7 Ttimer t_Clock 定时检测
8 Tbutton b_Clock_Close 关闭应用程序
---- 屏幕组件一览表
---- 屏幕组件一览图
---- 说明:dtp_Clock_1 的Kind属性设置为dtkDate , dtp_Clock_2 的Kind属性设置为d
tkTime,mp_Clock 的FileName属性设置为你主机上存在的任何mid、wav、avi文件。t_Clo
ck 的Interval属性设置为10。
---- 事件说明如下:
①、 t_Clock的OnTimer :
{
//按时触发演示程序
struct date d;
struct time t;
AnsiString thour,tmin,tsec;
int dyear;
int dintyear;
int dmon,dday;
AnsiString tinthour,tintmin,tintsec;
AnsiString dintmon,dintday;
//取当天日期
getdate(&d);
dyear=d.da_year;
dday=d.da_day;
dmon=d.da_mon;
dintyear=StrToInt(dint.SubString(1,2));
dintmon=dint.SubString(4,2);
dintday=dint.SubString(7,2);
//取当时时间
gettime(&t);
thour=AnsiString(t.ti_hour);
tmin=AnsiString(t.ti_min);
//tsec=AnsiString(t.ti_sec);
//tint=AnsiString(DateTimePicker1- >Time);
tinthour=tint.SubString(10,2);
tintmin=tint.SubString(13,2);
//tintsec=tint.SubString(16,2);
//闹钟服务功能
if ((StrToInt(thour)==StrToInt(tinthour))&&
(StrToInt(tmin)==StrToInt(tintmin))
&&(StrToInt(AnsiString(dyear).SubString(3,2))
==dintyear)&&(StrToInt(dmon)==StrToInt(dintmon))
&&(StrToInt(dday)==StrToInt(dintday)))
{
dTimer- >Enabled=false;
MediaPlayer1- >Open();
MediaPlayer1- >Play();
}
}
②、 b_Clock_Open 的OnClick:
{
t_Clock- >Enabled=true;
}
③、 b_Clock_Close的OnClick
{
Application- >Terminate();
}
---- 当然此程序还可以拓展、细化,如我仅将触发条件检测到分,当然它完全可以检测到
秒,也可以仅检测到时。
拨号上网IP地址的检知
随着INTERNET在世界范围内的迅速普及,上网的人数也越来越多。其中,绝大多数人是通
过普通电话线拨号上网的。我们知道,每一台上网的计算机,不论是用何种方式上网,都
被分配了一个或多个独立无二的IP地址。对于拨号上网的用户,一般是由其ISP在其每次拨
号上网时动态分配一个IP地址,这个地址可能每次都不相同(其原因主要是为了充分利用
有限资源)。那么,我们能否通过某种方法随时方便地检知自己上网时的IP地址呢?答案
是肯定的。下面我们就用C++BUILDER编制一个小巧的程序来实现这种功能。(注:本程序
在局域网中也同样能运行)
---- 首先用BCB的FILE菜单下的New Application创建一个新项目,取名为Ipcheck.bpr。
---- 然后,在窗体FORM1上添加五个标签(LABEL)和两个按钮(BUTTON),如图所示。
---- 接下来,双击窗体的OnCreate事件,在其中加上以下程序:
void __fastcall TForm1::FormCreate(Tobject *Sender)
{
WSAData wsaData;
if (WSAStartup(MAKEWORD(1,1),&wsaData)!=0)
{ //初始化WINSOCK调用
MessageBox(NULL,"Wrong WinSock
Version","Error",MB_OK);
return ;
}
Refresh1Click(Sender); //程序一开始,就调检知IP地址
}
再双击Refresh按钮,在其中加上以下程序
void __fastcall TForm1::Refresh1Click(Tobject *Sender)
//刷新IP地址
{
char HostName[80];
LPHOSTENT lpHostEnt;
struct in_addr addr[2];
//本程序假设主机不是多宿主机,即最多只有
// 一块网卡和一个动态IP
for (int I=0; I< 2; I++){
memset(&addr[I],0,sizeof(in_addr));
//对in_addr结构清0,以利后面填写
}
if (gethostname(HostName,sizeof(HostName))==SOCKET_ERROR)
{ // 得到本主机名
MessageBox(NULL,"Can't getting local host name.","Error",MB_OK);
return ;
}
Label3- >Caption=HostName;
lpHostEnt=gethostbyname(HostName);//利用得到的主机名去获得主机结构
if (!lpHostEnt){
MessageBox(NULL,"Yow! Bad host lookup.","Error",MB_OK);
return ;
}
for (int I=0; lpHostEnt- >h_addr_list[I]!=0; I++)
//从主机地址表中得到IP地址
{
memcpy(&addr[I],lpHostEnt- >h_addr_list[I],sizeof(in_addr));
}
Label4- >Caption=inet_ntoa(addr[0]);
Label5- >Caption=inet_ntoa(addr[1]);
}
再双击Refresh按钮,在其中加上以下程序
void __fastcall TForm1::Button2Click(Tobject *Sender)
{
WSACleanup(); //释放WINSOCK调用
Close();
}
---- 最后,不要忘了在程序头部加上#include<winsock.h>.....哦。
---- 好了,程序完成了,编译后就可运行了。本程序在中文WIN95/NT4.0下编译通过。
用C++ Builder编写Tray程序
Tray(托盘)是Windows9x任务条上的一个特殊区域,它的技术名称为“任务栏布告区”,一
些软件(如金山词霸Ⅲ)运行时会在托盘上放置一个图标,使用户一眼就能知道这个程序正
在后台运行,要想激活它也很容易,通常只需单击一下这个图标即可,非常方便。
Tray的编程比较特殊,但并不难,主要包括图标、工具提示和消息等三个方面,它是S
hell编程的一部分。ShellAPI提供了Shell—NotifyIcon函数,用它可以增加、删除或者修
改托盘中的图标,在托盘上放置图标后,Windows Shell会负责把发生在图标上的鼠标事件
通知应用程序。Shell—NotifyIcon函数定义如下:
WINSHELLAPI BOOL WINAPI Shell—NotifyIcon(DWORD dwMessage,PNOTIFYICONDATA
pnid);
dwMessage表示要完成的操作:NIM—ADD(增加图标)、NIM—DELETE(删除图标)、NIM—
MODIFY(修改图标或提示文本),pnid是一个指向NOTIFYICONDATA结构的指针,结构的定义
如下:
typedef struct —NOTIFYICONDATA{
DWORD cbSize;//结构所占的字节数,必须用结构的大小来初始化。
HWND hWnd;//接受Tray图标消息的窗口句柄
UINT uID;//由应用程序定义的图标ID
UINT uFlags;//用来鉴别那些需要改变其值的域,NIF_ICON表示hIcon有效,可用来修
改图标,NIF_MESSAGE表示uCallbackMessage有效,用来定义消息,NIF—TIP表示szTip参
数有效,可修改工具提示。
UINT uCallbackMessage;//应用程序定义的消息
HICON hIcon;//Tray图标的句柄
char szTip[64];//工具提示的文本
}NOTIFYICONDATA;
下面我们就通过一个具体例子来说明实现方法,程序运行时不会显示主窗体,只在托
盘上增加一个图标,双击图标可关闭程序。
程序运行时托盘区显示如下:
新建一个工程,放置一个Timer控件到窗体上。打开unit1.h文件,增加头文件说明#i
nclude <shellapi.h>,在TForm1定义的private段增加一些数据成员和方法的声明:
unsigned int iconmessage;//定义的消息
void AddTrayIcon();//在托盘上增加图标
void RemoveTrayIcon();//从托盘中删除图标
由于要增加对自定义消息的处理,所以必须重载窗口过程函数WndProc,在TForm1的定
义中增加protected段:virtual void ——fastcall WndProc(Messages::Tmessage&
Message);
在unit1.cpp中定义相应的成员函数:
void TForm1::AddTrayIcon()
{
NOTIFYICONDATA icondata;
memset(&icondata,0,sizeof(icondata));
//将结构icondata的各域初始化为0
icondata.cbSize=sizeof(icondata);
icondata.hWnd=Handle;
strncpy(icondata.szTip,″未知状态″,sizeof(icondata.szTip));
icondata.hIcon=Application->Icon->Handle;
icondata.uCallbackMessage=iconmessage;
icondata.uFlags=NIF—MESSAGE|NIF—ICON|NIF—TIP;
Shell—NotifyIcon(NIM—ADD,&icondata);
}
void TForm1::RemoveTrayIcon()
{
NOTIFYICONDATA icondata;
memset(&icondata,0,sizeof(icondata));
icondata.cbSize=sizeof(icondata);
icondata.hWnd=Handle;
Shell—NotifyIcon(NIM—DELETE,&icondata);
}
重载TForm1的WndProc函数,加入对自定义消息的处理代码,这其实相当于创建了TFor
m类的子类。
void __fastcall TForm1::WndProc(Messages::TMessage& Message)
{
if(Message.Msg==iconmessage)
{
if(Message.LParam==WM—LBUTTONDBLCLK)
{
Application->Terminate();
//如果双击图标,则关闭应用程序
}
return;
}
TForm::WndProc(Message);//对于其他的消息,调用基础类的WndProc函数让Windows
进行缺省处理。
}
创建窗体的OnCreate事件句柄:
void ——fastcall TForm1::FormCreate(TObject *Sender)
{
iconmessage=RegisterWindowMessage(″IconNotify″);
AddTrayIcon();
}
这里通过调用RegisterWindowMessage函数来定义一个用户消息,也可以通过WM_USER
+n来获得一个系统没有使用的消息编号。
void ——fastcall TForm1::FormDestroy(TObject *Sender)
{
RemoveTrayIcon();
//窗体在关闭时删除托盘中的图标
}
编写Timer1的Timer事件代码,当用户将鼠标停留在图标上时,显示提示文本:
void ——fastcall TForm1::Timer1Timer(TObject *Sender)
{
NOTIFYICONDATA icondata;
memset (&icondata, 0, sizeof (icondata));
icondata.cbSize = sizeof (icondata);
icondata.hWnd = Handle;
String s=″我的图标!″;//定义提示文本
strncpy (icondata.szTip, s.c_str(), sizeof (icondata.szTip));
icondata.uFlags = NIF—TIP ;
Shell—NotifyIcon (NIM—MODIFY,&icondata);
}
程序运行时不显示主窗体,只在托盘上放置相应的程序图标,从C++ Builder主选单
中选择View|Project Source,在WinMain函数的Application→Initialize()语句后增加代
码:
ShowWindow(Application→Handle,SW—HIDE);
Application→ShowMainForm=false;
按F9编译并运行程序,托盘上就会出现相应的图标。以上代码在C++ Builder3、Pwi
n98环境下编译、运行通过。
怎样用代码来最小化或恢复程序
你能够用下面三种方法之一来实现它。
方法一:发送一条Windows消息到主窗口的Handle属性或 Application->Handle。这条消息
就是 WM_SYSCOMMAND,将 wParam 设为 SC_MINIMIZE 或 SC_RESTORE。你可以调用SendMes
sage API函数来发送消息。 // 设置WPARAM为SC_MINIMIZE来最小化窗口
SendMessage(Application->Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
// 设置WPARAM为SC_RESTROE来恢复窗口
SendMessage(Application->Handle, WM_SYSCOMMAND, SC_RESTORE, 0);
方法二:调用 ShowWindow API 函数。
你必须传送Application对象句柄到ShowWindow函数。如果你传送给ShowWindow函数的句柄
是主窗口,那么主窗口将最小化到桌面(desktop)而不是任务条(taskbar)。 // 最小化:
传送 SW_MINIMIZE 到 ShowWindow
ShowWindow(Application->Handle, SW_MINIMIZE);
// 恢复:传送SW_RESTORE 到 ShowWindow
ShowWindow(Application->Handle, SW_RESTORE);
方法三:调用Application对象的Minimize或Restore函数。 // 调用Minimize最小化应用
程序
Application->Minimize();
// 调用Restore恢复应用程序
Application->Restore();
调用Application的方法较易用,但发送WM_SYSCOMMAND消息功能更强。
另外,WM_SYSCOMMAND消息允许你最大化程序,改变光标为帮助光标,滚动程序,移动一个
窗口,改变窗口大小,甚至模拟Alt-TAB切换到另一窗口。紧记,实现这些功能用API函数
更好。
尽管调用ShowWindow也能工作,你大概也不想用它来最小化或恢复程序。当隐藏的窗口被
最小化时ShowWindow会引起最小化动画出现。这看上去稍微有点傻,因为动画是从程序主
窗口的位置远离中心。
制作主窗口显示前的版权窗口
在工程文件中选File->New Form新建一个窗口,设计好窗口的外观。
给窗口起名为AboutBox,源文件命名为AboutBox.Cpp
选Project->Options,将新建的窗口从自动建立中去掉。
选View->Project Source,打开工程文件的源文件,加入句子。
#include "AboutBox.h"
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
DWORD lTime;
try
{
Application->Initialize();
AboutBox=new TAboutBox(AboutBox);
AboutBox->Show();
AboutBox->Update();
lTime=GetTickCount();
Application->CreateForm(__classid(TMainForm), &MainForm);
while((GetTickCount()-lTime) / 1000 <3);
AboutBox->Hide();
AboutBox->Free();
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
判断是否已经联到 internet
使用 NetMasters Powersock 控件读取本地IP 地址,如果是"0.0.0.0" 说明没有连接。
例子:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
if (Powersock1->LocalIP == "0.0.0.0")
ShowMessage("not connected");
else
ShowMessage(Powersock1->LocalIP);
}
获取登陆用户名
void __fastcall TForm1::Button2Click(TObject *Sender)
{
DWORD dwSize = 0;
// 确定字节所需内存
GetUserName(NULL, &dwSize);
// 定义缓冲区
char *szBuf = new char[dwSize];
szBuf[0] = '\0';
// 读用户名
GetUserName(szBuf, &dwSize);
Label2->Caption = szBuf;
delete [] szBuf;
}
隐藏桌面图标
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HWND hDesktop;
// 获取桌面句柄
hDesktop = FindWindow("ProgMan", NULL);
// 隐藏桌面上的图标
ShowWindow(hDesktop, SW_HIDE);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
HWND hDesktop;
hDesktop = FindWindow("ProgMan", NULL);
// 显示桌面上的图标
ShowWindow(hDesktop, SW_SHOW);
}
程序启动时运行
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TRegistry* Reg;
char AppFileName[256];
if( Edit1->Text=="" ) // 判断文件名是否为空
{
MessageBox(Handle,"应用程序名称不可以为空。","错误",MB_OK+MB_ICONERROR);
exit(0);
}
Edit1->GetTextBuf(AppFileName, 256);
Reg = new TRegistry();
try
{
Reg->RootKey = HKEY_LOCAL_MACHINE;
if( Reg->OpenKey("Software\\Microsoft\\Windows\\CurrentVersion\\Run", FALSE) )
{
// 在注册表中添加数值
Reg->WriteString("StartUp1",AppFileName);
}
else
MessageBox(Handle,"打开注册表失败。","错误",MB_OK|MB_ICONERROR);
}
catch(...)
{
Reg->CloseKey();
Reg->Free();
}
}
控制面板的调用
void __fastcall TForm1::Button1Click(TObject *Sender)
{
UINT Res; // WinExe的返回结果
// 显示控制面板
Res = WinExec("rundll32.exe shell32.dll,Control_RunDLL",9);
if( Res==0 )
MessageBox(Handle, "程序超出内存。", "错误", MB_OK|MB_ICONERROR);
else if( Res==ERROR_BAD_FORMAT )
MessageBox(Handle,"命令错误。", "错误", MB_OK|MB_ICONERROR);
else if( Res==ERROR_FILE_NOT_FOUND )
MessageBox(Handle,"指定文件没找到。", "错误", MB_OK|MB_ICONERROR);
else if( Res==ERROR_PATH_NOT_FOUND )
MessageBox(Handle,"指定路径没找到。", "错误", MB_OK|MB_ICONERROR);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
// 辅助选项 | 键盘
WinExec("rundll32.exe shell32.dll,Control_RunDLL access.cpl,,1", 9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
// 添加/删除程序 属性 | 安装/卸载
WinExec("rundll32.exe shell32.dll,Control_RunDLL Appwiz.cpl,,1", 9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button4Click(TObject *Sender)
{
// 显示 属性 | 背景
WinExec("rundll32.exe shell32.dll,Control_RunDLL desk.cpl,,0",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button5Click(TObject *Sender)
{
// Internet 属性 | 常规
WinExec("rundll32.exe shell32.dll,Control_RunDLL Inetcpl.cpl,,0",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button6Click(TObject *Sender)
{
// 区域设置 属性 | 区域设置
WinExec("rundll32.exe shell32.dll,Control_RunDLL Intl.cpl,,0",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button7Click(TObject *Sender)
{
// 游戏控制器 | 一般
WinExec("rundll32.exe shell32.dll,Control_RunDLL Joy.cpl,,0",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button8Click(TObject *Sender)
{
// 鼠标 属性 | 按钮
WinExec("rundll32.exe shell32.dll,Control_RunDLL Main.cpl",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button9Click(TObject *Sender)
{
// 多媒体 属性 | 音频
WinExec("rundll32.exe shell32.dll,Control_RunDLL Mmsys.cpl,,0",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button10Click(TObject *Sender)
{
// 调制解调器 属性
WinExec("rundll32.exe shell32.dll,Control_RunDLL Modem.cpl",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button11Click(TObject *Sender)
{
// 网络 | 配置
WinExec("rundll32.exe shell32.dll,Control_RunDLL Netcpl.cpl",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button12Click(TObject *Sender)
{
// 密码 属性 | 更改密码
WinExec("rundll32.exe shell32.dll,Control_RunDLL Password.cpl",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button13Click(TObject *Sender)
{
// 扫描仪与数字相机 属性 | 设备
WinExec("rundll32.exe shell32.dll,Control_RunDLL Sticpl.cpl",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button14Click(TObject *Sender)
{
// 系统 属性 | 常规
WinExec("rundll32.exe shell32.dll,Control_RunDLL Sysdm.cpl,,0",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button15Click(TObject *Sender)
{
// 日期/时间 属性 | 日期和时间
WinExec("rundll32.exe shell32.dll,Control_RunDLL timedate.cpl",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button16Click(TObject *Sender)
{
// 电源管理 属性 | 电源方案
WinExec("rundll32.exe shell32.dll,Control_RunDLL Powercfg.cpl",9);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button17Click(TObject *Sender)
{
// 拨号 属性 | 我的位置
WinExec("rundll32.exe shell32.dll,Control_RunDLL Telephon.cpl",9);
}
模拟键盘按键
void __fastcall TForm1::Button1Click(TObject *Sender)
{
// 模拟在Edit1组件中按下了字母a键
PostMessage(Edit1->Handle, WM_KEYDOWN, 65, 0);
}
//---------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
// 模拟在窗体Form1中按下了Tab键
PostMessage(Form1->Handle, WM_KEYDOWN, VK_TAB, 0);
}
//------------------------------------------
让标题栏闪烁
void __fastcall TForm1::Button1Click(TObject *Sender)
{
// 激活定时器
Timer1->Enabled = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
// 禁止定时器
Timer1->Enabled = false;
// 使窗体恢复原状
FlashWindow(Handle, false);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
// 使窗体标题栏闪烁
FlashWindow(Handle,True);
Beep(); // 声音提示
}
启动屏幕保护
SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_SCREENSAVE, 0);
年月日星期的取法
AnsiString iYear,iMonth,iDay,iHour,iMinute,iSecond;
iYear=Now().FormatString("yyyy");//取年
iMonth=Now().FormatString("mm");//取月
iDay=Now().FormatString("dd");//取日
iHour=Now().FormatString("hh");//取小时
iMinute=Now().FormatString("nn");//取分钟
iSecond=Now().FormatString("ss");//取秒
DayOfWeek(Now().CurrentDate())
键盘事件
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
TShiftState Shift)
{
if( Shift.Contains(ssShift) ) // 如果按下了Shift键则在第一个面板上显示Shift
StatusBar1->Panels->Items[0]->Text = "Shift";
if( Shift.Contains(ssAlt)) // 如果按下了Alt键则在第二个面板上显示Alt
StatusBar1->Panels->Items[1]->Text = "Alt";
if( Shift.Contains(ssCtrl) ) // 如果按下了Ctrl键则在第三个面板上显示Ctrl
StatusBar1->Panels->Items[2]->Text = "Ctrl";
if( Shift.Contains(ssAlt)&&(Shift.Contains(ssCtrl))) // 如果同时按下了Alt+Ctrl
键则在第二个面板上显示Alt+Ctrl
StatusBar1->Panels->Items[5]->Text = "Alt+Ctrl";
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key,
TShiftState Shift)
{
// 在Shift、Alt和Ctrl键弹起时清除状态栏中相应面板上的内容
if( !(Shift.Contains(ssShift)) )
StatusBar1->Panels->Items[0]->Text = "";
if( !(Shift.Contains(ssAlt)) )
StatusBar1->Panels->Items[1]->Text = "";
if( !(Shift.Contains(ssCtrl)) )
StatusBar1->Panels->Items[2]->Text = "";
if( !Shift.Contains(ssAlt)&&(!Shift.Contains(ssCtrl)))
StatusBar1->Panels->Items[5]->Text = "";
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
if( Shift.Contains(ssLeft) ) // 如果按下了左键则在第四个面板上显示left
StatusBar1->Panels->Items[3]->Text = "Left";
if( Shift.Contains(ssMiddle) ) // 如果按下了中键则在第五个面板上显示Middle
StatusBar1->Panels->Items[4]->Text = "Middle";
if( Shift.Contains(ssDouble) ) // 如果是双击则在第六个面板上显示Double
StatusBar1->Panels->Items[5]->Text = "Double";
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
// 在鼠标按键弹起时清除状态栏中相应面板上的内容
if( !(Shift.Contains(ssLeft)) )
StatusBar1->Panels->Items[3]->Text = "";
if( !(Shift.Contains(ssMiddle)) )
StatusBar1->Panels->Items[4]->Text = "";
if( !(Shift.Contains(ssDouble)) )
StatusBar1->Panels->Items[5]->Text = "";
}
隐藏任务栏
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HWND WndHandle;
// 获取任务栏的窗口句柄
WndHandle = FindWindow("Shell_TrayWnd", NULL);
ShowWindow(WndHandle, SW_SHOW); // 显示任务栏
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
HWND WndHandle;
WndHandle = FindWindow("Shell_TrayWnd", NULL);
ShowWindow(WndHandle, SW_HIDE); // 隐藏任务栏
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
SetWindowLong(Application->Handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW);
}
禁止关机
1.先打开头文件作如下修改:
class TForm1 : public TForm
{
__published: // IDE-managed Components
private: // User declarations
void __fastcall WMQueryEndSession(TWMQueryEndSession &msg);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_QUERYENDSESSION,TWMQueryEndSession,WMQueryEndSession)
END_MESSAGE_MAP(TForm)
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
2.然后对unit.cpp文件添加如下代码:
void __fastcall TForm1::WMQueryEndSession(TWMQueryEndSession &msg)
{
msg.Result = 0;
ShowMessage("你不能关闭系统");
}
怎样以最小化方式启动程序
可以调用Application->Minimize函数来最小化应用程序到任务条。如何你希望你的程序启
动时就最小化,只需在调用Run方法前调用Application->Minimize就行了。
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Minimize(); //以最小化方式启动
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
在Memo中增加一行后,如何使最后一行总能显示
SendMessage(Memo1->Handle,EM_SCROLL,SB_LINEDOWN,0 )
设置壁纸方法
通过IActiveDesktop接口来实现
比如设置壁纸,就可以这样
IActiveDesktop *a;
CoInitialize(NULL);
if(SUCCEEDED(CoCreateInstance(
Shlobj::CLSID_ActiveDesktop,NULL,CLSCTX_INPROC_SERVER,
IID_IActiveDesktop,(void **)&a)))
{
WideString c = "C:\\My Documents\\yw2.jpg";
OleCheck(a->SetWallpaper(c.c_bstr(),0));
OleCheck(a->ApplyChanges(AD_APPLY_ALL));
a->Release();
}
CoUninitialize();
其它接口可查看msdn
注意在cpp的第一行加入#define NO_WIN32_LEAN_AND_MEAN
并要#include <shlobj.h>
--
剑胆琴心,以观沧海
是非成败,付诸一笑
※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 220.113.18.141]
※ 修改:·wildwolf 於 09月21日20:43:02 修改本文·[FROM: 220.113.18.141]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:1,046.581毫秒