第1章 Lazarus的系统结构

第1章 Lazarus的系统结构

Lazarus 是一个基于 Object Pascal 语言的集成开发环境(IDE)。Free Pascal 编译器为 Lazarus 提供了后端支持。这种后端引擎与集成开发环境的组合类似于 Visual Studio 对于 C++ 或 Borland Delphi。它生成高效且高度优化的原生代码(Native Code)。相比之下,Java 和 C# 生成的字节码是需要在运行时环境中解释执行,或者由即时编译器(JIT)转换成优化效果一般的机器码。

Lazarus IDE 的强项之一是其快速应用开发(RAD)能力,这极大提高了图形程序的开发效率,与 Delphi 的功能非常相似。不过,Delphi 基于 Delphi 编译器,而 Lazarus 基于 Free Pascal 编译器(FPC),而且与 Delphi 2007 高度兼容。此外,Lazarus 不仅可以在 Windows 上运行,还可以在 Linux、macOS X、BSD 等多个平台上以原生模式运行。Free Pascal 编译器本身是用 Pascal 语言编写的,而 Lazarus 完全用 Lazarus 开发。FPC 还包括不同 Pascal版本语言特性支持,这使得源码的移植变得更加容易。为了强调其对不同平台的重视,FPC 能够检测出可能不具备可移植性的代码并生成警告。

Lazarus 的图形组件库称为 Lazarus 组件库(LCL)。LCL 中的许多单元、类和标识符与 Delphi 中的相同,因此 Delphi 代码可以直接移植到 Lazarus。然而,Lazarus 并不是 Delphi 的克隆,而是 Delphi 概念的扩展,它通过原生应用程序支持不同的平台,无需使用模拟器,因此无法完全兼容 Delphi 的组件库(VCL)。LCL 使用原生组件,确保在所有平台上,基于 LCL 的应用程序看起来和使用方式都与其他原生应用程序相同。LCL 通过使用原生的组件(如 Windows 上的 WinAPI、Linux/BSD 上的 Gtk 2 以及 macOS 上的 Carbon/Cocoa)实现了这一点。

Lazarus 与 Delphi 的主要区别在于:Lazarus 完全开源,目前支持超过十五个平台,对商业用户也是免费的,并且采用灵活的许可方案。得益于开源社区的努力,许多 Delphi 库已被移植到 Lazarus 并扩展到其他平台。此外,还有一些专门为 Lazarus 编写的全新库。众多论坛、邮件和网站提供了快速、直接的帮助。

Free Pascal 编译器及其配套库为 Lazarus 提供了坚实的基础。但 FPC 和 Lazarus 是不同的项目。Free Pascal 的第一个版本出现在1993年,而 Lazarus 的开发始于1999年。Free Pascal 是一个支持32位和64位系统的编译器,适用于 Windows 32、Windows 64、WinCE、Linux、FreeBSD、macOS X/Darwin、DOS、OS/2、Netware(Libe 和 Classic 版本)以及 MorphOS,运行在 Intel x86、AMD64/x86_64、PowerPC、PowerPC64、Sparc 和 ARM 架构上。

与 Delphi 一样,Free Pascal 编译器运行非常快,但由于 FPC 优化了多个平台以增强可扩展性,速度上略逊于 Delphi。就生成代码的大小和速度而言,在 Windows 下,FPC 生成的代码与 Delphi 相当(有时 FPC 更好,有时 Delphi 更好),而在 Linux 或 macOS X 下,FPC 生成的代码与 GNU C/C++ 编译器相当。

Lazarus 当前缺乏对 .NET 的支持,也没有与 Delphi 的 .bpl 库类似的包。然而,Lazarus编译的库可以在所有平台上编译和绑定。使用 Lazarus 可以开发多种类型的应用程序,包括原生图形应用程序、Windows 服务、Linux 守护进程、命令行工具、库、插件、数据库应用程序、Web 应用程序等。此外,Lazarus IDE 的功能范围可以通过设计时包进行扩展。

