多语言展示
当前在线:1044今日阅读:26今日分享:39

windows编程快速入门

易语言、编程原理
工具/原料
1

易语言开发环境

2

windows程序设计教材

方法/步骤
1

动态链接  Windows运作机制的核心是一个称作「动态链接」的概念。Windows提供了应用程序丰富的可呼叫函数,大多数用于实作其使用者接口和在视讯显示器上显示文字和图形。这些函数采用动态链接库(Dynamic Linking Library,DLL)的方式撰写。这些动态链接库是些具 有 .DLL 或 者 有 时 是 .EXE 扩 展 名 的 文 件 , 在 Windows 98 中 通 常 位 于\WINDOWS\SYSTEM子目录中,在Windows NT中通常位于\WINNT\SYSTEM和\WINNT\SYSTEM32子目录中。 在早期,Windows的主要部分仅通过三个动态链接库实作。这代表了Windows的三个主要子系统,它们被称作Kernel、User和GDI。当子系统的数目在Windows最近版本中增多时,大多数典型的Windows程序产生的函数呼叫仍对应到这三个模块之一。Kernel(日前由16位的KRNL386.EXE和32位的KERNEL32.DLL实现)处理所有在传统上由操作系统核心处理的事务-内存管理、文件I/O和多任务管理。User(由16位的USER.EXE和32位的USER32.DLL实作)指使用者接口,实作所有窗口运作机制。GDI(由16位的GDI.EXE整理编撰:Defoe.Tu tyysoft@yahoo.com.cn 和32位的GDI32.DLL实作)是一个图形设备接口,允许程序在屏幕和打印机上显示文字和图形。 Windows 98支持应用程序可使用的上千种函数呼叫。每个函数都有一个描述名称,例如CreateWindow。该函数(如您所猜想的)为程序建立新窗口。所有应用程序可以使用的Windows函数都在表头文件里预先声明过。 在Windows程序中,使用Windows函数的方式通常与使用如strlen等C语言链接库函数的方式相同。主要的区别在于C语言链接库函数的机械码连结到您的程序代码中,而Windows函数的程序代码在您程序执行文件外的DLL中。 当您执行Windows程序时,它通过一个称作「动态链接」的过程与Windows相接。一个Windows的.EXE文件中有使用到的不同动态链接库的参考数据,所使用的函数即在那些动态链接库中。当Windows程序被加载到内存中时,程序中的呼叫被指向DLL函数的入口。如果该DLL不在内存中,就把它加载到内存中。 当您连结Windows程序以产生一个可执行文件时,您必须连结程序开发环境提供的特定「引用链接库(import library)」。这些引用链接库包含了动态链接库名称和所有Windows函数呼叫的引用信息。连结程序使用该信息在.EXE文件中建立一个表格,在加载程序时,Windows使用它将呼叫转换为Windows函数。

2

表头文件  HELLOMSG.C以一个前置处理器指示命令开始,实际上在每个用C编写的Windows程序的开头都可看到: #include  WINDOWS.H是主要的含入文件,它包含了其它Windows表头文件,这些表头文件的某些也包含了其它表头文件。这些表头文件中最重要的和最基本的是: WINDEF.H 基本型态定义。  WINNT.H 支持Unicode的型态定义。  WINBASE.H Kernel函数。  WINUSER.H 使用者接口函数。  WINGDI.H 图形设备接口函数。  这些表头文件定义了Windows的所有数据型态、函数呼叫、数据结构和常数标识符,它们是Windows文件中的一个重要部分。使用Visual C++ Developer Studio的Edit菜单中的Find in Files搜索这些表头文件非常方便。您还可以在Developer Studio中打开这些表头文件并直接阅读它们。

3

