Category Archives: Delphi

Delphi数据库编程技巧

  我原来在万千的新闻组(news://news.webking.com.cn/)Delphi版上闲逛那段时间,发现经常有人提出一些数据库方面的技巧性问题,问题其实不难,但是要一个简单的解决方案,可能也破费思量,特别在此简单谈谈:

1. 如何动态设置ODBC源

很多时候程序员需要自动生成ODBC数据源,而不是指导客户"打开控制面板… … ",到底如何去做呢?相信很多人会选择编程修改注册表来实现这项功能,因为ODBC的详细信息全部存放在注册表的下述键值内:

"HKEY_LOCAL_MACHINESOFTWAREODBC"

修改一下ODBC的配置,通过前后的注册表比较,你可以发现一定的规律。在这里我只是想说,哥们,别这么犯傻了(如果我让你写一个通用的ODBC源处理程序,你得累死),用这个Windows ODBC API函数吧,

function SQLConfigDataSource(hwndParent: Integer; fRequest: Integer;

lpszDriverString: String; lpszAttributes: String): Integer;

stdcall;external ''ODBCCP32.DLL'';

了解数据库编程的朋友都知道,数据库的访问方式不论DAO、ADO、ODBC或是BDE或是其它第三方的数据库连接控件,归根结底,都是一些个函数集,只要你愿意,你可以编写出自己的数据库访问方式用以替代。深入研究这些底层函数,很多时候会为你提供相当地便利。

SQLConfigDataSource这个函数MSDN有详细的说明,我不想整段翻译下来让你扁我,我只是结合流行的SQL Server谈谈如何有技巧的调用该函数。其它的数据库大同小异。

SQLConfigDataSource(0, ODBC_ADD_SYS_DSN,''SQL Server'',
''DSN=Record_ODBC''+ chr(0) +
''Server=(local)''+ chr(0) +
''Database=master''+ chr(0) +
''Description=DragonPC SQLServer ODBC Source''+ chr(0));

这是我的Delphi程序中调用该函数的一个实例,第一个参数是父窗口句柄,设置为0则该函数不显示任何对话框。第二个参数是操作类型,你需要定义如下的操作类型常量:

Const
ODBC_ADD_DSN = 1; // Add a new user data source.
ODBC_CONFIG_DSN = 2; // Configure (modify) an existing user data source.
ODBC_REMOVE_DSN = 3; // Remove an existing user data source.
ODBC_ADD_SYS_DSN = 4; // Add a new system data source.
ODBC_CONFIG_SYS_DSN = 5; // Modify an existing system data source.
ODBC_REMOVE_SYS_DSN = 6; // Remove an existing system data source.

从名字我们知道,要添加ODBC源,我们需要调用的是ODBC_ADD_SYS_DSN 或是ODBC_ADD_DSN参数。第三个参数也没有什么好说的,我们添加的是SQL Server数据库的ODBC源,所以填入''SQL Server''参数,如果需要建立Excel文件的ODBC数据源,我们可以填入''Excel Files (*.xls)'',这些字符串参数相信各位同志在添加ODBC源时,已经多次见过。

关键的是第三个参数的设置,不同的数据库类型所支持的关键字是不一样的,这里仅仅就SQL Server所支持的关键字作一个简单说明:

DSN:你的ODBC数据源名称。

Server:你的数据库服务器名称,使用(local)指的是本地计算机安装的数据库。注:最新的SQL Server 2000支持一台计算机运行多个SQL Server服务,这个时候你需要指定SqlSever的InstanceName。

Address:指定SQL Server服务器的网络IP地址。

Database:指定默认数据库名称。

Language:指定默认语言。

Description:备注信息。

详细的参数和信息可以查阅微软网站的以下网址。

http://msdn.microsoft.com/library/psdk/dasdk/odch3kit.htm

http://msdn.microsoft.com/library/psdk/sql/od_odbc_c_99yd.htm

  2. 如何动态设置BDE别名

这个问题其实是考察程序员对BDE的 TSession组件的熟悉程度,一个数据库程序的建立,即使你没有显式的添加TSession组件,系统中依然存在一个名字为Session的 TSession对象,你可以在任何位置调用该对象的方法和属性。TSession类的很多方法可以帮助我们的应用程序获取系统BDE环境,下面介绍一个 代码片断用以添加一个BDE别名:

var
BDEList : TStringList ;
...
begin
...
BDEList := TStringList.Create () ;
try
Session.GetAliasNames(BDElist) ; // 获取系统所有BDE别名列表
if BDEList.IndexOf(''DragonPC'')= -1 then begin // 如果没有我们的BDE别名"DragonPC"
BDEList.Clear ;
BDEList.Add(''SERVER NAME='' + ''SQLServerName'')); // 数据库服务器名称
BDEList.Add(''DATABASE NAME=master'') ; // 默认数据库
BDEList.Add(''USER NAME=sa''); // 用户名
Session.AddAlias(''DragonPC'', ''MSSQL'', BDEList) ; // 添加一个MSSQL类型的BDE别名
ShowMessage(''系统即将建立BDE别名!'') ;
Session.SaveConfigFile() ; // 存储BDE配置
end;
finally
BDEList.Free ;
end;