Lazarus 支持交叉编译,例如在 Linux 系统上生成 Windows 程序或在 Windows 系统上生成 WinCE 应用程序。在这种情况下,代码导航和代码补全功能依然高效智能。IDE 的配置可以在不重启的情况下进行更改。Free Pascal 编译器支持条件编译,可以根据操作系统、CPU 类型或其他标准选择性地生成代码。IDE 允许在所有搜索路径中使用宏,这意味着可以根据操作系统选择特定的单元和包含文件。特别是在涉及两个以上平台时,这可以产生清晰、易懂的结构。LCL 本身也基于这一概念,对程序员来说表现为一组与平台无关的组件,而搜索路径中的宏则允许特定操作系统的模块访问原生窗口组件。

Lazarus 的包概念允许将多个单元组合成更大的实体,并为其提供独立的编译配置。这确保了代码可以轻松移植到具有不同路径和操作系统的其他计算机上。只需复制文件并打开包即可,无需修改路径或配置开关,这大大简化了包的更新过程。LCL 的视觉组件是事件驱动的,这意味着开发人员不必按顺序编写完整的应用程序,而是可以开发响应事件的方法。LCL 组件的状态完全由属性决定,这些属性可以按任意顺序进行修改,表单也可以在 IDE 中方便地进行图形化组合。如果缺少某个所需的功能,可以将任何组件类作为基础,创建用户自定义类,并在 IDE 中图形化使用。用户还可以生成自己的组件库。

在 Lazarus IDE 中,窗体可以使用设计器进行图形化开发。每个单元最多只能有一个窗体。窗体可以基于其他窗体(继承)或嵌套为子窗体(框架)。窗体的属性存储在 .lfm 文件中。窗体还可以通过代码生成。因为窗体编辑器与源代码编辑器一一对应。

当在窗体上放置一个按钮控件时,源代码中会生成一个新的变量;如果设置了按钮的 OnClick 事件,源代码中会生成一个新的方法。程序可以立即编译并运行,无需其他操作。一个好的编程语言应具备简单的语法和文件组合编译机制,而 FPC 在这方面非常便捷高效。

在 C 或 C++ 中,编译多个文件通常通过 Makefile 来指定。生成和维护 Makefile 并不容易,因此有许多工具帮助 C 用户生成 Makefile。对于 FPC 用户来说, Makefile 往往是不必要的,但是如果需要,仍然可以使用 Makefile,还有可以自动生成 Makefile的工具。Makefile 的任务是简化和标准化编译过程。它确保修改过的文件按正确的顺序自动编译,并可以自定义编译参数。Free Pascal 编译器(FPC)自动编译所有修改过的单元,速度比 Makefile 快得多,并且使用校验和。这些校验和在网络开发中尤其重要,因为客户端和服务器系统之间可能存在不同的时间戳。许多 C 程序员都熟悉文件时间戳出现问题的警告。FPC 在这方面的问题较少,因此程序员只需提供主文件,编译器就能自行找到其他文件。

例如:

fpc programl.pas

运行后,程序自动调用所需文件。对于跨平台编译到不同的CPU架构,需要第二个编译器,这个第二编译器当然也是FPC,但它编译的是目标架构的代码。编译器首先读取源文件,当遇到 uses 子句时,它会自动查找相应的单元。如果找到预编译的 .ppu 文件,它会使用这些文件。如果只找到源文件(或源文件比 .ppu 文件新),它会编译这些源文件。如果这些文件依赖于其他单元,编译器会递归地编译这些单元。

这一过程非常迅速——编译代码的速度通常在每秒一万到十万行之间。编译器能够快速找到指定的文件,并自动按照正确的顺序编译所有修改过的文件。相较于 C 编译器,FPC 的大多数编译参数更容易理解,一方面是因为很多设置直接位于源码中,另一方面是因为编译器和系统库在所有平台上都源自同一代码库。