Windows表头文件类型  正如您在第一章所看到的那样,一个Windows程序包括表头文件WINDOWS.H。该文件包括许多其它表头文件,包括WINDEF.H,该文件中有许多在Windows中使用的基本型态定整理编撰:Defoe.Tu tyysoft@yahoo.com.cn 义,而且它本身也包括WINNT.H。WINNT.H处理基本的Unicode支持。 WINNT.H的前面包含C的表头文件CTYPE.H,这是C的众多表头文件之一,包括wchar_t的定义。WINNT.H定义了新的数据型态,称作CHAR和WCHAR: typedef char CHAR ; typedef wchar_t WCHAR ; // wc 当您需要定义8位字符或者16位字符时,推荐您在Windows程序中使用的数据型态是CHAR和WCHAR。WCHAR定义后面的注释是匈牙利标记法的建议:一个基于WCHAR数据型态的变量可在前面附加上字母wc以说明一个宽字符。 WINNT.H表头文件进而定义了可用做8位字符串指针的六种数据型态和四个可用做const 8位字符串指针的数据型态。这里精选了表头文件中一些实用的说明数据型态语句: typedef CHAR * PCHAR, * LPCH, * PCH, * NPSTR, * LPSTR, * PSTR ; typedef CONST CHAR * LPCCH, * PCCH, * LPCSTR, * PCSTR ; 前缀N和L表示「near」和「long」,指的是16位Windows中两种大小不同的指标。在Win32中near和long指标没有区别。 类似地,WINNT.H定义了六种可作为16位字符串指针的数据型态和四种可作为const 16位字符串指针的数据型态: typedef WCHAR * PWCHAR, * LPWCH, * PWCH, * NWPSTR, * LPWSTR, * PWSTR ; typedef CONST WCHAR * LPCWCH, * PCWCH, * LPCWSTR, * PCWSTR ; 至此,我们有了数据型态CHAR(一个8位的char)和WCHAR(一个16位的wchar_t),以及指向CHAR和WCHAR的指标。与TCHAR.H一样,WINNT.H将TCHAR定义为一般的字符类型。如果定义了标识符UNICODE(没有底线),则TCHAR和指向TCHAR的指标就分别定义为WCHAR和指向WCHAR的指标;如果没有定义标识符UNICODE,则TCHAR和指向TCHAR的指标就分别定义为char和指向char的指标: #ifdef UNICODE typedef WCHAR TCHAR, * PTCHAR ; typedef LPWSTR LPTCH, PTCH, PTSTR, LPTSTR ; typedef LPCWSTR LPCTSTR ; #else typedef char TCHAR, * PTCHAR ; typedef LPSTR LPTCH, PTCH, PTSTR, LPTSTR ; typedef LPCSTR LPCTSTR ; #endif  如果已经在某个表头文件或者其它表头文件中定义了TCHAR数据型态,那么WINNT.H和WCHAR.H表头文件都能防止其重复定义。不过,无论何时在程序中使用其它表头文件时,都应在所有其它表头文件之前包含WINDOWS.H。 WINNT.H表头文件还定义了一个宏,该宏将L添加到字符串的第一个引号前。如果定义了整理编撰:Defoe.Tu tyysoft@yahoo.com.cn UNICODE标识符,则一个称作 __TEXT的宏定义如下: #define __TEXT(quote) L##quote 如果没有定义标识符UNICODE,则像这样定义__TEXT宏: #define __TEXT(quote) quote 此外, TEXT宏可这样定义: #define TEXT(quote) __TEXT(quote) 这与TCHAR.H中定义_TEXT宏的方法一样,只是不必操心底线。我将在本书中使用这个宏的TEXT版本。 这些定义可使您在同一程序中混合使用ASCII和Unicode字符串,或者编写一个可被ASCII或Unicode编译的程序。如果您希望明确定义8位字符变量和字符串,请使用CHAR、PCHAR(或者其它),以及带引号的字符串。为明确地使用16位字符变量和字符串,请使用WCHAR、PWCHAR,并将L添加到引号前面。对于是8位还是16位取决于UNICODE标识符的定义的变量或字符串,要使用TCHAR、PTCHAR和TEXT宏。

4

