<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>经验累积</title><link>http://softdream.cndev.org/category/772.aspx</link><description>经验累积</description><managingEditor>棒棒糖</managingEditor><dc:language>zh-CHS</dc:language><generator>.Text Version 0.95.2004.101</generator><item><dc:creator>棒棒糖</dc:creator><title>FR  经验</title><link>http://softdream.cndev.org/archive/2008/06/19/59052.aspx</link><pubDate>2008-06-19 17:40:00Z</pubDate><guid>http://softdream.cndev.org/archive/2008/06/19/59052.aspx</guid><wfw:comment>http://softdream.cndev.org/comments/59052.aspx</wfw:comment><comments>http://softdream.cndev.org/archive/2008/06/19/59052.aspx#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://softdream.cndev.org/comments/commentRss/59052.aspx</wfw:commentRss><trackback:ping>http://softdream.cndev.org/services/trackbacks/59052.aspx</trackback:ping><description>Fastreport使用经验(转) 在Delphi程序中访问报表对象
最基本的方法就是frxReport1.FindObject。然后把返回的对象强制转换成它的类型，当然，在报表中必须真的有这么个东东。如改变一个

Tfrxmemoview的内容，可以这样写TfrxMemoView(frxReport1.FindObject('memo1')).Text:='jade';

还可以用TfrxReportPage的FindBand方法，这个方法的参数是Band类，如报表抬头就可以直接使用这个方法，因为抬头一个页中只有一个，如

果有多个同样的类。则不能使用这种方法。如果要使用TfrxreportPage，一般可以用这样的代码
TfrxReportPage(frxReport1.Pages[0])。当然，如果你的这个页是对话框型的，则不行了。但一般都是报表型的。

******使用上下标
在Fastreport中使用上下标是很简单的，只要用一个Tfrxmemoview，把AllowHTMLTags属性设为真，就可以使用网页标签来实现上下标了，如

12&lt;sup&gt;2&lt;/sup&gt;与24&lt;sub&gt;3&lt;/sub&gt;。就分别是2为上标，3为下标。

******打印页码
打印页码是很简单的，只要加入一些常量即可，如打印第几页共几页就可以使用
第[Page#]页   共[TotalPages#]页
这里要注意的一点是如果想正确显示总页数，必须选中二次报表。


******动态建立变量及变量组
建立变量组名
frxreport1.Variables.Add.Name:=' '+变量组名;
建立变量名
frxreport1.Variables.AddVariable('组名，如果为不存的组或空，则为默认组，这里不需要空格',变量名,变量初始值);
例如要建立变量组Yuan,二个变量Yuan1,Yuan2，则为
frxreport1.Variables.Add.Name:=' Yuan';注意前面是空格
frxreport1.Variables.AddVariable('Yuan',Yuan1,初始值)
frxreport1.Variables.AddVariable('Yuan',Yuan2,初始值)

******共用TFrxreport及TfrxDBDataSet
一个程序中，不管多么大的程序，只要打印或预览时是模式的，则完全可以共用一个TFrxreport变量及几个TfrxDBDataSet。只不过，要注意完

成一个报表程序的步骤，主要是下面几步
1)清除报表，得到一个全新的报表内容。
Frxreport1.clear。
2)设置要使用的TfrxDBDataSet的别名，如果不需要可以省略这一步，但一般最好不同的报表用不同的别名。
注意这一步要在加载报表文件之前，因为一般设计报表文件时已经包含了别名信息。
frxDBDataSet1.UserName:=别名;
3)加载报表或动态建立一个TfrxReportPage。
Frxreport1.LoadFromFile(报表文件的完整文件名);
4)关联TfrxDBDataSet与TDataset，并设置要使用哪些TfrxDBDataSet。
Frxreport1.DataSets.Clear;//先清除原来的数据集
frxDBDataSet1.DataSet:=dataset1;//关联Fastreport的组件与TDataset数据集。
Frxreport1.DataSets.Add(frxDBDataSet1);//加载关联好的TfrxDBDataSet到报表中。
经过这几步后，就可以像单独使用一个Tfrxreport一样使用共用的报表组件了。


******加入自定义函数
Fastreport可以自己加入需要的函数，来实现特定的功能。过程就是：
1)添加函数到报表中。
frxreport1.AddFunction('完整的函数声明');
如有一个自定义函数，为GetName(Old:String):String;这个函数通过数据集的一个字段，得到另一个返回值。
则语句为：frxreport1.AddFunction('Function GetName(Old:String):String;');
2)脚本中使用函数。
在脚本中或报表中使用自定义函数，就像使用其它Fastreport内置函数一样。
3)程序中处理函数。
使用函数是通过frxreport1的OnUserFunction函数来实现的。
OnUserFunction的声明如下：Function(const MethodName: String;var Params: Variant): Variant;
比如上面的函数，首先要有一个函数，这个函数是GetName的实现部分。如有一个在程序中实现的函数。
function RealGetName(Old:String):String;这个函数名是无所谓的，也可以是GetName。
在OnUserFunction的事件处理中有如下代码即可完成自定义函数在报表中的使用。
if CompareText(MethodName,'GetName')=0 then Result:=RealGetName(VarToStr(Params[0]));
我一般都是使用CompareText来比较函数名，因为我发现二个版本的Fastreport，一个是MethodName全部自动变成了小写，一个是全部自动变成

了大写，所以干脆用CompareText来比较，肯定不会出错。

如果有多个参数，则依次传递Params[0],Params[1]即可，要保持顺序一致。
这里要注意一点，如果参数为指针，则不能直接使用Pointer(Integer(Params[0]))。因为实际传递过来的是指针的整数值，可以使用Pointer

(StrToInt(VarToStr(Params[0])))。


******使用脚本，脚本中使用变量
很多时候，我们希望把对报表的控制放到报表的脚本中，通常我这样做有二个原因：
1)能够根据字段内容的变化而使用不同的设置，因为如果想在程序中实现这样功能，就不得不用自定义函数，函数的实现要放到程序中，函数

可能需要传递很多参数，效率低下。
2)把不同报表的控制放到脚本中，可以实现报表的模块化，程序只是简单的设置数据集的关系，并加载硬盘上的报表文件，不同报表的不同实

现方式，显示方式，均放到报表文件中，程序简洁，易维护，易升级。
当然，这样的缺点就是程序中加载报表时的数据集别名必须与设计报表时的别名一致。
脚本的使用与通常程序的使用并没有太多的区别，就是像正常的程序那样引用控件的名称即可。
但注意对变量的使用，需要把变量名或表达式用&lt;&gt;括起来。

******在脚本中根据字段名改变Tfrxmemoview的内容
假设有数据表&amp;#8220;用户&amp;#8221;，字段ID为用户标识，Name为用户名，打印时要求，如果用户名为空，则打印&amp;#8220;无用户名&amp;#8221;，否则打印出&amp;#8220;用户名：实

际的用户&amp;#8221;，则可以在ID的Tfrxmemoview控件的OnAfterData事件中写如下脚本。
if &lt;frxDBDataSet1."Name"&gt;='' then
Memo2.Text:='无用户名'
else
Memo2.Text:='用户名：[frxDBDataSet1."Name"]'
Memo2是放置用户名称数据的Tfrxmemoview控件。
这里注意，要在脚本中访问变量需要把变量用&lt;&gt;包括起来。


******实现连续打印
很多人认为Fr不能实现连续打印，以为只能通过自己写函数调用打印函数来实现连续打印，实际上，Fr可以轻易的实现连续打印，同时，实现

时又是非常简单，你甚至可以在你的程序的打印设置中简单的让客户选择是否连续打印，其它都可以保持不变。

function PelsTomm(Pels:Extended):Extended;
begin      
Result:=Pels/Screen.PixelsPerInch*25.4;
end;