因此,编译器的命令行参数主要集中于指定搜索路径,而无需像在 C 中那样使用 ./configure 来收集系统参数。这完全符合 Lazarus 的口号:“一次编写,到处编译”。例如,在 C 中,configure 脚本可能会检查 int 变量的大小等信息;而在 Free Pascal 中,类型大小直接由代码定义,简化了这一过程。

在 Free Pascal 中,无需进行版本兼容性检查,因为其运行时库仅依赖几个极少变化的核心函数。因此,Pascal 程序可以在多个操作系统发行版上像 C 程序一样稳定运行。由于 Pascal 不直接依赖 C 的头文件,因此不需要进行特定库的兼容性检查。Lazarus 进一步扩展了单元的概念,将其涵盖到包和项目中。

一个包是一组文件,定义了它们的编译方式及所需的依赖包。它可以是一个库,也可以是大型项目中的一个逻辑单元。Lazarus 项目同样由一组文件组成,但还包含了构建程序所需的额外信息。此外,IDE 会为每个项目保存编辑器中打开的文件名、调试信息及其他相关数据。因此,可以说 Lazarus 的包或项目是增强版的 Makefile,允许即使是在没有 Makefile 的情况下,也能编译大型且相互独立的库。这一功能由命令行工具 lazbuild 实现。

例如,每个项目都包含一个 projectname.lpi 文件,用于存储所有元数据。这个 XML 文件中的所有数据都可以通过 IDE 轻松配置,因此一般不需要手动编辑(除非需要一次性重命名大量文件)。此外,项目通常还包含一个由用户命名的主文件,默认情况下,IDE 会将其命名为 project1.lpr。

我们已经简要介绍了 Lazarus 如何通过包和项目将文件组合成更大的实体。由于其独立性原则,即使是来自不同程序员和外部供应商的包也能被统一管理。接下来,我们将探讨 Lazarus 的源码编辑器如何在编程过程中帮助用户充分利用这些特性。

1.1源码编辑器

源码编辑器充分体现了 IDE 中“集成”的理念。它基于 SynEdit 组件,并利用 Codetools 的工具实现跨项目的导航和编辑。窗体编辑器、调试器和文档编辑器(FPDoc)也都与源码编辑器紧密结合。这一特性是 Lazarus 的一大亮点,在许多方面甚至超越了 Delphi 编辑器的功能。

用户可以为所有功能分配键盘快捷键,这些快捷键可以在Tools >Environment > Options > Editor > Key Mappings中进行配置。快捷键可以由一个或两个按键加上任意组合的修饰键组成。在 Windows 系统中,可用的修饰键包括 CtrlShiftAlt

1.1.1 代码工具

IDE 使用 codetools 来解析源代码,而非依赖编译器。相较于编译器,codetools 拥有以下优点:

  • 更强的容错能力:尤其在处理缺失单元文件时表现优异。
  • 广泛的代码兼容性:能解析 Delphi、Kylix 和 FPC 的代码。
  • 灵活的编译参数配置:可以在每个目录中使用自定义的编译参数。
  • 便捷的跨平台编辑:允许用户在特定平台上编辑其他平台的代码,如同直接在目标平台上工作一样。例如,在 Linux 上编辑 Windows 代码时,无需安装跨编译器。
  • 高效的更新机制:编辑大型项目的基单元时,编译器需要重新编译所有依赖单元,而 codetools 只更新必要的部分,提高了效率。
  • 支持注释和条件编译代码:虽然对注释中的语法检查可能不够精确,但 codetools 依然可以处理注释和由 IFDEF 关键字禁用的代码。

1.1.2 快速导航代码

通过 Ctrl+Shift+Ctrl+Shift+ 的组合键,用户可以在过程的声明和实现之间快速切换。这两个功能分别称为“查找过程定义”和“查找过程实现”,能够帮助你在方法、接口部分以及前置声明之间轻松跳转。这些功能几乎适用于所有场景,极大提高了编码效率。