总体结构  进行Windows程序设计,实际上是在进行一种对象导向的程序设计(OOP)。这一点在Windows中使用得最多的对象上表现最为明显。这种对象正是Windows之所以命名为「Windows」的原因,它具有人格化的特征,甚至可能会在您的梦中出现,这就是那个叫做「窗口」的东西。 桌面上最明显的窗口就是应用程序窗口。这些窗口含有显示程序名称的标题列、菜单甚至可能还有工具列和滚动条。另一类窗口是对话框,它可以有标题列也可以没有标题列。 装饰对话框表面的还有各式各样的按键、单选按钮、复选框、清单方块、滚动条和文字输入区域。其中每一个小的视觉对象都是一个窗口。更确切地说,这些都称为「子窗口」或「控件窗口」或「子窗口控件」。 作为对象,使用者会在屏幕上看到这些窗口,并通过键盘和鼠标直接与它们进行交互操作。更有趣的是,程序写作者的观点与使用者的观点极其类似。窗口以「消息」的形式接收窗口的输入,窗口也用消息与其它窗口通讯。对讯息的理解将是学习如何写作Windows程序所必须越过的障碍之一。 这有一个Windows的消息范例:我们知道,大多数的Windows程序都有大小合适的应用程整理编撰:Defoe.Tu tyysoft@yahoo.com.cn 序窗口。也就是说,您能够通过鼠标拖动窗口的边框来改变窗口的大小。通常,程序将通过改变窗口中的内容来响应这种大小的变化。您可能会猜测(并且您也是正确的),是Windows本身而不是应用程序在处理与使用者重新调整窗口大小相关的全部杂乱程序。由于应用程序能改变其显示的样子,所以它也「知道」窗口大小改变了。 应用程序是如何知道使用者改变了窗口的大小的呢?由于程序写作者习惯了往常的文字模式程序,操作系统没有设置将此类消息通知给使用者的机制。问题的关键在于理解Windows所使用的架构。当使用者改变窗口的大小时,Window给程序发送一个消息指出新窗口的大小。然后程序就可以调整窗口中的内容,以响应大小的变化。 「Windows给程序发送消息。」我们希望读者不要对这句话视而不见。它到底表达了什么意思呢?我们在这里讨论的是程序代码,而不是一个电子邮件系统。操作系统怎么给程序发送消息呢? 其实,所谓「Windows给程序发送消息」,是指Windows呼叫程序中的一个函数,该函数的参数描述了这个特定消息。这种位于Windows程序中的函数称为「窗口消息处理程序」。 无疑,读者对程序呼叫操作系统的做法是很熟悉的。例如,程序在打开磁盘文件时就要使用有关的系统呼叫。读者所不习惯的,可能是操作系统呼叫程序,而这正是Windows对象导向架构的基础。 程序建立的每一个窗口都有相关的窗口消息处理程序。这个窗口消息处理程序是一个函数,既可以在程序中,也可以在动态链接库中。Windows通过呼叫窗口消息处理程序来给窗口发送消息。窗口消息处理程序根据此消息进行处理,然后将控制传回给Windows。 更确切地说,窗口通常是在「窗口类别」的基础上建立的。窗口类别标识了处理窗口消息的窗口消息处理程序。使用窗口类别使多个窗口能够属于同一个窗口类别,并使用同一个窗口消息处理程序。例如,所有Windows程序中的所有按钮均依据同一个窗口类别。这个窗口类别与一个处理所有按钮消息的窗口消息处理程序(位于Windows的动态链接库中)联结。 在对象导向的程序设计中,对象是程序与数据的组合。窗口是一种对象,其程序是窗口消息处理程序。数据是窗口消息处理程序保存的信息和Windows为每个窗口以及系统中那个窗口类别保存的信息。 窗口消息处理程序处理给窗口发送消息。这些消息经常是告知窗口,使用者正使用键盘或者鼠标进行输入。这正是按键窗口知道它被「按下」的奥妙所在。在窗口大小改变,或者窗口表面需要重画时,由其它消息通知窗口。 Windows程序开始执行后,Windows为该程序建立一个「消息队列」。这个消息队列用来存放该程序可能建立的各种不同窗口的消息。程序中有一小段程序代码,叫做「消息循环」,用来从队列中取出消息,并且将它们发送给相应的窗口消息处理程序。有些消息直接发送给窗口消息处理程序,不用放入消息队列中。 如果您对这段Windows架构过于简略的描述将信将疑,就让我们去看看在实际的程序中,窗口、窗口类别、窗口消息处理程序、消息队列、消息循环和窗口消息是如何相互配合的。这或许会对您有些帮助。

5

