[收藏]C++ Builder的60个编程资料
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、取得本地internet机器的名字及IP地址
11、用C++Builder创建数字签名
12、用Enter 键控制焦点切换的方法
13、拦 截 Windows 消 息
14、使用CommaText
15、程序开始时先显示信息框
16、怎样获取程序的命令行参数?
17、如何监视剪贴板
18、如何使用OnIdle事件
19、用C++Builder编写串行异步通信程序
20、C++BUILDER非可视组件的消息处理技巧
21、用C++Builder 建立数据库VCL使用经验
22、用C++ Builder创建基于Internet的点对点Chat
23、用C++Builder获取应用程序图标
24、BIG5到GB的转换技术
25、C++BUILDER让你的任务栏图标动起来
26、TFORM
27、用BCB在windows桌面创建快捷方式
28、读磁片磁区
29、I/O 端口读写的实现
30、检测鼠标位置
31、令Win32 应用程序跳入系统零层
32、如何取得Memo的行和列
33、使用Sockets
34、Windows95/98下怎样隐藏应用程序不让它出现在CTRL-ALT-DEL对话框中?
35、怎样隐藏应用程序的任务条图标
36、编写自己的Ping.exe程序
37、用C++Builder在WINNT下编制一个Service
38、如何在C++ BUILDER中自动关闭WINDOWS屏保
39、显示/隐藏任务栏图标
40、信箱监视程序
41、C++Building制作闹钟
42、拨号上网IP地址的检知
43、用C++ Builder编写Tray程序
44、怎样用代码来最小化或恢复程序
45、制作主窗口显示前的版权窗口
46、判断是否已经联到 internet
47、获取登陆用户名
48、隐藏桌面图标
49、程序启动时运行
50、控制面板的调用
51、模拟键盘按键
52、让标题栏闪烁
53、启动屏幕保护
54、年月日星期的取法
55、键盘事件
56、隐藏任务栏
57、禁止关机
58、怎样以最小化方式启动程序
59、在Memo中增加一行后,如何使最后一行能显示
60、设置壁纸方法
1、怎样在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 "C" __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 "C" __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 "C" __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("xxx.dll");
ddd=GetProcAddress(dd,"test");
Caption=IntToStr(ddd());
FreeLibrary(dd);
三、注意:
创建DLL时编译链接时注意设置Project Options。
Packages标签:去除Builder with runtime packages检查框。
Linker标签:去除Use dynamic RTL检查框。
否则创建的DLL需要Runtime packages or Runtime library。
2、用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函数,具体如何编程大家可以参阅相关资料或者同我联系。
3、如何在C++Builder中检测硬件
在我们编写的程序中常常要和硬件打交道,那么如何在程序中确定系统中是否有该设备,它的运行状态又是怎样的呢?对于初学者来说,这个问题常常不好解决,其实只需简单地利用几个API函数,硬件的问题并不神秘。下面就让我们一起看看在C++ Build er中是如何检测硬件的。
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("您的CPU类型是:"+String( systeminfo.dwProcessorTy pe ));
}
运行它,点击Test试试,CPU型号出来了吧!
2.检测内存状态
获得内存状态的方法和CPU型号差不多,只是他用到的是另外一个API:GlobalMe moryStatus。其中,成员dwTotalPhys用来获得物理内存总量,而dwAvailPhys顾名思义是有效物理内存的意思。我们只要把下面几行代码加到上面程序的后面就可以了(不用重做,下同):
//获得内存状态
MEMORYSTATUS memory;
memory.dwLength =sizeof(memory); //初始化
memory.dwLength =sizeof(memory); //初始化
GlobalMemoryStatus(&memory);
Memo1->Lines->Add("您的物理内存是(Mb):"+String(int(memory.dwTotalPh ys /1024/1024)));
Memo1->Lines->Add("其中可用内存是(Kb):"+String(int( memory. /1024)) );
怎么样,看出点门道了么?两段程序的格式几乎一模一样,其实,GetSystemInf o和GlobalMemoryStatus还可以获得许多其他有关CPU和内存的信息,就按照上面的格式 去套就行了,更详细的资料可以去看C++ Builder4的Help。
3. 检测可用硬盘空间
好了,经过前面两个简单问题的热身,我们来处理一个稍微复杂的问题:我们知道安装程序大都有一个检测硬盘空间的过程,那么这是怎么实现的呢?他用到的是 API函数GetDiskFreeSpace,这个函数输入一个参数:目标盘的路径;返回四个参数,依次是每簇的扇区数、每扇区的字节数、空闲的簇数、总簇数。假如我们需要检测C盘的总容量 和可用容量,那么可以把以下代码加到上面的程序中:
//获得C盘可用空间
DWORD sector,byte,cluster,free;
long int freespace,totalspace;
GetDiskFreeSpace("C:",§or,&byte,&free,&cluster); //获得返回参 数
totalspace=int(cluster)*int(byte)*int(sector)/1024/1024; //计算总容量 freespace=int(free)*int(byte)*int
(sector)/1024/1024; //计算可用空间
Memo1->Lines->Add("C盘总空间(Mb):"+String(totalspace));
Memo1->Lines->Add("C盘可用空间(Mb):"+String(freespace));
怎么样?现在可以自己做安装程序了吧!
4. 检测CD-ROM
我们在编写程序时常常需要读取CD-ROM,可是究竟哪一个盘符是光驱呢?有人是 桓雠谭?是光驱呢?有人?? 将最后一个盘符当作光驱的,但是当遇到双光驱或者MO的情况时常常会出错。其实这个问题用一个API来解决并不困难,这就是:GetDriveType(),这个函数返回一个0~6之间的值,依次代表:0―未知盘、1―不存在、2―可移动磁盘、3―固定磁盘、4―网络磁盘、5―CD-ROM、6―内存虚拟盘。因此我们可以添加下面代码来寻找CD-ROM:
// 获得CD-ROM信息
UINT type;
char name;
for (name=‘C’;name<=‘Z’;name++) //循环检测A~Z
{ type = GetDriveType((String(name)+String(‘:’)).c_str()); //获得磁 盘类型
if (type==5)
Memo1->Lines->Add("您的光驱盘符为:"+String(name));
}
得到光驱盘符之后我们可以进一步利用API函数GetVolumeInformation检测光驱中 是否有光盘,这个函数如果成功调用,会得到磁盘的卷标序列号等信息;如果调用失败 则可知光驱中无光盘,程序如下://检测光盘(假设光驱为G:)
char volname[255],filename[100];//buffer[512];
DWORD sno,maxl,fileflag ;
if (!(GetVolumeInformation("G:", volname,255,&sno,&maxl,&fileflag ,filename,100)))
//如果返回值为假
Memo1->Lines->Add ("G驱中没有发现光盘");
else
//如果返回值为真
{Memo1->Lines->Add ("G驱中光盘卷标为:"+String(volname));
Memo1->Lines->Add ("G驱中光盘序号为:"+String(sno));
}
5. 检测声卡配置
在编制多媒体程序时,我们常常会用到声音文件,而当这些程序在没有配置声卡的机器上运行时,我们应该给出必要的警告。对于声卡的检测,可以分别通过waveOutG etNumDevs()和midiOutGetNumDevs()检测波形设备和MIDI设备,再利用waveOutGetDevC aps()和midiOutGetDevCaps()获得声音设备的细节资料。将下面一段代码加入上面的程 序即可,但要注意将#include 添至程序首部:
//检测声卡
int wavedevice,mididevice;
WAVEOUTCAPS wavecap;
MIDIOUTCAPS midicap;
wavedevice=(int)waveOutGetNumDevs(); //波形设备信息 mididevice=(int)midiOutGetNumDevs(); // MIDI设备信息mididevice=(int)midiOutGetNumDevs(); // MIDI设备信息
if (wavedevice==0)
Memo1->Lines->Add ("没有发现波形设备");
else
{waveOutGetDevCaps(0,&wavecap,sizeof(WAVEOUTCAPS));
Memo1->Lines->Add ("当前波形设备:"+String(wavecap.szPname));
}
if (mididevice==0)
Memo1->Lines->Add ("没有发现MIDI设备");
else
{midiOutGetDevCaps(0,&midicap,sizeof(MIDIOUTCAPS));
Memo19->Lines->Add ("当前MIDI设备:"+String(midicap.szPname));
}
6. 检测显示器信息
编写和图形图像有关的程序时常常需要检测显示器的分辨率和色深,最后我们来看看这个问题的解决办法。分辨率的求法很简单,直接调用Screen对象的属性就行了。而要求色深则要利用API函数GetDeviceCaps获得每像素的比特数和色彩的页面数,然后计算2的"每像素的比特数"次幂即得色彩的梯度数,再计算"色彩的梯度数"的"色彩 的页面数"次幂即得色深。程序如下:
//检测显示器
int tcs;
long int bpp,cp,tc;
Memo1->Lines->Add ("当前分辨率为:"+String(Screen->Width)+"*"+S tring(Screen->Height));
bpp=GetDeviceCaps(Form1->Canvas->Handle ,BITSPIXEL);
bpp=GetDeviceCaps(Form1->Canvas->Handle ,BITSPIXEL);
tcs=pow(2,bpp); //计算色彩的梯度数
cp= GetDeviceCaps(Form1->Canvas->Handle,PLANES);
tc= pow(tcs,cp); //计算色深
Memo1->Lines->Add("当前色深为:"+String(tc));
好了,现在在让我们点击一下Test吧,其实本文所涉 及的API函数的功能不止这些,大家下去可以查一查Win32 API手册,或者直接在C++ Builder 4中察看Help。相信自己开发一个硬件检测软件也不是难事哦!
4、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
5、利用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() $#@62; ll_EndTime
CloseStatusWindow()
6、用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工具来讲,开发这类程序也是相当方便的,按照前述的方法,可以在极短的时间开发出屏幕保护程序。对于屏幕保护程序,在本文中没有说明的就是如何设置口令的问题,这部分就由读者自己摸索吧。
7、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. */
};
8、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");
}
9、判断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();
}
}
//----------------------------------------------------------------------
注意: 有些目录是空的。有些特定的目录在这个文件系统上并没有一个相应的目录。
10、取得本地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();
}
11、用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(″文件没被修改″);
}
以上是一个数字签名的简单实现,得到的签名数据可以单独保存,也可以分开保存。
12、用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;
}
}
13、拦截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。
14、使用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;
}
15、程序开始时先显示信息框
一、软件进入主窗口前,先显示一个信息框,告诉用户一些有关该软件的信息,比如软件名称,版本号等。该信息框在显示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;
}
16、怎样获取程序的命令行参数?
你可以用下面的两种不同的技巧来解决这个问题。
技巧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
17、如何监视剪贴板
在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;
}
18、如何使用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;
19、用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(TObject�Sender)
{ 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;}
20、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中调试通过。
21、用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的记录。
22、用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事件(服务器端),这两个事件的处理程序只是将接收到的内容添加到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;
}
23、用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的帮助文件。
24、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码。
25、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( );
}
---- 由于篇幅有限,以上只列出了基本部分的代码,其他功能的实现,如关闭程序,打开窗口等,比较简单,不在赘述。程序运行时,你将看到在屏幕的右下角任务栏有一扇门打开又关闭的动画图标。是不是很有趣,快编一个你喜欢的吧。
26、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;
}
27、用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();
28、读磁片磁区
一、以前的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 = ( sDiskImage
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、取得本地internet机器的名字及IP地址
11、用C++Builder创建数字签名
12、用Enter 键控制焦点切换的方法
13、拦 截 Windows 消 息
14、使用CommaText
15、程序开始时先显示信息框
16、怎样获取程序的命令行参数?
17、如何监视剪贴板
18、如何使用OnIdle事件
19、用C++Builder编写串行异步通信程序
20、C++BUILDER非可视组件的消息处理技巧
21、用C++Builder 建立数据库VCL使用经验
22、用C++ Builder创建基于Internet的点对点Chat
23、用C++Builder获取应用程序图标
24、BIG5到GB的转换技术
25、C++BUILDER让你的任务栏图标动起来
26、TFORM
27、用BCB在windows桌面创建快捷方式
28、读磁片磁区
29、I/O 端口读写的实现
30、检测鼠标位置
31、令Win32 应用程序跳入系统零层
32、如何取得Memo的行和列
33、使用Sockets
34、Windows95/98下怎样隐藏应用程序不让它出现在CTRL-ALT-DEL对话框中?
35、怎样隐藏应用程序的任务条图标
36、编写自己的Ping.exe程序
37、用C++Builder在WINNT下编制一个Service
38、如何在C++ BUILDER中自动关闭WINDOWS屏保
39、显示/隐藏任务栏图标
40、信箱监视程序
41、C++Building制作闹钟
42、拨号上网IP地址的检知
43、用C++ Builder编写Tray程序
44、怎样用代码来最小化或恢复程序
45、制作主窗口显示前的版权窗口
46、判断是否已经联到 internet
47、获取登陆用户名
48、隐藏桌面图标
49、程序启动时运行
50、控制面板的调用
51、模拟键盘按键
52、让标题栏闪烁
53、启动屏幕保护
54、年月日星期的取法
55、键盘事件
56、隐藏任务栏
57、禁止关机
58、怎样以最小化方式启动程序
59、在Memo中增加一行后,如何使最后一行能显示
60、设置壁纸方法
1、怎样在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 "C" __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 "C" __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 "C" __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("xxx.dll");
ddd=GetProcAddress(dd,"test");
Caption=IntToStr(ddd());
FreeLibrary(dd);
三、注意:
创建DLL时编译链接时注意设置Project Options。
Packages标签:去除Builder with runtime packages检查框。
Linker标签:去除Use dynamic RTL检查框。
否则创建的DLL需要Runtime packages or Runtime library。
2、用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函数,具体如何编程大家可以参阅相关资料或者同我联系。
3、如何在C++Builder中检测硬件
在我们编写的程序中常常要和硬件打交道,那么如何在程序中确定系统中是否有该设备,它的运行状态又是怎样的呢?对于初学者来说,这个问题常常不好解决,其实只需简单地利用几个API函数,硬件的问题并不神秘。下面就让我们一起看看在C++ Build er中是如何检测硬件的。
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("您的CPU类型是:"+String( systeminfo.dwProcessorTy pe ));
}
运行它,点击Test试试,CPU型号出来了吧!
2.检测内存状态
获得内存状态的方法和CPU型号差不多,只是他用到的是另外一个API:GlobalMe moryStatus。其中,成员dwTotalPhys用来获得物理内存总量,而dwAvailPhys顾名思义是有效物理内存的意思。我们只要把下面几行代码加到上面程序的后面就可以了(不用重做,下同):
//获得内存状态
MEMORYSTATUS memory;
memory.dwLength =sizeof(memory); //初始化
memory.dwLength =sizeof(memory); //初始化
GlobalMemoryStatus(&memory);
Memo1->Lines->Add("您的物理内存是(Mb):"+String(int(memory.dwTotalPh ys /1024/1024)));
Memo1->Lines->Add("其中可用内存是(Kb):"+String(int( memory. /1024)) );
怎么样,看出点门道了么?两段程序的格式几乎一模一样,其实,GetSystemInf o和GlobalMemoryStatus还可以获得许多其他有关CPU和内存的信息,就按照上面的格式 去套就行了,更详细的资料可以去看C++ Builder4的Help。
3. 检测可用硬盘空间
好了,经过前面两个简单问题的热身,我们来处理一个稍微复杂的问题:我们知道安装程序大都有一个检测硬盘空间的过程,那么这是怎么实现的呢?他用到的是 API函数GetDiskFreeSpace,这个函数输入一个参数:目标盘的路径;返回四个参数,依次是每簇的扇区数、每扇区的字节数、空闲的簇数、总簇数。假如我们需要检测C盘的总容量 和可用容量,那么可以把以下代码加到上面的程序中:
//获得C盘可用空间
DWORD sector,byte,cluster,free;
long int freespace,totalspace;
GetDiskFreeSpace("C:",§or,&byte,&free,&cluster); //获得返回参 数
totalspace=int(cluster)*int(byte)*int(sector)/1024/1024; //计算总容量 freespace=int(free)*int(byte)*int
(sector)/1024/1024; //计算可用空间
Memo1->Lines->Add("C盘总空间(Mb):"+String(totalspace));
Memo1->Lines->Add("C盘可用空间(Mb):"+String(freespace));
怎么样?现在可以自己做安装程序了吧!
4. 检测CD-ROM
我们在编写程序时常常需要读取CD-ROM,可是究竟哪一个盘符是光驱呢?有人是 桓雠谭?是光驱呢?有人?? 将最后一个盘符当作光驱的,但是当遇到双光驱或者MO的情况时常常会出错。其实这个问题用一个API来解决并不困难,这就是:GetDriveType(),这个函数返回一个0~6之间的值,依次代表:0―未知盘、1―不存在、2―可移动磁盘、3―固定磁盘、4―网络磁盘、5―CD-ROM、6―内存虚拟盘。因此我们可以添加下面代码来寻找CD-ROM:
// 获得CD-ROM信息
UINT type;
char name;
for (name=‘C’;name<=‘Z’;name++) //循环检测A~Z
{ type = GetDriveType((String(name)+String(‘:’)).c_str()); //获得磁 盘类型
if (type==5)
Memo1->Lines->Add("您的光驱盘符为:"+String(name));
}
得到光驱盘符之后我们可以进一步利用API函数GetVolumeInformation检测光驱中 是否有光盘,这个函数如果成功调用,会得到磁盘的卷标序列号等信息;如果调用失败 则可知光驱中无光盘,程序如下://检测光盘(假设光驱为G:)
char volname[255],filename[100];//buffer[512];
DWORD sno,maxl,fileflag ;
if (!(GetVolumeInformation("G:", volname,255,&sno,&maxl,&fileflag ,filename,100)))
//如果返回值为假
Memo1->Lines->Add ("G驱中没有发现光盘");
else
//如果返回值为真
{Memo1->Lines->Add ("G驱中光盘卷标为:"+String(volname));
Memo1->Lines->Add ("G驱中光盘序号为:"+String(sno));
}
5. 检测声卡配置
在编制多媒体程序时,我们常常会用到声音文件,而当这些程序在没有配置声卡的机器上运行时,我们应该给出必要的警告。对于声卡的检测,可以分别通过waveOutG etNumDevs()和midiOutGetNumDevs()检测波形设备和MIDI设备,再利用waveOutGetDevC aps()和midiOutGetDevCaps()获得声音设备的细节资料。将下面一段代码加入上面的程 序即可,但要注意将#include 添至程序首部:
//检测声卡
int wavedevice,mididevice;
WAVEOUTCAPS wavecap;
MIDIOUTCAPS midicap;
wavedevice=(int)waveOutGetNumDevs(); //波形设备信息 mididevice=(int)midiOutGetNumDevs(); // MIDI设备信息mididevice=(int)midiOutGetNumDevs(); // MIDI设备信息
if (wavedevice==0)
Memo1->Lines->Add ("没有发现波形设备");
else
{waveOutGetDevCaps(0,&wavecap,sizeof(WAVEOUTCAPS));
Memo1->Lines->Add ("当前波形设备:"+String(wavecap.szPname));
}
if (mididevice==0)
Memo1->Lines->Add ("没有发现MIDI设备");
else
{midiOutGetDevCaps(0,&midicap,sizeof(MIDIOUTCAPS));
Memo19->Lines->Add ("当前MIDI设备:"+String(midicap.szPname));
}
6. 检测显示器信息
编写和图形图像有关的程序时常常需要检测显示器的分辨率和色深,最后我们来看看这个问题的解决办法。分辨率的求法很简单,直接调用Screen对象的属性就行了。而要求色深则要利用API函数GetDeviceCaps获得每像素的比特数和色彩的页面数,然后计算2的"每像素的比特数"次幂即得色彩的梯度数,再计算"色彩的梯度数"的"色彩 的页面数"次幂即得色深。程序如下:
//检测显示器
int tcs;
long int bpp,cp,tc;
Memo1->Lines->Add ("当前分辨率为:"+String(Screen->Width)+"*"+S tring(Screen->Height));
bpp=GetDeviceCaps(Form1->Canvas->Handle ,BITSPIXEL);
bpp=GetDeviceCaps(Form1->Canvas->Handle ,BITSPIXEL);
tcs=pow(2,bpp); //计算色彩的梯度数
cp= GetDeviceCaps(Form1->Canvas->Handle,PLANES);
tc= pow(tcs,cp); //计算色深
Memo1->Lines->Add("当前色深为:"+String(tc));
好了,现在在让我们点击一下Test吧,其实本文所涉 及的API函数的功能不止这些,大家下去可以查一查Win32 API手册,或者直接在C++ Builder 4中察看Help。相信自己开发一个硬件检测软件也不是难事哦!
4、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
5、利用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() $#@62; ll_EndTime
CloseStatusWindow()
6、用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工具来讲,开发这类程序也是相当方便的,按照前述的方法,可以在极短的时间开发出屏幕保护程序。对于屏幕保护程序,在本文中没有说明的就是如何设置口令的问题,这部分就由读者自己摸索吧。
7、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. */
};
8、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");
}
9、判断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();
}
}
//----------------------------------------------------------------------
注意: 有些目录是空的。有些特定的目录在这个文件系统上并没有一个相应的目录。
10、取得本地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();
}
11、用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(″文件没被修改″);
}
以上是一个数字签名的简单实现,得到的签名数据可以单独保存,也可以分开保存。
12、用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;
}
}
13、拦截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。
14、使用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;
}
15、程序开始时先显示信息框
一、软件进入主窗口前,先显示一个信息框,告诉用户一些有关该软件的信息,比如软件名称,版本号等。该信息框在显示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;
}
16、怎样获取程序的命令行参数?
你可以用下面的两种不同的技巧来解决这个问题。
技巧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
17、如何监视剪贴板
在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;
}
18、如何使用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;
19、用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(TObject�Sender)
{ 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;}
20、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中调试通过。
21、用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的记录。
22、用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事件(服务器端),这两个事件的处理程序只是将接收到的内容添加到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;
}
23、用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的帮助文件。
24、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码。
25、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( );
}
---- 由于篇幅有限,以上只列出了基本部分的代码,其他功能的实现,如关闭程序,打开窗口等,比较简单,不在赘述。程序运行时,你将看到在屏幕的右下角任务栏有一扇门打开又关闭的动画图标。是不是很有趣,快编一个你喜欢的吧。
26、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;
}
27、用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();
28、读磁片磁区
一、以前的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 = ( sDiskImage
没有评论:
发表评论