procedure PrintSerial(Frx:TFrxReport;SequencePage:Byte=0);
var
P:TfrxReportPage;
R,R1:Extended;
begin
{必须是二遍报表，否则无法计算总页数。
下面的方法只适用于没有页脚的情况，因为如果有页脚的话
FreeSpace就始终为0了。可以用报表脚来代替。
因为是连续打印，也可以看作只有一页，报表脚也就相当于页脚了}
if not Frx.Engine.DoublePass then Exit;
//SequencePage指要连续打印的页面，普通报表就是0
P:=TfrxReportPage(Frx.Pages[SequencePage]);
R1:=P.TopMargin+P.BottomMargin;
while Frx.PrepareReport do
begin
    if (Frx.Engine.TotalPages&lt;=1) then Break;
    R:=Pelstomm(Frx.Engine.TotalPages*Frx.Engine.PageHeight-
      Frx.Engine.FreeSpace)+R1;
    P:=TfrxReportPage(Frx.Pages[SequencePage]);
    P.PaperHeight:=R;
end;
{必须用上面的循环代码来得到准确的空白区域
不能用通过计算总页数减去各页的页边距的方法来获得空白区域
因为如果碰到一条记录过宽的情况导致换页，就不准确了。}
R:=Pelstomm(Frx.Engine.TotalPages*Frx.Engine.PageHeight-
    Frx.Engine.FreeSpace)+R1;
P:=TfrxReportPage(Frx.Pages[SequencePage]);
P.PaperHeight:=R;
end;

在预览或打印前先调用PrintSerial即可。


也谈为Delphi中数据库报表加网格开发者在线 Builder.com.cn 更新时间:2007-11-02作者：佚名 来源:中国计算机报社
本文关键词： delphi 数据库 报表 网格
看了贵报第63期《为Delphi 3.0中数据库报表加上网格线》一文，笔者发现原文中的程序在不同分辨率的打印机（如180dpi的针式打印机和

600dpi的激光打印机）上打印表格，效果会完全不同。如作者以针打作为他的输出打印机设计的程序，在激光打印机上输出，就会发现表格和

文字错位，而且表格会打印得很小。而且原文中打印坐标的确定，必须靠反复的试验才能达到比较满意的效果。

　　针对原目标，笔者设计了一段程序。首先在窗体上添加一个DBGrid来显示我们所要打印的数据，在这里，DBGrid不只是起到显示数据的作

用，而且用户对DBGrid作的调整，例如改变了各字段的排列顺序，各字段的显示宽度等，都将直接反映到打印结果中去，也就是说，我们实际

上就是要把DBGrid的内容直接输出到打印机。以下程序在Win 98+Delphi 4下编译通过，代码如下：

procedure TForm1.Button2Click(Sender: TObject);
const
　　 LeftBlank=1; //定义页边距，单位厘米
　　 RightBlank=1;
　　 TopBlank=1;
　　 BottomBlank=1;
var
　　 PointX,PointY:integer;
　　 PointScale,PrintStep:integer;
　　 s:string;
　　 x,y:integer;
　　 i:integer;
begin //获取当前打印机的分辨率
PointX:=Trunc(GetDeviceCaps(Printer.Handle,LOGPIXELSX)/2.54);
　　PointY:=Trunc(GetDeviceCaps(Printer.Handle,LOGPIXELSY)/2.54);

　　//根据打印机和屏幕的分辨率计算出从屏幕转换到打印机的比例　
PointScale:=Trunc(GetDeviceCaps(Printer.Handle,LOGPIXELSX)
/Screen.PixelsPerInch+0.5); //横向打印
　 printer.Orientation:=poLandscape;

　　 //打印的字体和大小
　　printer.Canvas.Font.Name:=′宋体′;
　　printer.canvas.Font.Size:=10;

　　 //根据字体的大小确定每行的高度
　　s:=′漳州市刑警支队′;
　　PrintStep:=printer.canvas.TextHeight(s)+16;

　　 //打印的起点位置
　　x:=PointX*LeftBlank;
　　y:=PointY*TopBlank;

　　 //DataSource1是DBGrid1所连接的数据源

　　if ((DataSource1.DataSet).Active=true) and ((DataSource1.DataSet).RecordCount〉0) then
　　begin
　　printer.BeginDoc;
　　(DataSo e1.DataSet).First;
　　while not (DataSource1.DataSet).Eof do
　　begin //打印DBGrid中的所有列
　　 for i:=0 to DBGrid1.FieldCount－1 do
　　 begin
　　//假如所要打印的列超出了打印范围，则忽略该列
　　if (x+DBGrid1.Columns.Items[i].Width*PointScale)〈=(Printer.PageWidth－PointX*RightBlank) then
　　 begin //画表格线
//每页的第一行打印表头
　 Printer.Canvas.Rectangle(x,y,x+DBGrid1.Columns.
Items[i].Width*PointScale,y+PrintStep);
　 if y=PointY*TopBlank then
　　 Printer.Canvas.TextOut(x+8,y+8,DBGrid1.Columns[i].Title.Caption)
　　 else
　　 Printer.Canvas.TextOut(x+8,y+8,DBGrid1.Fields[i].asString);
　　end; //计算下一列的横坐标

　　 x:=x+DBGrid1.Columns.Items[i].Width*PointScale;
　　end;

　 if not (y=PointY*TopBlank) then
(DataSource1.DataSet).next;

　　 x:=PointX*LeftBlank;
　 y:=y+PrintStep; //换页
　 if (y+PrintStep)〉(Printer.PageHeight－PointY*BottomBlank) then
　 begin
　　 Printer.NewPage;
　　 y:=PointY*TopBlank;
　　 end;
　 end;

　 printer.EndDoc;
　　 (DataSource1.DataSet).First;
　　 Application.MessageBox(′打印完成′,′打印′,32);
　　end;
end;&lt;img src ="http://softdream.cndev.org/aggbug/59052.aspx" width = "1" height = "1" /&gt;</description></item><item><dc:creator>棒棒糖</dc:creator><title>设计的两种境界</title><link>http://softdream.cndev.org/archive/2007/12/18/57636.aspx</link><pubDate>2007-12-18 15:06:00Z</pubDate><guid>http://softdream.cndev.org/archive/2007/12/18/57636.aspx</guid><wfw:comment>http://softdream.cndev.org/comments/57636.aspx</wfw:comment><comments>http://softdream.cndev.org/archive/2007/12/18/57636.aspx#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://softdream.cndev.org/comments/commentRss/57636.aspx</wfw:commentRss><trackback:ping>http://softdream.cndev.org/services/trackbacks/57636.aspx</trackback:ping><description>有两种方式构建软件设计：一种是把软件做得很简单以至于明显找不到缺陷；另一种是把它做得很复杂以至于找不到明显的缺陷。&lt;img src ="http://softdream.cndev.org/aggbug/57636.aspx" width = "1" height = "1" /&gt;</description></item><item><dc:creator>棒棒糖</dc:creator><title>backup and restore</title><link>http://softdream.cndev.org/archive/2007/06/18/52233.aspx</link><pubDate>2007-06-18 14:12:00Z</pubDate><guid>http://softdream.cndev.org/archive/2007/06/18/52233.aspx</guid><wfw:comment>http://softdream.cndev.org/comments/52233.aspx</wfw:comment><comments>http://softdream.cndev.org/archive/2007/06/18/52233.aspx#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://softdream.cndev.org/comments/commentRss/52233.aspx</wfw:commentRss><trackback:ping>http://softdream.cndev.org/services/trackbacks/52233.aspx</trackback:ping><description>A. 还原完整数据库


说明 MyNwind 数据库仅供举例说明。


下例显示还原完整数据库备份。

RESTORE DATABASE MyNwind
FROM MyNwind_1

B. 还原完整数据库备份和差异备份
下例还原完整数据库备份后还原差异备份。另外，下例还说明如何还原媒体上的另一个备份集。差异备份追加到包含完整数据库备份的备份设备上。

RESTORE DATABASE MyNwind
FROM MyNwind_1
WITH NORECOVERY
RESTORE DATABASE MyNwind
FROM MyNwind_1
WITH FILE = 2

C. 使用 RESTART 语法还原数据库
下例使用 RESTART 选项重新启动因服务器电源故障而中断的 RESTORE 操作。

-- This database RESTORE halted prematurely due to power failure.
RESTORE DATABASE MyNwind
FROM MyNwind_1
-- Here is the RESTORE RESTART operation.
RESTORE DATABASE MyNwind
FROM MyNwind_1 WITH RESTART

D. 还原数据库并移动文件
下例还原完整数据库和事务日志，并将已还原的数据库移动到 C:\Program Files\Microsoft SQL Server\MSSQL\Data 目录下。