HELLOWIN程序  建立一个窗口首先需要注册一个窗口类别,那需要一个窗口消息处理程序来处理窗口消息。处理窗口消息对每个Windows程序都带来了些负担。程序3-1所示的HELLOWIN程序中整整理编撰:Defoe.Tu tyysoft@yahoo.com.cn 个做的事情差不多就是料理这些事情。 程序3-1 HELLOWIN HELLOWIN.C  /*------------------------------------------------------------------------  HELLOWIN.C -- Displays 'Hello, Windows 98!' in client area  (c) Charles Petzold, 1998 -----------------------------------------------------------------------*/ #include   LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,  PSTR szCmdLine, int iCmdShow) {  static TCHAR szAppName[] = TEXT ('HelloWin') ;  HWND hwnd ;  MSG msg ;  WNDCLASwndclass ;   wndclass.style = CS_HREDRAW | CS_VREDRAW ;  wndclass.lpfnWndProc = WndProc ;  wndclass.cbClsExtra = 0 ;  wndclass.cbWndExtra = 0 ;  wndclass.hInstance = hInstance ;  wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;  wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;  wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH) ;  wndclass.lpszMenuNam = NULL ;  wndclass.lpszClassName= szAppName ;   if (!RegisterClass (&wndclass))  {  MessageBox ( NULL, TEXT ('This program requires Windows NT!'), 整理编撰:Defoe.Tu tyysoft@yahoo.com.cn  szAppName, MB_ICONERROR) ;  return 0 ;  }  hwnd = CreateWindow( szAppName, // window class name  TEXT ('The Hello Program'), // window caption  WS_OVERLAPPEDWINDOW, // window style  CW_USEDEFAULT,// initial x position  CW_USEDEFAULT,// initial y position  CW_USEDEFAULT,// initial x size  CW_USEDEFAULT,// initial y size  NULL, // parent window handle  NULL, // window menu handle  hInstance, // program instance handle  NULL) ; // creation parameters   ShowWindow (hwnd, iCmdShow) ;  UpdateWindow (hwnd) ;   while (GetMessage (&msg, NULL, 0, 0))  {  TranslateMessage (&msg) ;  DispatchMessage (&msg) ;  }  return msg.wParam ; }  LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {  HDC hdc ;  PAINTSTRUCT ps ;  RECT rect ;   switch (message) 整理编撰:Defoe.Tu tyysoft@yahoo.com.cn  {  case WM_CREATE:  PlaySound (TEXT ('hellowin.wav'), NULL, SND_FILENAME | SND_ASYNC) ;  return 0 ;   case WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;   GetClientRect (hwnd, &rect) ;   DrawText (hdc, TEXT ('Hello, Windows 98!'), -1, &rect,  DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;  EndPaint (hwnd, &ps) ;  return 0 ;   case WM_DESTROY:  PostQuitMessage (0) ;  return 0 ;  }  return DefWindowProc (hwnd, message, wParam, lParam) ; }  程序建立一个普通的应用程序窗口,如图3-1所示。在窗口显示区域的中央显示「Hello, Windows 98!」。如果安装了声卡,那么您还可以听到相应的朗读声音。

6

通盘考量  实际上,每一个Windows程序代码中都包括HELLOWIN.C程序的大部分。没人能真正记住此程序的全部写法;通常,Windows程序写作者在开始写一个新程序时总是会复制一个现有的程序,然后再做相应的修改。您可以按此习惯自由使用本书附带光盘中的程序。 上面提到,HELLOWIN将在其窗口的中央显示字符串。这种说法不是完全正确的。文字实际显示在程序显示区域的中央,它在图3-1中是标题列和边界范围内的大片白色区域。这区别对我们来说很重要;显示区域就是程序自由绘图并且向使用者显示输出结果的窗口区域。 如果您认真思考一下,将会发现虽然只有80行程序代码,这个窗口却令人惊讶地具有许多功能。您可以用鼠标按住标题列,在屏幕上移动窗口;可以按住大小边框,改变窗口的大小。整理编撰:Defoe.Tu tyysoft@yahoo.com.cn 在窗口大小改变时,程序自动地将「Hello, Windows 98!」字符串重新定位在显示区域的中央。您可以按最大化按钮,放大HELLOWIN以充满整个屏幕;也可以按最小化按钮,将程序缩小成一个图示。您可以在系统菜单中执行所有选项(就是按下在标题列最左端的小图示);也可以从系统菜单中选择 Close选项,或者单击标题列最右端的关闭按钮,或者双击标题列最左端的图标,来关闭窗口以终止程序的执行。 我们将在本章的余下部分对此程序作一详细的检查。当然,我们首先要从整体上看一下。 与前两章中的范例程序一样,HELLOWIN.C也有一个WinMain函数,但它还有另外一个函数,名为WndProc。这就是窗口消息处理程序。注意,在HELLOWIN.C中没有呼叫WndProc的程序代码。当然,在WinMain中有对WndProc的参考,而这就是该函数要在程序开头附近声明的原因。

