(delphi11最新学习资料) Object Pascal 学习笔记---第4章第4节(函数的高级特性)

4.4 函数的高级特性

​ 到目前为止,我已经介绍了与函数相关的核心功能,但还有一些高级功能值得探索。不过,如果你确实是软件开发方面的新手,你可能会想暂时跳过本章的其余部分,转到下一章。

4.4.1 Object Pascal 的调用约定
每当你的代码需要调用函数时,双方需要就参数从调用者传递给被调用者的实际方式达成一致,这就是所谓的调用约定。一般来说,函数调用是通过堆栈内存区域传递参数(并期望返回值)。不过,参数和返回值在堆栈中的顺序会因编程语言和平台的不同而改变,大多数编程语言都能使用多种不同的调用约定。

很久以前,32 位版本的 Delphi 引入了一种新的参数传递方法,即 “fastcall”: 只要有可能,最多可以在 CPU 寄存器中传递三个参数,从而使函数调用速度大大提高。Object Pascal 默认使用这种快速调用约定,但也可以通过使用register关键字来请求。

Fastcall 是默认的调用约定,使用这个调用约定的函数与外部库不兼容,如 Win32 中的 Windows API 函数。Win32 API 的函数必须使用 stdcall(标准调用)调用约定来声明,它是 Win16 API 的原始 pascal 调用约定和 C 语言的 cdecl 调用约定的混合体。Object Pascal 支持所有这些调用约定,但除非需要调用不同语言编写的库,如系统库,否则很少会使用与默认约定不同的调用约定。

需要摒弃默认快速调用约定的一个典型情况是需要调用平台的本地 API,根据操作系统的不同,需要使用不同的调用约定。即使是 Win64 也使用与 Win32 不同的模式,因此 Object Pascal 支持许多不同的选项,这里不值得详述。移动操作系统倾向于公开类,而不是本地函数,但即使在这些情况下,也必须考虑尊重特定调用约定的问题。

4.4.2 过程类型

​ Object Pascal 的另一个特点是存在过程类型。这实际上是一个高级的语言主题,只有少数程序员会使用。然而,由于我们将在后面的章节中将讨论相关主题(具体来说是方法指针,这是环境用于定义事件处理程序以及匿名方法的一种技术),在这里简要介绍一下是值得的。

