您正在查看: c++builder 分类下的文章

C++ Builder 研究--怎样在C++ Builder中创建使用DLL

  动态链接库(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;另外可用:tlibxxx.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。

C++ Builder 研究--在CB6下基于api函数编写串口通信程序简介

1-在C++ Builder 6.0下基于api函数编写串口通信程序简介:

在dos/win95/win98的年代,操作系统对串口是不保护的,也就是说将串口的的资源完全

开放给用户,用户可以用直接操作硬件的函数(比如说TC2.0下的inport()和outport()函数)

跟串口直接打交道,这时候用户使用直接操作串口的函数怎样"折磨"串口都是没有问题的,

操作系统根本就不管不问,对串口操作所造成的一切后果都是用户一个人承担的,这时候用

户对串口具有高度自由的支配权;但是,这种情况好景不长,从win2000操作系统开始,微软

为了"照顾好"计算机上的硬件,开始实施了对硬件的保护策略,也就是说任何用户在他的操作

系统下企图操纵串口时必须经过他的同意方可进行,其实也就是变相的将用户往必须使用他的

通信api函数才能操作串口这条"羊肠小路"上赶(当然也有别的方法操作串口,但那些并非我等

普通用户能研究明白的),形象一点说就好像你想怎样操作串口的意图必须经过win2000的翻译

(其实是win2000的设备驱动程序)才能转达给串口一样,基于这一点我们说(其实是很多资料上

说的)win2000下通过api函数操作串口是具有"设备无关性的",什么意思呢?就是说你想怎样

操作串口就用相应的api函数告诉操作系统你想对串口干什么,然后操作系统就把你的意思转

告给串口让其做出相应的动作,相对于dos/win95/win98下来说,据我理解也就相当于你原来

写的直接操作串口的函数在win2000下他替你完成了,但是你必须用win2000通信api函数清楚

地向操作系统表达清楚你到底想干什么,所以说在这种情况下要想写好串口驱动程序你就必须

至少弄明白win2000下的通信api函数都是干什么的方可,啰里啰唆唠叨了这么多... ...sorry,

还没完呢,至少还有一件事我想说,原来在dos/win95/win98系统下有好多高手用c/c++对串口

进行直接操作是非常熟练的,尤其是dos时代的turbo 2.0操作串口的高手他们写的串口驱动程

序直到win98的时候还用的非常洋洋得意,但是到了win2000的时候,他们的程序突然不好使了

,而他们有的可能还会因为知识结构上的滞后始终弄不明白怎么回事儿,兄弟们,你们该明白

了吧?闲话少叙,下面介绍笔者写串口通信函数时用到的各个api函数---------

2-CreateFile()

用途:打开串口

原型:HANDLE CreateFile(LPCTSTR lpFileName,

DWORD dwDesiredAccess,

DWORD dwShareMode,

LPSECURITY_ATTRIBUTES lpSecurityAttributes,

DWORD dwCreationDistribution,

DWORD dwFlagsAndAttributes,

HANDLE hTemplateFile);

参数说明:

-lpFileName:要打开的文件名称。对串口通信来说就是COM1或COM2。

-dwDesiredAccess:读写模式设置。此处应该用GENERIC_READ及GENERIC_WRITE。

-dwShareMode:串口共享模式。此处不允许其他应用程序共享,应为0。

-lpSecurityAttributes:串口的安全属性,应为0,表示该串口不可被子程序继承。

-dwCreationDistribution:创建文件的性质,此处为OPEN_EXISTING.

-dwFlagsAndAttributes:属性及相关标志,这里使用异步方式应该用FILE_FLAG_OVERLAPPED。

-hTemplateFile:此处为0。

操作说明:若文件打开成功,串口即可使用了,该函数返回串口的句柄,以后对串口操作时

即可使用该句柄。

举例:HANDLE hComm;

hComm=CreateFile("COM1", //串口号

GENERIC_READ|GENERIC_WRITE, //允许读写

0, //通讯设备必须以独占方式打开

NULL, //无安全属性

OPEN_EXISTING, //通讯设备已存在

FILE_FLAG_OVERLAPPED, //异步I/O

0); //通讯设备不能用模板打开

hComm即为函数返回的串口1的句柄。

3-CloseHandle()

用途:关闭串口

原型:BOOL CloseHandle(HANDLE hObjedt)

参数说明:

-hObjedt:串口句柄

操作说明:成功关闭串口时返回true,否则返回false

举例:CloseHandle(hComm);

4-GetCommState()

用途:取得串口当前状态

原型:BOOL GetCommState(HANDLE hFile,

LPDCB lpDCB);

参数说明:

-hFile:串口句柄

-lpDCB:设备控制块(Device Control Block)结构地址。此结构中含有和设备相关的

参数。此处是与串口相关的参数。由于参数非常多,当需要设置串口参数

时,通常是先取得串口的参数结构,修改部分参数后再将参数结构写入。

在此仅介绍少数的几个常用的参数:

DWORD BaudRate:串口波特率

DWORD fParity:为1的话激活奇偶校验检查

DWORD Parity:校验方式,值0~4分别对应无校验、奇校验、偶校验、校验

置位、校验清零

DWORD ByteSize:一个字节的数据位个数,范围是5~8

DWORD StopBits:停止位个数,0~2分别对应1位、1.5位、2位停止位

操作举例:DCB ComDCB; //串口设备控制块

GetCommState(hComm,&ComDCB);

5-SetCommState()

用途:设置串口状态,包括常用的更改串口号、波特率、奇偶校验方式、数据位数等

原型:BOOL SetCommState(HANDLE hFile,

LPDCB lpDCB);

参数说明:

-hFile:串口句柄

-lpDCB:设备控制块(Device Control Block)结构地址。要更改的串口参数包含在此结构中。

操作举例:DCB ComDCB;

GetCommState(hComm,&ComDCB);//取得当前串口状态

ComDCB.BaudRate=9600;//更改为9600bps,该值即为你要修改后的波特率

SetCommState(hComm,&ComDCB;//将更改后的参数写入串口

6-WriteFile()

用途:向串口写数据

原型:BOOL WriteFile(HANDLE hFile,

LPCVOID lpBuffer,

DWORD nNumberOfBytesToWrite,

LPDWORD lpNumberOfBytesWritten,

LPOVERLAPPED lpOverlapped);

参数说明:

-hFile:串口句柄

-lpBuffer:待写入数据的首地址

-nNumberOfBytesToWrite:待写入数据的字节数长度

-lpNumberOfBytesWritten:函数返回的实际写入串口的数据个数的地址,利用此变量可判断

实际写入的字节数和准备写入的字节数是否相同。

-lpOverlapped:重叠I/O结构的指针

操作举例:DWORD BytesSent=0;

unsigned char SendBytes[5]={1,2,3,4,5};

OVERLAPPED ov_Write;

ov_Write.Offset=0;

ov_Write.OffsetHigh=0;

WriteFile(hComm, //调用成功返回非零,失败返回零

SendBytes, //输出缓冲区

5, //准备发送的字符长度

&BytesSent, //实际发出的字符数

&ov_Write); //重叠结构

如果函数执行成功的话检查BytesSent的值应该为5,此函数是WriteFile函数执行完毕后

自行填充的,利用此变量的填充值可以用来检查该函数是否将所有的数据成功写入串口

7-ReadFile()

用途:读串口数据

原型:BOOL ReadFile(HANDLE hFile,

LPVOID lpBuffer,

DWORD nNumberOfBytesToRead,

lpNumberOfBytesRead,

lpOverlapped);

参数说明:

-hFile:串口句柄

-lpBuffer:存储被读出数据的首地址

-nNumberOfBytesToRead:准备读出的字节个数

-NumberOfBytesRead:实际读出的字节个数

-lpOverlapped:异步I/O结构,

操作举例:unsigned char ucRxBuff[20];

COMSTAT ComStat;

DWORD dwError=0;

DWORD BytesRead=0;

OVERLAPPED ov_Read;

ov_Read.hEvent=CreateEvent(NULL, true, false, NULL);//必须创建有效事件



ClearCommError(hComm,&dwError,&ComStat);//检查串口接收缓冲区中的数据个数

bResult=ReadFile(hComm, //串口句柄

ucRxBuff, //输入缓冲区地址

ComStat.cbInQue, //想读入的字符数

&BytesRead, //实际读出的字节数的变量指针

&ov_Read); //重叠结构指针

假如当前串口中有5个字节数据的话,那么执行完ClearCommError()函数后,ComStat

结构中的ComStat.cbInQue将被填充为5,此值在ReadFile函数中可被直接利用。

8-ClearCommError()

用途:清除串口错误或者读取串口现在的状态

原型:BOOL ClearCommError(HANDLE hFile,

LPDWORD lpErrors,

LPCOMATAT lpStat

);

参数说明:

-hFile:串口句柄

-lpErrors:返回错误数值,错误常数如下:

1-CE_BREAK:检测到中断信号。意思是说检测到某个字节数据缺少合法的停止位。

2-CE_FRAME:硬件检测到帧错误。

3-CE_IOE:通信设备发生输入/输出错误。

4-CE_MODE:设置模式错误,或是hFile值错误。

5-CE_OVERRUN:溢出错误,缓冲区容量不足,数据将丢失。

6-CE_RXOVER:溢出错误。

7-CE_RXPARITY:硬件检查到校验位错误。

8-CE_TXFULL:发送缓冲区已满。

-lpStat:指向通信端口状态的结构变量,原型如下:

typedef struct _COMSTAT{

...

...

DWORD cbInQue; //输入缓冲区中的字节数

DWORD cbOutQue;//输出缓冲区中的字节数

}COMSTAT,LPCOMSTAT;

该结构中对我们很重要的只有上面两个参数,其他的我们可以不用管。

操作举例:COMSTAT ComStat;

DWORD dwError=0;

ClearCommError(hComm,&dwError,&ComStat);

上式执行完后,ComStat.cbInQue就是串口中当前含有的数据字节个数,我们利用此

数值就可以用ReadFile()函数去读串口中的数据了。

9-PurgeComm()

用途:清除串口缓冲区

原型:BOOL PurgeComm(HANDLE hFile,

DWORD dwFlags

);

参数说明:

-hFile:串口句柄

-dwFlags:指定串口执行的动作,由以下参数组成:

-PURGE_TXABORT:停止目前所有的传输工作立即返回不管是否完成传输动作。

-PURGE_RXABORT:停止目前所有的读取工作立即返回不管是否完成读取动作。

-PURGE_TXCLEAR:清除发送缓冲区的所有数据。

-PURGE_RXCLEAR:清除接收缓冲区的所有数据。

操作举例:PurgeComm(hComm, PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);

清除串口的所有操作。

10-SetCommMask()

用途:设置串口通信事件。

原型:BOOL SetCommMask(HANDLE hFile,

DWORD dwEvtMask

);

参数说明:

-hFile:串口句柄

-dwEvtMask:准备监视的串口事件掩码

注:在用api函数撰写串口通信函数时大体上有两种方法,一种是查寻法,另外一种是事件通知法。

这两种方法的区别在于收串口数据时,前一种方法是主动的周期性的查询串口中当前有没有

数据;后一种方法是事先设置好需要监视的串口通信事件,然后依靠单独开设的辅助线程进行

监视该事件是否已发生,如果没有发生的话该线程就一直不停的等待直到该事件发生后,将

该串口事件以消息的方式通知主窗体,然后主窗体收到该消息后依据不同的事件性质进行处理。

比如说当主窗体收到监视线程发来的RX_CHAR(串口中有数据)的消息后,就可以用ReadFile()

函数去读串口。该参数有如下信息掩码位值:

EV_BREAK:收到BREAK信号

EV_CTS:CTS(clear to send)线路发生变化

EV_DSR:DST(Data Set Ready)线路发生变化

EV_ERR:线路状态错误,包括了CE_FRAME\CE_OVERRUN\CE_RXPARITY 3钟错误。

EV_RING:检测到振铃信号。

EV_RLSD:CD(Carrier Detect)线路信号发生变化。

EV_RXCHAR:输入缓冲区中已收到数据。

EV_RXFLAG:使用SetCommState()函数设置的DCB结构中的等待字符已被传入输入缓冲区中。

EV_TXEMPTY:输出缓冲区中的数据已被完全送出。

操作举例:SetCommMask(hComm,EV_RXCHAR|EV_TXEMPTY);

上面函数执行完毕后将监视串口中有无数据和发送缓冲区中的数据是否全部发送完毕。

11-WaitCommEvent()

用途:用来判断用SetCommMask()函数设置的串口通信事件是否已发生。

原型:BOOL WaitCommEvent(HANDLE hFile,

LPDWORD lpEvtMask,

LPOVERLAPPED lpOverlapped

);

参数说明:

-hFile:串口句柄

-lpEvtMask:函数执行完后如果检测到串口通信事件的话就将其写入该参数中。

-lpOverlapped:异步结构,用来保存异步操作结果。

操作举例:OVERLAPPED os;

DWORD dwMask,dwTrans,dwError=0,err;



memset(&os,0,sizeof(OVERLAPPED));

os.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);

if(!WaitCommEvent(hComm,&dwMask,&os)){

//如果异步操作不能立即完成的话,函数返回FALSE,并且调用GetLastError()函

//数分析错误原因后返回ERROR_IO_PENDING,指示异步操作正在后台进行.这种情

//况下,在函数返回之前系统设置OVERLAPPED结构中的事件为无信号状态,该函数

//等待用SetCommMask()函数设置的串口事件发生,共有9种事件可被监视:

//EV_BREAK,EV_CTS,EV_DSR,EV_ERR,EV_RING,EV_RLSD,EV_RXCHAR,

//EV_RXFLAG,EV_TXEMPTY;当其中一个事件发生或错误发生时,函数将

//OVERLAPPED结构中的事件置为有信号状态,并将事件掩码填充到dwMask参数中

if(GetLastError()==ERROR_IO_PENDING){

/**************************************************************/

/
在此等待异步操作结果,直到异步操作结束时才返回.实际上此时 /

/
WaitCommEvent()函数一直在等待串口监控的事件之一发生,当事件发/

/
生时该函数将OVERLAPPED结构中的事件句柄置为有信号状态,此时 /

/
GetOverlappedResult()函数发现此事件有信号后马上返回,然后下面/

/
的程序马上分析WaitCommEvent()函数等到的事件是被监视的串口事 /

/
件中的哪一个,然后执行相应的动作并发出相应消息. */

/**************************************************************/

GetOverlappedResult(hComm,&os,&dwTrans,true);

switch(dwMask){

case EV_RXCHAR:

PostMessage(Parent,WM_COMM_RXCHAR,0,0);

break;

case EV_TXEMPTY:

PostMessage(Parent,WM_COMM_TXEMPTY,0,0);

break;

case EV_ERR:

switch(dwError){

case CE_FRAME:

err=0;

break;

case CE_OVERRUN:

err=1;

break;

case CE_RXPARITY:

err=2;

break;

default:break;

}

PostMessage(Parent,WM_COMM_ERR,(WPARAM)0,(LPARAM)err);

break;

case EV_BREAK:

PostMessage(Parent,WM_COMM_BREAK,0,0);

break;

case ...://其他用SetCommMask()函数设置的被监视的串口通信事件。

... ...

break;

default:break;

}

}

12-以上简要介绍了大部分的串口通信api函数,笔者所写的串口通信软件用的是事件通知方式,该方式是

windows2000下效率较高的一种方式。而且只熟悉这些api函数也还是不够的,该机制下还要牵涉到多

线程和消息机制,其中读写串口的动作是由主线程来完成的,比如说操作者按下发送数据的按钮之后

,相应函数马上将某特定区域里面的数据发送出去,所以说用api函数写串口发送数据的功能是相对较

简单的。收数据的时候就要麻烦一点,在打开串口后首先主线程要设置要监视的串口通信事件,然后

将监视线程打开,用来监视主线程设置的这些串口通信事件是否已发生,当其中的某个事件发生后,

监视线程马上将该消息发送给主线程,其中监视线程在发送消息之前要确保主线程在收到消息后肯定

的知道串口究竟发生了什么样的事件,然后根据不同的事件类型进行处理。下面给出大致的主线程和

监视线程的大致工作流程:



主线程打开(其实就是主窗体打开之后)

|

|

V

打开串口(设置波特率、校验方式、数据位数、停止位数)

|

|

V

设置监视线程需要监视的串口通信事件

|

|

V

打开监视线程

|

|

V

等待各种事件的发生(比如发送数据单击事件,更改通信参数事件,监视线程发来的消息等)

--------------------------------------------------------------------------------

监视线程被打开

|

|

V

串口事件发生否(WaitCommEvent())(无论发生否均进入下面的代码)

|

|

V

异步操作是否正在后台进行?(if(GetLastError()==ERROR_IO_PENDING))

|

|

V

在此等待异步操作结果(GetOverlappedResult(hComm,&os,&dwTrans,true))

|

|

V

处理通信事件,根据事件类型的不同给主窗体发送不同的消息

---------------------------------------------------------------------------------

以上给出最最简单的工作流程,其实我感觉任何知识都是依靠查资料、问别人等等这些途径获得入门的

基本信息之后再依靠自己孜孜不倦的钻研得来的,饭来张口得到的那种东西往往不如自己嚼出来的东西

体会更深,有一句话我感觉很有味道:"书读百遍自然熟",但得是用心的读,或许一遍不懂,两遍不懂

,然后没有恒心的人便开始悄悄的准备放弃了,但有恒心的人当再读完一遍之后会越发的发现:奥,...

...,原来上次不懂的地方是这么回事...,但我个人认为,刚开始是很重要的,如果你连这个课题的最最

基本的东西都不知道的话那就很难了,所以任何人都应该有问别人的时候。

由于水平实在有限,只能介绍到这种层次了,而且直到现在我仍然在怀疑我写的串口软件是不是还

有什么不可靠的地方,但这只能靠时间来检验了,也希望大家帮我挑出毛病来,我的串口软件的位置在

http://www.chinabcb.com首页的"工具"一栏中,名字叫"串口大师(或串口监视软件)",欢迎试用。

虽然写的不好,但我感觉对于这方面的初学者还是应该有while(1){一点点}帮助的,如有对此话题

感兴趣者欢迎与我联系并讨论,欢迎转载,转载前请与本人联系!

作者:张景光

来自:沈阳

职业:电子工程师

邮箱:winloop@163.com

QQ: 43583576

C++ Builder 研究--在Windows NT 下实现对I/O地址的访问

Windows NT 操作系统设置的进程模式会使运行在其中的应用程序访问I/O地址的指令引起保护性的失败。这使得应用程序需要附以一个设备驱动程序进行I/O操作。设备驱动程序运行在内核模式,这使得在这种状态的中运行的进程可以执行I/O操作。



---- Windows 95/98 是仅为 Intel 类型机器设计的,没有额外复杂的I/O需求,而Windows NT 被设计成可以在不同机器机构上进行移植。这使得Windows NT 的系统模式要求驱动程序的编写者要考虑一台机器可能有多种类型的总线,这可能需要在总线之间传递地址。这种模式还要区别I/O空间和内存空间。在多总线的机器中每一总线可以既支持内存又支持I/O循环。



---- 根据定义,I/O寄存器或者端口访问是通过I/O循环实现的。然而,在一些系统中外部总线的I/O空间可以被映像到进程内存空间。硬件抽象层(Hardware Abstract Layer)决定这些。要访问I/O寄存器,驱动程序编写者必须知道寄存器在那一总线,它的I/O空间地址在那条总线。一条总线是由其接口类行 (如 ISA 、PCI 等)和编号(从零开始)决定的。



---- 下面是一个假象设备访问I/O的例子,接口类型:ISA 编号 0 地址 0xE700。设备描述如下: Offset Size Usage 0 1 Command register 1 1 Status register 2 2 Word data register 4 4 Dword data register



---- 用开发NT 设备驱动程序的工具包DriverDorks 可以用以下 步骤访问设备:

---- 建立一个KIoRange的对象映像设备寄存器。

KIoRange DeviceIos; Status = DevceIos.Initialize(
Isa, // 总线类型
0, // 总线号
0xE700, // 总线地址
8, // 设备数
TRUE // 映像到系统空间(如果端口是内存映像的)
);

if(NT_SUCCESS(status)) //建立成功



---- 可以用KIoRange 的成员函数访问寄存器:
//寄存器偏移量
#define COMMAND 0
#define STATUS 1
#define WDATA 2
#define DDATA 3


//读状态寄存器
UCHAR DeviceStatus = DeviceIos.inb(STATUS);


//写命令寄存器
DeviceIos.outb(COMMAND,CMD_RESET);


//写20个字到端口
DeviceIos.outw(WDATA,buffer,20);



---- 另外也可以建立KIoRegister 的对象来访问设备:
KIoRegister CommandReg = DeviceIos[COMMAND];
KIoRegister StatusReg = DeviceIos[STATUS];
CommandRge=(UCHAR)RESET; //写 RESET命令
UCHAR status=StatusReg; //读状态寄存器
如果在同一函数中频繁访问寄存器用KioRegiser 比用KIoRange 的成员函数的性能好一些。无论如何,数据类型必须正确(UCHAR,USHORT,ULONG),这些决定了到总线上数据的实际大小.

C++ Builder 研究--再谈CMOS密码

对于CMOS而言,相信大家已经不再陌生。但就CMOS密码而言,我想真正了解的人就不太多了,所以我们就做了些实验,研究了一下。以前已经有不少人讨论过了,但我觉得还是有再谈的必要,下面就把其中合适的部分拿出来,以飨各位。

在谈密码之前,还是先说说什么是CMOS(本文所言CMOS均针对Award而言)。CMOS实际上存放的是计算机的系统时钟和硬件配置方面的一些信息,供系统引导时读取;同时初始化计算机各个部件的状态,总共有128个字节,存放在RAM芯片中。



好了,先看一个例子,用来向大家说明一下CMOS的一些结构,下面这128个字节就是我的CMOS的内容:

00000000H 30 00 FF 00 39 00 FF 00 12 00 FF 00 01 00 18 00

秒 秒报警 分 分报警 小时 时报警 星期 日

00000010H 11 00 98 00 26 00 02 00 70 00 80 00 00 00 00 00

月 年 寄存器A 寄存器B 寄存器C 寄存器D 诊断 下电

00000020H 40 00 7E 00 F0 00 03 00 0F 00 80 00 02 00 00 00

软驱 密码域 硬盘 未知 设备 基本内存 扩充

00000030H 7C 00 2E 00 00 00 7F 00 15 00 86 00 00 00 00 00

内存 硬盘类型 未知 密码数据位 未知

00000040H 00 00 00 00 00 00 00 00 00 00 00 00 E2 00 22 00

未知

00000050H 0F 00 FF 00 FF 00 E1 00 22 00 3F 00 08 00 59 00

未知

00000060H 00 00 7C 00 19 00 80 00 FF 00 FF 00 FF 00 FF 00

未知 世纪值 未知

00000070H 7D 00 81 00 AA 00 0F 00 39 00 9B 00 E8 00 19 00

未知



上述的内容参考了其他资料,所以不一定完全正确,不过38H-3AH的密码数据倒可以肯定,所以接下来就切入正题,谈一谈CMOS的密码,由于我能找到的均为Award的BIOS,所以以下的结论均针对Award的CMOS,并且在以下的主板及相应的BIOS上验证通过。(本文一下数值均为16进制)

主板名及型号 BIOS版本 BIOS日期

Aopen(建基)AP58 R1.50c 1998-07-13

Aopen(建基)AX5T R1.80 1998-07-30

EPoX(磐英)MVP3E 未知 1998-08-03

EPoX(磐英)P2-112A 未知 1998-09-16

FIC(大众)PA-2007 v1.0A 1997-06-25

在38H-3BH这四个字节中,由于39H和3BH这两个字节一直为00H,所以就略过,那么CMOS密码的关键就集中到了38H和3AH这两个字节上。先介绍一点Award的密码规则,Award允许一位至八位密码,每一个字符的范围由20H-7FH,也就是由空格到ASCII码的127号。想必大家已经发现了,八个字符要放到两个字节中去,好象不压缩一下是不行的。的确,Award是将其压缩了,但是不是普通的压缩方法,我想Award另有将其加密的想法,因为在CMOS中空位还很多,要想放八个字节看来是没有什么问题的,不过这么裸露的密码就更加没有什么用处了。通常的压缩方式有无损压缩,如zip,arj等,或者是有损压缩,象mpeg,jpeg等。但是对这么几个字节,这些方法就没有什么用武之地了,而且压缩过的东西,应该是可以还原的,否则压来压去就没有什么意义了。不过Award的方法就不同了,他不仅仅进行了超级的有损压缩,而且这种压缩是不可还原的,下面就给出他的加密压缩方法(以下数值,运算均基于16进制):

假如有一密码,八位,记为:ABCDEFGH(每一位的取值范围为20H-7FH),将其按下列公式运算:H+4G+10F+40E+100D+400C+1000B+4000A ,将结果按由低到高保存到:H1,H2,H3,字节中,然后将H2保存到地址:3AH中,将H1和H3的和保存到38H中。如果密码不足八位,以此类推。下面举一实例:

我的密码为:r
vte,ASCII码为:72H、2AH、76H、74H、65H,按公式运算得:72100+2A40+7610+744+65=8615,于是H1=00H,H2=86H,H3=15H,所以3AH的值为86H,38H的值为15H。

看来密码就这么简单,在你每次输入密码的时候,BIOS将其算算,再与CMOS中的值比较一下,如果一样就放行,否则免谈。过程就是这样,不过还是有些问题要说明,先算算看,两个字节可以表达的密码可以有多少种:164=65536种,而八位密码,每一位有96种选择,则可以表示的密码有:968≈7.2×1015 种,所以理论上说,每一个密码,都可以找出大约1011这么多个可以起相同作用的密码。但是事实上并不是大家都是八位的密码,或许没有大得这么吓人,不过也挺多的,就如我那个密码,光与他相同功能的五位密码就有二十五万多个,而六位,七位,八位的更多,数量不详,因为从来没有把他算完过,时间太长了,耗不起。所以我随便把我们研究用的那个小程序附上了,可以搜索Award的CMOS密码,大家也可算算看。

CMOS的密码谈的也差不多了,也该停笔了。不过为了减少大家的疑问,再多谈几句。现在的BIOS都比较先进了,在CMOS的设置中,大多有User和SuperVisor密码的设置,我这里讨论的地址为User的,至于SuperVisor的,自各儿研究吧,因为密码这个东西,说得太明,大家都没趣了,对不。好了,就此打住。

C++ Builder 研究--用PcommPro开发串行通信程序

用C++Builder在Win9x下开发串行通信程序是程序员们经常遇到却又令人头痛的事情,不但要理解许多复杂的API函数,还要掌握多线程编程。令人欣慰的是有一些公司专门为C++Builder开发了编写串行通信程序的开发工具,例如MOXA公司的Pcomm(该软件可在http:\www.moxa.com.tw下载),因而帮我们解决了串行编程这一难题。



----下面结合一个具体的例子来说明串行通信程序的开发。本程序的编程环境是Win98和C++Builder3.0。这个编程示例的功能比较强,它具有发送数据和自动接收数据的双重功能。在它的基础上稍加修改,即可以让用户选定进行传输的通信端口,并设定这个端口的相关参数,包括波特率、数据位、停止位、奇偶校验和流量控制等。



一、Pcomm的设置

----启动C++Builder3.0,点击File/NewApplication,建立一个项目文件,修改表单的Name属性为Comm,然后存盘,命名项目(Project)为CommTest,命名单元(Unit)为Comm。

----PcommLibrary是一个动态连接库(DLL)文件,当使用C++Builder编译器编译PComm.dll库时,我们必须告诉C++Builder的编译器怎样找到这些函数(sio_xxx())。



----因此我们用PCommPro在BorlandC++Builder中开发一个串行程序时,必须做到以下两点:



假如你的PcommPro是安装在c:\Programfiles(缺省安装目录)目录下,把c:\Programfiles\PcommPro\Lib下的Pcommb.lib文件加入到C++Builder的View菜单中ProjectManager的项目中,使之成为项目的一个单元(unit)。

把#include"c:\Programfiles\PcommPro\Lib\PComm.h"包含在你的Comm.cpp中。

二、表单及属性的设置

----添加控件,设置各控件的Name和Caption属性。



----该通信程序的工作原理为中断方式,即当输入缓存内有数据时,就会触发Pcomm的中断函数sio_cnt_irq(Port,CntIrq,count),再由它启动中断服务程序CntIrq(),然后由数据接收函数sio_read(port,ibuf,len)来接收数据并做其他相应的处理;至于函数Open()、Sent()、Close()则分别为打开按钮、发送按钮、关闭按钮的click事件函数;SendData、ReceiveData分别为发送数据编辑框和接收数据编辑框相对应的字符串变量。



三、主程序的编制

----双击表单上的Button控件,就会产生相应的事件,如双击"打开"按钮,就会产生Open()事件函数。在这些函数中添加代码,以及PcommPro的串行控制函数,就能实现对串口事件的处理。其中一个需要注意的问题是SendData和ReceiveData都为AnsiString字符串,而PcommPro的函数所需发送和接收的字符串都为char型,因此要正确使用Pcomm函数,还要注意字符串转换。AnsiString字符串可通过c_str()函数转换为char型,而char行字符串转换为AnsiString则比较简单。可用AnsiString(char)把char型强制转换为AnsiString型。程序主要代码如下所示:

void__fastcallTComm::Open(TObject
Sender)

//串口打开函数

{

inti;

sio_open(port);//打开串口

sio_ioctl(port,B2400,P_NONE|BIT_7|STOP_1);

//设置串口参数

 //包括波特率、数据位、停止位、奇偶校验

void__stdcall(p)(int);

p=cntirq;

i=sio_cnt_irq(port,
p,1);//设置中断函数

}

//-----------------

void__fastcallTComm::Sent(TObjectSender)

//数据发送函数

{

char
SendData=newchar[20];

SendData=SentEdit->Text.c_str();

//把SendEdit中的AnsiString型字

符串转换为char型

sio_write(port,SendData,20);//发送数据

}

//-----------------

void__fastcallTComm::Close(TObjectSender)

//串口关闭函数

{

sio_close(port);//关闭串口

}

//-----------------

void__stdcallcntirq(intport)

//中断服务函数(手工生成函数)

{

charibuf[20];

AnsiStringReceiveData[20];

sio_read(port,ibuf,20);//接收数据

ReceiveData=Ansistring(ibuf);

//char字符串转换AnsiString型字符串

ReceiveEdit->Text=ReceiveData;

//显示接受到的字符串

}



----在程序中,我们使用了一些sio_xxx()型的函数,它们都是Pcommpro自带的串行通信函数(函数的具体用法可以参考Pcommpro的帮助),通过这些函数,我们可以对串行端口进行设置。



----sio_open(port)和sio_close(port)为打开串口和关闭串口函数,参数port可设置具体操作的串口;sio_ioctl(intport,intbaud,intmode)为串口控制函数,可设置串口的波特率、数据位、停止位、奇偶校验;至于sio_write(port)和sio_read(port),则为读串口和写串口函数;sio_cnt_irq(intport,VOID(CALLBACK
func)(intport),intcount)为中断函数,当串口有数据时,就触发该函数,然后该函数就会启动其中断服务程序VOID(CALLBACK*func)(intport)(为一函数指针),这里是调用cntirq()函数接收数据,该函数需要程序员手工生成。



----由此我们可以看出,只要我们对程序稍加修改,在表单上再添加一些控件,使得sio_xxx()这些函数的参数可以由用户界面输入,就可以做到由用户选定进行数据传输的通信端口,并设定这个端口的相关参数,包括波特率、数据位、停止位、奇偶校验和流量控制等.