这么简单,用户就可以随时建立、删除和修改BDE别名(有兴趣的朋友可以查看TSession组件的源代码,看看调用了哪些BDE函数)。另外像 DeleteAlias,ModifyAlias,GetDatabaseNames,GetDriverNames, GetStoredProcNames,GetTableNames,GetPassword等等TSession类的方法,使用起来非常简单,通过 Delphi的随机帮助,读者可以试着自己调用一下看看。通过对Session的灵活应用,再配合我下面将要提到的扑捉SQL异常的技巧,你完全可以写一 个媲美SQL Explorer的通用数据库查询工具。

  3. 如何扑捉运行SQL语句时的错误

老是有朋友在开发一些开放的数据库接口(比如Delphi的SQL Explorer工具)时发愁,既然是开放的,当然需要允许用户使用SQL语句访问数据库,这些还好办,一旦用户的运行SQL语句出现错误,程序员如何扑捉该异常呢?很简单,看看下面的函数:

Const
ExecSQLMode = 0 ;
OpenSQLMode = 1 ;
ResultRight = ''SQL query result is right'' ;
...
function RunSql(RunQuery: TQuery; Sqls: TStringList; var ErrorMsg: string;
Mode: integer) : integer ;
begin
ErrorMsg := ResultRight ;
Result := 0 ;
try
RunQuery.DatabaseName := ''RecordDB'' ;
RunQuery.SQL.Clear() ;
RunQuery.SQL.AddStrings(Sqls) ;
if Mode = ExecSQLMode then
RunQuery.ExecSQL()
else
RunQuery.Open() ;
except
on e:exception do
ErrorMsg := e.Message + #13 + #10 +''--- 错误是俺发现的 ---'' ;
end;
end;

朋友看明白了吧,我的函数很简单,将SQL语句代码段作为参数传递给TQuery组件,通过设置查询方式(ExecSQLMode、 OpenSQLMode)来处理有结果集返回的数据查询语句(select)或是没有结果集返回的数据操作语言(update、delete、 insert、create等)。而异常的扑捉呢,我们扑捉所有异常的老祖宗Exception,因为Delphi的所有异常都是从Exception继 承下来的,这样一个简单的SQL语句运行和异常处理函数就完成了,一旦返回的ErrorMsg的值不是ResultRight,ErrorMsg就会返回 异常的信息,程序员就可以加以判断以处理不同的情况。

Read: 908

全力提升 TIniFile 的读取速度

全力提升 TIniFile 的读取速度