RESTORE DATABASE MyNwind
FROM MyNwind_1
WITH NORECOVERY,
MOVE 'MyNwind' TO 'c:\Program Files\Microsoft SQL Server\MSSQL\Data\NewNwind.mdf',
MOVE 'MyNwindLog1' TO 'c:\Program Files\Microsoft SQL Server\MSSQL\Data\NewNwind.ldf'
RESTORE LOG MyNwind
FROM MyNwindLog1
WITH RECOVERY

E. 使用 BACKUP 和 RESTORE 创建数据库的复本
下例显示使用 BACKUP 和 RESTORE 语句创建 Northwind 数据库的复本。MOVE 语句使数据和日志文件还原到指定的位置。RESTORE FILELISTONLY 语句用于确定待还原数据库内的文件数及名称。该数据库的新复本称为 TestDB。有关更多信息，请参见 RESTORE FILELISTONLY。

BACKUP DATABASE Northwind
TO DISK = 'c:\Northwind.bak'
RESTORE FILELISTONLY
FROM DISK = 'c:\Northwind.bak'
RESTORE DATABASE TestDB
FROM DISK = 'c:\Northwind.bak'
WITH MOVE 'Northwind' TO 'c:\test\testdb.mdf',
MOVE 'Northwind_log' TO 'c:\test\testdb.ldf'
GO

F. 使用 STOPAT 语法还原到即时点和使用多个设备进行还原
下例将数据库还原到其在 1998 年 4 月 15 日中午 12 点时的状态，并显示涉及多个日志和多个备份设备的还原操作。

RESTORE DATABASE MyNwind
FROM MyNwind_1, MyNwind_2
WITH NORECOVERY
RESTORE LOG MyNwind
FROM MyNwindLog1
WITH NORECOVERY
RESTORE LOG MyNwind
FROM MyNwindLog2
WITH RECOVERY, STOPAT = 'Apr 15, 1998 12:00 AM'

G. 使用 TAPE 语法还原
下例显示从 TAPE 备份设备还原完整数据库备份。

RESTORE DATABASE MyNwind
FROM TAPE = '\\.\tape0'

H. 使用 FILE 和 FILEGROUP 语法还原
下例还原一个包含两个文件、一个文件组和一个事务日志的数据库。

RESTORE DATABASE MyNwind
FILE = 'MyNwind_data_1',
FILE = 'MyNwind_data_2',
FILEGROUP = 'new_customers'
FROM MyNwind_1
WITH NORECOVERY
-- Restore the log backup.
RESTORE LOG MyNwind
FROM MyNwindLog1

I. 将事务日志还原到标记处
下例显示将事务日志还原到名为"RoyaltyUpdate"的标记处。

BEGIN TRANSACTION RoyaltyUpdate
WITH MARK 'Update royalty values'
GO
USE pubs
GO
UPDATE roysched
SET royalty = royalty * 1.10
WHERE title_id LIKE 'PC%'
GO
COMMIT TRANSACTION RoyaltyUpdate
GO
--Time passes. Regular database
--and log backups are taken.
--An error occurs.
USE master
GO

RESTORE DATABASE pubs
FROM Pubs1
WITH FILE = 3, NORECOVERY
GO
RESTORE LOG pubs
FROM Pubs1
WITH FILE = 4,
STOPATMARK = 'RoyaltyUpdate'&lt;img src ="http://softdream.cndev.org/aggbug/52233.aspx" width = "1" height = "1" /&gt;</description></item><item><dc:creator>棒棒糖</dc:creator><title>Delphi的组件读写机制(1)[转载]</title><link>http://softdream.cndev.org/archive/2007/01/27/50583.aspx</link><pubDate>2007-01-27 20:34:00Z</pubDate><guid>http://softdream.cndev.org/archive/2007/01/27/50583.aspx</guid><wfw:comment>http://softdream.cndev.org/comments/50583.aspx</wfw:comment><comments>http://softdream.cndev.org/archive/2007/01/27/50583.aspx#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://softdream.cndev.org/comments/commentRss/50583.aspx</wfw:commentRss><trackback:ping>http://softdream.cndev.org/services/trackbacks/50583.aspx</trackback:ping><description>Delphi 的消息

目  录
===============================================================================
  一个 GUI Application 的执行过程：消息循环的建立
  TWinControl.Create、注册窗口过程和创建窗口
  补充知识：TWndMethod 概述
  VCL 的消息处理从 TWinControl.MainWndProc 开始
  TWinControl.WndProc
  TControl.WndProc
  TObject.Dispatch
  TWinControl.DefaultHandler
  TControl.Perform 和 TWinControl.Broadcast
  TWinControl.WMPaint
  以 TWinControl 为例描述消息传递的路径
===============================================================================

 

正  文
===============================================================================
  一个 GUI Application 的执行过程：消息循环的建立
===============================================================================
通常一个 Win32 GUI 应用程序是围绕着消息循环的处理而运行的。在一个标准的 C 语言 Win32 GUI 程序中，主程序段都会出现以下代码：

while (GetMessage(&amp;msg, NULL, 0, 0))  // GetMessage 第二个参数为 NULL，
                                      // 表示接收所有应用程序产生的窗口消息
{
    TranslateMessage(&amp;msg);      // 转换消息中的字符集
    DispatchMessage(&amp;msg);       // 把 msg 参数传递给 lpfnWndProc
}

lpfnWndProc 是 Win32 API 定义的回调函数的地址，其原型如下：
int __stdcall WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

Windows 回调函数(callback function) 也通常被称为窗口过程(window procedure)，本文随意使用这两个名称，代表同样的意义。

应用程序使用 GetMessage 不断检查应用程序的消息队列中是否有消息到达。如果发现了消息，则调用 TranslateMessage。TranslateMessage 主要是做字符消息本地化的工作，不是关键的函数。然后调用 DispatchMessage(&amp;msg)。DispatchMessage(&amp;msg) 使用 msg 为参数调用已创建的窗口的回调函数(WndClass.lpfnWndProc)。lpfnWndProc 是由用户设计的消息处理方法。

当 GetMessage 在应用程序的消息队列中发现一条 WM_QUIT 消息时，GetMessage 返回 False，消息循环才告结束，通常应用程序在这时清理资源后也结束运行。

使用最原始的 Win32 API 编写的应用程序的执行过程是很容易理解的，但是用 Delphi VCL 组件封装消息系统，并不是容易的事。首先，Delphi 是一种面向对象的程序设计语言，不但要把 Win32 的消息处理过程封装在对象的各个继承类中，让应用程序的使用者方便地调用，也要让 VCL 组件的开发者有拓展消息处理的空间。其次，Delphi 的对象模型中所有的类方法都是对象相关的(也就是传递了一个隐含的参数 Self)，所以 Delphi 对象的方法不能直接被 Windows 回调。Delphi VCL 必须用其他的方法让 Windows 回调到对象的消息处理函数。

让我们跟踪一个标准的 Delphi Application 的执行过程，查看 Delphi 是如何开始一个消息循环的。

program Project1;
begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

在 Project1 的 Application.Initialize 之前，Delphi 编译器会自动插入一行代码：
SysInit._InitExe。_InitExe 主要是初始化 HInstance 和模块信息表等。然后 _InitExe 调用 System._StartExe。System._StartExe 调用 System.InitUnit；System.InitUnit 调用项目中所有被包含单元的 Initialization 段的代码；其中有 Controls.Initialization 段，这个段比较关键。在这段代码中建立了 Mouse、Screen 和 Application 三个关键的全局对象。

Application.Create 调用 Application.CreateHandle。Application.CreateHandle 建立一个窗口，并设置 Application.WndProc 为回调函数(这里使用了 MakeObjectInstance 方法，后面再谈)。Application.WndProc 主要处理一些应用程序级别的消息。

我第一次跟踪应用程序的执行时没有发现 Application 对象的创建过程，原来在 SysInit._InitExe 中被隐含调用了。如果你想跟踪这个过程，不要设置断点，直接按 F7 就发现了。

然后才到了 Project1 的第 1 句： Application.Initialize;
这个函数只有一句代码：

  if InitProc &lt;&gt; nil then TProcedure(InitProc);

