Delphi 之 第九课 Windows编程

  Delphi 利用Object Pascal 和可视控件库(VCL)对底层的Windows API 进行了完美的封装,所以很少需要使用基础Pascal 语言来建立Windows应用程序,也无需直接调用Windows API 函数。尽管如此,如果遇到特殊情况,VCL 又不支持,Delphi程序员还得直接面对Windows编程。不过只有在极其特殊的情况下,例如:基于不寻常API 调用的Delphi新控件开发, 你才需要这样做,这里我不想讨论这方面内容,我只想让大家看一下与操作系统交互的几个Delphi元素以及Delphi程序员能从中获益的Windows编程技术。

Windows 句柄

Delphi从Windows 引入了不少数据类型,其中句柄最重要。这种数据类型名为THandle,该类型在Windows 单元中定义:

type
  THandle = LongWord;

句柄数据类型通过数字实现,但并不当数字用。在Windows 中,句柄是一个系统内部数据结构的引用。例如,当你操作一个窗口,或说是一个Delphi 窗体时,系统会给你一个该窗口的句柄,系统会通知你:你正在操作142号窗口,就此,你的应用程序就能要求系统对142号窗口进行操作——移动窗口、改变窗口大小、把窗口极小化为图标,等等。实际上许多Windows API 函数把句柄作为它的第一个参数,如GDI (图形设备接口)句柄、菜单句柄、实例句柄、位图句柄等等,不仅仅局限于窗口函数,。

换句话说,句柄是一种内部代码,通过它能引用受系统控制的特殊元素,如窗口、位图、图标、内存块、光标、字体、菜单等等。Delphi中很少需要直接使用句柄,因为句柄藏在窗体、位图及其他Delphi对象的内部。当你要调用Delphi不支持的Windows API 函数时,句柄才会有用。

现在举一个简单的Windows句柄例子,完善这节内容。例WHandle 程序的窗体很简单,只有一个按钮。正如下面主窗体文本所定义的那样,我在代码中添加了窗体的OnCreate 事件和按钮的OnClick 事件:

object FormWHandle: TFormWHandle
  Caption = ‘Window Handle‘
  OnCreate = FormCreate
  object BtnCallAPI: TButton
    Caption = ‘Call API‘
    OnClick = BtnCallAPIClick
  end
end

窗体一创建,程序就会通过窗体本身的Handle 属性,获取窗体对应的窗口句柄。调用IntToStr ,把句柄数值转换为一个字符串,然后再把它添加到窗体标题中,如图9.1:

procedure TFormWHandle.FormCreate(Sender: TObject);
begin
  Caption := Caption + ‘ ‘ + IntToStr (Handle);
end;

因为FormCreate 是窗体类的方法,它可直接访问同类的其他属性和方法。因此,在这个过程中我们能够直接访问窗体的Caption 属性和Handle 属性。

图 9.1: 例 WHandle 显示窗体句柄,每次运行程序得到的句柄值不同

如果你多此次执行该程序,通常会获得不同的句柄值。这个值实际上是由Windows 操作系统确定并返回给应用程序的。(句柄从来不是由程序决定的,而且句柄没有预定义值,句柄是由系统决定的,每执行一次程序,产生一个新值。)

当你单击按钮,程序将调用Windows API 函数SetWindowText,它会根据第一个传递参数改变窗口的标题。更准确地说,所用的API 函数其第一个参数是需要修改窗体的句柄:

procedure TFormWHandle.BtnCallAPIClick(Sender: TObject);
begin
  SetWindowText (Handle, ‘Hi‘);
end;

这段代码与前面所讲的事件处理程序等效,它通过给窗体的Caption 属性赋一个新值,改变窗体的标题。对上面这种情况,调用一个API 函数没有什么意义,因为用Delphi来做更简单。然而有些API在Delphi中没有相应的函数,就需要直接调用API,这一点你会在后面的高级例子中看到。

外部声明

Windows 编程中涉及的另一个重要元素是外部声明。外部声明原先用于在Pascal代码中连接汇编语言写的外部函数,现在外部声明用于Windows编程,用来调用动态连接库DLL函数。在Delphi的Windows 单元中有许多这种声明:

