(说明:发布在电脑编程技巧与维护2007年第4期上)
摘 要 本文介绍用DLL封装通用的软件注册,系统登录对话窗体、修改密码窗体和关于窗体。形成登录框架,供不同软件系统调用,实现代码复用。
关键字 DLL,Delphi,登录对话,注册表,软件保护,代码复用
一、前言
在软件系统的开发过程中,为了维护软件所有者的权益和保证系统的安全性,软件需要注册授权后才能运行,操作员需要登录授权后才能登录使用。对于软件公司,这部分重复工作量很大。能不能把这部分功能封装起来,实现代码复用呢?
本文提出用DLL封装登录框架的新方法,新开发一个项目时,只需要几行代码调用就可以分别实现软件注册,系统登录,修改密码和关于对话框等功能。
二、DLL简介
DLL(动态链接库,Dynamic Link Library)简单来说是一种可通过调用执行的已编译的代码模块。当某个函数或过程需要被使用时,才从硬盘调用它进入内存,一旦没有程序再调用该DLL了,将其从内存中清除。多个应用程序调用同一个DLL,在内存里只有一个代码副本。而不象静态编译的程序那样每一个都必须全部的被装入。装载DLL时,它将被映射到进程的地址空间,同时使用DLL的动态链接并非将库代码拷贝,而仅仅记录函数的入口点和接口。
DLL还能带来共享的好处。一家公司开发的不同软件可能需要一些公用的函数/过程,这些函数/过程可以被封装为DLL,供不同的软件调用。本文就利用DLL这一优势,来实现代码复用。
三、设计与实现
1、数据库设计
首先在SQL Server2000中新建一个数据库,命名为DeanMis。然后在DeanMis数据库中建立表tSys_User,如图1所示:
在表tSys_User中添加一条操作员信息,如图2所示:
图 2 tSys_User表中数据
2、建立DLL
打开Delphi7,新建一个工程,保存,再打开File|New|Other|New|Dll Wizard,修改library名称为DeanLogin
加入4个Form窗体及相应控件,如图3所示:
图示3 软件界面
我们用setConn方法来设置数据库连接字符串,再用CheckPWD函数来调用系统登录对话框。如果登录成功,返回登录信息,通过EXPORTS引出例程名称。注意对于传出参数要在参数前加Var,主要代码如下:
library DeanLogin;
uses
FastMM4,SysUtils,Classes,Controls,Forms,Windows,
UfrmLogin in 'UfrmLogin.pas' {FrmLogin},//登录对话框
UfrmRegistry in 'UfrmRegistry.pas' {FrmRegistry},//软件注册
UfrmChangePW in 'UfrmChangePW.pas' {FrmChangePW},//修改密码对话框
UfrmAbout in 'UfrmAbout.pas' {FrmAbout},//关于对话框
UCommon in 'UCommon.pas',//简单加解密函数
UMAC in 'UMAC.pas';//获取计算机网卡MAC作为机器识别码
var
myConn:string;//定义公共变量,数据库连接字符串
{$R *.res}
procedure setConn(Conn:pChar); stdcall;
begin
myConn := StrPas(Conn);
end;
//验证系统登录,如果登录成功返回登录ID,用户名和姓名
function CheckPWD(AppHandle:THandle;var opID:pChar;var opName:pChar;var opUserName:pChar):Boolean; stdcall;
var
FrmLogin:TfrmLogin;
ResultModal:Integer;//窗体返回值
begin
opID:=pChar('');opName:=pChar('');opUserName:=pChar(''); Result:=false;
if myConn = '' then exit;
Application.Handle:= AppHandle;//把调用程序的句柄传递给Dll,避免任务栏出现Dll窗体的实例
FrmLogin:=TfrmLogin.Create(nil);
try
frmLogin.ADOQ.ConnectionString:=myConn;
ResultModal:=frmLogin.ShowModal;//打开登录对话窗体
if ResultModal=mrok then
begin
opID:=pChar(FrmLogin.sopID);
opName:=pChar(FrmLogin.sopName);
opUserName:=pChar(FrmLogin.sopUserName);
Result:=true;
end;
finally
frmLogin.free; //释放资源
Application.Handle:=0;
end;
end;
EXPORTS//导出例程名称
setConn,CheckPWD;
begin
end.
3、登录窗体和软件注册
首先登录对话框通过注册表检测软件是否已经注册,如果未注册显示注册按钮并提示用户注册。如果注册成功,把注册码写入注册表,供下次登录校验。
procedure TFrmLogin.btRegistryClick(Sender: TObject);
var
_Registry:TRegistry;
begin
//打开注册窗口
HardCode:=Trim(GetMACAddress);//得到机器识别码,即MAC
with TfrmRegistry.Create(self) do
begin
eID.Text:=HardCode;
if showmodal=mrOk then //如输入的注册码正确,则把注册码保存到注册表
begin
_Registry := TRegistry.Create;
with _Registry do
begin
RootKey:=HKEY_LOCAL_MACHINE;
if OpenKey('Software\DeanZhang',True) then
WriteString('Mis',eCode.text);
Free;
end;
HasReg:=True;
CheckReg;//根据注册情况,设置窗体上的控件
end;
Free;
end;
end;
如果验证软件已经注册,需向数据库校验用户登录信息,登录成功返回登录用户信息。
procedure TFrmLogin.btLoginClick(Sender: TObject);
var
UserName, UserPW: string;
begin
ModalResult := mrNone;
UserName := Trim(eUserName.Text);
UserPW := eUserPW.Text;
//………………校验输入数据
if UserLogin(UserName, UserPW) then begin
LoginOK := True;
ModalResult := mrOK;
end;
end;
function TfrmLogin.UserLogin(const UserName, UserPW: string): Boolean;
begin //向数据库验证登录信息
Screen.Cursor:=crHourGlass;
try
ADOQ.Close;
ADOQ.SQL.Text:='select * from tsys_user where fUserName=:UserName';
ADOQ.Parameters.ParamByName('UserName').Value:=trim(UserName);
ADOQ.Open;
except
MessageBox(Handle,'网络错误!','错误提示',MB_ICONHAND+MB_OK+MB_DEFBUTTON2);Result := False;
exit;
end;
if ADOQ.IsEmpty then
begin
Screen.Cursor := crDefault;
MessageBox(Handle,'无效的用户名!','错误提示',MB_ICONHAND+MB_OK+MB_DEFBUTTON2);eUserName.SetFocus;
Result := False;
exit;
end;
if not (ADOQ.FieldByName('fPwd').AsString=trim(UserPW)) then
begin
Screen.Cursor:=crDefault;
MessageBox(Handle,'密码错误!','错误提示',MB_ICONHAND+MB_OK+MB_DEFBUTTON2);eUserPW.SetFocus;
Result := False;
Exit;
End;
if ADOQ.FieldByName('fUserSign').AsString<>'1' then
begin
Screen.Cursor:=crDefault;
MessageBox(Handle,'此用户已禁用!','信息提示',MB_ICONWARNING+MB_OK+MB_DEFBUTTON2);Result := False;
Exit;
end;
Screen.Cursor:=crDefault;
_opID:=ADOQ.fieldbyname('fID').value; // 操作员编号
_opName:=ADOQ.fieldbyname('fTrueName').value; //操作员姓名
_opUserName:=ADOQ.fieldbyname('fUserName').value;//操作员登录用户名
if ADOQ.Active then ADOQ.Close;
Result := True;
end;
要返回的登录信息,我们通过定义属性来完成。如下:
property sopID: string read GetopID write SetopID;
4、其它窗体
其它窗体和登录对话窗体类似,通过ChangPWD函数来调用修改密码窗体、ShowAbout过程来调用关于窗体。注意要传入调用程序的句柄,不然会在任务栏出现被调用窗体的实例。
四、调用
DLL的调用有两种方式,一种是静态(隐式)调用,一种是动态(显式)调用。静态调用方法简单,但DLL会在启动调用程序时即被调入。动态调用相对比较复杂,仅在调用外部例程时才将DLL装载内存,节约内存空间。
DLL动态调用的原理是首先声明一个函数/过程类型并创建一个指针变量。为保证该指针与外部例程指针一致以确保赋值正确,函数/过程的声明必须和外部例程的原始声明一致。接下来通过Windows API函数LoadLibrary引入指定的库文件,LoadLibrary的参数是DLL文件名,返回一个THandle。如果该步骤成功,再通过另一个API函数GetProcAddress获得例程的入口地址,参数分别为LoadLibrary的指针和例程名,最终返回例程的入口指针。将该指针赋值给我们预先定义好的函数/过程指针,然后就可以使用这个函数/过程了。最后使用API函数FreeLibrary来减少DLL引用记数,以保证DLL使用结束后可以清除出内存。
本文调用实例设置数据库连接采用静态调用,其它窗体调用采用动态调用,静态调用声明:
procedure setConn(Conn:PChar); stdcall; external 'DeanLogin.dll';
动态调用声明:
HChangPWD: Thandle;
CheckPWD:function(AppHandle:THandle;var opID:pChar;var opName:pChar;var opUserName:pChar):boolean; stdcall;
调用登录窗体:
procedure TFrmMain.FormCreate(Sender: TObject);
var
popID,popName,popUserName:pChar;
bCheckPWD:boolean;
begin
setConn(pChar(cnnstr));//设置数据库连接字符串
HCheckPWD:= LoadLibrary('DeanLogin.dll');
try
@CheckPWD:=GetProcAddress(HCheckPWD,'CheckPWD');
bCheckPWD:= CheckPWD(Application.Handle,popID,popName,popUserName);
finally
FreeLibrary(HCheckPWD);
end;
if bCheckPWD then
begin//如果登录成功,给状态栏赋值 opID:=StrPas(popID);opName:=StrPas(popName);opUserName:=StrPas(popUserName);StatusBar1.Panels[1].Text:=opID;StatusBar1.Panels[3].Text:=opUserName;
StatusBar1.Panels[5].Text:=opName;
end
else application.Terminate;
end;
注意,在DLL用到ADO来连接数据库,在调用程序uses里面需引入ADODB单元。DLL的搜索路径的顺序是:当前目录;Path路径;windows目录;widows系统目录(system、system32),要确保调用的DLL在以上路径能找到。
五、结束语
本文通过对登录框架的DLL封装,熟悉DLL的开发和调用过程。把复用的代码封装,实现代码复用。以上程序在Delphi 7,Sql Server 2000 Sp4, Windows 2003 SP1环境下编译通过。在源程序中还引入FastMM(http://fastmm.sourceforge.net)内存管理,替换了Borland的内存管理器,提升了内存管理效率,并不再需要任何DLL的支持。
参考文献
1、 Delphi帮助文件