也就是说如果用户想在应用程序的执行前运行一个特定的过程，可以设置 InitProc 指向该过程。(为什么用户不在 Application.Initialize 之前或在单元的 Initliazation 段中直接运行这个特定的过程呢？一个可能的答案是：如果元件设计者希望在应用程序的代码执行之前执行一个过程，并且这个过程必须在其他单元的 Initialization 执行完成之后执行[比如说 Application 对象必须创建]，则只能使用这个过程指针来实现。)

然后是 Project1 的第 2 句：     Application.CreateForm(TForm1, Form1);
这句的主要作用是创建 TForm1 对象，然后把 Application.MainForm 设置为 TForm1。

最后是 Project1 的第 3 句：     Application.Run;
TApplication.Run 调用 TApplication.HandleMessage 处理消息。Application.HandleMessage 的代码也只有一行：

  if not ProcessMessage(Msg) then Idle(Msg);

TApplication.ProcessMessage 才真正开始建立消息循环。ProcessMessage 使用 PeekMessage API 代替 GetMessage 获取消息队列中的消息。使用 PeekMessage 的好处是 PeekMessage 发现消息队列中没有消息时会立即返回，这样就为 HandleMessage 函数执行 Idle(Msg) 提供了依据。

ProcessMessage 在处理消息循环的时候还特别处理了 HintMsg、MDIMsg、KeyMsg、DlgMsg 等特殊消息，所以在 Delphi 中很少再看到纯 Win32 SDK 编程中的要区分 Dialog Window、MDI Window 的处理，这些都被封装到 TForm 中去了(其实 Win32 SDK 中的 Dialog 也是只是 Microsoft 专门写了一个窗口过程和一组函数方便用户界面的设计，其内部运作过程与一个普通窗口无异)。

function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
  Handled: Boolean;
begin
  Result := False;
  if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then  // 从消息队列获取消息
  begin
    Result := True;
    if Msg.Message &lt;&gt; WM_QUIT then
    begin
      Handled := False;  // Handled 表示 Application.OnMessage 是否已经处理过
                         // 当前消息。
                         // 如果用户设置了Application.OnMessage 事件句柄，
                         // 则先调用 Application.OnMessage
      if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
      if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
        not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
                         // 思考：not Handled 为什么不放在最前？
      begin
        TranslateMessage(Msg);                // 处理字符转换
        DispatchMessage(Msg);                 // 调用 WndClass.lpfnWndProc
      end;
    end
    else
      FTerminate := True;                     // 收到 WM_QUIT 时应用程序终止
                                              // (这里只是设置一个终止标记)
  end;                                                        
end;

从上面的代码来看，Delphi 应用程序的消息循环机制与标准 Win32 C 语言应用程序差不多。只是 Delphi 为了方便用户的使用设置了很多扩展空间，其副作用是消息处理会比纯 C Win32 API 调用效率要低一些。

===============================================================================
  TWinControl.Create、注册窗口过程和创建窗口
===============================================================================
上面简单讨论了一个 Application 的建立到形成消息循环的过程，现在的问题是 Delphi 控件是如何封装创建窗口这一过程的。因为只有建立了窗口，消息循环才有意义。

让我们先回顾 Delphi VCL中几个主要类的继承架框：
  TObject           所有对象的基类 
  TPersistent       所有具有流特性对象的基类
  TComponent        所有能放在 Delphi Form Designer 上的对象的基类
  TControl          所有可视的对象的基类
  TWinControl       所有具有窗口句柄的对象基类

Delphi 是从 TWinControl 开始实现窗口相关的元件。所谓窗口，对于程序设计者来说，就是一个窗口句柄 HWND。TWinControl 有一个 FHandle 私有成员代表当前对象的窗口句柄，通过 TWinControl.Handle 属性来访问。

我第一次跟踪 TWinControl.Create 过程时，竟然没有发现 CreateWindow API 被调用，说明 TWinControl 并不是在对象创建时就建立 Windows 窗口。如果用户使用 TWinControl.Create(Application) 以后，立即使用 Handle 访问窗口会出现什么情况呢？

答案在 TWinControl.GetHandle 中，Handle 是一个只读的窗口句柄：

  property TWinControl.Handle: HWnd read GetHandle;

TWinControl.GetHandle 代码的内容是：一旦用户要访问 FHandle 成员，TWinControl.HandleNeeded 就会被调用。HandleNeeded 首先判断 TWinControl.FHandle 是否是等于 0 (还记得吗？任何对象调用构造函数以后所有对象成员的内存都被清零)。如果 FHandle 不等于 0，则直接返回 FHandle；如果 FHandle 等于 0，则说明窗口还没有被创建，这时 HandleNeeded 自动调用 TWinControl.CreateHandle 来创建一个 Handle。但 CreateHandle 只是个包装函数，它首先调用 TWinControl.CreateWnd 来创建窗口，然后生成一些维护 VCL Control 运行的参数(我还没细看)。CreateWnd 是一个重要的过程，它先调用 TWinControl.CreateParams 设置创建窗口的参数。(CreateParams 是个虚方法，也就是说程序员可以重载这个函数，定义待建窗口的属性。) CreateWnd 然后调用 TWinControl.CreateWindowHandle。CreateWindowHandle 才是真正调用 CreateWindowEx API 创建窗口的函数。

够麻烦吧，我们可以抱怨 Borland 为什么把事情弄得这么复杂，但最终希望 Borland 这样设计自有它的道理。上面的讨论可以总结为 TWinControl 为了为了减少系统资源的占用尽量推迟建立窗口，只在某个方法需要调用到控件的窗口句柄时才真正创建窗口。这通常发生在窗口需要显示的时候。一个窗口是否需要显示常常发生在对 Parent 属性 (在TControl 中定义) 赋值的时候。设置 Parent 属性时，TControl.SetParent 方法会调用 TWinControl.RemoveControl 和 TWinControl.InsertControl 方法。InsertControl 调用 TWinControl.UpdateControlState。UpdateControlState 检查 TWinControl.Showing 属性来判断是否要调用 TWinControl.UpdateShowing。UpdateShowing 必须要有一个窗口句柄，因此调用 TWinControl.CreateHandle 来创建窗口。

不过上面说的这些，只是繁杂而不艰深，还有很多关键的代码没有谈到呢。

你可能发现有一个关键的东西被遗漏了，对，那就是窗口的回调函数。由于 Delphi 建立一个窗口的回调过程太复杂了(并且是非常精巧的设计)，只好单独拿出来讨论。

cheka 的《VCL窗口函数注册机制研究手记，兼与MFC比较》一文中对 VCL 的窗口回调实现进行了深入的分析，请参考：http://www.delphibbs.com/delphibbs/dispq.asp?lid=584889

我在此简单介绍回调函数在 VCL 中的实现：

TWinControl.Create 的代码中，第一句是 inherited，第二句是

  FObjectInstance := Classes.MakeObjectInstance(MainWndProc);

我想这段代码可能吓倒过很多人，如果没有 cheka 的分析，很多人难以理解。但是你不一定真的要阅读 MakeObjectInstance 的实现过程，你只要知道：

MakeObjectInstance 在内存中生成了一小段汇编代码，这段代码的内容就是一个标准的窗口过程。这段汇编代码中同时存储了两个参数，一个是 MainWndProc 的地址，一个是 Self (对象的地址)。这段汇编代码的功能就是使用 Self 参数调用 TWinControl.MainWndProc 函数。

MakeObjectInstance 返回后，这段代码的地址存入了 TWinControl.FObjectInstance 私有成员中。

这样，TWinControl.FObjectInstance 就可以当作标准的窗口过程来用。你可能认为 TWinControl 会直接把 TWinControl.FObjectInstance 注册为窗口类的回调函数(使用 RegisterClass API)，但这样做是不对的。因为一个 FObjectInstance 的汇编代码内置了对象相关的参数(对象的地址 Self)，所以不能用它作为公共的回调函数注册。TWinControl.CreateWnd 调用 CreateParams 获得要注册的窗口类的资料，然后使用 Controls.pas 中的静态函数 InitWndProc 作为窗口回调函数进行窗口类的注册。InitWndProc 的参数符合 Windows 回调函数的标准。InitWndProc 第一次被回调时就把新建窗口(注意不是窗口类)的回调函数替换为对象的 TWinControl.FObjectInstance (这是一种 Windows subclassing 技术)，并且使用 SetProp 把对象的地址保存在新建窗口的属性表中，供 Delphi 的辅助函数读取(比如 Controls.pas 中的 FindControl 函数)。