// forward declaration
function LineTo (DC: HDC; X, Y: Integer): BOOL; stdcall;

// external declaration (instead of actual code)
function LineTo; external ‘gdi32.dll‘ name ‘LineTo‘;

这段声明表示函数LineTo 的代码同名保存在GDI32.DLL 动态链接库中(最重要的Windows 系统库之一)。实际应用时,外部声明中的函数名与DLL中的函数名可以不同。

一般你不需要象刚才所例举的那样写声明,因为Windows 单元和一些Delphi 系统单元中已包含了这些声明。只有在调用自定义DLL,或调用Delphi 中未定义的Windows 函数时,你才能需要写外部声明。

注意:在16位Delphi中,外部声明使用不带扩展名的库名,后面跟name指令(如上所示)或是一个index指令,后面跟DLL中函数的序号。尽管Win32 仍然允许通过序号访问DLL函数,但是微软公司已经声明未来将不支持这种访问方式,这一改变反映了系统库访问方式的改变。还要注意的是:目前Delphi的Windows 单元已取代了16位Delphi的WinProcs 和WinTypes 单元。

回调函数

从第六章已经了解到Objet Pascal 支持过程类型。过程类型常用于给Windows API函数传递回调函数。

首先,什么是回调函数呢?回调函数就是能对一系列系统内部元素执行给定操作的API函数,例如能对所有同类窗口进行操作的函数。这种函数也叫枚举函数,它是作为参数传递的函数,代表对所有内部元素执行的操作,该函数或过程的类型必须与给定的过程类型兼容。Windows 回调函数的应用不止上述一种,不过这里仅研究以上简单应用。

现在考虑 EnumWindows API 函数,它的原型如下(从Win32 帮助文件拷贝而来):

BOOL EnumWindows(
  WNDENUMPROC lpEnumFunc,  // address of callback function
  LPARAM lParam // application-defined value
  );

当然,这是个C语言的定义。我们可以查看Windows 单元,从中找到相应的Pascal 语言定义:

function EnumWindows (
  lpEnumFunc: TFNWndEnumProc;
  lParam: LPARAM): BOOL; stdcall;

查阅帮助文件,我们发现作为参数传递的函数应该属于下面的类型(也是在C中):

BOOL CALLBACK EnumWindowsProc (
  HWND hwnd, // handle of parent window
  LPARAM lParam // application-defined value
  );

这与下面的Delphi 过程类型定义一致:

type
  EnumWindowsProc = function (Hwnd: THandle;
    Param: Pointer): Boolean; stdcall;

其中第一个参数是各主窗体的句柄,第二个参数则是调用EnumWindows 函数时所传递的值。实际上,Pascal 中没有相应的TFNWndEnumProc类型定义 ,它只是个指针。这意味着我们需要传递一个带有合适参数的函数,将它用作一个指针,也就是取函数的地址而不是调用它。不幸的是,这也意味着如果某个参数类型出现错误时,编译器不会给予提示。

每当调用Windows API函数或传递一个回调函数给系统时,Windows 要求程序员遵循stdcall 调用协定。缺省情况下,Delphi使用另一种更高效的调用协定,其关键字为register。

下面是一个与定义兼容的回调函数,此函数把窗口的标题读到字符串中,然后添加到给定窗体的一个列表框中:

function GetTitle (Hwnd: THandle; Param: Pointer): Boolean; stdcall;
var
  Text: string;
begin
  SetLength (Text, 100);
  GetWindowText (Hwnd, PChar (Text), 100);
  FormCallBack.ListBox1.Items.Add (
    IntToStr (Hwnd) + ‘: ‘ + Text);
  Result := True;
end;

窗体有一个几乎覆盖整个窗体的列表框,窗体顶部有一个小面板,面板上有一个按钮。当按下按钮时,EnumWindows API函数被调用,并且GetTitle 函数作为参数传递给它:

procedure TFormCallback.BtnTitlesClick(Sender: TObject);
var
  EWProc: EnumWindowsProc;
begin
  ListBox1.Items.Clear;
  EWProc := GetTitle;
  EnumWindows (@EWProc, 0);