procedure DoSomething(i: Integer); // from here 
implementation 
procedure DoSomething(i: Integer) ; 
begin 
// to here, or vice versa 
end; 

当使用快捷键时,如果未找到完全匹配的过程,IDE 会自动查找最接近的,并将光标定位到两者之间的第一个差异处。这一特性有助于快速定位和修正代码中的细微差别,提升开发效率。

procedure DoSomething(Parameterl: Integer); // from here 
implementation 
procedure DoSomething(i: Integer) ; 
begin  // ?to here 

“查找声明”功能(快捷键 Alt+)可以帮助用户搜索标识符的声明,并总是跳转到最近的声明位置。例如,如果你在一个方法的实现中使用了某个变量或函数,并希望通过快捷键快速定位到该变量或函数的声明处,“查找声明”功能会将光标直接定位到最近的声明位置,方便你查看或编辑。

type 
var 
TMyInteger = 1..2; // does not find any other 
TMyInteger = 1..3; // jumps to the first TMylnteger 
i: TMyInteger; // jumps to the second TMylnteger 

当光标位于前置声明时,“查找声明”功能会自动跳转到实际的声明位置。对于使用前置声明的标识符,默认情况下该功能会跳转到最近的前置声明。如果你希望 IDE 直接跳转到实际的声明处,可以在 codetools 的设置中进行相应配置,确保每次都能直接定位到最终的声明位置,从而提高开发效率。

type
TDog = class; // jumps downward to the actual declaration
TPaw = class
public
Dog:TDog   // jumps upward
           // or directly downward, depending on the setting
end;

TDog = class
public
Paws:
array[1..4] of TPaw;
end;  

上下文功能, (快捷键Ctrl + Shift +Space)
如果光标位于参数列表中,按下快捷键会显示该参数的声明。例如,当光标位于源代码中构造函数参数列表的括号内时:

fs=TFileStream.Create();

将显示类 TFileStream 的所有构造函数,包括参数列表,并且当前光标所在的参数会被高亮显示。当用户按下 Esc 键或光标移出参数列表时,上下文信息将消失。

代码浏览器
代码浏览器是一个独立的窗口,可以通过菜单“(View > Code Browser)”打开。它展示了当前单元中的所有 Pascal 标识符,类似于 Delphi 中的功能。这些标识符可以根据字母顺序或类别进行排序,方便快速定位所需内容。

1.1.3 自动补全

(快捷键Ctrl+Space注意:这个和输入法的中英文快捷键冲突,我的解决办法是修改了输入法的快捷键)
标识符补全功能会在你输入类的方法或属性时显示一个过滤后的列表。随着你输入方法名的字母,列表会实时更新,仅显示匹配项。用户可以通过鼠标点击或使用键盘方向键来浏览列表,并选择所需的方法或属性。此功能通过按下 Ctrl+Space 组合键激活。
例如,假设你在编写代码时,光标位于以下源代码中标识符 L 后面的 . 上:

L:TStrings;
i:Integer;   
  
for i:=0 to L.; 

因为变量 L 是 TStrings 类型,所以按下 Ctrl+Space 后,IDE 会弹出一个包含 TStrings 类的所有属性和方法的列表。

代码补全功能的效果取决于光标的位置。当光标位于类的声明部分时,IDE 会启用“类补全”功能。此时,IDE 不仅会尝试完成类的声明,还会为声明中的所有方法生成空的实现代码。在这一过程中,IDE 能够识别属性的 getter 和 setter 方法,并通过智能算法推测哪些内容应该转换为字段或方法,从而自动生成相应的代码。
例如,如果在以下类声明中按下 Ctrl+Shift+C 触发代码补全功能:

type
TMyComponent = class (TComponent)
public
property Active: Boolean Read FActive Write SetActive;
end;  

IDE 自动生成后的代码:

TMyComponent = class (TComponent) 
private 
FActive: Boolean; 
procedure SetActive(const AValue: Boolean); 
public 
property Active: Boolean Read FActive Write SetActive; 
end; 