​ 在 Object Pascal 中(但不在更传统的 Pascal 语言中),存在过程类型的概念(与 C 语言的函数指针概念类似——这是 C# 和 Java 等语言已经放弃的功能,因为它与全局函数和指针相关)。过程类型的声明指出参数列表,在函数的情况下还包括返回类型。例如,您可以使用以下代码声明一个新的过程类型,其中包含一个按引用传递的 Integer 参数:

type
  TIntProc = procedure(var Num: Integer);

​ 这个过程类型与具有完全相同参数的任何例程兼容(或者使用 C 的术语来说,具有相同的函数签名)。以下是一个兼容例程的示例:

procedure DoubleIt(var Value: Integer);
begin
  Value := Value * 2;
end;

​ 过程类型可以用于两种不同的目的:您可以声明过程类型的变量,或将过程类型(即函数指针)作为参数传递给另一个例程。鉴于前述类型和过程声明,您可以编写以下代码:

var
  IP: TIntProc;
  X: Integer;
begin
  IP := DoubleIt;
  X := 5;
  IP(X);
end;

这段代码与以下较短版本具有相同的效果:

var
  X: Integer;
begin
  X := 5;
  DoubleIt(X);
end;

​ 第一个版本显然更复杂,那么我们为什么要使用它,什么时候使用它呢?在某些情况下,能够延后决定实际调用哪个函数的能力会非常强大。我们可以建立一个复杂的示例来展示这种方法。不过,我更愿意让大家探索一个相当简单的示例,名为 ProcType。

​ 这个示例基于两个过程。一个过程用于将参数值加倍,就像我已经展示过的那样。第二个过程用于将参数值加三倍,因此被命名为 TripleIt:

procedure TripleIt(var Value: Integer);
begin
  Value := Value * 3;
end;

​ 我们不直接调用这些函数,而是将其中一个或另一个保存在程序类型变量中。当用户选择复选框时,该变量就会被修改,而当用户点击按钮时,当前过程就会以这种通用方式被调用。程序使用了两个初始化的全局变量(要调用的过程和当前值),因此这些值会随着时间的推移而保留。这是完整的代码,除去上面已经显示的实际过程的定义:

var
  IntProc: TIntProc = DoubleIt;
  Value: Integer = 1;

procedure TForm1.CheckBox1Change(Sender: TObject);
begin
  if CheckBox1.IsChecked then
    IntProc := TripleIt
  else
    IntProc := DoubleIt;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  IntProc(Value);
  Show(Value.ToString);
end;

​ 当用户更改复选框状态时,随后的所有按钮点击都将调用活动函数。因此,如果您按两次按钮,更改选择,然后再按两次按钮,您将先将当前值加倍两次,然后将其加倍两次,生成以下输出:

2 
4 
12 
36

​ 使用过程类型的另一个实际示例是当您需要将函数传递给像 Windows 这样的操作系统时(通常称为“回调函数”)。正如本节开头提到的,除了过程类型,Object Pascal 开发人员还使用方法指针(在第 10 章中介绍)和匿名方法(在第 15 章中介绍)。

注解:在面向对象的机制中,获得后期绑定的函数调用(即运行时可以改变的函数调用)的最常见的方法是使用虚方法。虚方法在 Object Pascal 中非常常见,而过程类型却很少使用。然而,技术基础在某种程度上是相似的。虚函数和多态性将在第 8 章中讨论。

4.4.3 外部函数声明

​ 外部声明是系统编程的另一个重要元素。外部声明最初用于将代码链接到用汇编语言编写的外部函数,在 Windows 编程中,外部声明已成为调用 DLL(动态链接库)函数的常用方法。外部函数声明意味着可以调用编译器或链接器无法完全使用的函数,但需要加载外部动态链接库并调用其中的一个函数。

注解:每当在您的 Object Pascal 代码中调用某个平台的 API 时,您失去了在任何其他平台上重新编译应用程序的能力,除非调用被平台特定的 $IFDEF 编译指令所包围。

​ 这就是您可以从 Delphi 应用程序中调用 Windows API 函数的方式。如果打开 Winapi.Windows 单元,您将找到许多函数声明和定义,如下所示:

// 前置声明
function GetUserName(lpBuffer: LPWSTR;
  var nSize: DWORD): BOOL; stdcall;
// 外部声明(而不是实际代码)
function GetUserName; external advapi32
  name 'GetUserNameW';

​ 由于 Windows 单元和许多其他系统单元中已经列出了这些声明,因此您很少需要编写类似刚才说明的声明。需要编写外部声明代码的唯一原因是调用自定义 DLL 中的函数,或调用平台 API 中未翻译的 Windows 函数。

​ 此声明意味着函数 GetUserName 的代码将以 GetUserNameW 的名称存储在 advapi32 动态库中(advapi32 是与 DLL 全名 "advapi32.dll "相关联的常量),因为此 API 函数既有 ASCII 版本,也有 WideString 版本。在外部声明中,我们可以指定我们的函数引用一个 DLL 函数,而该 DLL 函数最初的名称是不同的。

DLL 函数的延迟加载

​ 在 Windows 操作系统中,有两种方法可以调用 Windows SDK(或任何其他 DLL)的 API 函数:一种是让应用程序加载器解决所有外部函数的引用问题,另一种是编写特定代码来查找函数并在函数可用时执行它。

​ 前一种代码更容易编写(正如我们在上一节中所看到的):因为你所需要的只是外部函数声明。但是,如果你想调用的函数库或哪怕只有一个函数不可用,你的程序将无法在不提供该函数的操作系统版本上启动。

​ 动态加载允许更大的灵活性,但需要手动加载库,使用 GetProcAddress API 查找要调用的函数,并在将指针转换为适当类型后调用该函数。这种代码相当繁琐,而且容易出错。

​ 因此,Object Pascal 编译器和链接器专门支持 Windows 操作系统中的一项功能,而且一些 C++ 编译器已经使用了这项功能,即在调用函数之前延迟加载函数。这种声明的目的不是为了避免 DLL 的隐式加载(无论如何都要加载),而是为了允许在 DLL 中延迟绑定特定函数。

​ 基本上,你编写代码的方式与 DLL 函数的经典执行方式非常相似,但函数地址是在首次调用函数时解析的,而不是在加载时。这意味着,如果函数不可用,就会出现运行时异常,即 EExternalException。不过,一般情况下,您可以验证操作系统的当前版本或您要调用的特定库的版本,并提前决定是否要进行调用。

注解:如果你想要一个比异常更具体、更容易在全局级别上处理的方式,你可以挂钩延迟加载调用的错误机制,正如 Allen Bauer 在他的博客文章中解释的那样:https://blog.therealoracleatdelphi.com/2009/08/exceptional-procrastination_29.html

​ 从 Object Pascal 语言的角度来看,唯一的区别在于外部函数的声明,而不是编写:

function MessageBox;
  external user32 name 'MessageBoxW';

​ 现在可以编写(同样来自 Windows 单元中的实际示例):

function GetSystemMetricsForDpi(nIndex: Integer; dpi: UINT): Integer;
	stdcall; external user32 name 'GetSystemMetricsForDpi' delayed;

​ 在运行时,考虑到该 API 是首次添加到 Windows 10 1607 版本中,您可能希望编写如下代码:

if (TOSVersion.Major >= 10) and (TOSVersion.Build >= 14393) then
  NMetric := GetSystemMetricsForDpi(SM_CXBORDER, 96);

​ 这比在旧版本 Windows 上,没有延迟加载的情况下运行相同程序所需的代码要少得多。

​ 另一个相关的观察是,在构建自己的 DLL 并在Object Pascal 中调用它们时,只要对新函数使用延迟加载,就可以使用相同的机制,提供一个可以绑定到同一 DLL 的多个版本的单一可执行文件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/383522.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【开源】SpringBoot框架开发桃花峪滑雪场租赁系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 游客服务2.2 雪场管理 三、数据库设计3.1 教练表3.2 教练聘请表3.3 押金规则表3.4 器材表3.5 滑雪场表3.7 售票表3.8 器材损坏表 四、系统展示五、核心代码5.1 查询教练5.2 教练聘请5.3 查询滑雪场5.4 滑雪场预定5.5 新…

Linux(Ubuntu) 环境搭建:远程终端软件(MobeXterm)

一、MobaXterm下载地址 服务器的远程终端软件我选择的是:MobaXtermMobaXterm 官方网站下载地址:https://mobaxterm.mobatek.net/download.htmlMobaXterm 汉化版下载地址:https://github.com/RipplePiam/MobaXterm-Chinese-Simplified 二、官…

机器人学中的数值优化(一)

Preliminaries 0 前言 最优解 x ∗ x^{*} x∗在满足约束的所有向量中具有最小值。 两个基本的假设: (1)目标函数有下界 目标函数不能存在负无穷的值,这样会使得最小值无法在计算机中用浮点数表示,最小值可以很小但必须…

文章页的上下篇功能是否有必要?boke112百科取消上下篇功能

也不知道是从什么时候开始,我们很多站长的博客网站文章页都会在文末添加上“上一篇”和“下一篇”功能,目的是进行站内SEO优化和方便用户阅读上下篇文章。 boke112百科不管是以前使用的Three主题还是现在使用的YIA主题,刚开始的文章页都是有…

HTTP网络通信协议基础

目录 前言: 1.HTTP协议理论 1.1协议概念 1.2工作原理 2.HTTP抓包工具 2.1Fiddler工具 2.2抓包原理 3.HTTP协议格式 3.1HTTP请求 3.2HTTP响应 3.3格式总结 前言: 在了解完网络编程的传输层UDP和TCP通信协议后,就需要开始对数据进行…

电动汽车上哪些部位用到了电机?

一、背景 电动汽车中除了主驱动电机之外的其他电机的控制复杂度因电机的种类和功能而异。 一般来说,助力转向电机、空调风扇电机、冷却水泵电机等辅助电机的控制相对较为简单。这些电机通常只需要进行简单的开/关控制或速度调节,以满足车辆的基本需求。…

mac电脑上使用android studio创建flutter项目

mac电脑环境配置可以看这篇文章:https://xiaoshen.blog.csdn.net/article/details/136068650 配置玩环境之后,开始创建第一个flutter项目:点击new flutter project或者new project都可以 然后选择flutter: 并将sdk配置为解压后的…

java nio零拷贝

零拷贝是一种计算机执行IO操作的优化技术,其核心目标是减少数据拷贝次数,从而提高系统性能。它主要体现在以下几个方面: 1. **定义与原理**:零拷贝字面上的意思包括“零”和“拷贝”。其中,“拷贝”是指数据从一个存储…

政安晨:在Jupyter中【示例演绎】Matplotlib的官方指南(一){Pyplot tutorial}

介绍 Matplotlib是一个Python的绘图库,可以用于创建各种静态、动态、交互式的图表和可视化效果。它提供了一种方便的方式来可视化数据,并支持多种图表类型,包括线图、散点图、柱状图、饼图、等高线图等。 Matplotlib可以与NumPy一起使用&am…

【前端web入门第五天】02 盒子模型基础

文章目录: 1.盒子模型的组成 1.1盒子模型重要组成部分1.2 盒子模型-边框线1.3 盒子模型–内边距 1.3.1 盒子模型–内边距-多值写法 1.4 盒子模型–尺寸计算 1.5 盒子模型-版心居中 1.盒子模型的组成 不同组件之间的空白就是盒子模型的功劳 作用:布局网页,摆放盒子…

【Linux】信号保存与信号捕捉处理

信号保存与信号捕捉 一、信号保存1. 信号的发送2. 理解信号保存(1)信号保存原因(2)信号保存概念 3. 信号保存系统接口(1)sigset_t(2)sigprocmask()(3)sigpend…

Blazor SSR/WASM IDS/OIDC 单点登录授权实例5 - Winform 端授权

目录: OpenID 与 OAuth2 基础知识Blazor wasm Google 登录Blazor wasm Gitee 码云登录Blazor SSR/WASM IDS/OIDC 单点登录授权实例1-建立和配置IDS身份验证服务Blazor SSR/WASM IDS/OIDC 单点登录授权实例2-登录信息组件wasmBlazor SSR/WASM IDS/OIDC 单点登录授权实例3-服务端…

Netty应用(八) 之 ByteBuf 半包粘包问题 半包粘包解决方案-封帧解码器

目录 19.ByteBuf 19.1 ByteBuf的基本使用 19.2 ByteBuf的扩容机制 19.3 ByteBuf与内存的关系 19.4 ByteBuf的内存结构 19.5 ByteBuf的API 19.5.1 ByteBuf的写操作 19.5.2 ByteBuf的读操作 19.5.3 ByteBuf的slice 19.6 ByteBuf的内存释放 19.6.1 实现API 19.6.2 如何…

肿瘤微环境各种浸润细胞及maker(学习)

目录 Tumor Infiltrating Leukocytes(肿瘤浸润性白细胞) TISCH2数据库收录的TIL 免疫细胞的分类 28种不同免疫细胞类型 Tumor Infiltrating Leukocytes(肿瘤浸润性白细胞) Gene expression markers of Tumor Infiltrating Le…

[2024]常用的pip指令

[2024]常用的pip指令 HI,这里是肆十二,好久不见,大家! 新年好! pip是Python的包管理工具,它可以用来安装、升级、卸载Python包。以下是一些常用的pip指令: 安装包: bash复制代码…

钓鱼邮件便捷发送工具(GUI)

简介 本程序利用Python语言编写,使用Tkinter实现图形化界面,可使用Pyinstaller进行exe打包,程序主界面截图如下: 功能 支持腾讯企业邮、网易企业邮、阿里企业邮、自建邮服SMTP授权账号(其他邮服,可在自建…

医疗处方架构设计和实现的实战经验总结

医疗处方是医生开具给患者的药物治疗建议。在现代医疗系统中,设计和实现一个高效而可靠的医疗处方架构至关重要。本文将介绍医疗处方架构的设计原则和关键组件,以及如何实现一个可扩展和安全的处方管理系统。 内容: 1. 引言 - 医疗处方的…

vue3 之 商城项目—home

home—整体结构搭建 根据上面五个模块建目录图如下&#xff1a; home/index.vue <script setup> import HomeCategory from ./components/HomeCategory.vue import HomeBanner from ./components/HomeBanner.vue import HomeNew from ./components/HomeNew.vue import…

HarmonyOS 状态管理装饰器 Observed与ObjectLink 处理嵌套对象/对象数组 结构双向绑定

本文 我们还是来说 两个 harmonyos 状态管理的装饰器 Observed与ObjectLink 他们是用于 嵌套对象 或者 以对象类型为数组元素 的数据结构 做双向同步的 之前 我们说过的 state和link 都无法捕捉到 这两种数据内部结构的变化 这里 我们模拟一个类数据结构 class Person{name:…

java实现文件随机加密

1、引言 有时候我们需要对我们的某些文件数据进行加密&#xff0c;并且不希望被轻易破译&#xff0c;此时最好不要使用已知的加密方法&#xff0c;这里我就给大家提供一种数据加密的方式&#xff0c;用以实现文件数据的加密&#xff0c;我称之为随机加密&#xff0c;即使是对相…