总之，TWinControl.FObjectInstance 最终是被注册为窗口回调函数了。

这样，如果 TWinControl 对象所创建的窗口收到消息后(形象的说法)，会被 Windows 回调 TWinControl.FObjectInstance，而 FObjectInstance 会呼叫该对象的 TWinControl.MainWndProc 函数。就这样 VCL 完成了对象的消息处理过程与 Windows 要求的回调函数格式差异的转换。注意，在转换过程中，Windows 回调时传递进来的第一个参数 HWND 被抛弃了。因此 Delphi 的组件必须使用 TWinControl.Handle (或 protected 中的 WindowHandle) 来得到这个参数。Windows 回调函数需要传回的返回值也被替换为 TMessage 结构中的最后一个字段 Result。

为了使大家更清楚窗口被回调的过程，我把从 DispatchMessage 开始到 TWinControl.MainWndProc 被调用的汇编代码(你可以把从 FObjectInstance.Code 开始至最后一行的代码看成是一个标准的窗口回调函数)：

DispatchMessage(&amp;Msg)    // Application.Run 呼叫 DispatchMessage 通知
                         // Windows 准备回调

Windows 准备回调 TWinControl.FObjectInstance 前在堆栈中设置参数：
            push LPARAM
            push WPARAM
            push UINT
            push HWND
            push (eip.Next)             ; 把Windows 回调前下一条语句的地址
                                        ; 保存在堆栈中
            jmp FObjectInstance.Code    ; 调用 TWinControl.FObjectInstance

FObjectInstance.Code 只有一句 call 指令:
call ObjectInstance.offset  
            push eip.Next
            jmp InstanceBlock.Code      ; 调用 InstanceBlock.Code

InstanceBlock.Code:
            pop ecx                     ; 将 eip.Next 的值存入 ecx, 用于
                                        ; 取 @MainWndProc 和 Self
            jmp StdWndProc              ; 跳转至 StdWndProc

StdWndProc 的汇编代码:
function StdWndProc(Window: HWND; Message, WParam: Longint;
  LParam: Longint): Longint; stdcall; assembler;
asm
            push ebp
            mov ebp, esp
        XOR     EAX,EAX
            xor eax, eax
        PUSH    EAX
            push eax                    ; 设置 Message.Result := 0
        PUSH    LParam                  ; 为什么 Borland 不从上面的堆栈中直接
            push dword ptr [ebp+$14]    ; 获取这些参数而要重新 push 一遍？
        PUSH    WParam                  ; 因为 TMessage 的 Result 是
            push dword ptr [ebp+$10]    ; 记录的最后一个字段，而回调函数的 HWND
        PUSH    Message                 ; 是第一个参数，没有办法兼容。
            push dword ptr [ebp+$0c]
        MOV     EDX,ESP
            mov edx, esp                ; 设置 Message 在堆栈中的地址为
                                        ; MainWndProc 的参数
        MOV     EAX,[ECX].Longint[4]
            mov eax, [ecx+$04]          ; 设置 Self 为 MainWndProc 的隐含参数
        CALL    [ECX].Pointer
            call dword ptr [ecx]        : 呼叫 TWinControl.MainWndProc(Self,
                                        ; @Message)
        ADD     ESP,12
            add esp, $0c
        POP     EAX
            pop eax
end;
            pop ebp
            ret $0010
            mov eax, eax

看不懂上面的汇编代码，不影响对下文讨论的理解。&lt;img src ="http://softdream.cndev.org/aggbug/50583.aspx" width = "1" height = "1" /&gt;</description></item><item><dc:creator>棒棒糖</dc:creator><title>Windows 服务详解</title><link>http://softdream.cndev.org/archive/2006/08/16/46700.aspx</link><pubDate>2006-08-16 12:54:00Z</pubDate><guid>http://softdream.cndev.org/archive/2006/08/16/46700.aspx</guid><wfw:comment>http://softdream.cndev.org/comments/46700.aspx</wfw:comment><comments>http://softdream.cndev.org/archive/2006/08/16/46700.aspx#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://softdream.cndev.org/comments/commentRss/46700.aspx</wfw:commentRss><trackback:ping>http://softdream.cndev.org/services/trackbacks/46700.aspx</trackback:ping><description>引自 流浪的海 的Blog.
&lt;img src ="http://softdream.cndev.org/aggbug/46700.aspx" width = "1" height = "1" /&gt;</description></item><item><dc:creator>棒棒糖</dc:creator><title>Dll  调用大全  （隐，显）</title><link>http://softdream.cndev.org/archive/2006/08/16/46697.aspx</link><pubDate>2006-08-16 11:36:00Z</pubDate><guid>http://softdream.cndev.org/archive/2006/08/16/46697.aspx</guid><wfw:comment>http://softdream.cndev.org/comments/46697.aspx</wfw:comment><comments>http://softdream.cndev.org/archive/2006/08/16/46697.aspx#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://softdream.cndev.org/comments/commentRss/46697.aspx</wfw:commentRss><trackback:ping>http://softdream.cndev.org/services/trackbacks/46697.aspx</trackback:ping><description>该文转自计算机世界网 
http://www.ccw.com.cn/htm/app/aprog/01_10_29_2.asp 
Delphi中如何调用VC++创建的动态链接库 

肖运勇   
01-10-29 下午 02:06:09 

-------------------------------------------------------------------------------- 


Delphi以其独特的面向控件的开发方式、强大的数据库功能、快速的编译技术以及简单易学的编程特性,使得它自发布之日起即格外引人注目，许多程序员也因此将它作为首选的开发工具。然而，Delphi在科学计算、低端编程等方面的功能不如VC++。VC++功能强大、齐全，但是整个系统比较复杂、庞大，尤其对于初学者来说比较难学，其用户界面的开发远不如Delphi那样方便、快捷。那么，我们能否将两者的优点结合起来呢？答案是肯定的！具体做法是：将涉及到比较低级的操作、计算等方面的程序用VC++写成函数放在动态链接库中，而涉及到界面及与用户交互的编程则用Delphi来实现，最后只需在Delphi中调用VC++编写的动态链接库即可。 
一、动态链接库简介 
动态链接库（DLL,即 &amp;#8220;Dynamic-Link Library&amp;#8221;）是一个能够被应用程序和其它的DLL调用的过程和函数的集合体，它里面包含的是公共代码或资源。DLL是Windows的基石，所有的Win32 API函数都包含在DLL中。 
使用DLL有许多优点：   
1、一个DLL可以提供给不同的程序使用，如果有多个程序使用相同的DLL，也只需将DLL在内存中装载一次，这样就节省了内存开销。 
2、DLL可以使我们的编程更加模块化，将功能相对独立的模块编成一个动态链接库，这样改动程序时不需将整个程序重新编译，只需重新编译所改动的模块。   
3、使用了DLL组件包可以大大减小可执行文件的规模。   
4、对于一个大型的、不断更新的应用程序，可以将许多重复的功能写成DLL，用主程序调用，这样既减少了开发的工作量，又提高了访问速度。   
5、DLL独立于编程语言，大多数WINDOWS编程环境都允许主程序调用DLL中的函数。即可以用VC++、VB、PowerBuilder、Delphi、汇编语言等建立DLL，然后在不同语言编制的应用程序中调用它。这样就给多人使用不同的编程语言开发项目提供了极大的方便。 