7

Windows函数呼叫  HELLOWIN至少呼叫了18个Windows函数。下面以它们在HELLOWIN中出现的次序列出这些函数以及各自的简明描述: LoadIcon 加载图标供程序使用。  LoadCursor 加载鼠标光标供程序使用。  GetStockObject 取得一个图形对象(在这个例子中,是取得绘制窗口背景的画刷对象)。  RegisterClass 为程序窗口注册窗口类别。  MessageBox 显示消息框。  CreateWindow 根据窗口类别建立一个窗口。  ShowWindow 在屏幕上显示窗口。  UpdateWindow 指示窗口自我更新。  GetMessage 从消息队列中取得消息。  TranslateMessage 转译某些键盘消息。  DispatchMessage 将消息发送给窗口消息处理程序。  PlaySound 播放一个声音文件。  BeginPaint 开始绘制窗口。  GetClientRect 取得窗口显示区域的大小。  整理编撰:Defoe.Tu tyysoft@yahoo.com.cn DrawText 显示字符串。  EndPaint 结束绘制窗口。  PostQuitMessage 在消息队列中插入一个「退出程序」消息。  DefWindowProc 执行内定的消息处理。  这些函数均在Platform SDK文件中说明,并在不同的表头文件中声明,其中绝大多数声明在WINUSER.H中。

8

大写字母标识符  读者可能注意到,HELLOWIN.C中有几个大写的标识符,这些标识符是在Windows表头文件中定义的。有些标识符含有两个字母或者三个字母的前缀,这些前缀后头接着一个底线:   这些是简单的数值常数。前缀指示该常数所属的类别,如表3-1所示。 表3-1  前缀 类别 CS 窗口类别样式 CW 建立窗口 DT 绘制文字 IDI 图示ID IDC 游标ID MB 消息框 SND 声音 WM 窗口消息 WS 窗口样式 奉劝程序写作者不要费力气去记忆Windows程序设计中的数值常数。实际上,Windows中使用的每个数值常数在表头文件中均有相应的标识符定义。

9

新的数据型态  HELLOWIN.C中的其它标识符是新的数据型态,也在Windows表头文件中使用typedef叙整理编撰:Defoe.Tu tyysoft@yahoo.com.cn 述或者#define叙述加以定义了。最初是为了便于将Windows程序从原来的16位系统上移植到未来的使用32位(或者其它)技术的操作系统上。这种作法并不如当时每个人想象的那样顺利,但是这种概念基本上是正确的。 有时这些新的数据型态只是为了方便缩写。例如,用于WndProc的第二个参数的UINT数据型态只是一个unsigned int (无正负号整数),在Windows 98中,这是一个32位的值。用于WinMain的第三个参数的PSTR数据型态是指向一个字符串的指针,即是一个char *。 其它数据型态的含义不太明显。例如,WndProc的第三和第四个参数分别被定义为WPARAM和LPARAM,这些名字的来源有点历史背景:当Windows还是16位系统时,WndProc的第三个参数被定义为一个WORD,这是一个16位的 无正负号短(unsigned short)整数,而第四个参数被定义为一个LONG,这是一个32位有正负号长整数,从而导致了文字「PARAM」前面加上了前置前缀「W」和「L」。当然,在32位的Windows中,WPARAM被定义为一个UINT,而LPARAM被定义为一个LONG(这就是C中的long整数型态),因此窗口消息处理程序的这两个参数都是32位的值。这也许有点奇怪,因为WORD数据型态在Windows98中仍然被定义为一种16位的 无正负号整数,因此「PARAM」前的「W」就有点误用了。 WndProc函数传回一个型态为LRESULT的值,该值简单地被定义为一个LONG。WinMain函数被指定了一个WINAPI型态(在表头文件中定义的所有Windows函数都被指定这种型态),而WndProc函数被指定一个CALLBACK型态。这两个标识符都被定义为_stdcall,表示在Windows本身和使用者的应用程序之间发生的函数呼叫的呼叫参数传递方式。 HELLOWIN还使用了Windows表头文件中定义的四种数据结构(我们将在本章稍后加以讨论)。这些数据结构如表3-2所示。 表3-2  结构 含义 MSG 消息结构 WNDCLASS 窗口类别结构 PAINTSTRUCT 绘图结构 RECT 矩形结构 前面两个数据结构在WinMain中使用,分别定义了两个名为msg和wndclass的结构,后面两个数据结构在WndProc中使用,分别定义了ps和rect结构。