{ TMyComponent } 
procedure TMyComponent.SetActive(const AValue: Boolean);
begin
  if FActive=AValue then Exit;
  FActive:=AValue;
end;   


在这个例子中,IDE 不仅识别了 SetActive 方法和字段 FActive,还为 SetActive 方法生成了主体代码。当然,IDE 会对每个不完整的属性或方法进行类似的处理。这个例子只包含几个属性定义,但在实际开发中,IDE 会对遇到的每一个不完整的属性或方法都进行同样的处理,确保代码的一致性和完整性。
不仅正向操作可以自动生成代码,反向操作也是可行的。例如,如果在类的实现部分添加了一个新的过程或方法,IDE 可以帮助你自动更新类的声明部分,确保声明和实现保持一致。

procedure TMyComponent.CheckActive;
begin
  if not Active then RaiseException;
end; 

如果此时光标位于 procedure 和 end; 之间,并按下 Ctrl+Shift+C 触发代码补全功能,IDE 将自动更新类的声明部分,确保声明和实现保持一致:

TMyComponent = class (TComponent)
private
  FActive: Boolean;
  procedure CheckActive; //here
  procedure SetActive(AValue: Boolean);
public
property Active: Boolean Read FActive Write SetActive;
end; 

除了类和方法的自动补全外,IDE 还支持变量自动补全功能。

procedure TForm1.Button1Click(Sender: TObject);
begin
  for i:=0 to L.Count do;
end;

例如,在上面 for 循环语句中,如果光标位于代码变量 i 前并触发代码补全功能(并按下 Ctrl+Shift+C ),IDE 会根据上下文自动识别并定义变量 i 为整数类型 (Integer)。

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  for i:=0 to L.Count do;
end;    

在此情况下,代码补全功能会自动推断变量的类型。这一功能同样适用于参数:

procedure TForm1.FormCreate(Sender: TObject);
begin
  SetBounds(Left, Top, Width, Height);
end; 

如果在光标位于新变量 Left 上时触发代码补全功能,IDE 会根据 SetBounds 方法的参数签名自动将 Left 声明为整数类型 (Integer),如下所示:


procedure TForm1.FormCreate(Sender: TObject);
var Left:Integer;    
begin
  SetBounds(Left, Top, Width, Height);
end;

(这个方法,我测试没能实现,译者注)

代码补全功能还可以生成完整的事件处理器代码。这一功能类似于 Delphi 的对象检查器(Object Inspector),当为组件分配事件处理器时,它会自动生成完整的声明和实现。同样地,这一功能也可以直接在源代码中使用。

procedure TForml.ButtonlClick(Sender: TObject) ; 
Var l:TStringList ; 
begin 
  L.OnChange := @DoListChange; 
end;

如果光标位于标识符 DoListChange 上并触发代码补全功能,IDE 会识别这是一个事件处理器,并自动生成具有事件处理器方法。

procedure TForm1.DoListChange(Sender: TObject);
begin
 
end; 

除了代码补全功能外,IDE 还支持代码模板。代码模板是预定义的代码段,可以通过一个快捷键组合 (Ctrl+J) 快速插入到源代码中。每个代码模板都有一个名称和简短描述。要插入代码模板,只需输入模板的名称,然后按下 Ctrl+J 组合键。IDE 将在光标位置插入相应的代码段。值得注意的是,无需输入完整的模板名称,只要输入唯一的初始字符串即可。如果有多个模板具有相同的初始字符串,IDE 会显示一个选择窗口,类似于标识符补全时的行为。Lazarus 提供了许多预定义的代码模板。
例如,如果光标位于源代码中单词trye 的末尾:

begin 
trye 
end; 

按组合键 Ctrl+J 会使 IDE 将 trye 扩展为:

begin 
try 
except 
end; 
end; 