二、在Delphi中调用VC++创建的动态链接库的实例 
（一）实验环境 
本实例的编程工具及运行环境为：Windows 98，VC++6.0，Delphi 5.0　。 
（二）实验内容 
1．用VC++6.0建立一个动态链接库MaxMin.DLL，该库中包含有两个函数：返回三个整数中最大整数的函数Max1( )和返回三个整数中最小整数的函数Min1( )。 
2．用Delphi编写测试程序调用动态链接库MaxMin.DLL中的两个函数。 
（三）实验步骤 
1．用VC++6.0建立动态链接库MaxMin.DLL 
第一步：启动VC++6.0，选择&amp;#8220;File/New/MFC AppWizzard(Dll)&amp;#8221;,工程名设为&amp;#8220;MaxMin&amp;#8221;，按&amp;#8220;确定&amp;#8221;钮后，选择&amp;#8220;Regular DLL Using shared MFC DLL&amp;#8221;,按&amp;#8220;Finish&amp;#8221;钮后，即创造了一个DLL的框架工程。 
第二步：选择&amp;#8220;File/New&amp;#8221;,在出现的对话框中选择&amp;#8220;C/C++ Header File&amp;#8221;，在文件名处输入&amp;#8220;MyDLL&amp;#8221;,按&amp;#8220;确定&amp;#8221;钮，即创建了一个空的头文件&amp;#8220;MyDLL.h&amp;#8221;。在该文件中输入以下两行内容： 
extern "C" _declspec(dllexport) int Min1(int x,int y,int z); 
extern "C" _declspec(dllexport) int Max1(int x,int y,int z); 
选择&amp;#8220;File/Save&amp;#8221;保存该文件的内容。 
第三步：选择&amp;#8220;File/New&amp;#8221;,在出现的对话框中选择&amp;#8220;C/C++ Source File&amp;#8221;， 在文件名处输入&amp;#8220;MyDLL&amp;#8221;,按&amp;#8220;确定&amp;#8221;钮，即创建了一个空的源文件&amp;#8220;MyDLL.cpp&amp;#8221;。在该文件中输入以下内容： 
#include "stdafx.h" 
#include "MyDll.h" 
extern "C" __declspec(dllexport) int Min1(int x,int y,int z) 
{ 
if ((x&amp;lt;=y) &amp; (x&amp;lt;=z)) return x; 
else if ((y&amp;lt;=x) &amp; (y&amp;lt;=z)) return y; 
else return z; /*找出x，y，z中的最小整数*/ 
} 

extern "C" __declspec(dllexport) int Max1(int x,int y,int z) 
{ 
if ((x&amp;gt;=y) &amp; (x&amp;gt;=z)) return x; 
else if ((y&amp;gt;=x) &amp; (y&amp;gt;=z)) return y; 
else return z; /*找出x，y，z中的最大整数*/ 
} 
选择&amp;#8220;File/Save&amp;#8221;保存该文件的内容。 
第四步：按下运行图标&amp;#8220;！&amp;#8221;，即生成了MyDLL.DLL（在当前工程目录的DEBUG子目录下）。 

2．用Delphi编写调用MaxMin.DLL的测试程序 
调用动态链接库有两种方法，即隐式调用和显式调用。 
（1）隐式调用 
第一步：启动Delphi，选择&amp;#8220;New Application&amp;#8221;，生成一个空的应用程序，在Form的&amp;#8220;Name&amp;#8221;属性处输入&amp;#8220;TestVcDLLForm&amp;#8221;，Caption属性处输入&amp;#8220;VC++的DLL隐式调用测试&amp;#8221;，在Form中放入控件如表1所示（其中所有的Edit控件的&amp;#8220;Text&amp;#8221;属性均设为空）： 


（表1：所用到的控件及其属性） 



（图1：应用程序屏幕效果） 

最后设计的Form的屏幕效果如图1所示。选择&amp;#8220;File/Save all&amp;#8221;，在&amp;#8220;Save unit1 as&amp;#8221;对话框中将源文件名设为&amp;#8220;main.pas&amp;#8221;，按&amp;#8220;保存&amp;#8221;钮；在&amp;#8220;Save Project1 as&amp;#8221;对话框中将工程名设为&amp;#8220;TestVcDLL&amp;#8221;，按&amp;#8220;保存&amp;#8221;钮。 
第二步：选择&amp;#8220;File/New&amp;#8230;&amp;#8221;，在出现的&amp;#8220;New Item&amp;#8221;对话框中选择&amp;#8220;unit&amp;#8221;,按&amp;#8220;OK&amp;#8221;钮，生成一个空的源文件，在该文件中输入以下内容： 
unit MaxMin; 

interface 
function Min1(x,y,z:Integer):Integer; stdcall; 
function Max1(x,y,z:Integer):Integer; stdcall; 

implementation 
function Min1;external 'MaxMin.DLL' name 'Min1'; 
function Max1;external Max'Min.DLL' name 'Max1'; 
end. 
选择&amp;#8220;File/Save As&amp;#8230;&amp;#8221;,将上述文件存为&amp;#8220;MaxMin.pas&amp;#8221;。 
第三步：在Main.pas文件中，在&amp;#8220;implementation&amp;#8221;语句后加入： 
uses 
MaxMin; 
第四步：在Form上双击&amp;#8220;运行&amp;#8221;按钮对该按钮的&amp;#8220;Click&amp;#8221;事件编程，代码如下： 
procedure TTestVcDLLForm.btnRunClick(Sender: TObject); 
begin 
edtMax.Text:=IntToStr(Max1(StrToInt(edtInt1.Text), 
StrToInt(edtInt2.Text),StrToInt(edtInt3.Text))); //调用动态链接库中的函数Max1 
edtMin.Text:=IntToStr(Min1(StrToInt(edtInt1.Text), 
StrToInt(edtInt2.Text),StrToInt(edtInt3.Text))); //调用动态链接库中的函数Min1 
end; 
保存该文件。 
第五步：将上述1.中VC++6所建立的动态链接库&amp;#8220;MaxMin.DLL&amp;#8221;拷入Delphi的当前工作目录中。 
第六步：运行。结果如图2所示。 


图2：隐式调用DLL运行结果 



图3：显式调用DLL运行结果 

（2）显示调用 
第一步：同隐式调用。只是将Form的&amp;#8220;Caption&amp;#8221;属性改为&amp;#8220;VC++的DLL显式调用测试&amp;#8221;。 
第二步：选择&amp;#8220;File/New&amp;#8230;&amp;#8221;，在出现的&amp;#8220;New Item&amp;#8221;对话框中选择&amp;#8220;unit&amp;#8221;,按&amp;#8220;OK&amp;#8221;钮，生成一个空的源文件，在该文件中输入以下内容： 
unit Unit1; 

interface 

type 
TMin1=function(x,y,z:Integer):Integer; stdcall; 
TMax1=function(x,y,z:Integer):Integer; stdcall; 
THandle=Integer; 

implementation 

end. 
选择&amp;#8220;File/Save As&amp;#8230;&amp;#8221;,将上述文件存为&amp;#8220;MaxMin.pas&amp;#8221;。 
第三步：在Main.pas文件中，在&amp;#8220;implementation&amp;#8221;语句后加入： 
uses 
MaxMin; 
第四步：在Form上双击&amp;#8220;运行&amp;#8221;按钮对该按钮的&amp;#8220;Click&amp;#8221;事件编程，代码如下： 
procedure TTestVcDLLForm.btnRunClick(Sender: TObject); 
var 
Handle:THandle; 
Min1:TMin1; 
Max1:TMax1; 
begin 
Handle:=LoadLibrary('MaxMin.dll'); //将&amp;#8220;MaxMin.dll&amp;#8221;的文件映象映射进调用进程的地址空间 
if Handle&amp;lt;&amp;gt;0 then 
begin 
@Min1:=GetProcAddress(Handle,'Min1'); //取得DLL中函数Min1( )的地址 
@Max1:=GetProcAddress(Handle,'Max1'); //取得DLL中函数Max1( )的地址 
if (@Min1&amp;lt;&amp;gt;nil) and (@Min1&amp;lt;&amp;gt;nil) then 
begin 
edtMin.Text:=IntToStr(Min1(StrToInt(edtInt1.Text), 
StrToInt(edtInt2.Text),StrToInt(edtInt3.Text))); //调用动态链接库中的函数Min1 
edtMax.Text:=IntToStr(Max1(StrToInt(edtInt1.Text), 
StrToInt(edtInt2.Text),StrToInt(edtInt3.Text))); //调用动态链接库中的函数Max1 
end else ShowMessage('调用函数&amp;#8220;GetProcAddress&amp;#8221;时出错！'); 
FreeLibrary(Handle); //从进程的地址空间中解除&amp;#8220;MaxMin.dll&amp;#8221;文件的映射 
end; 
end; 
保存该文件。 
第五步：将上述1.中VC++6所建立的动态链接库&amp;#8220;MaxMin.DLL&amp;#8221;拷入Delphi的当前工作目录中。 
第六步：运行。结果如图3所示。 