软件保存设置一般是写入本地目录,或者写入注册表。前者使用的文件格式很多,多为 IniFile (现在 XML 也开始流行)。后者是将信息储存在注册表中,并通过的 TRegistry 或者标准的 WindowsAPI 来读写。后者有个很大的好处:不需要对多用户系统进行考虑,Windows 可以很好的控制多用户的切换。但缺点是往往卸载软件不将注册表中生成的记录信息删除干净。而制作绿色软件的作者多采用 IniFile 方式保存信息。

IniFile 本来也没有什么好多说的,有现成的工具 TIniFile,也有标准的 WindowsAPI。不过偶然发现当读取 IniFile 的一整个记录较多的 Section 时,性能很差。根据我的测试,当一个 Section 有超过 61 条记录的时候,比直接用 TStringList 打开一个文件,并分析出 Section 的记录要慢。问题出在哪里呢?我估计可能是大量的时间被耗费在打开文件和记录定位上了。

设想一下:如果我们将带有 600 条记录的语言文件保存在 IniFile 中。用上述2种方法在速度上的差异甚大。我在 GOSURF 浏览器中做了比较。使用标准的 TIniFile 读取需要 2.78 秒,而使用了利用 TStringList 之后,时间缩短为 0.102秒。这 2 秒的时间对于一个软件的启动来说,算是相当长的了。

随后我对 TIniFile 的各种操作进行了测试,发现 ReadSectionValues 是有性能明显性能瓶颈的。于是动手改造了一番:

procedure TTntIniFile.ReadSectionValuesEx(const Section: WideString; Strings: TTntStrings);

var

BeginIdx, EndIdx: Integer;

KeyValues: TTntStringList;

I: Integer;

S: WideString;

begin

KeyValues := TTntStringList.Create;

try

    KeyValues.LoadFromFile(FileName);

    BeginIdx := 0;

    while (BeginIdx <= KeyValues.Count - 1) do

    begin

      if Trim(KeyValues[BeginIdx]) = '[' + Section + ']' then Break;

      Inc(BeginIdx);

    end;

    EndIdx := BeginIdx + 1;

    while (EndIdx <= KeyValues.Count - 1) do

    begin

      S := Trim(KeyValues[EndIdx]);

      if (S <> '') and (S[1] = '[') and (S[Length(S)] = ']') then Break;

      Inc(EndIdx);

    end;

    Strings.BeginUpdate;

    try

      for I := BeginIdx + 1 to EndIdx - 1 do

        if KeyValues[I] <> '' then

           Strings.Add(KeyValues[I]);

    finally

      Strings.EndUpdate;

    end;

finally

    KeyValues.Free;

end;

end;

注1:如果没有安装过 TntControls 的朋友,只要将 WideString 改成 string,将 TTnt 开头的对象都改成 T 即可。

注2:第二个参数 Strings: TTntStrings 是抽象类。你可以使用 TStringList,不过我推荐使用 THashedStringList。当记录数量很大的时候搜索某条记录 (如:Strings.Values['LastRecode']) 可能也相当耗时。不使用 THashedStringList 那 ReadSectionValuesEx 的优势无法完全体现。

另外,DELPHI 中还提供了一种 TMemIniFile。原理是一次性将整个 IniFile 文件读入内存,以提高响应速度。但灵活性和稳定性不如使用 TIniFile + ReadSectionValuesEx 来的好。当然将常用的调用单条记录制作成函数会更加灵活方便。如 IniReadString(const FileName, Section, Ident, Default: WideString): WideString; 这些函数就请你自己扩充了 :D

最后说一下 XML。它的性质和 IniFile 差不多。不过他的结构更多样化,你可以轻松把一个 Object 保存在文件中,阅读也比较直观。我把 IniFile 文件看成简单的一层目录结构,那 XML 是多层目录结构。当然分析一个 XML 也相对会稍慢一些。待有时间,再向大家推荐好用高效的 TXMLParser。

Read: 1012

【转】浅谈Object Pascal的指针