假设光标位于关键字 try 和 except 之间,准备输入其余的源代码,在空白行按下 Ctrl+J,IDE 会显示一个模板列表,让用户从中选择一个合适的模板。这使得用户可以快速插入预定义的代码段,可以在 Code Templates 对话框中编辑可用模板,可通过 Tools >Code Templates 菜单配置。

代码模板必须满足以下三个条件:

  1. 名称必须已知:输入的字符串将与模板名称的初始字母进行匹配。
  2. 必须有简短描述:每个模板应附带简短描述,帮助用户理解其用途。
  3. 必须提供源代码文本:模板中要插入的源代码文本必须存在。该代码将如同手动输入一样插入到源代码中。
    当模板代码中包含竖线符号 | 时,光标将被定位在该符号的位置;否则,光标将位于插入代码段的起始位置。
    trye 的代码模板为:
try
  | 
except

end;

除了竖线符号 | 外,代码模板还支持多种宏(macros),这些宏可以通过“ Insert Macro”按钮启用。还可以选择让模板在输入名称后按下 EnterSpace 键时自动插入。
如果光标位于源代码中 repeat 之后

begin
repeat

end; 

并按下 Enter 键,IDE 会自动插入缺少的 until。

begin
repeat

until;
end; 

如果你不喜欢代码自动生成功能,可以在“Tools > Options > Editor >Completion and Hints >Add close statement for Pascal blocks”中禁用此功能。许多编辑器能够为多种编程语言提供语法高亮功能,Lazarus 也不例外,SynEdit 工程中提供了几个为 Free Pascal 和 Lazarus 预配置的语法高亮器。与 Delphi IDE 不同,Lazarus 的所有工具都能处理包含文件(include files)。这在需要将平台特定的代码拆分到单独文件中时特别有用,从而避免了难以阅读的 IFDEF 结构,当需要支持多个平台时,这种方法尤其方便。
通过在搜索路径中使用宏,可以轻松且地为每个平台选择适当的包含文件(include files)。这种方法在 Free Pascal 和 Lazarus 代码中比在 Delphi 中更为常见。

较大文件处理
即使是包含几十万行代码的大文件也可以通过 Lazarus 编辑器进行高效处理和编辑。codetools 还为程序员提供了各种重构工具,如下所述。
标识符重命名
标识符的所有实例都可以进行重命名,这涉及到上下文相关的“Find or Rename identifier”操作。
方法提取
用户可以标记一组语句,并将它们重构为一个独立的方法。
这样可以减小程序的大小,提高源代码的可读性使其更易于理解。

自动完成类的功能有时会生成空方法。这些空方法可以使用 IDE 的自动移除功能来查找并删除。具体设置路径为:“Tools > Options > Editor >Completion and Hints >Auto remove empty methods”。

显示未使用的单元(Show unused units)功能会分析当前单元的 uses 子句,并将未使用的单元分为两类进行展示。使用方法,在单元(unit)中弹出右键菜单,然后从Refactoring(重构)下的子菜单中选择。在这里,“未使用”意味着没有直接使用的标识符。然而,如果单元通过初始化部分注册了全局对象,则标识符可能是间接使用的。例如,LCL 的图像格式(如 .tga)就是通过这种方式操作的。绑定 /az/ga 单元确保所有 TOpenPictureDialog 实例都能识别 .tga 图像格式。类似地,Lazarus 表单也通过初始化部分加载表单数据。如果某个表单未被使用,其相关数据也是不必要的。编译器能够看到初始化部分,因此不会报告该单元未被使用,智能链接也不会移除这些未使用的数据。此外,CodeTools 还可以用于查找抽象方法。如果光标位于类声明中,可以通过上述弹出菜单中的显示抽象方法(Show abstract methods)功能,查看需要重写的父类方法列表。在选择了所需的抽象方法后,IDE 会自动生成相应的方法声明和主体实现。类似于其他编辑器,Lazarus 提供了一套强大的查找与替换功能,但具有更多高级选项,不再展开叙述。

posted @   三言两语学编程  阅读(282)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示