[code:1:d5dc046d55]
1.怎样在C++Builder中创建使用DLL
2.用C++Bulider在WIN.INI中保存信息
3.如何在C++Builder中检测硬件
4.C++Builder如何响应消息及自定义消息
5.利用C++ Builder开发动画DLL
6.用C++ Builder 3制作屏幕保护程序
7.TCP/IP头格式
8.UDP
9.判断windows的Desktop及其它目录
10用C++Builder创建数字签名
11用Enter 键控制焦点切换的方法
12.拦 截 Windows 消 息
13.使用CommaText
14.程序开始时先显示信息框
15.怎样获取程序的命令行参数?
16.如何监视剪贴板
17.如何使用OnIdle事件
18.用C++Builder编写串行异步通信程序
19.C++BUILDER非可视组件的消息处理技巧
20.用C++Builder 建立数据库VCL使用经验
21.用C++ Builder创建基于Internet的点对点Chat
22.用C++Builder获取应用程序图标
23.BIG5到GB的转换技术
24.C++BUILDER让你的任务栏图标动起来
25.TFORM
26.用BCB在windows桌面创建快捷方式
27.读磁片磁区
28.I/O 端口读写的实现
29.检测鼠标位置
30.令Win32 应用程序跳入系统零层
31.如何取得Memo的行和列
32.使用Sockets
33.Windows95/98下怎样隐藏应用程序不让它出现在CTRL-ALT-DEL对话框中?
34.怎样隐藏应用程序的任务条图标
35.编写自己的Ping.exe程序
36.用C++Builder在WINNT下编制一个Service
37.如何在C++ BUILDER中自动关闭WINDOWS屏保
38.显示/隐藏任务栏图标
39.信箱监视程序
40.C++Building制作闹钟
41.拨号上网IP地址的检知
42.用C++ Builder编写Tray程序
43.怎样用代码来最小化或恢复程序
44.制作主窗口显示前的版权窗口
45.判断是否已经联到 internet
46.获取登陆用户名
47.隐藏桌面图标
48.程序启动时运行
49.控制面板的调用
50.模拟键盘按键
51.让标题栏闪烁
52.启动屏幕保护
53.年月日星期的取法
54.键盘事件
55.隐藏任务栏
56.禁止关机
57.怎样以最小化方式启动程序
58.在Memo中增加一行后,如何使最后一行能显示
59.设置壁纸方法
怎样在C++Builder中创建使用DLL
自从C++Builder从去年浪漫情人节上市以来,吸引了大量的Delphi、VC、Vb的程序员到它的怀抱,大量的C、C++程序员感叹道:总算有了C的可视化开发工具,对我也是一样,从BC、Delphi到C++Builder。
动态链接库(DLL)是Windows编程常遇到的编程方法,下面我就介绍一下在BCB (C++Builder下简称BCB) 中如何创建使用DLL和一些技巧。
一、创建:
使用BCB File|NEW建立一个新的DLL工程,并保存好文件BCB,生成一个DLL的程序框架。
1.DllEntryPoint函数为一个入口方法,如果使用者在DLL被系统初始化或者注销时被调用,用来写入对DLL的初始化程序和卸载程序;参数:hinst用来指示DLL的基地址;reason用来指示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为当GetProfileInt没有找到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:GlobalMemoryStatus。
其中,成员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)));
怎么样,看出点门道了么?两段程序的格式几乎一模一样,其实,GetSystemInfoGlobalMemoryStatus还可以获得许多其他有关CPU和内存的信息,就按照上面的格式去套就行了,更详细的资料可以去看C++ Builder4的Help。
3. 检测可用硬盘空间
好了,经过前面两个简单问题的热身,我们来处理一个稍微复杂的问题:我们知道安装程序大都有一个检测硬盘空间的过程,那么这是怎么实现的呢?他用到的是API函数GetDiskFreeSpace,这个函数输入一个参数:目标盘的路径;返回四个参数,依次是每簇的扇区数、每扇区的字节数、空闲的簇数、总簇数。假如我们需要检测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环境下执行拷贝文件、查找文件或计算机等耗时比较长的操作时,Windows会显示一个小小的动画,指示正在进行的操作,与死板的静止图像相比增色不少。那么我们自己开发软件时,能否也显示一个这样的动画提示呢?我在开发一个外贸应用软件系统时,遇到的数据量很大,当通过复合条件查找时,因为不是数据库表的每个项目都有索引,所以很费时,系统也会表现出长时间停顿,用户感觉极为不爽。我经过一段时间的探索,开发了一个能够在采用的开发环境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下的Timer控件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_ACTIVATE消息的处理形式如下:
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 键 将 焦 点 移 至 该 控 件 上。 这 种 控 制 焦 点 切 换 的 方 法 有 时 不 符 合 用 户 的 习 惯。 就 图 一 而 言, 用 户 就 希 望 用Enter 键, 控 制 焦 点 由Edit1 切 换 到 Edit2。 要 实 现 这 样 的 功 能 需 借 助WinAPI 函 数 SendMessage 来 完 成。 方 法 是: 先 设Form1 的KeyPreview 属 性 为true, 然 后 在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_SYSCOMMAND和WM_HSCROLL(或WM_VSCROLL)消息,就能实现上述功能。Windows API的缺点是编程十分麻烦,太多的时间要耗在细节上面,但它的功能却是最强大的。C++ Builder的VCL在功能上只是它的一个子集,因为VCL是在API的基础上封装的,封装时舍弃了一些不常用到的功能。但是程序员的想象力没有被封装,他们总怀着更大的热情去实现别出心裁的想法,修改系统菜单和给跟踪栏增加StartTrack和ndTrack事件只是其中的小把戏而已。可是VCL并没有这些功能,怎么办?
---- 幸好,Borland公司没有把路堵死,而是留了个后门--允许程序员自己拦截并处理Windows消息,就象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消息是一项高级编程技术,能让你尽量挖掘Windows的潜力,尤其让曾用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=fsStayOnTop, 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文件拷贝到Program 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|GENERIC-WRITE;参数dwShareMode定义资源共享方式,此处必须设为0,为独占方式;lpSecurityAttributes定义安全属性,Win 95下为NULL;dwCreationDistribution定义文件创建方式;dwFlagsAndAttributes定义文件属性和标记,应设为FILE-FLAG-OVERLAPPED,表示异步通信方式;hTemplateFile 指向一个模板文件的句柄,在 Windows 95下为NULL。
然后用BuildCommDCB( )和SetCommState( )函数通过通信设备控制块DCB(Device Control Block)设置串口通信参数(如波特率、停止位、数据位、校验位等),其中BuildCommDCB( )中的字符串参数lpDef 定义同DOS命令中MODE的参数格式,关于DCB更具体的设置需要根据用户对数据流定义、握手信号及通信控制要求具体定义,参见有关Windows技术资料。用GetCommState()可以得到当前的DCB参数值。如果需要还可通过SetCommTimeouts()和GetCommTomeouts()重新设置读写的超时参数;读写缓冲区的设置使用SetupComm(),参数dwInQueue和 dwOutQueue分别定义为输入和输出缓冲区的大小。
在串口初始化完毕后,还要建立与通信有关的事件对象。一般使用CreateEvent()函数,它返回一事件句柄,其中参数lpEventAttributes指向安全属性结构地址,在Win 95(无安全属性)中为NULL;布尔参数bManualReset 定义事件重置方式,true 表示手工重置,false表示自动重置(相关函数为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为要读写的字节数,lpNumberOfBytes为实际读写的字节数,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);
} 这个函数发送一个消息组件的窗口句柄(记住,这个窗口句柄是以前存入到Fhandle数据成品中的)。现在我们已经完成了创建组件选择,用SelectFile|ColseAll来保存我们的工作测试组件。
下一步将测试组件。如果你使用BCB3,那么你必须把组件加入到“包”(Packege)中,然后用Componet|install(可以使用DCLSTD35 Packege来快速测试)。再选择你刚存的TestBCB.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,Query三个控件加入以下代码:
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、TServerSocket组件并不是Socket对象,其属性Socket将返回各自的Socket对象。TClientSocket用来处理客户端到服务器端之间的socket连接,TServerSocket用来处理由客户端发来的socket连接,一旦客户端和服务器端都接通了socket,客户端和服务器端就可以相互通信了。
---- 建立一新项目,创建应用程序的用户界面:
---- 1.将组件页切换到Internet页,放一个TServerSocket组件和一个TClientSocket组件到窗体上,这样应用程序既可以是TCP/IP服务器,也可以是TCP/IP客户。将Port属性都设为同一个值(如1000),确定Socket之间的连接类型为NonBlocking(非阻塞方式)。
---- 2.放两个TMemo组件到窗体上,用来分别显示双方的谈话内容,将Memo2的ReadOnly属性设为True。
---- 3.在窗体的顶部放上一个Panel组件,在其上放三个按钮:监听(btnlisten)、连接(btnconnect)、断开(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;
}
---- 程序运行后,如果用户按下"监听"钮,则将该程序设为服务器端,这时应将TServerSocket的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]);
}
}
---- 在本例中我们采用非阻塞传输方式,当其中的一方进行写操作时,另一方会触发OnRead事件(客户端)或OnClientRead事件(服务器端&
---- 在本例中我们采用非阻塞传输方式,当其中的一方进行写操作时,另一方会触发OnRead事件(客户端)或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控件中显示了出来。本程序所用的是Windows 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编码写入一个文件,然后,使用具有BIG5码到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控件,两个Image,分别装载“开门”和“关门”两幅图。开始加入代码。
---- 应用程序必须用发送消息的办法通知任务栏增加,删除,和修改图标。发送消息必须调用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 的ControlCount=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:/Drive目录下。
//----------------------------------------------------------------------
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_REGISTERS 这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++ 仅 支 持16 位应 用 程 序 的 端 口 操 作, 对32 位 应 用 程 序 的 端 口 操 作 不 再 支持, 而C++ Builder 开 发 出 来 的 程 序 是32 位 的。 我 个 人 以 为, 这是C++ Builder 设 计 者 的 败 笔。 因 为PC 机 中,I/O 地 址 空 间 与 内存 地 址 空 间 从 来 都 是 各 自 独 立 的。 看 看Delphi, 不 就 通 过Port 数 组 实 现 了 对I/O 端 口 的 访 问 了 吗? 搞 不 清 楚 为 什 么C++ Builder 就 没 有 提 供 类 似 的 机 制 ? 下 面 这 几 个 函 数 是 笔 者 从 网 上淘 下 来 的, 经 过 验 证, 在Windows95 环 境 下, 的 确 可 实 现 对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 大 家 早 有 所 闻 了, 在VxD 里, 不 但 可 以 执 行CPU 的 所 有 指 令, 而 且 可 以 调 用VMM( 虚 拟 机 管 理 器) 和 其 他VxD 提 供 的 上 千 个 系 统 级 服 务。 获 得 这 一 能 力 的 最 本 质 原 因 在 于 它 运 行 在Ring0, 与 系 统 内 核 同 一 级 别。 但 是 它 体 系 的 复 杂 性、 开 发 工 具 的 不 易 获 得、 帮 助 文 档 的 不 完 备, 使Microsoft 排 除 了 一 大 批 程 序 员 和 竞 争 对 手。 而 将 在Windows2000(Windows98 也 开 始 支 持) 中 取 代VxD 的WDM 对Win95 程 序 员 也 是 个 噩 梦, 它 需 要 了 解Windows NT 核 心 驱 动 模 型。
----有 没 有 简 单 一 些 的 办 法 呢 ? 我 们 可 以 令 一 个 普 通Win32 应 用 程 序 运 行 在Ring0 下, 从 而 获 得VxD 的 能 力 吗 ? 答 案 是 肯 定 的。 下 面 我 们 就 简 述 一 下 这 一 技 巧, 有 关Intel x86 保 护 模 式 的 基 础 知 识 请 大 家 看 有 关 书 籍。
----首 先 此 技 巧 基 于 以 下 理 论 根 据:
----一、SIDT 指 令( 将 中 断 描 述 符 表 寄 存 器 IDTR - -64 位 宽,16 ~47Bit 存 有 中 断 描 述 符 表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)。使用server sockets,一个应用程序可以提供这些服务中的一个去连接一个希望使用服务的客户程序。Client sockets允许一个应用使用这些服务中的一个去连接提供这个服务的服务应用。
使用sockets去写应用程序,你必须理解下面这些知识:
一、服务工具
当你需要写网络服务或客户应用时,Sockets提供一种接合。对于许多服务,象
HTTP 或 FTP,第三方服务商提供这些服务已经相当有效。有些甚至随着操作系统捆绑而来,以便不用你自己写。然而,当你想更多的控制服务的实现,如想让你的应用程序与网络通信更加紧密,或当没有一个服务能提供你特殊需要的服务时,你可能想建立你自己的服务或客户应用。例如,工作在分布式data sets时,你可能想为数据库写一层与其他系统通信的应用。想使用Sockets实现一个服务,你必须理解:
1.服务协议
在你写一个网络服务或客户程序前,你必须明白你的应用将提供或使用什么服务。你的网络应用必须支持许多服务的标准协议。如果你为标准的服务例如HTTP,FTP写网络应用,或even finger or time,你必须先理解与其他系统通信所使用的协议。特殊服务细节你必须看提供的或使用的文档。
如果你的应用程序提供一个新的服务与其他系统通信,第一步是为这个服务的
服务端和客户端设计通信协议。什么信息将发送?如何整理这些信息?如何对这些信息进行编码?
应用程序通信
经常的,你的网络服务端或客户端应用程序要提供一层在网络软件和一个应用之间使用的服务。例如,一个HTTP服务站点在INternet与一个Web 服务应用之间为HTTP请求信息提供内容和应答。
在你的网络应用(或客户应用)和网络软件之间Sockets 提供一个接口。你必须提供一个接口,在你的应用程序与应用间使用。你可以拷贝第三方服务商提供的标准API(例如ISAPI),或你可以设计和发布你自己的API.
2.理解服务和端口
许多标准服务都有关联的、指定的端口号。当 执行服务时,你可以为服务考虑一个端口号。如果你实现一个标准服务, Windows socket objects 提供一些方法让你为服务寻找端口号。如果提供一个新的服务,在基于Windows 95 或 NT机器上,你能够在文件Services中为你的服务指定一个相关联的端口号。设置Services文件的更多信息请看微软 的Windows 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 在连接两端的地址。在你开始一个socket连接前,你必须完整的描述你想得到的连接。有些信息可以从你的应用
软件运行的系统平台上得到。例如,你不需要描述一个客户端socket的本地IP地址--这个信息可以从操作系统上获得。你必须提供你工作所依靠的socket的类型的信息。客户端socket必须描述他们想连接的服务器。侦听服务器sockets必须描述他们提供反应的服务器的端口。一个socket 连接终端的完整描述包括两部分:
1.IP地址
主机是这样一个系统,它运行着包含有socket的应用程序。你必须描述主机给socket,通过给出主机的IP地址来完成这个描述。IP地址是一个有四个数字(byte)值的,在标准internet点付内的字符串。
例如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地址上侦听客户端请求。当一个服务器socket同意一个连接,客户端提供一个远程IP地址。客户sockets必须指定远程主机通过提供主机名或者IP地址。
在主机名和IP地址间作一个选择
许多应用软件使用一个主机名去指定一个系统。主机名容易记住和容易检查排版错误。进一步讲,服务器能改变系统或与IP地址关联的特殊的主机名。使用一个主机名,能够允许客户端通过主机名描述找到抽象的站点,即使主机使用一个新的IP地址。
如果主机名是未知的,客户socket必须指定服务器系统使用的IP地址。通过给一个IP地址来指定服务器将更快。当你提供主机名时,socket在定位服务器系统前,必须搜寻与这个主机名相关的IP地址。
2.端口号
虽然IP得地址提供了足够的信息去找到socket连接中位于另一端的系统,你通常还需要指定那个系统的端口号。没有端口号,一个系统在同一时间只能进行一个单一的连接。端口号是唯一标识那允许一个独立系统连接到支持同时多个连接的主机,每个连接都必须指定一个端口号。
在网络应用中,对于服务器工具来说端口号是一个数字代码。有一个习惯就是侦听服务连接到他们自己固定的端口号上,以便他们能找到客户端sockets.服务器socket监听为他们提供服务的相关端口号。当他们允许给予一个客户端socket连接时,他们创建一个独立的socket连接,使用不同的专用的端口号。通过这个方法,能持续的监听相关服务的端口号。
客户端socket使用一个专用的本地端口号,就不用其他的socket去寻找它们。他们指定他们想连接的服务器端socket的端口号,这样他们就能找到服务器应用程序。常常的,这个端口号是通过命名想连接的服务来间接指定的。
四、使用socket控件
C++Builder提供两个socket控件,客户端sockets和服务器sockets.他们允许你的网络应用构成连接其他的机器和允许你通过这个连接来读写信息。与每个socket控件相关联的是windows socket对象,它们在终端的的作用是一个实际的socket连接。socket控件使用windows 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是一个好的主意,能够减少设置端口号时的错误。如果你既指定了Port 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。你能够使用Handle属性去访问windows从socket连接处得来的信息。AsyncStyles属性决定windows handle将接收哪种类型的信息。
E.关闭连接
当你决定关闭监听连接时,调用Close方法。这将关闭所有打开着的,连接到客户应用的连接,取消任何尚未同意的连接,接着关闭监听连接以便你的服务socket控件不在接受任何新的连接。当客户端关闭他们自己独立的连接到你的server socket的连接时,你可以在OnClientDisconnect事件中得到讯息。
五、socket事件的应答
当使用sockets写应用程序时,大多数工作发生在socket控件的handler事件中.当通过socket连接开始读或写时,OnRead和OnWrite事件在non-blocking client sockets中发生从而通知sockets.同样的,服务器sockets(blocking or non-blocking)收到OnClientRead和OnClientWrite事件.
当服务器结束一个连接时,客户scokets收到一个OnDisconnect事件.当客户端结束一个连接时,服务器socket收到一个OnClientDisconnect事件.
另外,客户端Sockets和服务器端socket从连接中收到一个错误信息时,都将产生有个错误事件.
错误事件:客户sockets和服务器sockets通常会产生一个OnError事件,当他们从连接中收到一个错误信息的时候.你能够写一个OnError事件处理去响应这些错误信息.这个OnError事件处理提供传送关于socket试图做什么的时候这个错误发生的信息,以及错误信息提供的错误代码.你可以在OnError事件处理中对这个错误作出响应,并且把错误代码改为0,以避免socket产生一个例外.
当开始和完成发生时,socket控件通常会收到一个事件号(number of events).如果你的应用程序需要改变socket开始操作的处理过程或通过连接开始读或写操作时,你将写事件handlers去应答这些client events和server events.
A.client events
当一个客户socket打开一个连接时,以下事件发生:
1.一个OnLookup事件最先发生,它试图去定位server socket.在这里你不能改变Host,Address,Port,Service属性去改变你想定位的服务器.你能够使用Socket属性去访问client windows socket object,并且使用它的SocketHandle属性去调用windows API,以便改变socket的客户属性.例如,如果你想在客户应用软件中设置端口号,你必须在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,你可以在OnGetSocket 事件 handler中建立,将被用来替代TServerClientWinSocket.
2.一个OnAccept事件发生,传送新的TServerClientWinSocket对象给事件句柄.这是第一个要点,当你使用TServerClientWinSocket的属性去获得被连接中服务的那端的客户的信息时.
3.如果服务类型是stThreadBlocking,一个OnGetThread事件发生.如果你想提供自己定义的TServerClientThread子类,你可以在OnGetThread事件句柄中建立一个,它将替代TServerClientThread.
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时,连接的另一端企图读或写时读和写事件将把这个信息通知你的socket.
读和写操作事件
Non-blocking sockets想通过连接读或写时,它会产生一个读和写操作事件通知你的socket.在客户端sockets,你可以在OnRead或OnWrite事件句柄中对这些事件做出反应.在服务器端Scokets,可以在OnClientRead或OnClientWrite事件句柄中对这些事件做出反应.与socket连接相关联的windows socket object在事件句柄的读或写中被当作一个参数.Windows socket object提供一个方法号(number of methods)以允许你通过连接读或写.
通过socket连接读,使用ReceiveBuf或ReceiveText方法.在使用ReceiveBuf方法前,使用Receivelength方法去确定在连接的另一端socket准备发送的字节数(number of bytes).
通过socket连接写,使用SendBuf,SendStream,或SendText方法.如果你通过socket发送信息后不在需要socket连接,你可以使用SendStreamThenDrop方法. SendStreamThenDrop在写完所有的信息后将关闭Socket连接,它能够从stream读信息.如果你使用SendStream或SendStreamThenDrop方法,不要释放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和OnClientWrite事件.可是,这些事件发生在监听socket上时,不是本地线程(thread-local).如果客户请求频繁,你将想建立你自己的TServerClientThread子类去提供一个安全线程(Thread-Safe)去完成读和写操作.
当写客户线程或写服务器线程时,你能够使用TwinSocketStream去做实际的读写操作.
A)写客户端线程
为客户端连接写一个线程,定义一个新线程对象,使用新线程对象对话框.你的新线程对象Execute方法的句柄的通过线程连接进行读写操作的细节,可以建立一个TWinSocketStream对象,然后使用它来读或写.
使用你自己的线程,在OnConnect事件句柄中建立它.关于建立和运行线程的更多信息,请看Executing 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同意一个连接时,服务客户线程必须使用TServerClientWinSocket对象来建立.这可以利用共公共的CientSocket属性.另外,你能够使用HandleException这个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函数GetWindowLong和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()方法后,在超时时间仍然没有与服务器连接,将触发控件的OnConnectFailed事件,在此事件的处理中,我们将连接失败的消息显示给用户。具体实现代码如下:
void __fastcall TForm1::NMEcho1ConnectionFailed(TObject Sender)
{
ShowMessage("Connection failed!");
}
除了NMEcho控件可以实现以上功能外,NetMaster的NMDayTime、NMTime这两个控件也能实现。方法与NMEcho控件一样,区别是NMDayTime和NMTime这两个控件不用首先调用Connect()方法,它们与服务器的连接是在使用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是访问权限,本程序中用SERVICE_ALL_ACCESS。wServiceType,指明SERVICE类型,本程序中用SERVICE_WIN32_OWN_PROCESS| SERVICE_INTERACTIVE_PROCESS。dwStartType为Service启动方式,本程序采用自启动,即dwStartType等于SERVICE_AUTO_START。 dwErrorControl说明当Service在启动中出错时采取什么动作,本程序采用SERVICE_ERROR_IGNORE即忽约错误,读者可以改为其他的。LpBinaryPathName指明Service本体程序的路径名。剩下的五个参数一般可设为NULL。如函数调用成功则返回这个新Service的句柄,失败则返回NULL。与此函数对应的是DeleteService( 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程序,而控制程序则是一个普通的Win32应用程序(当然,用户不用控制程序而通过控制面板也可对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()。StartServiceCtrlDispatcher()在被调用后并不立即返回,它把本Service的主线程连接到service control manager,从而让service control manager通过这个连接发送开始、停止等控制命令给主线程。主线程在这时就扮演了一个命令的转发器的角色,它或者调用Handle( )去处理停止、继续等控制要求,或者产生一个新线程去执行ServiceMain。StartServiceCtrlDispatcher()在整个Service结束时才返回。
---- ServiceMain()是Service真正的入口点,必须在main()中进行了正确的定义。ServiceMain( )的两个参数是由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类的OnMessage事件来处理WINDOWS发来的这条消息,如果在接收到的消息后将handled参数设为true,这个响应的消息值就可以阻止屏幕保护运行。
---- 在C++ BUILDER 4.0的过程如下:
---- 1、从主菜单中选择File | New APPlication 来创建一个新的空工程文件。然后在Forn 上加上一个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属性设置为dtkTime,mp_Clock 的FileName属性设置为你主机上存在的任何mid、wav、avi文件。t_Clock 的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的编程比较特殊,但并不难,主要包括图标、工具提示和消息等三个方面,它是Shell编程的一部分。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文件,增加头文件说明#include <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函数,加入对自定义消息的处理代码,这其实相当于创建了TForm类的子类。
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、Pwin98环境下编译、运行通过。
怎样用代码来最小化或恢复程序
你能够用下面三种方法之一来实现它。
方法一:发送一条Windows消息到主窗口的Handle属性或 Application->Handle。这条消息就是 WM_SYSCOMMAND,将 wParam 设为 SC_MINIMIZE 或 SC_RESTORE。你可以调用SendMessage 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>
1.怎样在C++Builder中创建使用DLL
2.用C++Bulider在WIN.INI中保存信息
3.如何在C++Builder中检测硬件
4.C++Builder如何响应消息及自定义消息
5.利用C++ Builder开发动画DLL
6.用C++ Builder 3制作屏幕保护程序
7.TCP/IP头格式
8.UDP
9.判断windows的Desktop及其它目录
10用C++Builder创建数字签名
11用Enter 键控制焦点切换的方法
12.拦 截 Windows 消 息
13.使用CommaText
14.程序开始时先显示信息框
15.怎样获取程序的命令行参数?
16.如何监视剪贴板
17.如何使用OnIdle事件
18.用C++Builder编写串行异步通信程序
19.C++BUILDER非可视组件的消息处理技巧
20.用C++Builder 建立数据库VCL使用经验
21.用C++ Builder创建基于Internet的点对点Chat
22.用C++Builder获取应用程序图标
23.BIG5到GB的转换技术
24.C++BUILDER让你的任务栏图标动起来
25.TFORM
26.用BCB在windows桌面创建快捷方式
27.读磁片磁区
28.I/O 端口读写的实现
29.检测鼠标位置
30.令Win32 应用程序跳入系统零层
31.如何取得Memo的行和列
32.使用Sockets
33.Windows95/98下怎样隐藏应用程序不让它出现在CTRL-ALT-DEL对话框中?
34.怎样隐藏应用程序的任务条图标
35.编写自己的Ping.exe程序
36.用C++Builder在WINNT下编制一个Service
37.如何在C++ BUILDER中自动关闭WINDOWS屏保
38.显示/隐藏任务栏图标
39.信箱监视程序
40.C++Building制作闹钟
41.拨号上网IP地址的检知
42.用C++ Builder编写Tray程序
43.怎样用代码来最小化或恢复程序
44.制作主窗口显示前的版权窗口
45.判断是否已经联到 internet
46.获取登陆用户名
47.隐藏桌面图标
48.程序启动时运行
49.控制面板的调用
50.模拟键盘按键
51.让标题栏闪烁
52.启动屏幕保护
53.年月日星期的取法
54.键盘事件
55.隐藏任务栏
56.禁止关机
57.怎样以最小化方式启动程序
58.在Memo中增加一行后,如何使最后一行能显示
59.设置壁纸方法
怎样在C++Builder中创建使用DLL
自从C++Builder从去年浪漫情人节上市以来,吸引了大量的Delphi、VC、Vb的程序员到它的怀抱,大量的C、C++程序员感叹道:总算有了C的可视化开发工具,对我也是一样,从BC、Delphi到C++Builder。
动态链接库(DLL)是Windows编程常遇到的编程方法,下面我就介绍一下在BCB (C++Builder下简称BCB) 中如何创建使用DLL和一些技巧。
一、创建:
使用BCB File|NEW建立一个新的DLL工程,并保存好文件BCB,生成一个DLL的程序框架。
1.DllEntryPoint函数为一个入口方法,如果使用者在DLL被系统初始化或者注销时被调用,用来写入对DLL的初始化程序和卸载程序;参数:hinst用来指示DLL的基地址;reason用来指示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为当GetProfileInt没有找到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:GlobalMemoryStatus。
其中,成员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)));
怎么样,看出点门道了么?两段程序的格式几乎一模一样,其实,GetSystemInfoGlobalMemoryStatus还可以获得许多其他有关CPU和内存的信息,就按照上面的格式去套就行了,更详细的资料可以去看C++ Builder4的Help。
3. 检测可用硬盘空间
好了,经过前面两个简单问题的热身,我们来处理一个稍微复杂的问题:我们知道安装程序大都有一个检测硬盘空间的过程,那么这是怎么实现的呢?他用到的是API函数GetDiskFreeSpace,这个函数输入一个参数:目标盘的路径;返回四个参数,依次是每簇的扇区数、每扇区的字节数、空闲的簇数、总簇数。假如我们需要检测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环境下执行拷贝文件、查找文件或计算机等耗时比较长的操作时,Windows会显示一个小小的动画,指示正在进行的操作,与死板的静止图像相比增色不少。那么我们自己开发软件时,能否也显示一个这样的动画提示呢?我在开发一个外贸应用软件系统时,遇到的数据量很大,当通过复合条件查找时,因为不是数据库表的每个项目都有索引,所以很费时,系统也会表现出长时间停顿,用户感觉极为不爽。我经过一段时间的探索,开发了一个能够在采用的开发环境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下的Timer控件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_ACTIVATE消息的处理形式如下:
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 键 将 焦 点 移 至 该 控 件 上。 这 种 控 制 焦 点 切 换 的 方 法 有 时 不 符 合 用 户 的 习 惯。 就 图 一 而 言, 用 户 就 希 望 用Enter 键, 控 制 焦 点 由Edit1 切 换 到 Edit2。 要 实 现 这 样 的 功 能 需 借 助WinAPI 函 数 SendMessage 来 完 成。 方 法 是: 先 设Form1 的KeyPreview 属 性 为true, 然 后 在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_SYSCOMMAND和WM_HSCROLL(或WM_VSCROLL)消息,就能实现上述功能。Windows API的缺点是编程十分麻烦,太多的时间要耗在细节上面,但它的功能却是最强大的。C++ Builder的VCL在功能上只是它的一个子集,因为VCL是在API的基础上封装的,封装时舍弃了一些不常用到的功能。但是程序员的想象力没有被封装,他们总怀着更大的热情去实现别出心裁的想法,修改系统菜单和给跟踪栏增加StartTrack和ndTrack事件只是其中的小把戏而已。可是VCL并没有这些功能,怎么办?
---- 幸好,Borland公司没有把路堵死,而是留了个后门--允许程序员自己拦截并处理Windows消息,就象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消息是一项高级编程技术,能让你尽量挖掘Windows的潜力,尤其让曾用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=fsStayOnTop, 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文件拷贝到Program 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|GENERIC-WRITE;参数dwShareMode定义资源共享方式,此处必须设为0,为独占方式;lpSecurityAttributes定义安全属性,Win 95下为NULL;dwCreationDistribution定义文件创建方式;dwFlagsAndAttributes定义文件属性和标记,应设为FILE-FLAG-OVERLAPPED,表示异步通信方式;hTemplateFile 指向一个模板文件的句柄,在 Windows 95下为NULL。
然后用BuildCommDCB( )和SetCommState( )函数通过通信设备控制块DCB(Device Control Block)设置串口通信参数(如波特率、停止位、数据位、校验位等),其中BuildCommDCB( )中的字符串参数lpDef 定义同DOS命令中MODE的参数格式,关于DCB更具体的设置需要根据用户对数据流定义、握手信号及通信控制要求具体定义,参见有关Windows技术资料。用GetCommState()可以得到当前的DCB参数值。如果需要还可通过SetCommTimeouts()和GetCommTomeouts()重新设置读写的超时参数;读写缓冲区的设置使用SetupComm(),参数dwInQueue和 dwOutQueue分别定义为输入和输出缓冲区的大小。
在串口初始化完毕后,还要建立与通信有关的事件对象。一般使用CreateEvent()函数,它返回一事件句柄,其中参数lpEventAttributes指向安全属性结构地址,在Win 95(无安全属性)中为NULL;布尔参数bManualReset 定义事件重置方式,true 表示手工重置,false表示自动重置(相关函数为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为要读写的字节数,lpNumberOfBytes为实际读写的字节数,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);
} 这个函数发送一个消息组件的窗口句柄(记住,这个窗口句柄是以前存入到Fhandle数据成品中的)。现在我们已经完成了创建组件选择,用SelectFile|ColseAll来保存我们的工作测试组件。
下一步将测试组件。如果你使用BCB3,那么你必须把组件加入到“包”(Packege)中,然后用Componet|install(可以使用DCLSTD35 Packege来快速测试)。再选择你刚存的TestBCB.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,Query三个控件加入以下代码:
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、TServerSocket组件并不是Socket对象,其属性Socket将返回各自的Socket对象。TClientSocket用来处理客户端到服务器端之间的socket连接,TServerSocket用来处理由客户端发来的socket连接,一旦客户端和服务器端都接通了socket,客户端和服务器端就可以相互通信了。
---- 建立一新项目,创建应用程序的用户界面:
---- 1.将组件页切换到Internet页,放一个TServerSocket组件和一个TClientSocket组件到窗体上,这样应用程序既可以是TCP/IP服务器,也可以是TCP/IP客户。将Port属性都设为同一个值(如1000),确定Socket之间的连接类型为NonBlocking(非阻塞方式)。
---- 2.放两个TMemo组件到窗体上,用来分别显示双方的谈话内容,将Memo2的ReadOnly属性设为True。
---- 3.在窗体的顶部放上一个Panel组件,在其上放三个按钮:监听(btnlisten)、连接(btnconnect)、断开(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;
}
---- 程序运行后,如果用户按下"监听"钮,则将该程序设为服务器端,这时应将TServerSocket的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]);
}
}
---- 在本例中我们采用非阻塞传输方式,当其中的一方进行写操作时,另一方会触发OnRead事件(客户端)或OnClientRead事件(服务器端&
---- 在本例中我们采用非阻塞传输方式,当其中的一方进行写操作时,另一方会触发OnRead事件(客户端)或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控件中显示了出来。本程序所用的是Windows 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编码写入一个文件,然后,使用具有BIG5码到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控件,两个Image,分别装载“开门”和“关门”两幅图。开始加入代码。
---- 应用程序必须用发送消息的办法通知任务栏增加,删除,和修改图标。发送消息必须调用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 的ControlCount=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:/Drive目录下。
//----------------------------------------------------------------------
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_REGISTERS 这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++ 仅 支 持16 位应 用 程 序 的 端 口 操 作, 对32 位 应 用 程 序 的 端 口 操 作 不 再 支持, 而C++ Builder 开 发 出 来 的 程 序 是32 位 的。 我 个 人 以 为, 这是C++ Builder 设 计 者 的 败 笔。 因 为PC 机 中,I/O 地 址 空 间 与 内存 地 址 空 间 从 来 都 是 各 自 独 立 的。 看 看Delphi, 不 就 通 过Port 数 组 实 现 了 对I/O 端 口 的 访 问 了 吗? 搞 不 清 楚 为 什 么C++ Builder 就 没 有 提 供 类 似 的 机 制 ? 下 面 这 几 个 函 数 是 笔 者 从 网 上淘 下 来 的, 经 过 验 证, 在Windows95 环 境 下, 的 确 可 实 现 对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 大 家 早 有 所 闻 了, 在VxD 里, 不 但 可 以 执 行CPU 的 所 有 指 令, 而 且 可 以 调 用VMM( 虚 拟 机 管 理 器) 和 其 他VxD 提 供 的 上 千 个 系 统 级 服 务。 获 得 这 一 能 力 的 最 本 质 原 因 在 于 它 运 行 在Ring0, 与 系 统 内 核 同 一 级 别。 但 是 它 体 系 的 复 杂 性、 开 发 工 具 的 不 易 获 得、 帮 助 文 档 的 不 完 备, 使Microsoft 排 除 了 一 大 批 程 序 员 和 竞 争 对 手。 而 将 在Windows2000(Windows98 也 开 始 支 持) 中 取 代VxD 的WDM 对Win95 程 序 员 也 是 个 噩 梦, 它 需 要 了 解Windows NT 核 心 驱 动 模 型。
----有 没 有 简 单 一 些 的 办 法 呢 ? 我 们 可 以 令 一 个 普 通Win32 应 用 程 序 运 行 在Ring0 下, 从 而 获 得VxD 的 能 力 吗 ? 答 案 是 肯 定 的。 下 面 我 们 就 简 述 一 下 这 一 技 巧, 有 关Intel x86 保 护 模 式 的 基 础 知 识 请 大 家 看 有 关 书 籍。
----首 先 此 技 巧 基 于 以 下 理 论 根 据:
----一、SIDT 指 令( 将 中 断 描 述 符 表 寄 存 器 IDTR - -64 位 宽,16 ~47Bit 存 有 中 断 描 述 符 表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)。使用server sockets,一个应用程序可以提供这些服务中的一个去连接一个希望使用服务的客户程序。Client sockets允许一个应用使用这些服务中的一个去连接提供这个服务的服务应用。
使用sockets去写应用程序,你必须理解下面这些知识:
一、服务工具
当你需要写网络服务或客户应用时,Sockets提供一种接合。对于许多服务,象
HTTP 或 FTP,第三方服务商提供这些服务已经相当有效。有些甚至随着操作系统捆绑而来,以便不用你自己写。然而,当你想更多的控制服务的实现,如想让你的应用程序与网络通信更加紧密,或当没有一个服务能提供你特殊需要的服务时,你可能想建立你自己的服务或客户应用。例如,工作在分布式data sets时,你可能想为数据库写一层与其他系统通信的应用。想使用Sockets实现一个服务,你必须理解:
1.服务协议
在你写一个网络服务或客户程序前,你必须明白你的应用将提供或使用什么服务。你的网络应用必须支持许多服务的标准协议。如果你为标准的服务例如HTTP,FTP写网络应用,或even finger or time,你必须先理解与其他系统通信所使用的协议。特殊服务细节你必须看提供的或使用的文档。
如果你的应用程序提供一个新的服务与其他系统通信,第一步是为这个服务的
服务端和客户端设计通信协议。什么信息将发送?如何整理这些信息?如何对这些信息进行编码?
应用程序通信
经常的,你的网络服务端或客户端应用程序要提供一层在网络软件和一个应用之间使用的服务。例如,一个HTTP服务站点在INternet与一个Web 服务应用之间为HTTP请求信息提供内容和应答。
在你的网络应用(或客户应用)和网络软件之间Sockets 提供一个接口。你必须提供一个接口,在你的应用程序与应用间使用。你可以拷贝第三方服务商提供的标准API(例如ISAPI),或你可以设计和发布你自己的API.
2.理解服务和端口
许多标准服务都有关联的、指定的端口号。当 执行服务时,你可以为服务考虑一个端口号。如果你实现一个标准服务, Windows socket objects 提供一些方法让你为服务寻找端口号。如果提供一个新的服务,在基于Windows 95 或 NT机器上,你能够在文件Services中为你的服务指定一个相关联的端口号。设置Services文件的更多信息请看微软 的Windows 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 在连接两端的地址。在你开始一个socket连接前,你必须完整的描述你想得到的连接。有些信息可以从你的应用
软件运行的系统平台上得到。例如,你不需要描述一个客户端socket的本地IP地址--这个信息可以从操作系统上获得。你必须提供你工作所依靠的socket的类型的信息。客户端socket必须描述他们想连接的服务器。侦听服务器sockets必须描述他们提供反应的服务器的端口。一个socket 连接终端的完整描述包括两部分:
1.IP地址
主机是这样一个系统,它运行着包含有socket的应用程序。你必须描述主机给socket,通过给出主机的IP地址来完成这个描述。IP地址是一个有四个数字(byte)值的,在标准internet点付内的字符串。
例如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地址上侦听客户端请求。当一个服务器socket同意一个连接,客户端提供一个远程IP地址。客户sockets必须指定远程主机通过提供主机名或者IP地址。
在主机名和IP地址间作一个选择
许多应用软件使用一个主机名去指定一个系统。主机名容易记住和容易检查排版错误。进一步讲,服务器能改变系统或与IP地址关联的特殊的主机名。使用一个主机名,能够允许客户端通过主机名描述找到抽象的站点,即使主机使用一个新的IP地址。
如果主机名是未知的,客户socket必须指定服务器系统使用的IP地址。通过给一个IP地址来指定服务器将更快。当你提供主机名时,socket在定位服务器系统前,必须搜寻与这个主机名相关的IP地址。
2.端口号
虽然IP得地址提供了足够的信息去找到socket连接中位于另一端的系统,你通常还需要指定那个系统的端口号。没有端口号,一个系统在同一时间只能进行一个单一的连接。端口号是唯一标识那允许一个独立系统连接到支持同时多个连接的主机,每个连接都必须指定一个端口号。
在网络应用中,对于服务器工具来说端口号是一个数字代码。有一个习惯就是侦听服务连接到他们自己固定的端口号上,以便他们能找到客户端sockets.服务器socket监听为他们提供服务的相关端口号。当他们允许给予一个客户端socket连接时,他们创建一个独立的socket连接,使用不同的专用的端口号。通过这个方法,能持续的监听相关服务的端口号。
客户端socket使用一个专用的本地端口号,就不用其他的socket去寻找它们。他们指定他们想连接的服务器端socket的端口号,这样他们就能找到服务器应用程序。常常的,这个端口号是通过命名想连接的服务来间接指定的。
四、使用socket控件
C++Builder提供两个socket控件,客户端sockets和服务器sockets.他们允许你的网络应用构成连接其他的机器和允许你通过这个连接来读写信息。与每个socket控件相关联的是windows socket对象,它们在终端的的作用是一个实际的socket连接。socket控件使用windows 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是一个好的主意,能够减少设置端口号时的错误。如果你既指定了Port 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。你能够使用Handle属性去访问windows从socket连接处得来的信息。AsyncStyles属性决定windows handle将接收哪种类型的信息。
E.关闭连接
当你决定关闭监听连接时,调用Close方法。这将关闭所有打开着的,连接到客户应用的连接,取消任何尚未同意的连接,接着关闭监听连接以便你的服务socket控件不在接受任何新的连接。当客户端关闭他们自己独立的连接到你的server socket的连接时,你可以在OnClientDisconnect事件中得到讯息。
五、socket事件的应答
当使用sockets写应用程序时,大多数工作发生在socket控件的handler事件中.当通过socket连接开始读或写时,OnRead和OnWrite事件在non-blocking client sockets中发生从而通知sockets.同样的,服务器sockets(blocking or non-blocking)收到OnClientRead和OnClientWrite事件.
当服务器结束一个连接时,客户scokets收到一个OnDisconnect事件.当客户端结束一个连接时,服务器socket收到一个OnClientDisconnect事件.
另外,客户端Sockets和服务器端socket从连接中收到一个错误信息时,都将产生有个错误事件.
错误事件:客户sockets和服务器sockets通常会产生一个OnError事件,当他们从连接中收到一个错误信息的时候.你能够写一个OnError事件处理去响应这些错误信息.这个OnError事件处理提供传送关于socket试图做什么的时候这个错误发生的信息,以及错误信息提供的错误代码.你可以在OnError事件处理中对这个错误作出响应,并且把错误代码改为0,以避免socket产生一个例外.
当开始和完成发生时,socket控件通常会收到一个事件号(number of events).如果你的应用程序需要改变socket开始操作的处理过程或通过连接开始读或写操作时,你将写事件handlers去应答这些client events和server events.
A.client events
当一个客户socket打开一个连接时,以下事件发生:
1.一个OnLookup事件最先发生,它试图去定位server socket.在这里你不能改变Host,Address,Port,Service属性去改变你想定位的服务器.你能够使用Socket属性去访问client windows socket object,并且使用它的SocketHandle属性去调用windows API,以便改变socket的客户属性.例如,如果你想在客户应用软件中设置端口号,你必须在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,你可以在OnGetSocket 事件 handler中建立,将被用来替代TServerClientWinSocket.
2.一个OnAccept事件发生,传送新的TServerClientWinSocket对象给事件句柄.这是第一个要点,当你使用TServerClientWinSocket的属性去获得被连接中服务的那端的客户的信息时.
3.如果服务类型是stThreadBlocking,一个OnGetThread事件发生.如果你想提供自己定义的TServerClientThread子类,你可以在OnGetThread事件句柄中建立一个,它将替代TServerClientThread.
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时,连接的另一端企图读或写时读和写事件将把这个信息通知你的socket.
读和写操作事件
Non-blocking sockets想通过连接读或写时,它会产生一个读和写操作事件通知你的socket.在客户端sockets,你可以在OnRead或OnWrite事件句柄中对这些事件做出反应.在服务器端Scokets,可以在OnClientRead或OnClientWrite事件句柄中对这些事件做出反应.与socket连接相关联的windows socket object在事件句柄的读或写中被当作一个参数.Windows socket object提供一个方法号(number of methods)以允许你通过连接读或写.
通过socket连接读,使用ReceiveBuf或ReceiveText方法.在使用ReceiveBuf方法前,使用Receivelength方法去确定在连接的另一端socket准备发送的字节数(number of bytes).
通过socket连接写,使用SendBuf,SendStream,或SendText方法.如果你通过socket发送信息后不在需要socket连接,你可以使用SendStreamThenDrop方法. SendStreamThenDrop在写完所有的信息后将关闭Socket连接,它能够从stream读信息.如果你使用SendStream或SendStreamThenDrop方法,不要释放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和OnClientWrite事件.可是,这些事件发生在监听socket上时,不是本地线程(thread-local).如果客户请求频繁,你将想建立你自己的TServerClientThread子类去提供一个安全线程(Thread-Safe)去完成读和写操作.
当写客户线程或写服务器线程时,你能够使用TwinSocketStream去做实际的读写操作.
A)写客户端线程
为客户端连接写一个线程,定义一个新线程对象,使用新线程对象对话框.你的新线程对象Execute方法的句柄的通过线程连接进行读写操作的细节,可以建立一个TWinSocketStream对象,然后使用它来读或写.
使用你自己的线程,在OnConnect事件句柄中建立它.关于建立和运行线程的更多信息,请看Executing 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同意一个连接时,服务客户线程必须使用TServerClientWinSocket对象来建立.这可以利用共公共的CientSocket属性.另外,你能够使用HandleException这个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函数GetWindowLong和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()方法后,在超时时间仍然没有与服务器连接,将触发控件的OnConnectFailed事件,在此事件的处理中,我们将连接失败的消息显示给用户。具体实现代码如下:
void __fastcall TForm1::NMEcho1ConnectionFailed(TObject Sender)
{
ShowMessage("Connection failed!");
}
除了NMEcho控件可以实现以上功能外,NetMaster的NMDayTime、NMTime这两个控件也能实现。方法与NMEcho控件一样,区别是NMDayTime和NMTime这两个控件不用首先调用Connect()方法,它们与服务器的连接是在使用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是访问权限,本程序中用SERVICE_ALL_ACCESS。wServiceType,指明SERVICE类型,本程序中用SERVICE_WIN32_OWN_PROCESS| SERVICE_INTERACTIVE_PROCESS。dwStartType为Service启动方式,本程序采用自启动,即dwStartType等于SERVICE_AUTO_START。 dwErrorControl说明当Service在启动中出错时采取什么动作,本程序采用SERVICE_ERROR_IGNORE即忽约错误,读者可以改为其他的。LpBinaryPathName指明Service本体程序的路径名。剩下的五个参数一般可设为NULL。如函数调用成功则返回这个新Service的句柄,失败则返回NULL。与此函数对应的是DeleteService( 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程序,而控制程序则是一个普通的Win32应用程序(当然,用户不用控制程序而通过控制面板也可对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()。StartServiceCtrlDispatcher()在被调用后并不立即返回,它把本Service的主线程连接到service control manager,从而让service control manager通过这个连接发送开始、停止等控制命令给主线程。主线程在这时就扮演了一个命令的转发器的角色,它或者调用Handle( )去处理停止、继续等控制要求,或者产生一个新线程去执行ServiceMain。StartServiceCtrlDispatcher()在整个Service结束时才返回。
---- ServiceMain()是Service真正的入口点,必须在main()中进行了正确的定义。ServiceMain( )的两个参数是由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类的OnMessage事件来处理WINDOWS发来的这条消息,如果在接收到的消息后将handled参数设为true,这个响应的消息值就可以阻止屏幕保护运行。
---- 在C++ BUILDER 4.0的过程如下:
---- 1、从主菜单中选择File | New APPlication 来创建一个新的空工程文件。然后在Forn 上加上一个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属性设置为dtkTime,mp_Clock 的FileName属性设置为你主机上存在的任何mid、wav、avi文件。t_Clock 的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的编程比较特殊,但并不难,主要包括图标、工具提示和消息等三个方面,它是Shell编程的一部分。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文件,增加头文件说明#include <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函数,加入对自定义消息的处理代码,这其实相当于创建了TForm类的子类。
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、Pwin98环境下编译、运行通过。
怎样用代码来最小化或恢复程序
你能够用下面三种方法之一来实现它。
方法一:发送一条Windows消息到主窗口的Handle属性或 Application->Handle。这条消息就是 WM_SYSCOMMAND,将 wParam 设为 SC_MINIMIZE 或 SC_RESTORE。你可以调用SendMessage 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>