以上实例均编译通过，运行正确。 

三、结束语   
动态链接库为不同编程环境下的应用程序之间的连接提供了方便，节省了内存，提高了速度，同时也丰富了PowerScript语言的编程能力。动态链接库是Windows下程序组织的一种重要方式，使用动态链接库可以极大地保护用户在不同开发工具、不同时期所做的工作；利用动态链接库，用户可以逐步去构筑自己的程序模块库，为今后的工作积累素材。 
&lt;img src ="http://softdream.cndev.org/aggbug/46697.aspx" width = "1" height = "1" /&gt;</description></item><item><dc:creator>棒棒糖</dc:creator><title>关于Delphi的编译指令2</title><link>http://softdream.cndev.org/archive/2006/08/10/46659.aspx</link><pubDate>2006-08-10 09:21:00Z</pubDate><guid>http://softdream.cndev.org/archive/2006/08/10/46659.aspx</guid><wfw:comment>http://softdream.cndev.org/comments/46659.aspx</wfw:comment><comments>http://softdream.cndev.org/archive/2006/08/10/46659.aspx#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://softdream.cndev.org/comments/commentRss/46659.aspx</wfw:commentRss><trackback:ping>http://softdream.cndev.org/services/trackbacks/46659.aspx</trackback:ping><description>在Delphi中，也有与C相似的预编译指令，虽然该类指令只在当前的单个文件有效（也有可能是笔者未全面了解该类指令的真正用法），但是这一类指令对于进行多版本的制作工作（如从标准版中出学习版），确实有着相当不错的用途。

 

一．指令介绍：

1．  DEFINE指令：

格式：{$DEFINE 名称}

              说明  ：用于定义一个在当前单元有效的符号（Symbol）。定义了

               之后可以使用IF DEF和IFNDEF指令来判断该符号是否存在。

 

2．  UNDEF指令：

格式：{$UNDEF 名称}

说明：用于取消一个在当前单元已经定义的符号（Symbol）。该指令和DEFINE

配合使用。

 

3．  IFDEF指令：

格式：{$IFDEF 名称}

说明：如果该指令后的名称已经定义，则编译该指令后直到{$ELSE}或{$ENDIF}之间的代码段。

 

4．  IFNDEF指令：

格式：{$IFNDEF 名称}

说明：如果该指令后的名称没有定义，则编译该指令后直到{$ELSE}或{$ENDIF}之间的代码段。

 

5．  IFOPT指令：

格式：{$IFOPT 开关}

说明：如果该指令后的开关已经设立，则编译该指令后直到{$ELSE}或{$ENDIF}之间的代码段。

举例：{$IFOPT R+}

 Writeln('编译时打开范围检查开关');

{$ENDIF}

 

6．  ELSE指令：

格式：{$ELSE}

说明：通过判断前缀Ifxxx的条件式来确定该指令到{$ENDIF}之间的代码段是否应该被编译或者忽略掉。

 

7．  ENDIF指令：

格式：{$ENDIF}

说明：和Ifxxx配合，指明条件预编译段源代码段的结束位置。

 

二．范例：

编写例子，通过预先定义不同的编译符号，进行不用代码段的编译工作。

1．  新建一个Delphi项目，在Unit1单元的窗体上添加一个Button按钮。

2．  编写程序如下：

   unit Unit1;

 

 interface

 

 uses

     Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

 StdCtrls;

type

  TForm1 = class(TForm)

        Button1: TButton;

    procedure FormCreate(Sender: TObject);

        procedure Button1Click(Sender: TObject);

  private

        { Private declarations }

  public

        { Public declarations }

  a : String;

  end;

 

var

  Form1: TForm1;

 

implementation

 

{$R *.DFM}

 

{$DEFINE AAA}                            // 定义行。

 

procedure TForm1.FormCreate(Sender: TObject);

begin

a := 'Other';

{$IFDEF AAA}

  a := 'AAA';

{$ENDIF}

{$IFDEF BBB}

  a := 'BBB';

{$ENDIF}

end;

 

procedure TForm1.Button1Click(Sender: TObject);

begin

  Caption := a;

end;

 

end.

{注：粗体字部分为输入的代码}

3．  编译后运行，按下Button，则看到窗体标题栏显示&amp;#8220;AAA&amp;#8221;。程序编译了a := &amp;#8217;AAA&amp;#8217;的语句。

4．  改变定义行的程序段：

当改为

  {$DEFINE BBB}

时，再次编译运行，则看到窗体标题栏显示&amp;#8220;BBB&amp;#8221;。程序编译了a := &amp;#8217;BBB&amp;#8217;的语句。

当取消定义行或改为

  {$DEFINE NOTHING}

或其他名称时，再次编译运行，则看到窗体标题栏显示&amp;#8220;Other&amp;#8221;。程序只编译了a := &amp;#8217;Other&amp;#8217;的语句。

 

三．如何快速的制作和更改版本：

使用预编译指令，在制作同一个程序的多个版本时，只需找出各版本中有区别的单元，依次定义统一的版本符号（Symbol），然后在程序段中加入条件预编译指令，就可以在实际编译中取舍编译不同的程序部分，这样对于程序的规范性（定义统一的版本符号）和保密性（不同的版本编译不同的程序部分）有很好的作用。

然而，由于该类预编译指令只能作用于当前单元，所以不便之处在于不能在一个公共单元定义一次版本符号，而必须在各单元中定义统一版本符号才行，故此，在更换版本时，需要确定所有的版本符号都已改变，这样才能保证各版本的正确性，对此，可以使用Delphi IDE的&amp;#8220;Find in Files&amp;#8230;&amp;#8221;（多个文件中查找字符串）的功能，找出所有定义版本符号的文件和位置，然后依次更改，保证所有位置已经改正。
&lt;img src ="http://softdream.cndev.org/aggbug/46659.aspx" width = "1" height = "1" /&gt;</description></item><item><dc:creator>棒棒糖</dc:creator><title>关于Delphi中的编译指令1</title><link>http://softdream.cndev.org/archive/2006/08/10/46658.aspx</link><pubDate>2006-08-10 09:12:00Z</pubDate><guid>http://softdream.cndev.org/archive/2006/08/10/46658.aspx</guid><wfw:comment>http://softdream.cndev.org/comments/46658.aspx</wfw:comment><comments>http://softdream.cndev.org/archive/2006/08/10/46658.aspx#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://softdream.cndev.org/comments/commentRss/46658.aspx</wfw:commentRss><trackback:ping>http://softdream.cndev.org/services/trackbacks/46658.aspx</trackback:ping><description>
    《Delphi下深入Windows核心编程》（附录A Delphi编译指令说明）
摘抄人：麻子 qq:71892967

Delphi快速高小的编译器主要来自Object PASCAL的严谨，使用Delphi随时都在与编译器交流，大部分情况下不需要干涉编译器的运行，但是有时也需要对编译器进行必要的设置。

**********************************************************************************************

A.1 使用编译设置对话框

编译器的编译指令是用于指定编译器对项目编译过程的动作和行为。可以通过[Project]-&gt;[Options]-&gt;[Complier]选项页进行设置，绝大部分的编译环境都可以通过这一对话框进行调整，他包含了对代码、语法、调试信息等的设置。

1.代码设置(Code Generation)

Optimization: 代码优化开关

Aligned record fields: 字对齐数据。这个编译指令能够在变量和类型化常量的字节对齐和字对齐之间进行切换，其作用是全局的。

Stack frames: Windows 堆栈帧。其作用域是局部的，他使编译器成为远端过程和函数生成特定的开头和结尾代码。这个指令用于Windows 3.0的实模式，对所有Delphi应用程序他应该是关闭的。

Pentium-safe FDIV: Pentium安全FDIV检查。此指令能够在Pentium处理器中指定编译器是否创建能够检测和使用有缺陷的浮点除法指令的代码。

2.运行期错误(Runtime Errors)

Range Checking: 范围检查。这个指令的作用范围是局部的，用于控制范围检验代码的生成。