end;

你可以直接调用GetTitle函数,不必先把值保存到过程类型临时变量中,上例这么做是为了使回调过程更清楚。程序运行结果确实很有意思,正如你在图9.2中看到的那样,结果显示了系统中正在运行的所有主窗口,其中大部分是隐藏的,你通常看不到,许多实际上没有标题。

图 9.2: 例CallBack输出结果--当前所有主窗体,其中包括可见及隐藏的窗体

最小的Windows 程序

为了完整介绍Windows 编程及Pascal 语言,现在我展示一个简单但完整的应用程序,建立该程序没有使用VCL库。这个程序只是简单地采用命令行参数(保存在系统全程变量cmdLine中),并利用ParamCount 和 ParamStr 这两个Pascal 函数从参数中提取信息。其中第一个函数返回参数的个数,第二个返回给定位置的参数。

尽管在图形用户界面环境下用户很少操纵命令行参数,但是Windows 命令行参数对系统本身却很重要。例如,一旦你定义了文件扩展名和应用程序的关联,只要双击所关联的文件就能执行这个程序。实际上,当你双击一个文件,Windows 即启动关联程序并把选定的文件作为命令行参数传递给它。

下面是工程文件的完整源代码(一个DPR 文件,不是PAS 文件):

program Strparam;

uses
  Windows;

begin
  // show the full string
  MessageBox (0, cmdLine,
    ‘StrParam Command Line‘, MB_OK);

  // show the first parameter
  if ParamCount > 0 then
    MessageBox (0, PChar (ParamStr (1)),
      ‘1st StrParam Parameter‘, MB_OK)
  else
    MessageBox (0, PChar (‘No parameters‘),
      ‘1st StrParam Parameter‘, MB_OK);
end.

输出代码使用MessageBox API 函数,很容易就避开了VCL库。实际上,象上面那样纯粹的Windows 程序,其优点就是占的内存少,程序执行文件大约才16k字节。

为了给程序提供命令行参数,你可以用Delphi的 Run > Parameters 菜单命令。另一个方法是:打开Windows 资源管理器,查找包含程序执行文件的目录,然后把你要执行的文件拖到可执行文件上,Windows 资源管理器会把拖放的文件名用作命令行参数,开始执行程序。图9.3显示了资源管理器及相应的输出。

图9.3: 把一个文件拖放到执行文件上,给例StrParam提供命令行参数

结束语

在这一章中,我们对Windows 编程的底层内容进行了介绍,讨论了句柄和简单的Windows 程序。对于常规的Windows 编程任务,通常只需使用Delphi 提供的可视开发工具及VCL可视控件库。但是这超出了本书讨论的范围,因为本书讨论的是Pascal 语言

时间: 2024-08-27 14:29:39

Delphi 之 第九课 Windows编程的相关文章

VB API 之 第九课 图像编程(二)

用到2个API函数,Polyiine,Polylineto函数原型如下 Declare Function Polyline Lib "gdi32" Alias "Polyline" (ByVal hdc As Long, lpPoint As POINTAPI, ByVal nCount As Long) As LongDeclare Function PolylineTo Lib "gdi32" Alias "PolylineTo&q

【Windows编程】系列第九篇:剪贴板使用

 上一篇我们学习了常见的通用对话框,本篇来了解剪贴板的使用,它常用于复制粘贴功能. 剪贴板是Windows最早就加入的功能,由于该功能非常实用,我们几乎每天都会使用到.通过剪贴板,我们就可以将数据从一个应用程序传递到另一个应用程序,是一种简单的进程间通信. 许多文档处理软件都有复制.剪切.粘贴功能,这些都是用Windows剪贴板实现的,当然我们也可以在我们的程序中实现自己的剪贴板功能,本篇我们就来实现自己的剪贴板.使用剪贴板时,都是先把源数据先传到剪贴板上,再在需要的时候从剪贴板传输到目的处

【C语言探索之旅】 第二部分第九课: 实战"悬挂小人"游戏