大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上。因此,说 指针是C语言的灵魂,一点都不为过。同时,这种说法也让很多人 产生误解,似乎只有C语言的指针才能算指针。Basic不支持指针,在此不论。其实,Pascal语言本身也是支持指针的。从最初的Pascal发展至今 的Object Pascal,可以说在指针运用上,丝毫不会逊色于C语言的指针。

以下内容分为八个部分,分别是
一、类型指针的定义
二、无类型指针的定义
三、指针的解除引用
四、取地址(指针赋值)
五、指针运算
六、动态内存分配
七、字符数组的运算
八、函数指针

一、类型指针的定义。对于指向特定类型的指针,在C中是这样定义的:
int *ptr;
char *ptr;
与之等价的Object Pascal是如何定义的呢?
var
ptr : ^Integer;
ptr : ^char;
其实也就是符号的差别而已。

二、无类型指针的定义。C中有void *类型,也就是可以指向任何类型数据的指针。Object Pascal为其定义了一个专门的类型:Pointer。于是,
ptr : Pointer;
就与C中的
void *ptr;
等价了。

三、指针的解除引用。要解除指针引用(即取出指针所指区域的值),C 的语法是 (*ptr),Object Pascal则是 ptr^。

四、取地址(指针赋值)。取某对象的地址并将其赋值给指针变量,C 的语法是
ptr = &Object;
Object Pascal 则是
ptr := @Object;
也只是符号的差别而已。

五、指针运算。在C中,可以对指针进行移动的运算,如:
char a[20];
char *ptr=a;
ptr++;
ptr+=2;
当执行ptr++;时,编译器会产生让ptr前进sizeof(char)步长的代码,之后,ptr将指向a[1]。ptr+=2;这句使得ptr前进两 个sizeof(char)大小的步长。同样,我们来看一下Object Pascal中如何实现:
var
a : array [1..20] of Char;
ptr : PChar; //PChar 可以看作 ^Char
begin
ptr := @a;
Inc(ptr); // 这句等价于 C 的 ptr++;
Inc(ptr, 2); //这句等价于 C 的 ptr+=2;
end;

六、动态内存分配。C中,使用malloc()库函数分配内存,free()函数释放内存。如这样的代码:
int *ptr, *ptr2;
int i;
ptr = (int*) malloc(sizeof(int) * 20);
ptr2 = ptr;
for (i=0; i<20; i++){
*ptr = i;
ptr++;
}
free(ptr2);
Object Pascal中,动态分配内存的函数是GetMem(),与之对应的释放函数为FreeMem()(传统Pascal中获取内存的函数是New()和 Dispose(),但New()只能获得对象的单个实体的内存大小,无法取得连续的存放多个对象的内存块)。因此,与上面那段C的代码等价的 Object Pascal的代码为:
var ptr, ptr2 : ^integer;
i : integer;
begin
GetMem(ptr, sizeof(integer) * 20);
//这句等价于C的 ptr = (int*) malloc(sizeof(int) * 20);
ptr2 := ptr; //保留原始指针位置
for i := 0 to 19 do
begin
ptr^ := i;
Inc(ptr);
end;
FreeMem(ptr2);
end;
对 于以上这个例子(无论是C版本的,还是Object Pascal版本的),都要注意一个问题,就是分配内存的单位是字节(BYTE),因此在使用GetMem时,其第二个参数如果想当然的写成 20,那么就会出问题了(内存访问越界)。因为GetMem(ptr, 20);实际只分配了20个字节的内存空间,而一个整形的大小是四个字节,那么访问第五个之后的所有元素都是非法的了(对于malloc()的参数同 样)。