I/O Checking: I/O检查。这个指令的作用域为局部，用来生成对一个文件的输入和输出过程和调用结果进行检查的代码。一般应该使其功能打开。

Overflos Checking: 溢出检查。其作用是生成对算术溢出检查的代码。

3.语法设置(Syntax Optings)

Strict Var- strings: 静态变量串开关。用来控制对以变量参数形式传递的串的类型检查。

Comlete Boolean Eval: 完全布尔表达量判定。用于执行强制的完整表达式判定。完全布尔量判定，有可能导致系统崩溃，所以一般不使用他。

Extended Syntax: 扩展语法开关。他能允许或禁止Delphi的扩展语法。

Typed @ Operator: 类型化的@运算符检查。应用于局部的变量引用上，控制@运算符返回的指针值类型。

Open Parameters: 开放参数开关。用于控制使用String关键字声明的变量参数。开放参数允许将大小不一的串变量传递到同一个过程或函数中，一般在汇编中会使用到。

Huge Strings: 字符串类型开关。用于控制Ansistring和Shortstring类型的切换。当打开时符合Ansistring，关闭时符合Shortstring。

Assignable Typed Constants: 可分配类型常量。用于向下与Delphi 1.0兼容。

4.调试(Debugging)

Debug Information: 调试信息开关。用于设置是否把调试信息写入以编译的单元文件(.dcu)。

Local Symbols: 局部符号开关。允许或禁止局部符号信息的创建。

Reference Info/Definitions Only: 符号信息开关。用于允许或禁止由Delohi的对象浏览器使用的符号引用信息的生成。

Assertions: 用于控制局部代码的属性。

Use Debug DCUs: 使用或禁止VCL的DCU文件调试。

5.信息(Messages)

Show Hints: 显示暗示。

Show Warnings: 显示警告。

**********************************************************************************************

A.2 使用编译指令

除了使用编译设置对话框对编译器进行设置外，还可以通过编译指令来对编译器进行设置。
对于局部的编译器设置，只有使用编译指令来完成。

对于开关编译指令，通过在编译指令后加入指示开关状态的加号和减号来控制编译器。例如：

{$B+} : 打开完全布尔量检查。
{$Q-} : 关闭溢出检查。

通常，编译指令的作用域是在编译指令后的代码部分，而对于全程的编译指令应该防在单元接口部分的开头。
编译设置对话框的设置都有与之对应的编译指令用于在代码中对编译器进行设置,如下表所示。

设置项 编译指令

Optimizations {$O}

Aligned record fields {$A}

Stack frames {$W}

Pentium-safe FDIV {$U}

Range Checking {$R}

I/O Checking {$I}

Overflow Checking {$Q}

Strict Var-strings {$V}

Comlete Boolean Eval {$B}

Extended Syntax {$X}

Typed @ Operator {$T}

Open Parameters {$P}

Huge Strings {$H}

Assertions typed constants {$J}

Debug information {$D}

Local sysnbols {$L}

在这些编译指令以外还有一些非常有用的编译指令。
$R Filename : 这个编译指令是最为常用的编译指令，他是资源文件编译指令，用于指定连接到执行文件和库的资源文件，例如在工程文件(.dpr)中会有{$R *.RES}的编译指令，表明把后缀为 .RES的与工程文件同名的资源文件连接入执行文件，也可以指定一个资源文件，资源文件的使用对于编写Windows程序来说是很重要的基础。

$I Filename :这个编译指令功能类似于C语言的#Include , 用于指定编译时包括的文件。

**********************************************************************************************

A.3 使用条件编译指令

条件编译指令是非常重要的编译指令，他控制着在不同条件下（例如，不同的操作系统）产生不同的代码。条件编译指令是包含在
注释括号之内的，如下表所示。

条件编译指令 含义

$DEFINE 用于定义一个条件符号，一旦定义，条件符号就为真

$ELSE 与$IFDEF配合使用，如果$IFDEF条件为假，则只对源文件$ELSE后一小部分进行编译

$ENDIF 结束一个以$IF开始的条件段

$IFDEF 对条件符号进行判断，为真则编译源文件

$IFNDEF 对条件符号进行判断，为假则编译源文件

$IFOPT 根据编译开关状态，对源文件编译

$UNDEF 撤消以前的条件符号定义

这些条件编译指令是非常有用的。例如，可以通过开关的状态来控制编译：

{IFOPT R+}
showmessage('Compiled with range-checking');
{$ENDIF}

也可以通过定义条件符号来控制编译：

{$Define s}
&amp;#8230;&amp;#8230;
{$ifdef s}
showmessage('yes');
{$else}
showmessage('no');
{$endif}

他的编译结果是显示'yes'，但是如果省去{$Define s}则显示'no'。
在Delphi中已经预定义了一些关键的条件符号，如下表所示。

条件符号 含义

VERxx 编译器版本，XX表示版本，例如：Delphi 1.0 的编译器版本为80、Delphi 5.0 的编译器版本为130

WIN32 是否WIN32的运行环境(Windows 95.98/NT/2000)

CPU386 是否Intel386以上的处理器

CONSOLE 是否控制台程序 Delphi的编译器指令除了以上的指令外还有一些，不过最为常用的指令已经全部介绍完了。对于普通的程序，Delphi是不需要编程者去添加编译器指令的，Delphi已经自动完成，但是要得到高品质的应用程序或者有特殊的要求的程序就必须熟悉Delphi的编译指令。Delphi不仅有最快的编译器而且编译器的功能也非常强大。&lt;img src ="http://softdream.cndev.org/aggbug/46658.aspx" width = "1" height = "1" /&gt;</description></item><item><dc:creator>棒棒糖</dc:creator><title>在DbGrid中按回车到下一格的方法 。</title><link>http://softdream.cndev.org/archive/2006/08/02/46616.aspx</link><pubDate>2006-08-02 19:13:00Z</pubDate><guid>http://softdream.cndev.org/archive/2006/08/02/46616.aspx</guid><description>在onkeypress事件写如下代码：
if key=#13 then
   begin
     keybd_event(vk_tab,0,0,0);
     keybd_event(vk_tab,0,keyeventf_keyup,0);
   end;&lt;img src ="http://softdream.cndev.org/aggbug/46616.aspx" width = "1" height = "1" /&gt;</description></item><item><dc:creator>棒棒糖</dc:creator><title>fastreport中打印空白行的方法</title><link>http://softdream.cndev.org/archive/2006/08/02/46615.aspx</link><pubDate>2006-08-02 19:04:00Z</pubDate><guid>http://softdream.cndev.org/archive/2006/08/02/46615.aspx</guid><description>FastReport 3.X


var
  PageLine: integer;       //在現在頁列印到第幾行
  PageMaxRow: integer=15;  //設定每頁列數

procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
begin
  PageLine := &lt;line&gt; mod PageMaxRow;
  if (PageLine = 1) and (&lt;line&gt; &gt; 1) then
    Engine.newpage;
end;

//Footer1高度設為0

procedure Footer1OnBeforePrint(Sender: TfrxComponent);
var
  i: integer;
begin
  i := iif(PageLine=0, PageMaxRow, PageLine);
  while i &lt; PageMaxRow do begin
    i := i + 1;
    Engine.ShowBand(Child1);  //印空白表格
  end;
end;

begin
end.


FastReport 2.X

在資料字典設定變數
PageMaxRow 設定每頁列數。
PageLine 在現在頁列印到第幾行。
每 PageMaxRow 筆新自動跳頁(在Band的OnBeforePrint)



  PageLine := [line#] mod PageMaxRow;
  if (PageLine = 1) and ([line#] &gt; 1) then
    newpage;


一頁列印15筆，不足筆數的列印空白表格列的方法
Master1
detail1----固定印PageMaxRow筆，有表格
detailFooter1---在OnBeforePrint輸入下列程式碼，高度設為 0
child1---空白表格

**detailFooter1.OnBeforePrint



  i := PageLine;
  if i = 0 then
    i := PageMaxRow;
  while i &lt; PageMaxRow do begin
    i:=i+1;
    ShowBand(Child1);
  end;

&lt;img src ="http://softdream.cndev.org/aggbug/46615.aspx" width = "1" height = "1" /&gt;</description></item></channel></rss>