内容简介 1.课程大纲 2.第二部分第九课: 实战"悬挂小人"游戏 3.第二部分第十课预告: 安全的文本输入 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个游戏. C语言编程基础知识 什么是编程? 工欲善其事,必先利其器 你的第一个程序 变量的世界 运算那点事 条件表达式 循环语句 实战:第一个C语言小游戏 函数 练习题 习作:完善第一个C语言小游戏 C语言高级技术 模块化编程 进击的指针,C语言王牌 数组 字符串 预处理 创建

【C语言探索之旅】 第二部分第九课: 实战[悬挂小人]游戏

内容简介 1.课程大纲 2.第二部分第九课: 实战"悬挂小人"游戏 3.第二部分第十课预告: 安全的文本输入 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个游戏. C语言编程基础知识 什么是编程? 工欲善其事,必先利其器 你的第一个程序 变量的世界 运算那点事 条件表达式 循环语句 实战:第一个C语言小游戏 函数 练习题 习作:完善第一个C语言小游戏 C语言高级技术 模块化编程 进击的指针,C语言王牌 数组 字符串 预处理 创建

操作系统,windows编程,网络,socket

首发:个人博客,更新&纠错&回复 之前关于c/s的一篇博文只记了思路没记代码,而且表达不清晰,事后看不知所云,这个习惯要改. 这十几天学了点关于操作系统.windows编程和网络,主要看的书有以下几本,都没看完或者只看了一点,记下书名先:<现代操作系统><深入解析windows操作系统><windows程序设计><c#入门经典><wpf编程宝典><图解tcp/ip>另外<tcp/ip详解><java网

【C++探索之旅】第一部分第九课:数组威武,动静合一

内容简介 1.第一部分第九课:数组威武,动静合一 2.第一部分第十课预告:文件读写,海阔凭鱼跃 数组威武,动静合一 上一课<[C++探索之旅]第一部分第八课:传值引用,文件源头>中,我们学习了函数参数的不同传递形式:值传递和引用传递,也学习了如何用头文件和源文件来更好地组织项目. 在不少程序中,我们都需要使用多个相同类型的变量.例如:一个网站的用户名列表(一般是string类型):或者一场比赛的前10个最佳得分(一般是int类型). 类似地,C++和大多数编程语言一样,也有将多个相同类型的数据

【Linux探索之旅】第二部分第九课:查找文件,无所遁形

内容简介 1.第二部分第九课:查找文件,无所遁形 2.第二部分测试题 查找文件,无所遁形 这一课不难,但挺重要的. 之前的课程我们见识过了Linux下文件的组织形式是很特别的,跟Windows不一样. 我们也用ls / 这个命令来列出根目录下的所有目录,有/bin,/etc,/var,/home,等等.而这些目录下又有子目录和文件,错综复杂. 这些目录中有一部分是历史遗留的,从Unix时代就有了.问题是:我们如何在这"茫茫文海"中查找我们需要的文件. "人潮人海中,有你有我.

【C语言探索之旅】 第一部分第九课:函数

内容简介 1.课程大纲 2.第一部分第九课:函数 3.第一部分第十课预告: 练习题+习作 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个游戏. C语言编程基础知识 什么是编程? 工欲善其事,必先利其器 你的第一个程序 变量的世界 运算那点事 条件表达式 循环语句 实战:第一个C语言小游戏 函数 练习题 习作:完善第一个C语言小游戏 C语言高级技术 模块化编程 进击的指针,C语言王牌 数组 字符串 预处理 创建你自己的变量类型 文件读写 动

BeagleBone Black板第九课:测试读取ADC数据

BBB板第九课:测试读取ADC数据 之前我们初步学习了GPIO的使用方法,接下来认识下BBB板上ADC数据的读取操作,因为用BBB板做外部控制,接收外部传感器的一些模拟数据,模数转换ADC是需要用到的.所以在这里我也测试一下这BBB板的ADC简单功能,以后有深入学习到知识再进行补充. ADC资源引脚,P9_32至P9_40共九个引脚,其中P9_32为电源正极,P9_34为共地,其他七个对应七个ADC输入脚,因为有一个Ain_7估计是内部使用了,所以我们只使用Ain_0至Ain_6这七个ADC.