七、字符数组的运算。C语言中,是没有字符串类型的,因此,字符串都是用字符数组来实现,于是也有一套str打头的库函数以进行字符数组的运算,如以下代码:
char str[15];
char *pstr;
strcpy(str, "teststr");
strcat(str, "_testok");
pstr = (char*) malloc(sizeof(char) * 15);
strcpy(pstr, str);
printf(pstr);
free(pstr);
而 在Object Pascal中,有了String类型,因此可以很方便的对字符串进行各种运算。但是,有时我们的Pascal代码需要与C的代码交互(比如:用 Object Pascal的代码调用C写的DLL或者用Object Pascal写的DLL准备允许用C写客户端的代码)的话,就不能使用String类型了,而必须使用两种语言通用的字符数组。其实,Object Pascal提供了完全相似C的一整套字符数组的运算函数,以上那段代码的Object Pascal版本是这样的:
var str : array [1..15] of char;
pstr : PChar; //Pchar 也就是 ^Char
begin
StrCopy(@str, 'teststr'); //在C中,数组的名称可以直接作为数组首地址指针来用
//但Pascal不是这样的,因此 str前要加上取地址的运算符
StrCat(@str, '_testok');
GetMem(pstr, sizeof(char) * 15);
StrCopy(pstr, @str);
Write(pstr);
FreeMem(pstr);
end;

八、函数指针。在动态调用DLL中的函数时,就会用到函数指针。假设用C写的一段代码如下:
typedef int (*PVFN)(int); //定义函数指针类型
int main()
{
HMODULE hModule = LoadLibrary("test.dll");
PVFN pvfn = NULL;
pvfn = (PVFN) GetProcAddress(hModule, "Function1");
pvfn(2);
FreeLibrary(hModule);
}
就我个人感觉来说,C语言中定义函数指针类型的typedef代码的语法有些晦涩,而同样的代码在Object Pascal中却非常易懂:
type PVFN = Function (para : Integer) : Integer;
var
fn : PVFN;
//也可以直接在此处定义,如:fn : function (para:Integer):Integer;
hm : HMODULE;
begin
hm := LoadLibrary('test.dll');
fn := GetProcAddress(hm, 'Function1');
fn(2);
FreeLibrary(hm);
end;

Read: 673

类的定义和使用

转自:http://mrmzy.bokee.com/tb.b?diaryId=15467469

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Edit4: TEdit;
    Label5: TLabel;
    Edit5: TEdit;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Label6: TLabel;
    Label7: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
private
    { Private declarations }
public
    { Public declarations }
end;

var
Form1: TForm1;
type
    Tstudent=class
    private
      name:string;
      yy:real;
      math:real;
      hx:real;
      total:real;
    public
      procedure getname(nm:string);virtual;
      procedure getyy(cj:real);virtual;
      procedure getmt(cj:real);virtual;
      procedure gethx(cj:real);virtual;
      procedure caculat();virtual;
      function returntotal():real;virtual;
end;

implementation

function Tstudent.returntotal():real;
begin
returntotal:=total;
end;

procedure Tstudent.getname(nm:string);
begin
if name<>nm then
    name:=nm;
end;

procedure Tstudent.getyy(cj:real);
begin
if yy<>cj then
    yy:=cj;
end;

procedure Tstudent.getmt(cj:real);
begin
if math<>cj then
    math:=cj;
end;

procedure Tstudent.gethx(cj:real);
begin
if hx<>cj then
    hx:=cj;
end;

procedure Tstudent.caculat();
begin
total:=yy+math+hx;
end;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
student:Tstudent;
begin
if (edit1.Text='') or (edit2.Text='') or (edit3.Text='') or (edit4.Text='') then
     edit5.Text:='请检查输入!'
else
     begin
       student:=Tstudent.Create;
       student.getname(edit1.Text);
       student.getyy(strtofloat(edit2.Text));
       student.getmt(strtofloat(edit3.Text));
       student.gethx(strtofloat(edit4.Text));
       student.caculat();
       edit5.Text:=floattostr(student.returntotal());
     end
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
edit1.Text:='';
edit2.Text:='';
edit3.Text:='';
edit4.Text:='';
edit5.Text:='';
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
close;
end;

end.

Read: 168