10

句柄简介  最后,还有三个大写标识符(见表3-3),用于不同型态的「句柄」: 表3-3  标识符 含义 HINSTANCE 执行实体(程序自身)句柄 HWND 窗口句柄 HDC 设备内容句柄 句柄在Windows中使用非常频繁。在本章结束之前,我们将遇到HICON(图标句柄)、HCURSOR(鼠标光标句柄)和HBRUSH(画刷句柄)。 整理编撰:Defoe.Tu tyysoft@yahoo.com.cn 句柄是一个(通常为32位的)整数,它代表一个对象。Windows中的句柄类似传统C或者MS-DOS程序设计中使用的文件句柄。程序几乎总是通过呼叫Windows函数取得句柄。程序在其它Windows函数中使用这个句柄,以使用它代表的对象。代号的实际值对程序来说是无关紧要的。但是,向您的程序提供代号的Windows模块知道如何利用它来使用相对应的对象。

11

匈牙利表示法  读者可能注意到,HELLOWIN.C中有一些变量的名字显得很古怪。如szCmdLine,它是传递给WinMain的参数。 许多Windows程序写作者使用一种叫做「匈牙利表示法」的变量命名通则。这是为了纪念传奇性的Microsoft程序写作者Charles Simonyi。非常简单,变量名以一个或者多个小写字母开始,这些字母表示变量的数据型态。例如,szCmdLine中的sz代表「以0结尾的字符串」。在hInstance和hPrevInstance中的h前缀表示「句柄」;在iCmdShow中的i前缀表示「整数」。WndProc的后两个参数也使用匈牙利表示法。正如我在前面已经解释过的,尽管wParam应该更适当地被命名为uiParam(代表「无正负号整数」),但是因为这两个参数是使用数据型态WPARAM和LPARAM定义的,因此保留它们传统的名字。 在命名结构变量时,可以用结构名(或者结构名的一种缩写)的小写作为变量名的前缀,或者用作整个变量名。例如,在HELLOWIN. C的WinMain函数中,msg变量是MSG型态的结构;wndclass是WNDCLASSEX型态的一个结构。在WndPmc函数中,ps是一个PAINTSTRUCT结构,rect是一个RECT结构。 匈牙利表示法能够帮助程序写作者及早发现并避免程序中的错误。由于变量名既描述了变量的作用,又描述了其数据型态,就比较容易避免产生数据型态不合的错误。 表3-4列出了在本书中经常用到的变量前缀。 表3-4  前缀 数据型态 c char或WCHAR或TCHAR by BYTE (无正负号字符) n short i int x, y int分别用作x坐标和y坐标 cx, cy int分别用作x长度和y长度;C代表「计数器」b或f BOOL (int);f代表「旗标」 w WORD (无正负号短整数) l LONG (长整数) dw DWORD (无正负号长整数) fn function(函数) s string(字符串) sz 以字节值0结尾的字符串 h 句柄 p 指标

注意事项
1

windows编程看起来枯燥乏味,只有编程才能感受快乐

2

windows编程一看就想睡觉,把你所知道的写成上课笔记,分享给每一位易语言学习者。

推荐信息