Google

星期一, 三月 31, 2008

[转载]使用AJAX的十大理由(译文)

保守来说,AJAX在现在是热得不能再热的技术。没有人能否认,它拥有大批的支持者。在CNN上,它从二月份的一个不被看好的词语到十月份成长成一个初具 雏形的技术。所以,有必要要看看为什么AJAX能发展成为现在的样子,为什么它能不断成长,并且在短的时间内迅速变得无处不在。所以,我用午夜谈话的风 格,来给出 需要AJAX技术的十大理由。

使用AJAX的十大理由:

10。XAML, XUL, XForms...等等。
9。服务端技术的不确定性。
8。Web2.0。
7。被软件工业领袖们强势采用。
6。和Flex 和 Flash等技术的很好的集成。
5。边际成本低。
4。能使常规的Web应用受益。
3。跨浏览器和跨平台。
2。以可用性和用户体验为王。
1。基于公开标准。

十大理由第一名:公开标准

让 我们从第一条开始,AJAX技术是基于被各大浏览器和平台都支持的公开标准的技术。这意味着该技术不怕技术提供商的技术封锁。组成AJAX技术的大多数技 术都能放心的使用很多年,而那些不是热点的、最新的和未经考验的技术只能使用一段时间。现在,对于绝大多数的用户和企业来说,浏览器是一个可信任的应用平 台,这在五年前就不是个问题了。对于AJAX来说,FIREFOX浏览器的基础Mozilla 1.0的发布并且支持XML HTTP Request对 象是一个转折点。这种允许异步数据交换的技术好多年前就被IE浏览器支持了。这种支持和FIREFOX浏览器的大量被采用真正的使人们理解了跨浏览器的富 Internet应用成为了可能。

1)JavaScript or ECMA Script (Standard ECM A- 262): 一个有趣的事情是,Javascript是经过长时间后才成为被人们接受的技术,长时间以来,很多公司采用非Javascript技术的方针,幸运的是, 这种状况被迅速的改变。
http://www.ecma-international.org/publications/standards/Ecma-262.htm

2)XML:是一个来自W3C的、被广泛应用的标准。
http://www.w3.org/XML/

3)HTML: http://www.w3.org/MarkUp/

4)CSS: http://www.w3.org/Style/CSS/

5)XML HTTP Request Object:被Internet Explorer、Mozilla-based、Safari和Opera浏览器支持。

十大理由第二名:可用性

开 发人员和设计人员开始认识到不仅大型的用户体验在市场上是成功的,而且也认识到这样体验是怎么来影响用户的开销的。基于AJAX技术的google地图比 传统的选择MapQuest更成功,证明了提供更好的用户体验的产品的成功。AJAX技术是使网络应用有更好的可用性的一个领导性的技术。它允许从服务器 端请求少量的信息,而不是整个网页。它增加了页面数据的更新但同时减少了页面的刷新和刷新等待,这些问题从网络已诞生就折磨着Web应用。

人们已经知道他们需要一个优秀的用户界面并且有对该界面的投资意愿。前提条件是:用户能够快速的取得信息不管数据是一个内部网的应用还是一个广域网的服务。

十大理由第三名:跨浏览器和跨平台的兼容性

IE 和基于Mozilla的FIREFOX是占据市场分额最大的两个浏览器,并且它们都支持在浏览器上轻松创建基于AJAX的WEB应用。现在开发运行在更为 先进的WEB浏览器上的基于AJAX的富WEB应用成为了可能。这是为什么AJAX应用变得如此流行的一个最重要的原因。其实很多开发人员多年前就意识到 AJAX技术流行的可能,但一直没有流行是因为浏览器厂商的原因。感谢Mozilla和FIREFOX。

十大理由第四名:使常规的WEB应用受益

AJAX 技术是当今WEB应用的门面——WEB应用获得的利益超过了桌面应用。这些利益包括部署应用的低投入、维护方便、缩短开发时间和不需要安装。这些都是促使 商业和用户自从上世纪九十年代以来采用WEB应用的优点。AJAX技术不但能使WEB应用获得益处,而且使最终用户受益。

十大理由第五名:促使技能、工具和技术的升级

由 于AJAX基于这些年一直使用的一些公开标准,很多的开发人员就会有新的技术方面的要求以便能够开发AJAX应用。但这并不意味着开发团队从基于HTML 和FORM的应用转移到富AJAX型应用需要很高的学习曲线。同时,这意味着开发WEB应用的开发团体需要加速将他们的用户接口升级到AJAX,但并不需 要一个大规模的升级和重写他们的WEB应用。自从上世纪九十年代以来,在开发基于浏览器应用方法花了大量投资的那些系统强烈的希望能在现有的应用的基础上 增加用户体验。

十大理由第六名:能和Flex 和 Flash等技术的很好的集成

大多数的开发社区都不再 支持Flash vs AJAX的火热讨论,这两种技术都在不同的场合拥有各自的优点和缺点,但是它们有大量的机会可以集成到一起工作。很多的开发人员和 技术提供商意识到这一点,并且开发出了伟大的产品来集成Flex和AJAX协调使用。我们也热切的期望看到两者能在Macromedia里一起工作。

十大理由第七名:采用率

AJAX 被业内领袖广泛采用证明了市场的欢迎程度和该技术组的正确。每一个该技术的使用者都成为了胜利者:包括google、yahoo、Amazon和微软等 等。是google地图吸引了WEB开发人员的目光,当人们开始调查是什么原因使得google有着如此惊人的用户体验的时候,人们揭开了罩在AJAX头 上的面纱。

当然,仅仅是google使用AJAX是不够使得这项技术跨越从支流到主流的鸿沟的。但是,如果你看一看使用AJAX技术的客户如eBusiness Applications (www.ebusinessapps.com) or Tibco (http://www.tibco.com/)等的表单时,你就会发现财富500强包括主要的金融机构、政府机构、航空公司和其他主要商业机构采用AJAX,并且在AJAX成为硬通货之前很早就开始使用了。

十大理由第八名:WEB2.0

喜 欢也好,厌恶也罢。WEB2.0运行吸引了开发人员、风险投资商、市场和最终用户等所有的目光。这些明确的促进了AJAX的早期应用。当大肆的宣传过去以 后,我们将会看到什么呢?从BackPack到google地图,AJAX界面是WEB2.0应用的主要的组成。大量的宣传有助于加速采用AJAX,而在 可用性上的获益会使得该技术被广发应用。WEB2.0的一个主要原则是使用WEB作为一个应用开发的平台,而不仅仅是一个网页。高的可用性和交互能力的用 户界面是一切应用平台的主要组成部分。

十大理由第九名:AJAX基于服务器技术的不确定性

和AJAX技术 的浏览器的独立性相同,该技术也兼容所有的标准型的服务器和服务端语言,如 PHP, ASP. ASP.Net, Perl, JSP, Cold Fusion等等,选择属于你的那种然后开始。这使得AJAX开发独立,因为所 有的开发人员都能使用并且一起讨论相同的表现层。

十大理由第十名:基于WEB的下一代RIA技术还没有出现

今 天就使用XUL技术开发应用的人是伟大的,因为现在90%的浏览器还不支持这种技术,对于大多数的实际应用来说,使用这种技术不切合实际。然而,AJAX 开发人员应该给出一部分的注意力在这些技术,如XAML 和XUL上。毫无疑问,这些技术将使开发富WEB应用变得简单。但是它们可能相互不兼容并且拥有 不同的市场需求或动力。

在今后一段时间,AJAX技术将极大的提高WEB应用的可用性。AJAX技术并不完美,不是“火箭科学”许多的开 发人员和技术公司始终在尝试RIA的其他更好的技术。而实际的问题是AJAX技术现在已经存在并且应用的很好,它跨浏览器、跨平台,而且不管是用户还是开 发人员都喜欢它的作用。特征鲜明的AJAX应用如google地图已经成为了本领域的领导者(还有人使用MapQuest吗?)同样的,领先的财富500 强使用AJAX技术并且贡献了开发工具给社区了。一般来说,业内在使用AJAX技术上取得了一致并且正在使用它。再强调一次,RIA应用和WEB应用使用 了AJAX获得的一个主要的优势不仅仅是开发人员的一个工具,而是一个现象:它改变了我们开发WEB应用的方式。没人能说得清楚在RIA应用方面,哪一种 技术会取代它,会在什么时候取代它;但是很多因素都支持AJAX应用应该持续好多年。

关于作者

Andre Charland 从事Internet软件开始超过十年之久,他是eBusiness Applications (www.ebusinessapps.com)公司的 主席和创建者之一。他和Dave Johnson在1998年创建了该公司。他主要的经验在可用性、市场、项目管理和基于构件的软件开发。所受教育包括: 在Vancouver, BC的Simon Fraser University,他在那里读计算机科学和工商管理。他作为开发者、管理者和架构师等不同 身份有上百个Internet项目的经验。

原文链接 http://www.developer.com/java/other/article.php/3567706

标签:

Joomla站点的迁移

以下以joomla站点迁移到IP为218.16.121.121的虚拟主机,域名为www.joomla.com
数据库帐号是:wcf,数据库密码是:88623132,数据库名是wcf,
请通过localhost连接。

1、 到你注册的域名的站点的域名管理中将www.joomla.com和joomla.com以“主机名/A记录”的形式解析到218.16.121.121。
2、 将数据库导出成sql脚本文件
2、将导出的sql脚本文件导入到迁移环境的数据库中。
3、修改configuration.php文件如下的配置
1) 修改$mosConfig_absolute_path 为你迁移后的站点的绝对路径
$mosConfig_absolute_path =’/www/winetcn/joomla;
2) 修伽$mosConfig_cachepath为你迁移后的站点的cache的路径
$mosConfig_cachepath = '/www/winetcn/joomla/cache';
3) 修改$mosConfig_db 为你迁移后的数据库名
$mosConfig_db = wcf;
4) 修改$mosConfig_live_site = 为你站点访问的URL。
$mosConfig_live_site = 'http://www.joomla.com';
5) 修改$mosConfig_password为你迁移后的数据的连接密码
$mosConfig_password = ‘88623132’;
6) 修改$mosConfig_user 为你迁移后的数据的访问用户名
$mosConfig_user = wcf;
4、如果整合了smf,需要修改smf的安装目录下的Settings.php文件中如下的配置:
1) 修改$boardurl为你迁移后的站点smf访问URL,如:$boardurl =’ http://www.wcfonline.com/forum’;
2) 修伽$db_name=’ wcf;
3) 修改$db_user = ‘wcf;
4) 修改$db_passwd = ‘88623132’;
5) 修改$boarddir= ' http://www.jooomla.com/forum;
6) 修改$sourcedir = '/www/winetcn/joomla/forum/Sources';
7) 登录joomla控制台,修改SMF桥接器的SMF中的绝对路径.
将其绝对路径修改为:/www/winetcn/joomla/forum
8) 以smf管理员的身份进入smf的管理控制台,修改smf风格的路径,管理——>目前所使用的风格
修改风格网址和图片地址分别为:
http://www.joomla.com/ forum/Themes/Neutron
http://www.joomla.com/ forum/ Themes/Neutron/images
所在文件夹修改为/www/winetcn/joomla/forum/ Themes/ Neutron

http://www.wcfonline.cn/content/view/12/27/

标签:

学习linux/unix编程方法的建议

建议学习路径

  首先先学学编辑器,vim, emacs什么的都行。
然后学make file文件,只要知道一点就行,这样就可以准备编程序了。

  然后看看《C程序设计语言》K&R,这样呢,基本上就可以进行一般的编程了,顺便找本数据结构的书来看。

  如果想学习UNIX/LINUX的编程,《APUE》绝对经典的教材,加深一下功底,学习《UNP》的第二卷。这样基本上系统方面的就可以掌握了。

  然后再看Douglus E. Comer的《用TCP/IP进行网际互连》第一卷,学习一下网络的知识,再看《UNP》的第一卷,不仅学习网络编程,而且对系统编程的一些常用的技巧就 很熟悉了,如果继续网络编程,建议看《TCP/IP进行网际互连》的第三卷,里面有很多关于应用协议telnet、ftp等协议的编程。
如果想写设备驱动程序,首先您的系统编程的接口比如文件、IPC等必须要熟知了,再学习《LDD》2。

  对于几本经典教材的评价:

  《The C Programing Language》K&R 经典的C语言程序设计教材,作者是C语言的发明者,教材内容深入浅出。虽然有点老,但是必备的一本手册,现在有时候我还常翻翻。篇幅比较小,但是每看一 遍,就有一遍的收获。另外也可用谭浩强的《C语言程序设计》代替。

  《Advanced Programing in Unix Envirement》 W.Richard Stevens:也是非常经典的书(废话,Stevens的书哪有不经典的!),虽然初学者就可以看,但是事实上它是《Unix Network Programing》的一本辅助资料。国内的翻译的《UNIX环境高级编程》的水平不怎么样,现在有影印版,直接读英文比读中文来得容易。

  《Unix Network Programing》W.Richard Stevens:第一卷讲BSD Socket网络编程接口和另外一种网络编程接口的,不过现在一般都用BSD Socket,所以这本书只要看大约一半多就可以了。第二卷没有设计到网络的东西,主要讲进程间通讯和Posix线程。所以看了《APUE》以后,就可以 看它了,基本上系统的东西就由《APUE》和《UNP》vol2概括了。看过《UNP》以后,您就会知道系统编程的绝大部分编程技巧,即使卷一是讲网络编 程的。国内是清华翻译得《Unix网络编程》,翻译者得功底也比较高,翻译地比较好。所以建议还是看中文版。

  《TCP/IP祥解》一共三卷,卷一讲协议,卷二讲实现,卷三讲编程应用。我没有怎么看过。,但是据说也很经典的,因为我没有时间看卷二,所以不便评价。

  《用TCP/IP进行网际互连》Douglus.E.Comer 一共三卷,卷一讲原理,卷二讲实现,卷三讲高级协议。感觉上这一套要比Stevens的那一套要好,就连Stevens也不得不承认它的第一卷非常经典。 事实上,第一卷即使你没有一点网络的知识,看完以后也会对网络的来龙去脉了如指掌。第一卷中还有很多习题也设计得经典和实用,因为作者本身就是一位教师, 并且卷一是国外研究生的教材。习题并没有答案,留给读者思考,因为问题得答案可以让你成为一个中级的Hacker,这些问题的答案可以象Douglus索 取,不过只有他只给教师卷二我没有怎么看,卷三可以作为参考手册,其中地例子也很经典。如果您看过Qterm的源代码,就会知道Qterm的telnet 实现部分大多数就是从这本书的源代码过来的。对于网络原理的书,我推荐它,而不是Stevens的《TCP/IP祥解》。

  《Operating System - Design and Implement》这个是讲操作系统的书,用Minix做的例子。作者母语不是英文,所以英文看起来比较晦涩。国内翻译的是《操作系统 设计与实现》,我没看过中文版,因为翻译者是尤晋元,他翻译的《APUE》已经让我失望头顶了。读了这本书,对操作系统的底层怎么工作的就会
有一个清晰的认识。

  《Linux Device Driver》2e ,为数不多的关于Linux设备驱动程序的好书。不过内容有些杂乱,如果您没有一些写驱动的经验,初次看会有些摸不着南北。国内翻译的是《Linux设备 驱动程序》第二版,第一版,第二版的译者我都有很深的接触,不过总体上来说,虽然第二版翻译的有些不尽人意,但是相比第一版来说已经超出了一大截。要读这 一本书,至少应该先找一些《计算机原理》《计算机体系结构》的书来马马虎虎读读,至少应该对硬件和计算机的工作过程有一些了解。

http://bbs.chinaunix.net/thread-1070966-1-1.html

标签:

星期日, 三月 30, 2008

Joomla学习,模块如何创建-3

好了。xml文件创建完毕,我们该进行下一步了,创建.php文件。
我们来看一下mod_latestnews.php。
/**
* @version $Id: mod_latestnews.php 9764 2007-12-30 07:48:11Z ircmaxell $
* @package Joomla
* @copyright Copyright (C) 2005 - 2008 Open Source Matters. All rights reserved.
* @license GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/

// no direct access
defined('_JEXEC') or die('Restricted access'); // 不允许直接调用

// Include the syndicate functions only once
require_once (dirname(__FILE__).DS.'helper.php'); // 此处引用模型文件

$list = modLatestNewsHelper::getList($params); // 调用模型文件内的类,获得参数列表
require(JModuleHelper::getLayoutPath('mod_latestnews')); // 调用显示模块

标签: ,

Joomla学习,模块如何创建-2

按照我前面所做的分析,我在joomla的modules目录中创建了一个子目录,mod_demos,然后创建了一个基本没有什么内容的mod_demo.xml文件。



Demo module
Ou Lanhui
Mar. 30, 2008
All rights reserved.
Free for personal use but need denote if commercial use
ouland@gmail.com
http://www.ouland.com/
1.5.0
Demo module for joomla

mod_demo.php






在Joomla的界面里如果选择Extension->Module manager创建一个新的模块,就可以看到这个模块了,并且能够创建。至于它能否显示,因为mod_demo.php文件不存在,所以就不要想了。

标签: ,

Joomla学习,模块如何创建

模块的创建应该分成2个部分。感觉上,Joomla在实现的方面模块主要是用来处理View方面的工作。因为没有什么时间详细研究,好用即可就是标准。至于component的功能是什么现在没有什么感觉,Component能够生成页面,那应该是比module更为全面的吧,module明显具有只是页面某一部分的特点,因为在使用的时候你可以指定该模块显示在页面的某一位置,由index.php指定的位置。

今天青歌手有道题问的是赵州桥,看到图片问中国最古老的石拱桥是什么我也能答上来,但是是李春造的,在河北,并且是隋朝造的还是让人感触万千。希望我们的作品也能如此吧。我们从什么时候开始不注意质量的?我的思路有点问题,一讲下去就跑远了。不过对于作品,特别是软件,还是要了解好我们需要做什么,确保每一步不出现问题。虽然BUG可能还是会有,但是别出大问题吧。;)

说远了,回到模块上,大的上面模块应该有2个部分,即用于界面显示的,放置在modules里的,还有放在administrator/modules里,用来配置模块的管理部分。哦,现在问题有点清楚了,components,应该是用来具体实现功能的吧。(乱讲)比如想做的一个具体信息分类管理,后台采用模块进行分类的管理,前台呢,按我们计划的有分类的列表视图,分类下的信息展示视图,都可以做为页面的内容。再看后台的管理介面,模块中有mod_mainmenu模块用于显示主栏目菜单,而主栏目菜单上的项目由component对应的视图指定,又倒过来了吧,modules->components。反正是分析着玩,等分析完再对照能找得到的文档看看,这活干得也有意思一点。

好,说完这两种模块,即普通模块和管理模块。我们看看做一个普通模块要做什么事情。首先要在/modules下建立一个目录,这个目录就是你想创建的模块了,通常情况下,它具有这样的名字mod_<模块名>。比如我们要做一个用户想要实现的界面功能的收集系统(这个就是我们以后的参照系统吧),目标是这样:每个用户都可以注册一个或者多个项目,每个项目从属于某一个或多个分类,感谢google,我从你们的邮件系统里学到了label方式,就是每个用户可以指定多个标记来说明项目所属的分类。每个项目用户都需要输出项目相关的内容,我们来想一下,项目的描述,项目的规模,当然,用户可以选择这个项目能不能被别人看到,而能被别人看到的内容,网站的运营者应该审核之后才能被看到,这样防止用户把自己的联系方式都直接写上,这样网站就没有办法赢利了。;)然后用户可以将他所策划的功能做以描述,就是填写另外一张表,来说明系统有什么功能,每个功能是怎么回事。难道我要做的是一个需求管理工具?;)好,其实就是这么一回事了。想太多这个模块没法子写了。

创建好目录之后,首先我们需要几个文件,用于描述模块的文件mod_<模块名>.xml,模块的主文件mod_<模块名>.php也应该是模块的控制文件吧,模块的模型文件helper.php,然后是模块的视图目录tmpl,该目录下有default.php作为缺省的视图。Joomla对MVC的设计非常好,可以的是我知道MVC,但是知其然而不知其所以然,因为我一直是实用主义者,可用是首先,就象我虽然了解设计模式,但是并不强求设计模式的严格使用。我向ACE,Boost的作者致敬,但不表示我不崇尚更为敏捷的开发过程。

描述模块的文件我们看看:

-- 安装信息,指明模块的版本
Latest News -- 模块的名称
Joomla! Project -- 作者
July 2004 -- 创建日期
Copyright (C) 2005 - 2008 Open Source Matters. All rights reserved.
-- 版权信息
http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
-- 采用的协议类型,GPL的后续还可能是什么?
admin@joomla.org
-- 开发者的email地址
www.joomla.org-- 作者的网站地址
1.5.0 -- 这个版本是什么版本?适用于Joomla的版本?
DESCLATESTNEWS
-- 模块的描述信息,相当于商业软件的鼓吹信息
-- 文件列表
mod_latestnews.php
-- 文件的名称

-- 模块的参数

-- 单个参数的内容指定
-- name-参数名 type-参数类型 default-缺省值 label-标签显示内容 description - 参数的相关描述


























标签: ,

星期五, 三月 28, 2008

WIN32下DELPHI中的多线程【变量存储】(三)

线程中的变量
由于每个线程都代表了一个不同的执行路径,因此,最好有一种只限于一个线程内部使用的数据,
要实现上述目的有以下几种方式:
1、局部变量(基于栈),很简单,在你的线程函数中你定义的变量既是如此。由于每个线程都在各自的栈中,各个线程将都有一套局部变量的副本,这样,就不会相互影响。对于那些只在过程或函数的生存期有意义的变量,应当把它们声明为局部变量。
2、存储在线程对象中。还记得createthread函数中的lpparameter参数吗,它可以接受一个无类型的指针。结合本文第一章的内容,你应 该还记得,它被存储在线程内核对象的上下文结构中,你可以通过context结构中的context_integer部分的ebx来读取它的地址。
下面是一段示例代码,用来演示读取context结构,这段代码一般用不到,但它可以说明cratethread函数中的lpparameter被存储的位置

{
作者:wudi_1982
联系方式:wudi_1982@hotmail.com
转载请著名出处,本代码为演示代码,只贴出了一些关键部分
}


type
//传递给线程函数的结构和指针的声明
tinfo = record
count : integer;
x : integer;
y : integer;
end;
pinfo
= ^tinfo;

var
mythreadhad : thandle;
//一个全局变量,用来保存线程的句柄

//线程函数
function mythread(info : pointer):dword; stdcall;
var
i : integer;
begin
//根据传递来信息决定在窗口的那个位置输出什么信息
for i := 0 to pinfo(info)^.count-1 do
form1.image1.canvas.textout(pinfo(info)
^.x,pinfo(info)^.y,inttostr(i));
//freemem(info);
result := 0;
end;

//创建一个线程
procedure tform1.button4click(sender: tobject);
var
ppi : pinfo;
mythreadid : dword;
begin
//分配空间并赋初值
ppi :=allocmem(sizeof(tinfo));
ppi
^.count := 1000000;
ppi
^.x := 10;
ppi
^.y := 10;
//创建
mythreadhad := createthread(nil,0,@mythread,ppi,create_suspended,mythreadid);
//在窗体上显示线程函数的地址和传递给它的参数的地址
labthreadaddr.caption := inttostr( integer(@mythread));
labthreadpvparam.caption :
= inttostr(integer(ppi));
end;

//读取context结构,注意context结构是和cpu有关的,我这里测试时,工作在intel的cpu上
procedure tform1.btnrcontextclick(sender: tobject);
var
con : _context;
begin
//初始化结构
con.contextflags := context_full;
//读取
getthreadcontext(mythreadhad,con);
//显示在窗体的listbox上
with lbxcontextinfo.items do
begin
// clear;
add(------------context--------------);
add(
);
add(
context_debug_registers-----);
add(
dr0:+#9+inttostr(con.dr0));
add(
dr1:+#9+inttostr(con.dr1));
add(
dr2:+#9+inttostr(con.dr2));
add(
dr3:+#9+inttostr(con.dr3));
add(
dr6:+#9+inttostr(con.dr6));
add(
dr7:+#9+inttostr(con.dr7));
add(
context_segments---------);
add(
seggs:+#9+inttostr(con.seggs));
add(
segfs:+#9+inttostr(con.segfs));
add(
seges:+#9+inttostr(con.seges));
add(
segds:+#9+inttostr(con.segds));
add(
context_integer.---------);
add(
edi: +#9+inttostr(con.edi));
add(
esi: +#9+inttostr(con.esi));
add(
ebx: +#9+inttostr(con.ebx));
add(
edx: +#9+inttostr(con.edx));
add(
ecx: +#9+inttostr(con.ecx));
add(
eax: +#9+inttostr(con.eax));
add(
context_control----------);
add(
ebp: +#9+inttostr(con.ebp));
add(
eip: +#9+inttostr(con.eip));
add(
segcs: +#9+inttostr(con.segcs));
add(
eflags: +#9+inttostr(con.eflags));
add(
esp: +#9+inttostr(con.esp));
add(
segss: +#9+inttostr(con.segss));
end;

end;

把上面代码整理之后,添加到你的程序中,你可以发现(如果也是intel的cpu),那么你可以从eax寄存器读取到线程函数的地址,从ebx中读取到传递给线程函数的参数地址。
在delphi中的tthread对象的构造函数中,你可以看到这段代码
fhandle := beginthread(nil, 0, @threadproc, pointer(self), create_suspended, fthreadid);
再 观察beginthread的实现,你会发现tthread的调用createthread时,将pointer(self),也就是tthread对象 本身当作线程函数的参数传递过去,换言之,你在tthread的派生类中定义的变量,对于一个线程而言,将存储在这个线程单独的堆栈中,而它在堆栈的地址 存储在线程的上下文结构中。
可以做一个简单的试验,将一个线程生成多次,你可以发现存储在线程对象内部的变量将互不影响。
说到这里,必须谈论一个问题,效率的问题,我在一本书上曾经看到过这样一段话“由于访问线程对象中的数据比访问线程局部变量要快10倍,因此,你应当尽可 能地把线程专用的信息保存在线程对象中。”对此,我一直没有特别理解。如果一定要相信这句话,那我会这么理解,就是存储在线程对象中的变量因为上下文结构 记录了它的地址等原因,所以它更快。尽信书不如无书,我还在思考,不过好在这种速度的影响对于通常的使用而言影响不大。

3、在delphi中,用object pascal的关键字threadvar来声明变量,以利用操作系统级的线程局部存储。
在前面我们了解到:虽然对于局部变量,在每个线程中都一个副本,然而应用程序的全局变量是被所有线程所共享的。当多个线程对这个全局变量进行访问时,将可 能出现很多未知的问题,win32提供了一种称为线程局部存储的方式,它能使你在第一个运行的线程中创建一个全局变量的拷贝。delphi利用关键字 threadvar封装此功能。在threadvar关键字下你可以声明任何局部存储的变量。
4、全局变量,多线程最让人头疼的地方就是全局变量 了,好的同步方式将决定你高效、安全的访问全局变量,虽然上述的threadvar是解决全局变量线程局部存储的一个办法,但在我实际的编码工作中,几乎 很少用它,它的局限性太多。多线程访问全局变量的方法将在下一文中详细描述。

http://www.west263.com/www/info/41142-1.htm

标签: ,

WIN32下DELPHI中的多线程【线程的调度】(二)

线程的调度

每个线程是拥有一个上下文结构的,这个结构维护在线程的内核对象中。这个上下文结构反映了线程上次运行时该线程的c p u寄存器的状态。每隔20ms左右,windows要查看当前存在的所有线程内核对象。在这些对象中,只有某些对象被视为可以调度的对象。windows 选择可调度的线程内核对象中的一个,将它加载到c p u的寄存器中,它的值是上次保存在线程的环境中的值。这项操作称为上下文转换。windows实际上保存了一个记录,它说明每个线程获得了多少个运行机 会。
windows被称为抢占式多线程操作系统,因为一个线程可以随时停止运行,随后另一个线程可进行调度。如你所见,可以对它进行一定程度的控制,但是不能 太多。注意,无法保证线程总是能够运行,也不能保证线程能够得到整个进程,无法保证其他线程不被允许运行等等。
我在编写串口通讯程序的时候,起初,我有一个天真的想法,“在win32平台下,如何能够保证从串口传送过来的数据,在数据到达后1ms内开始运行?”。 为此,我曾经做了许多试验,但当我真正了解了一些win32平台的知识,我得到了答案,办不到。只有实时操作系统才能作出这样的承诺,但windows不 是实时操作系统。实时操作系统必须清楚地知道它是在什么硬件上运行,这样它才能知道它的硬盘控制器和键盘等的等待时间。microsoft对 windows规定的目标是,使它能够在各种不同的硬件上运行,即能够在不同的cpu、不同的驱动器和不同的网络上运行。简而言之,windows没有设 计成为一种实时操作系统。
windows系统只调度可以调度的线程。那么什么是可以调度的线程,什么是不可以调度的线程呢?例如,有些线程对象的暂停计数大于1(记录在线程内核对 象的上下文结构中)。这意味着该线程已经暂停运行,不应该给它安排任何c p u时间。还记得上文中曾经提到的create_suspended标志吗?在创建一个线程的时候,createthread函数接收的倒数第二个参数中赋 值create_suspended就可以创建一个暂停的线程。除了暂停的线程外,其他许多线程也是不可调度的线程,因为它们正在等待某些事情的发生。例 如,如果记事本程序,如果你不键入任何数据,那么它的线程就没有什么事情要做。系统不给无事可做的线程分配cpu时间。当移动它的窗口时,或者它的窗口需 要刷新它的内容,或者将数据键入记事本,系统就会自动使它的线程成为可调度的线程。但切记,这并不意味着它的线程立即获得了cpu时间。它只是表示记事本 的的线程有事情可做,系统将设法在某个时间(不久的将来)对它进行调度。

线程的暂停和执行
我们前面说过,在线程内核对象的内部有一个值,用于指明线程的暂停计数。当调用createthread函数时,就创建了线程的内核对象,并且它的暂停计 数被初始化为1。这可以防止线程被调度到cpu中。当然,这是很有用的,因为线程的初始化需要时间,你不希望在系统做好充分的准备之前就开始执行线程。当 线程完全初始化好了之后, 要查看是否已经传递了create_suspended标志。如果已经传递了这个标志,那么这些函数就返回,同时新线程处于暂停状态。如果尚未传递该标 志,那么该函数将线程的暂停计数递减为0。当线程的暂停计数是0的时候,除非线程正在等待其他某种事情的发生,否则该线程就处于可调度状态。
在暂停状态中创建一个线程,就能够在线程有机会执行任何代码之前改变线程的运行环境(如优先级)。一旦改变了线程的环境,必须使线程成为可调度线程。要进 行这项操作,可以调用resumethread,将线程句柄传递给它,如果resumethread,函数运行成功,它将返回线程的前一个暂停计数,否则 返回0xffffffff。注意这里,它返回的是前一个暂停计数。
单个线程可以暂停若干次。如果一个线程暂停了3次,它必须恢复3次,然后它才可以被分配给一个c p u。当创建线程时,除了使用create_suspended外,也可以调用suspendthread函数来暂停线程的运行。任何线程都可以调用该函数 来暂停另一个线程的运行(只要拥有线程的句柄)。不用说,线程可以自行暂停运行,但是不能自行恢复运行。suspendthread返回的是线程的前一个 暂停计数。线程暂停的最多次数可以是maximum_suspend_count次。值得注意的是,suspendthread与内核方式的执行是异步进 行的,但是在线程恢复运行之前,不会发生用户方式的执行。在实际环境中,调用suspendthread时必须小心,因为不知道暂停线程运行时它在进行什 么操作。如果线程试图从堆栈中分配内存,那么该线程将在该堆栈上设置一个锁。当其他线程试图访问该堆栈时,这些线程的访问就被停止,直到第一个线程恢复运 行。只有确切知道目标线程是什么(或者目标线程正在做什么),并且采取强有力的措施来避免因暂停线程的运行而带来的问题或死锁状 态,suspendthread才是安全的。

线程的睡眠
线程也能告诉系统,它不想在某个时间段内被调度。这是通过调用sleep函数来实现的:
void sleep(dword cmilliseconds)

该函数可使线程暂停自己的运行,直到cmilliseconds过去为止。关于sleep函数,有下面几个重要问题值得注意:
• 调用sleep,可使线程自愿放弃它剩余的时间片。
• 系统将在大约的指定毫秒数内使线程不可调度。不错,如果告诉系统,想睡眠100ms,那么可以睡眠大约这么长时间,但是也可能睡眠数秒钟或者数分钟。还是 那个反复重申的概念, windows不是个实时操作系统。虽然线程可能在规定的时间被唤醒,但是它能否做到,取决于系统
中还有什么操作正在进行。
• 可以调用sleep,并且为cmilliseconds)参数传递infinite。这将告诉系统永远不要调度该线程。这不是一件值得去做的事情。最好是让线程退出,并还原它的堆栈和内核对象。
• 可以将0传递给sleep。这将告诉系统,调用线程将释放剩余的时间片,并迫使系统调度另一个线程。但是,系统可以对刚刚调用sleep的线程重新调度。 如果不存在多个拥有相同优先级的可调度线程,就会出现这种情况。sleep(0)是一个非常有意思的方法。要小心sleep()神秘的时间调整问题。 sleep()可能会使你的机器出现特别的问题。这种问题在另一台机器上可能无法再现。

切换到另一个线程
系统提供了一个称为switchtothread的函数,使得另一个可调度线程(如果存在能够运行)。当调用这个函数的时候,系统要查看是否存在一个迫切 需要c p u时间的线程。如果没有线程迫切需要c p u时间switchtothread就会立即返回。如果存在一个迫切需要c p u时间的线程,switchtothread就对该线程进行调度(该线程的优先级可能低于调用switchtothread的线程)。这个迫切需要c p u时间的线程可以运行一个时间段,然后系统调度程序照常运行。该函数允许一个需要资源的线程强制另一个优先级较低、而目前却拥有该资源的线程放弃该资源。 如果调用switchtothread函数时没有其他线程能够运行,那么该函数返回false,否则返回一个非0值。调用switchtothread函 数与调用sleep是相似的,差别是switchtothread允许优先级较低的线程运行。即使低优先级线程迫切需要cpu时间,而sleep则可能因 为优先级关系使得刚放弃cpu的线程被立即重新调度。

优先级
操作系统会负责为每个线程分配cpu时间。一个线程所分配到的cpu时间主要取决于该线程的优先级,而线程的优先级又取决于进程的优先级类和线程本身的相对优先级。
1. 进程的优先级类
进程的优先级类用来描述一个进程的优先程度。win32支持四种不同的优先级类: idle、normal、high 和realtime。其中,normal是默认的优先级。在windows单元中,每一种优先级类都对应着一个标志。当要进行进程的优先级设置时,可以用 一种优先级类与createprocess()的参数dwcreationflags进行或操作。另外,还可以动态地为一个已有的进程调整优先级类。这时 候,通常你要用到下面api函数
bool setpriorityclass(handle hprocess,dword fdwpriority),其中第一个参数是进程的句柄,你可以通过getcurrentprocess来获得当前进程的句柄。每个优先级类也对应一个数 字,值在4~ 24之间。注意在windows nt/2000下,要有特殊的权限才能修改进程的优先类。默认的设置允许进程设置它们的优先级类,但是,这些都可以由系统管理员来关闭,尤其是在高负载的 winnt/2000服务器上。
大多数情况下,进程的优先级类不要被设为realtime。因为,大多数操作系统本身的线程的优先级类比realtime低。如果一个进程得到的c p u时间比操作系统本身还多,后果是无法想象的。即使将进程的优先级类设为high ,也可能引起问题。因为,当高优先级的线程没有大部分空时间或等待外部事件时,它要从低优先级的线程和进程中抢夺cpu时间,直到它被一事件阻塞或处于空 闲状态或处理消息。所以,在抢占式多任务操作系统中如果不能合理地安排优先级,就很容易崩溃。

优先级类 说明
实时 进程中的线程必须立即对事件作出响应,以便执行关键时间的任务。
该进程中的线程还会抢先于操作系统组件之前运行。使用本优先级类
时必须极端小心
进程中的线程必须立即对事件作出响应,以便执行关键时间的任务。
task manager(任务管理器)在这个类上运行,以便用户可以撤消脱
离控制的进程
高于正常 进程中的线程在正常优先级与高优先级之间运行(这是wi n d o w s
2 0 0 0中的新优先级类)
正常 进程中的线程没有特殊的调度需求
低于正常 进程中的线程在正常优先级与空闲优先级之间运行(这是wi n d o w s
2 0 0 0中的新优先级类)
空闲 进程中的线程在系统空闲时运行。该进程通常由屏幕保护程序或后
台实用程序和搜集统计数据的软件使用

2. 相对优先级
决定一个线程全面的优先级的另一方面是相对优先级。优先级类是针对进程的,它对进程内部的
所有线程都有效。而相对优先级是针对某个线程的。一个线程的相对优先级可设为以下七种: idle、lowest、below normal、normal、above normal、highest 和time critical。
要 设置一个线程的相对优先级,可以通过api函数setthreadpriority来完成,再delphi中,你可以通过tthread对象的 priority属性来设置。获得线程相对优先级的api函数是int getthreadpriority(handle hthread);

系统何如根据优先级来调度线程
每个线程都会被赋予一个从0(最低)到31(最高)的优先级号码。当系统确定将哪个线程分配给cpu时,它首先观察优先级为31的线程,并以循环方式对它 们进行调度。如果优先级为31的线程可以调度,那么就将该线程赋予一个cpu。在该线程的时间片结束时,系统要查看是否还有另一个优先级为31的线程可以 运行,如果有,它将允许该线程被赋予一个cpu。只要优先级为31的线程是可调度的,系统就绝对不会将优先级为0到30的线程分配给c p u。这种情况称为渴求调度(starvation)。当高优先级线程使用大量的cpu时间,从而使得低优先级线程无法运行时,便会出现渴求情况。在多处理 器计算机上出现渴求情况的可能性要少得多,因为在这样的计算机上,优先级为31和优先级为30的线程能够同时运行。系统总是设法使cpu保持繁忙状态,只 有当没有线程可以调度的时候, cpu才处于空闲状态。
人们可能认为,在这样的系统中,低优先级线程永远得不到机会运行。不过正像前面指出的那样,在任何一个时段内,系统中的大多数线程是不能调度的。例如,如 果进程的主线程调用getmessage函数,而系统发现没有线程可以供它使用,那么系统就暂停进程的线程运行,释放该线程的剩余时间片,并且立即将 cpu分配给另一个等待运行的线程。如果没有为getmessage函数显示可供检索的消息,那么进程的线程将保持暂停状态,并且决不会被分配给cpu。 但是,当消息被置于线程的队列中时,系统就知道该线程不应该再处于暂停状态。此时,如果没有更高优先级的线程需要运行,系统就将该线程分配给一个cpu。

高优先级线程将抢在低优先级线程之前运行,不管低优先级线程正在运行什么。例如,如果一个优先级为5的线程正在运行,系统发现一个高优先级的线程准备要运 行,那么系统就会立即暂停低优先级线程的运行(即使它处于它的时间片中),并且将c p u分配给高优先级线程,使它获得一个完整的时间片。还有,当系统引导时,它会创建一个特殊的线程,称为0页线程。该线程被赋予优先级0,它是整个系统中唯 一的一个在优先级0上运行的线程。当系统中没有任何线程需要执行操作时,0页线程负责将系统中的所有空闲r a m页面置0。

动态提高线程的优先级等级
通过将线程的相对优先级与线程的进程优先级类综合起来考虑,系统就可以确定线程的优先级等级。有时这称为线程的基本优先级等级。

系统常常要提高线程的优先级等级,以便对窗口消息或读取磁盘等i/o事件作出响应。
例如,在高优先级类进程中的一个正常优先级等级的线程的基本优先级等级是13。如果用户按下一个操作键,系统就会将一个wm_keydown消息放入线 程的队列中。由于一个消息已经出现在线程的队列中,因此该线程就是可调度的线程。此外,键盘设备驱动程序也能够告诉系统暂时提高线程的优先级等级。该线程 的优先级等级可能提高2级,其当前优先级等级改为15。系统在优先级为15时为一个时间片对该线程进行调度。一旦该时间片结束,系统便将线程的优先级递减 1,使下一个时间片的线程优先级降为14。该线程的第三个时间片按优先级等级13来执行。如果线程要求执行更多的时间片,均按它的基本优先级等级13来执 行。注意,线程的当前优先级等级决不会低于线程的基本优先级等级。此外,导致线程成为可调度线程的设备驱动程序可以决定优先级等级提高的数量。 microsoft并没有规定各个设备驱动程序可以给线程的优先级提高多少个等级。这样就使得microsoft可以不断地调整线程优先级提高的动态等 级,以确定最佳的总体响应性能。系统只能为基本优先级等级在1至15之间的线程提高其优先级等级。实际上这是因为这个范围称为动态优先级范围。此外,系统 决不会将线程的优先级等级提高到实时范围(高于15)。由于实时范围中的线程能够执行大多数操作系统的函数,因此给等级的提高规定一个范围,就可以防止应 用程序干扰操作系统的运行。另外,系统决不会动态提高实时范围内的线程优先级等级。
另一种情况也会导致系统动态地提高线程的优先级等级。比如有一个优先级为4的线程准备运行但是却不能运行,因为一个优先级为8的线程正连续被调度。在这种 情况下,优先级为4的线程就非常渴望得到cpu时间。当系统发现一个线程在大约3至4s内一直渴望得到c p u时间,它就将这个渴望得到cpu时间的线程的优先级动态提高到15,并让该线程运行两倍于它的时间量。当到了两倍时间量的时候,该线程的优先级立即返回 到它的基本优先级。
系统动态的改变优先级,在我们编程的时候会产生不良影响,为此,还有两个api函数可以使得系统的此功能不起作用。
bool setprocesspriorityboost(handle hprocess,bool disablepriorityboost);
bool setthreadpriorityboost(handle hthread,bool disablepriorityboost);
从名字你就应该可以看出,第一个api函数可以激活或停用指定进程所有线程的优先级提高功能,而后面一个则是针对特定线程的。

例子:关键的代码如下

{
作者:wudi_1982
联系方式:wudi_1982@hotmail.com
转载请著名出处
本代码旨在演示线程的调度,很多位置没有加入适当的控制和资源释放,请按照后续操作执行
}

type
tsleeptype
=(stsleep,stswitch);

//演示线程调度的tthread派生类
tprithread1=class(tthread)
private
curcount : integer;
//当前计数
flb : tlabel; //用来显示当前计数的label
fcansleep : boolean; //是否自动释放时间片
fsleepms : integer;
fsleeptype : tsleeptype;
//释放时间片的方式
procedure getrestult;
protected
procedure execute;
override;
public
constructor create(createsuspended: boolean;alabel : tlabel);
property cansleep : boolean read fcansleep write fcansleep;
property sleepms : integer read fsleepms write fsleepms;
property sleeptype : tsleeptype read fsleeptype write fsleeptype;
end;

....

{ tprithread1的实现 }

constructor tprithread1.create(createsuspended: boolean; alabel: tlabel);
begin
//构造函数
flb := alabel;
fsleepms :
= 0;
fcansleep :
= true;
fsleeptype :
= stsleep;
inherited create(createsuspended);
end;

procedure tprithread1.execute;
var
i : integer;
begin
inherited;
freeonterminate :
= true;
curcount :
= 0;
for i := 0 to 100000 do
begin
curcount :
= i;//改变当前计数
synchronize(getrestult);//显示结果
if fcansleep then//是否自动释放时间片
begin
//根据释放时间片的不同方式进行相应操作
case fsleeptype of
stsleep : sleep(sleepms);
//睡眠
stswitch : switchtothread;//调用其他线程
end;
end;
end;
end;

procedure tprithread1.getrestult;
begin
flb.caption :
= inttostr(curcount);
end;

{form1的主要代码}
procedure tform1.btnpthread1createclick(sender: tobject);
begin
//生成两个线程
mypthread1 := tprithread1.create( not ckbx1state.checked,lab1);
mypthread2 :
= tprithread1.create(not ckbx2state.checked,lab2);
//得到他们当前的优先级
lb1p.caption := inttostr(getthreadpriority(mypthread1.handle));
lb2p.caption :
= inttostr(getthreadpriority(mypthread2.handle));

end;

procedure tform1.btnpthread1resclick(sender: tobject);
begin
//执行线程
mypthread1.resume;
ckbx1state.checked :
= true;

mypthread2.resume;
ckbx2state.checked :
= true;
end;

procedure tform1.btnpthread1sudclick(sender: tobject);
begin
//挂起线程
mypthread1.suspend;
ckbx1state.checked :
= false;
mypthread2.suspend;
ckbx2state.checked :
= false;
end;

procedure tform1.btnuppthread1click(sender: tobject);
begin
//在线程挂起时,提高第一个线程的相对优先级
mypthread1.priority := tphigher;
//显示当前的优先级到屏幕
lb1p.caption := inttostr(getthreadpriority(mypthread1.handle));
// mypthread2.priority := tphigher;
end;

procedure tform1.btnupdatesleepclick(sender: tobject);
begin
//修改两个线程的时间片释放方式
mypthread1.cansleep := ckbxallowsleep1.checked;
case radiogroup1.itemindex of
0 : mypthread1.sleeptype := stsleep;
1 : mypthread1.sleeptype := stswitch;
end;

mypthread2.cansleep :
= ckbxallowsleep2.checked;
case radiogroup2.itemindex of
0 : mypthread2.sleeptype := stsleep;
1 : mypthread2.sleeptype := stswitch;
end;

end;

窗体效果:

线程调度程序的界面

让我们来用这个程序测试一些效果:
1、基本执行。程序运行之后,使用默认设置,点击【创建线程】按钮,线程将被创建,并且挂起,这是你可以 间隔的点击【执行线程】和【挂起线程】按钮,你会在屏幕上看到线程的当前计数,注意这两个计数之间的差值,以及整个界面的执行效果(我指的是在你让线程不 断的执行和挂起之间界面是否会出现不刷新的情况),当线程执行完毕之后,关闭程序。
2、通过sleep(0)释放时间片演示线程调度。运行程序, 使用默认设置,点击【创建线程】按钮,然后将两个线程的自释放时间片功能统统去掉(也就是去掉ckbxallowsleep1 and 2的勾勾),然后点击【修改睡眠方式】按钮,随后你可以进行间隔点击【执行线程】和【挂起线程】按钮,多做几次这样的操作,观察两个计数之间的差值,和测 试1的差值比较一下。我想你应该能想到些什么。然后,几乎可以肯定你的界面将会出现无法刷新的情况,并且你的鼠标无法立即在此界面上进行其他的操作。这个 时候,稍等一下,你会发现过了一会儿,两个当前计数都被刷新了。为什么??这时,我们除了考虑我们创建的两个线程之外,你还要考虑的你程序本身的主线程以 及其他可能存在的附属线程,我们再去程序中线程的那段循环代码,
curcount := i;//改变当前计数
synchronize(getrestult);//显示结果
if fcansleep then//是否自动释放时间片
begin
//根据释放时间片的不同方式进行相应操作
case fsleeptype of
stsleep : sleep(sleepms);//睡眠
stswitch : switchtothread;//调用其他线程
end;
end;
你应该看到线程将当前计数显示在屏幕上的操作是执行了synchronize(getrestult),这里,因为我们的线程和vcl界面发生了交互,我 们必须对synchronize有所了解,去看vcl的源码,你会发现,当你在程序中第一次创建一个附属线程时, vcl将会从主线程环境中创建和维护一个隐含的线程窗口。此窗口唯一的目的是把通过synchronize()调用的方法排队。 synchronize()把由method参数传递过来的方法保存在tthread的fmethod字段中,然后,给线程窗口发一个 cm_execproc消息,并且把消息的lparam参数设为self(这里指线程对象)。当线程窗口的窗口过程收到这个消息后,它就调用 fmethod字段所指定的方法。由于线程窗口是在主线程内创建的,线程窗口的窗口过程也将被主线程执行。因此,fmethod字段所指定的方法就在主线 程内执行。
在我们选择释放时间片的模式下,在这里,无论我们是用sleep还是switchtothread,当前线程都会立即释放时间片,因为这时我们并没有修改 线程的优先级,他们都在同样的优先级环境下运行,那么当占用cpu的线程释放时间片后,其他线程将可以相对轻松的得到cpu,所以在使用释放时间片的模式 下,界面的刷新会良好。并且调度相对有序。
3、sleep和switchtothread区别的演示。运行程序,使用默认设置,点击【创建线程】 按钮,然后点击【提高线程1的优先级】按钮,再点击【执行线程】这是,两个线程将不再是同样的优先级,其他设置依然是默认的(使用sleep方式释放时间 片),你会看到线程1首先执行,线程2处于可调度模式,但并没有被调度(当前计数没有刷新),并且屏幕也不刷新,在稍等一段时间之后,屏幕刷新,线程2也 开始运行,并且此时屏幕刷新正常。为什么呢?回头去看本文上面的内容,当线程1的优先级提高之后,系统会首先调度它,虽然它使用sleep(0)来释放时 间片,但当时间片释放后,因为它的优先级相对较高,系统依然会调度线程1,所以此时,线程2将不能执行,界面也不能有效刷新。在这个思路下,再做一个测 试,使用同样的方式,只不过这次,在线程执行之前,除了提高线程1的优先级之外,还将线程1释放时间片方式改为switchtothread,此时你就可 以看到两个线程都有机会执行,并且界面也将有效刷新。
4、你还可以做其他配置信息的测试,相信会加深对win32平台下线程调度的了解。

参考文献
1、《delphi5开发人员指南》
2、《windows核心编程》


http://www.west263.com/www/info/41141-1.htm

标签: ,

WIN32下DELPHI中的多线程【深入VCL源码】

线程的基础知识
线程的组成。线程有两部分组成。
1、一个是线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。
2、另一个是线程堆栈,它用于维护线程在执行代码时需要的所有函数参数和局部变量。
进 程从来不执行任何东西,它只是线程的容器。线程总是在某个进程环境中创建的,而且它的整个寿命期都在该进程中。这意味着线程在它的进程地址空间中执行代 码,并且在进程的地址空间中对数据进行操作。因此,如果在单进程环境中,你有两个或多个线程正在运行,那么这两个线程将共享单个地址空间。这些线程能够执 行相同的代码,对相同的数据进行操作。这些线程还能共享内核对象句柄,因为句柄表依赖于每个进程而不是每个线程存在。
线程是一种操作系统对象,它表示在进程中代码的一条执行路径。在每一个wi n32的应用程序中都至少有一个线程,它通常被称为主线程或默认线程。在应用程序中也可以自由地创建别的线程去执行其他任务。线程技术使不同的代码可以同 时运行。当然,只有在多c p u的计算机上,多个线程才能够真正地同时运行。在单个cpu上,由于操作系统把c p u的时间分成很短的片段分配给每个线程,这样给人的感觉好像是多个线程真的同时运行,他们只是“看起来”同时在运行。
win32是一种抢占式操作系统,操作系统负责管理哪个线程在什么时候执行。如果当线程1暂停执行时,线程2才有机会获得c p u时间,我们说线程1是抢占的。如果某个线程的代码陷入死循环,这并不可怕,操作系统仍会安排时间给其他线程。

创建一个线程
注意:每个线程必须拥有一个进入点函数,线程从这个进入点开始运行。线程函数可以使用任何合法的名字。可以给线程函数传递单个参数,参数的含义由你自己定 义。线程函数必须由一个返回值,它将成为该线程的退出代码。线程函数应该尽可能的使用函数参数和局部变量。线程函数类似下面的样子(object pascal):

//注意最后的stdcall,后面我会描述一些有用的东西
function mythread(info : pointer):dword; stdcall;
var
i : integer;
begin
for i := 0 to pinfo(info)^.count-1 do
form1.canvas.textout(pinfo(info)
^.x,pinfo(info)^.y,inttostr(i));
result :
= 0;
end;


上面的的代码功能很简单,你可以在程序中直接调用,例如这样:

type
tinfo
= record
count : integer;
x : integer;
y : integer;
end;
pinfo
= ^tinfo;
...
procedure tform1.button4click(sender: tobject);
var
ppi : pinfo;
begin
ppi :
=allocmem(sizeof(tinfo));
ppi
^.count := 1000000;
ppi
^.x := 100;
ppi
^.y := 400;
mythread(ppi);
end;


当你在一个窗口中用这样的方式调用时,你会发现在执行的过程中,你将无法在窗口上进行其他操作,因为它工作于你程序的主线程之中。如果此时,你还希望窗口可以进行其他操作。怎么办?让它在后台工作,让它成为另一个线程,使得不同的代码可以同时运行。
做法很简单,如果想要创建一个或多个辅助线程,只需要让一个已经在运行的线程来调用createthread,原型如下:

handle createthread(
lpsecurity_attributes lpthreadattributes,
// pointer to thread security attributes
dword dwstacksize, // initial thread stack size, in bytes
lpthread_start_routine lpstartaddress, // pointer to thread function
lpvoid lpparameter, // argument for new thread
dword dwcreationflags, // creation flags
lpdword lpthreadid // pointer to returned thread identifier
);


当createthread,被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程 内核对象视为由关于线程的统计信息组成的一个小型数据结构。系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相 同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非 常容易地互相通信。
下面来说这个函数的几个参数:
1、psa 此参数是指向security_attributes结构的指针。如果想要该线程内核对象的默认安全属性,可以(并且通常能够)传递null。如果希望所 有的子进程能够继承该线程对象的句柄,必须设定一个security_attributes结构,它的binherithandle(是否可继承)成员被 初始化为true,关于security_attributes,因为此文的目的不是介绍它,所以这里不做详细介绍,具体可以参考msdn。通常使用,我 们传递null就够了。
2、cbstack 用于设定线程可以将多少地址空间用于它自己的堆栈。当调用cratethread时,如果传递的值不是0,就能使该函数将所有的存储器保留并分配给线程的 堆栈。由于所有的存储器预先作了分配,因此可以确保线程拥有指定容量的可用堆栈存储器。通常状况下,我们会设置为0。
3、pfnstartaddr and pvparam,pfnstartaddr 参数用于指明想要新线程执行的线程函数的地址。线程函数的pvparam参数与原先传递给createthread的pvparam参数是相同的。 createthread使用该参数不做别的事情,只是在线程启动执行时将该参数传递给线程函数。该参数提供了一个将初始化值传递给线程函数的手段。该初 始化数据既可以是数字值,也可以是指向包含其他信息的一个数据结构的指针。此时回头再去看我上面例子上的mythread,你会发现它由一个无类型的指针 参数(用c来描述,应该是pvoid),在创建线程时,这个参数就通过pvparam来赋值。
4、fdwcreate 此参数可以设定用于控制创建线程的其他标志。它可以是两个值中的一个。如果该值是0,那么线程创建后可以立即进行调度。如果该值是create_ suspended,系统可以完整地创建线程并对它进行初始化,但是要暂停该线程的运行,这样它就无法进行调度。在delphi的windows.pas 单元,你可以发现它的定义
create_suspended= $00000004;
5、pdwthreadid 最后一个参数必须是dword的一个有效地址,createthread
使用这个地址来存放系统分配给新线程的id.

有了上面这些基础,下面我们就使用createthread来创建刚才那个mythread线程(delphi7);

...
//一个自定义类型
type
tinfo
= record
count : integer;
//计数器个数
x : integer;//要显示在窗体上位置的横座标
y : integer;//纵坐标
end;
pinfo
=^tinfo;

var
mythreadhad : thandle;
//一个全局变量,用来接受createthread创建新线程的句柄
...
procedure tform1.button4click(sender: tobject);
var
ppi : pinfo;
mythreadid : dword;
begin
{分配空间,注意,因为这里我只是一个用来演示createthread使用的代码,所以没有释放pp,但优秀的代码最后记得分配了空间一定要释放}
ppi :
=allocmem(sizeof(tinfo));
//初始化
ppi^.count := 100000;
ppi
^.x := 100;
ppi
^.y := 400;
//下面这行代码是关键
mythreadhad := createthread(nil,0,@mythread,ppi,0,mythreadid);
end;


执行此段代码,你会发现,它依然会在屏幕指定区域输出文字,和最开始时我们用把mythread在主线程中运行不同的是,此时,你依然可以对窗口进行其他操作。
看代码的最后一行,它使用了createthread,看它的参数,第一个nil以及第二个0意外着,它使用默认的安全设置以及默认的线程堆栈大小,第三 个参数是mythread的地址(注意@符号),然后我们传递了ppi这个pinfo类型的指针,使得线程函数接受一个参数,如果你不准备让线程接受这个 参数,用nil,fdwcreate参数,我们赋值为0,意味着我们希望线程立即执行,最后一个参数用来接受新线程的id。

让我们来看看createthread都干了些什么。

线程创建和初始化示意图
上图显示了系统在创建线程和对线程进行初始化时必须做些什么工作。调用createthread可使系统创建一个线程内核对象。该对象的初始使用计数是 2(在线程停止运行和从createthread返回的句柄关闭之前,线程内核对象不会被撤消)。线程的内核对象的其他属性也被初始化,暂停计数被设置为 1,退出代码始终为still_active(0 x 1 0 3),该对象设置为未通知状态。
一旦内核对象创建完成,系统就分配用于线程的堆栈的内存。该内存是从进程的地址空间分配而来的,因为线程并不拥有它自己的地址空间。然后系统将两个值写 入新线程的堆栈的上端(线程堆栈总是从内存的高地址向低地址建立)。写入堆栈的第一个值是传递给createthread的pvparam参数的值。紧靠 它的下面是传递给createthread的pfnstartaddr参数的值。每个线程都有它自己的一组c p u寄存器,称为线程的上下文。该上下文反映了线程上次运行时该线程的cpu寄存器的状态。线程的这组c p u寄存器保存在一个context结构。context结构本身则包含在线程的内核对象中。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器。线程总是在进程的上下文中运行的。因此,这些地址都用于标识拥有线程的进程地址空间中的内 存。当线程的内核对象被初始化时,context结构的堆栈指针寄存器被设置为线程堆栈上用来放置pfnstartaddr的地址。当线程完全初始化后, 系统就要查看create_suspended标志是否已经传递给createthread。如果该标志没有传递,系统便将线程的暂停计数递减为0,该线 程可以调度到一个进程中。然后系统用上次保存在线程上下文中的值加载到实际的c p u寄存器中。这时线程就可以执行代码,并对它的进程的地址空间中的数据进行操作。
在这里,我还要简单的描述一下context结构,因为win32是抢占式操作系统,一个线程几乎不可能永远的占据cpu,也就是说,它会在一定时间后 (在windows中,大概式20ms的时间),被cpu放在一边,一段时间之后,才可以重新获得cpu时间片,此时就有一个问题,线程现在执行到了那 里,cpu在再次分配给它时间片执行的时候,必须知道这些信息,难道要从0开始吗?context结构的作用就是用来解决这个问题。
在platform sdk中,你可以看到下面的信息:
“context结构包含了特定处理器的寄存器数据。系统使用context结构执行各种内部操作。目前,已经存在为intel、mips、alpha和powerpc处理器定义的context结构。若要了解这些结构的定义,参见头文件winnt.h”。
该文档并没有说明该结构的成员,也没有描述这些成员是谁,因为这些成员要取决于windows在哪个cpu上运行。实际上,在windows定义的所有数 据结构中,context结构是特定于cpu的唯一数据结构。那么context结构中究竟存在哪些东西呢?它包含了主机c p u上的每个寄存器的数据结构。在x86计算机上,数据成员是eax、ebx、ecx、edx等等。如果是alpha处理器,那么数据成员包括intv0、 intt0、intt1、ints0、in tra和intzero等等。
windows实际上允许查看线程内核对象的内部情况,以便抓取它当前的一组cpu寄存器。若要进行这项操作,只需要调用getthreadcontext函数。关于此函数的使用,我们下次再说。

线程的终止
终止一个线程的运行,有4个方法:
1、线程函数返回,这是最好的
2、调用exitthread函数,线程将自动撤销
3、调用terminatethread函数
4、包含线程的进程终止运行

线程函数返回
始终都应该将线程设计成这样的形式,即当想要线程终止运行时,它们就能够返回。这是确保所有线程资源被正确地清除的唯一办法。如果

线程能够返回,就可以确保下列事项的实现:
• 在线程函数中创建的所有c + +对象均将通过它们的撤消函数正确地撤消。
• 操作系统将正确地释放线程堆栈使用的内存。
• 系统将线程的退出代码(在线程的内核对象中维护)设置为线程函数的返回值。
• 系统将递减线程内核对象的使用计数。

调用exitthread函数
void exitthread(dword dwexitcode);
该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是程序中用到的资源(例如delphi类对象)将不被撤消。

调用terminatethread函数
bool terminatethread(handle hthread,dword dwexitcode);
关产这个函数和exitthread的区别,你会发现它除了有dwexitcode这个退出码参数之外,还包含了可指定线程的句柄参数。看到这里你就应该 会想到两者的区别,exitthread总是撤消调用的线程,而terminatethread能够撤消任何线程。hthread参数用于标识被终止运行 的线程的句柄。当线程终止运行时,它的退出代码成为你作为dwexitcode参数传递的值。同时,线程的内核对象的使用计数也被递减。值得注意的是,此 函数是异步运行的函数,也就是说,它告诉系统你想要线程终止运行,但是,当函数返回时,不能保证线程被撤消。如果需要确切地知道该线程已经终止运行,必须 调用waitforsingleobject或者类似的函数,传递线程的句柄。

在进程终止时撤销线程
这是很容易想到的。无须过多解释。

线程终止时发生的操作
当线程终止运行时,会发生下列操作:
• 线程拥有的所有用户对象均被释放。在windows中,大多数对象是由包含创建这些对象的线程的进程拥有的。但是一个线程拥有两个用户对象,即窗口和挂 钩。当线程终止运行时,系统会自动撤消任何窗口,并且卸载线程创建的或安装的任何挂钩。其他对象只有在拥有线程的进程终止运行时才被撤消。
• 线程的退出代码从still_active改为传递给exitthread或terminatethread的代码
• 线程内核对象的状态变为已通知。
• 如果线程是进程中最后一个活动线程,系统也将进程视为已经终止运行。
• 线程内核对象的使用计数递减1。当一个线程终止运行时,在与它相关联的线程内核对象的所有未结束的引用关闭之前,该内核对象不会自动被释放。
一旦线程不再运行,系统中就没有别的线程能够处理该线程的句柄。然而别的线程可以调用getexitcodethread来检查由hthread标识的线程是否已经终止运行。如果它已经终止运行,则确定它的退出代码.
bool getexitcodethread(handle hthread,pdword pdwexitcode);
退出代码的值在pdwexitcode);指向的dword中返回。如果调用getexitcodethread时线程尚未终止运行,该函数就用still_active标识符(定义为0x103)填入dword。如果该函数运行成功,便返回t r u e。

上面描述了结束线程的多种办法,这里必须说明一点,如果有可能,那尽量使用第一种方式来结束线程,它可以确保你释放了所有的资源。好的程序应该尽可能的减少对客户资源的浪费。

stdcall

准确的说,stdcall这个标示符本来和线程没有直接的联系,但因为我这里的示例代码是用object pascal写的,而我们调用的createthread则是用c实现的,这两种语言的函数入栈的方式是不同的,pascal是从左到右。加上 stdcall,可以使得入栈方式改为从右到左以符合别的语言的习惯。我们上面调用createthread函数时,因为我传递了那个无类型的指针参数, 所以,必须加上stdcall指明入栈方式,否则会出现地址访问错误。当然,如果你并不决定传递参数,你也可以不使用stdcall。不过作为一种好的编 码习惯,你最好还是加上。

delphi中创建线程
如果你只想做一个代码搬运工,你完全可以不了解上面的内容,但如果你想成为一个合格的win32程序员,深入这些内容,比你肤浅的多学一门语言有用。
delphi把有关线程的api封装在tthread这个object pascal的对象中。结合上面的内容,先去看tthread源码

tthread = class
private
{$ifdef mswindows}
fhandle: thandle;
fthreadid: thandle;
{$endif}
{$ifdef linux}
// ** fthreadid is not thandle in linux **
fthreadid: cardinal;
fcreatesuspendedsem: tsemaphore;
finitialsuspenddone: boolean;
{$endif}
fcreatesuspended: boolean;
fterminated: boolean;
fsuspended: boolean;
ffreeonterminate: boolean;
ffinished: boolean;
freturnvalue: integer;
fonterminate: tnotifyevent;
fsynchronize: tsynchronizerecord;
ffatalexception: tobject;
procedure callonterminate;
class procedure synchronize(asyncrec: psynchronizerecord); overload;
{$ifdef mswindows}
function getpriority: tthreadpriority;
procedure setpriority(value: tthreadpriority);
{$endif}
{$ifdef linux}
// ** priority is an integer value in linux
function getpriority: integer;
procedure setpriority(value: integer);
function getpolicy: integer;
procedure setpolicy(value: integer);
{$endif}
procedure setsuspended(value: boolean);
protected
procedure checkthreaderror(errcode: integer); overload;
procedure checkthreaderror(success: boolean); overload;
procedure doterminate;
virtual;
procedure execute;
virtual; abstract;
procedure synchronize(method: tthreadmethod); overload;
property returnvalue: integer read freturnvalue write freturnvalue;
property terminated: boolean read fterminated;
public
constructor create(createsuspended: boolean);
destructor destroy;
override;
procedure afterconstruction;
override;
procedure resume;
procedure suspend;
procedure terminate;
function waitfor: longword;
class procedure synchronize(athread: tthread; amethod: tthreadmethod); overload;
class procedure staticsynchronize(athread: tthread; amethod: tthreadmethod);
property fatalexception: tobject read ffatalexception;
property freeonterminate: boolean read ffreeonterminate write ffreeonterminate;
{$ifdef mswindows}
property handle: thandle read fhandle;
property priority: tthreadpriority read getpriority write setpriority;
{$endif}
{$ifdef linux}
// ** priority is an integer **
property priority: integer read getpriority write setpriority;
property policy: integer read getpolicy write setpolicy;
{$endif}
property suspended: boolean read fsuspended write setsuspended;
{$ifdef mswindows}
property threadid: thandle read fthreadid;
{$endif}
{$ifdef linux}
// ** threadid is cardinal **
property threadid: cardinal read fthreadid;
{$endif}
property onterminate: tnotifyevent read fonterminate write fonterminate;
end;

从tthread的声明中可以看出,它定义了windows和linux下分别要完成的操作,这里我们只谈win32,tthread直接从tobject继承,因为,它不是组件。你还可以看到它有一个execute的方法

procedure execute; virtual; abstract;

并且你可以看到,它是抽象的,因为,不能创建tthread的实例,你只能创建它的派生类的实例。再去看看它的构造函数,你会看到这样一句代码
fhandle := beginthread(nil, 0, @threadproc, pointer(self), create_suspended, fthreadid);再深入去看这个beginthread,
result := createthread(securityattributes, stacksize, @threadwrapper, p,creationflags, threadid);你看到了什么?是的,createthread,结合这两句,看看它都干了些什么,默认的安全属性,默认的堆栈大小,一个入口地址, 一个参数,一个创建标志,还有一个threadid。你和本文最开始的那些内容对上了吗?我们又看到它传递的线程函数是threadproc,再去看看 它。下面只帖了一些和本文有关系的代码

try
if not thread.terminated then
try
thread.execute;
except
thread.ffatalexception :
= acquireexceptionobject;
end;
finally


它首先根据tthread类中的一个属性terminated(布尔类型)来判断线程的状态,如果你没有通过外部代码将terminated甚至为 true,它将会执行execute(注意这个方法,我们刚才提到过它是一个抽象的,你必须让它干点什么,也就是说,tthread.execute将是 你的线程将要执行的操作)。然后是异常的处理。你是否对delphi的tthread有点了解了呢?如果有兴趣,好好看看它的源码吧。
说到这里,delphi中tthread创建一个线程的基本流程就出来了。调用自己的构造函数,传递一个布尔类型的变量,这个变量对应 createthread函数的fdwcreate参数,用来决定线程是立即执行还是挂起,构造函数又调用了一个beginthread,而正是这个 beginthread调用了win api createthread,它将一个threadproc线程函数传递给createthread,而这个threadproc则调用你必须覆盖的方法 execute来完成你想要进行的操作。
再来看看它的终止,继续刚才的内容,看threadproc这个函数的下面代码,你会发现,当execute执行完毕之后,它就认为这个线程终止了,它调 用了endthread(result),然后这个endthread又调用了exitthread(exitcode)。当结束使用tthread对象 时,应该确保已经把这个object pascal对象从内存中清除了。这才能确保所有内存占有都释放掉。尽管在进程终止时会自动清除所有的线程对象,但及时清除已不再用的对象,可以使内存的 使用效率提高。还是threadproc的源码,你会发现当线程的execute执行完之后,它要根thread.ffreeonterminate来决 定是否释放资源。freethread := thread.ffreeonterminate;...if freethread then thread.free;这是非常好的,也就是说,你可以通过在对freeonterminate这个属性赋值为true(观察它的源 码,freeonterminate是ffreeonterminate这个私有变量的访问器),来让tthread对象自动在线程执行完毕之后自动释放 资源。
看了这么多,我们可以梳理一下思路了,使用tthread对象,我们必须从它派生一个类,然后你必须覆盖execute这个方法,在这里,完成你要让线程 做的事情。如果有可能(或者说尽量,除非你对这个线程还有别的需求),还可以在这里通过设置freeonterminate := true,使得线程在执行完毕之后自动释放资源。我们可以通过tthread对象构造函数的参数来决定线程是否立即运行。
一个例子:

...
//声明一个线程,我们叫它tfrist
tfrist = class(tthread)
protected
procedure execute;
override;//覆盖execute这个抽象的方法,这是你必须做的事情
end;

var
form1: tform1;
ci : array[
0..1000] of integer;//一个全局变量,我们将用tfrist来访问它

...
{ tfrist }

procedure tfrist.execute;
var
i : integer;
begin
inherited;
onterminate :
= form1.threaddone;//注意一下这里
freeonterminate := true;
for i := 0 to 1000 do
ci[i] :
= i;
end;

procedure tform1.button1click(sender: tobject);
begin
//初始化全局变量
fillmemory(@ci,1000,0);
tfrist.create(
false);
end;

procedure tform1.threaddone(sender: tobject);
var
i : integer;
begin
for i := 0 to 1000 do
listbox1.items.add(inttostr(ci[i]))
end;

上面我省略了一些代码,但大意已表。我们声明了一个tfrist的类,它从tthread继承而来,它将对一个全局变量的的数组ci进行初始化,并且将初始化的结果显示在窗体的listbox1上。

写到这里,你会发现上述代码中的几个“疑点”,其中一个我现在要说明的就是onterminate := form1.threaddone;这一句,观察threaddone的源码,你会发现它其实就是完成将全局变量的内容显示在窗体的listbox中,这 时,你可能会问,直接写在线程里,不可以吗?为什么要这样?原因很简单。大多数v c l在被设计时,都只考虑了在任何时刻只有一个线程来访问它。其局限性尤其体现在v c l的用户界面部分。同时,一些非用户界面部分也不是线程安全的。
1. 非用户界面的v c l
实际上v c l只有很少的部分保证是线程安全的。可能在这很少的部分中,最让人注意的是v c l的属性流机制。v c l的流机制确保了组件流能被多线程安全地读写。请记住即使最基础的v c l类(诸如tlist),也不是为安全地同时操作多个线程而设计的。对某些情况, v c l提供了一些线程安全的替代,比如,用tthreadlist 来替代tlist可以解决多个线程操作的问题。
2. 用户界面的v c l
v c l要求所有的用户界面控制要发生在一个应用程序的主线程的环境中(线程安全的tcanvas类除外)。当然,利用技术手段是可以有效地利用附属线程更新用户界面的(后面将会讨论)。
对v c l的访问只能在主线程中。这将意味着:所有需要与用户打交道的代码都只能在主线程的环境中执行。这是其结构上明显的不足,并且这种需求看起来只局限在表面 上,但它实际上有一些优点。首先,只有一个线程能够访问用户界面,这减少了编程的复杂性。win32要求每个创建窗口的线程都要使用 getmessage()建立自己的消息循环。正如你所想的,这样的程序将会非常难于调试,因为消息的来源实在太多了。其次,由于v c l只用一个线程来访问它,那些用于把线程同步的代码就可以省略了,从而改善了应用程序的性能。
那么,如果有多个线程要访问vcl,怎么办呢?有这么几个方法:
1、利用tthread的onterminate属性,它是一个tnofityevent类型,它指定的过程将在线程执行完毕之后运行,并且是运行在主线程环境中的,我上面的代码就是使用了这种方法
2、利用tthread的synchronize,
class procedure synchronize(asyncrec: psynchronizerecord); overload;
它的作用是在主线程中执行一个方法,我们上面的例子,如果不用onterminate,那么可以这么改,

tfrist = class(tthread)
private
procedure getresut;
//我们声明了一个过程getresutlt;它不包含任何参数
protected
procedure execute;
override;
end;
//getresut的实现部分
procedure tfrist.getresut;
var
i : integer;
begin
for i := 0 to 1000 do
form1.listbox1.items.add(inttostr(ci[i]))
end;

procedure tfrist.execute;
var
i : integer;
begin
inherited;
onterminate :
= form1.threaddone;
// freeonterminate := true;
for i := 0 to 1000 do
ci[i] :
= i;
//调用synchronize
synchronize(getresut);
end;


3、利用通讯来完成。例如我们可以利用消息,看上面的execute,在它的循环执行完毕之后,我们可以发送一个自定义消息,然后窗口处理这个消息。

参考文献:
1、《delphi5开发人员指南》
2、《windows核心编程》

http://www.west263.com/www/info/41140-1.htm

标签: ,

产品设计体会(四十)——销售渠道

做付费产品,就必然要牵涉到卖的问题,最近公司正好“e网打进”火爆销售ingplus前段时间浏览过《渠道为王》,就说说相关的体会。

销售有两大模式:直销vs分销,分销要通过渠道,渠道又分代理(赚佣金,没有产品所有权和库存风险)和经销(赚差价,产品所有权发生转移,比如批发商),现在的网络付费产品,因为多是个人应用,所以直销比较多,而我们的“e”是给企业用户的,加之国内中小企业现在相应的知识很薄弱,直销成本太高,所以我们选择了渠道销售。

在渠道的推拉战术方面,“e”显然用的是推的方法。所谓“拉”是通过PR、广告、传播等手段启动市场,刺激消费者,促使渠道来找厂商;“推”是集中力量做渠道工作,用高额利润去刺激渠道主动推销产品,快速抢占市场。推适合企业规模小、技术含量高、销售过程复杂的产品,一般来说:新产品推,老产品拉,“e”的驱动路线“PDà阿里的渠道销售à渠道à终端用户”。

从 产品设计的角度,对于通过渠道销售的产品,在设计上,新增功能和改动功能的时候,还需要额外考虑渠道销售人员的培训成本、渠道商的培训成本,他们习惯了卖 推广,要把一个功能说明白很不容易;另一方面,既然选择通过渠道来销售,就说明终端用户对互联网的应用能力不足,相应的设计思路也要转变。

再有一点,在渠道终端的用户一般是企业,企业用户与个人用户的差异也不得不考虑,比如支付,企业用户就有开发票的问题,不能简单的只考虑网上支付的途径,另外由于渠道的介入,多级的定价,分成比例,开发票的流程,渠道政策都要有相应的系统支撑。

白鸦的一篇《如何保证顾客的整体体验?》让 爱好用户体验的人对销售渠道又提出了另一个层面的问题,社会发展导致对效率的优化——分工,也是出于成本考虑,我们的产品采用渠道销售——一种业务的外包 形式,我们的终端客户是不会了解中间细节的,他们会把外包服务的不爽怪罪到产品上,给产品的体验减分,那么最终一个很大的问题,似乎也只有折中解决的问 题,就是:如何保证渠道的服务质量来保障我们产品的整体体验?

http://iamsujie.spaces.live.com/

标签:

星期四, 三月 27, 2008

有用网址

Collabnet作品,版本管理工具。
http://downloads.open.collab.net/sfee15.html

SQL Having

今天发现一个特别好的SQL语句学习的站点:

那我们如何对函数产生的值来设定条件呢?举例来说,我们可能只需要知道哪些店的营业额有超过 $1,500。在这个情况下,我们不能使用 WHERE 的指令。 那要怎么办呢?很幸运地,SQL 有提供一个 HAVING 的指令,而 我们就可以用这个指令来达到这个目标。 HAVING 子句通常是在一个 SQL 句子的最后。一个含有 HAVING 子句的 SQL 并不一定要包含 GROUP BY 子句。HAVING 的语法如下:

SELECT "栏位1", SUM("栏位2")
FROM "表格名"
GROUP BY "栏位1"
HAVING (函数条件)

请读者注意: GROUP BY 子句并不是一定需要的。

在我们Store_Information 表格这个例子中,

Store_Information 表格

store_name Sales Date
Los Angeles $1500 Jan-05-1999
San Diego $250 Jan-07-1999
Los Angeles $300 Jan-08-1999
Boston $700 Jan-08-1999

我们打入,

SELECT store_name, SUM(sales)
FROM Store_Information
GROUP BY store_name
HAVING SUM(sales) > 1500

结果:

store_nameSUM(Sales)
Los Angeles
$1800

http://sql.1keydata.com/cn/sql-having.php

标签:

星期三, 三月 26, 2008

要是让微软设计vi的话。。。。。。

原文:http://blog.chinaunix.net/u/9465/showart.php?id=500859

标签:

SQL Server数据导入导出技术概述与比较(3)

二、性能的比较

使用Transact-SQL方式。如果是SQL Server数据库之间的导入导出,速度将非常快,但是使用OPENDATASOURCE和OPENROWSET方法利用OLE DB Provider打开并操作数据库时速度会慢一些。

使用bcp命令方式。如果不需要对数据进行验证等操作的话,使用它还是非常快的,这是因为它的内部使用c接口的DB-library,所以在操作数据库时速度有很大的提升。

使用DTS方式导数据应该是最好的方式了。由于它整合了Microsoft Universal Data Access技术与Microsoft ActiveX技术,因此不仅可以灵活地处理数据,而且在数据导入导出的效率是非常高的。

总结

SQL Server提供了丰富的数据导入导出方法,这给我们提供了更多的选择,但是这又会给我们带来一个新问题:如何根据具体情况选择合适的数据导入导出方法呢?我在这里提供一些个人的建议,希望能对读者起到一定的指导作用。

如果是在SQL Server数据库之间进行数据导入导出时,并且不需要对数据进行复杂的检验,最好使用Transact-SQL方法进行处理,因为在SQL Server数据库之间进行数据操作时,SQL是非常快的。当然,如果要进行复杂的操作,如数据检验、转换等操作时,最好还是使用DTS进行处理,因为 DTS不光导数据效率高,而且能够对数据进行深度控制。但是DTS的编程接口是基于com的,并且这个接口十分复杂,因此,使用程序调用DTS将变也会变 得很复杂,因此, 当数据量不是很大,并且想将数据导入导出功能加入到程序中,而且没有复杂的数据处理功能时,可以使用OPENDATASOURCE或OPENROWSET 进行处理。

bcp命令并不太适合通过程序来调用,如果需要使用批量的方式导数据,可以通过批处理文件调用bcp命令,这样做即不需要编写大量的程 序,也无需在企业管理器中通过各种操作界面的切换来进行数据导入导出。因此,它比较适合在客户端未安企业管理器或使用SQL Server Express时对数据进行快速导入导出的场合。

http://soft.zdnet.com.cn/software_zone/2007/0911/500935.shtml

标签:

SQL Server数据导入导出技术概述与比较(2)

(2) 灵活度不同。

OPENDATASOURCE只能打开相应数据库中的表或视图,如果需要过滤的话,只能在SQL Server中进行处理。而OPENROWSET可以在打开数据库的同时对其进行过滤,如上面的例子,在OPENROWSET中可以使用SELECT * FROM table1对abc.mdb中的数据表进行查询,而OPENDATASOURCE只能引用table1,而无法查询table1。因 此,OPENROWSET比较OPENDATASOURCE更加灵活。

2. 使用命令行bcp导入导出数据

很多大型的系统不仅仅提供了友好的图形用户接口,同时也提供了命令行方式对系统进行控制。在SQL Server中除了可以使用SQL语句对数据进行操作外,还可以使用一个命令行工具bcp对数据进行同样的操作。

bcp是基于DB-Library 客户端库的工具。它的功能十分强大,bcp能够以并行方式将数据从多个客户端大容量复制到单个表中,从而大大提高了装载效率。但在执行并行操作时要注意的 是只有使用基于 ODBC 或 SQL OLE DB 的 API 的应用程序才可以执行将数据并行装载到单个表中的操作。

bcp可以将SQL Server中的数据导出到任何OLE DB所支持的数据库的,如下面的语句是将authors表导出到excel文件中

bcp pubs.dbo.authors out c:temp1.xls -c -q -S"GNETDATA/GNETDATA" -U"sa" -P"password"

bcp不仅能够通过命令行执行,同时也可以通过SQL执行,这需要一个系统存储过程xp_cmdshell来实现,如上面的命令可改写为如下形式。

EXEC master..xp_cmdshell 'bcp pubs.dbo.authors out
c:temp1.xls -c -q -S"GNETDATA/GNETDATA" -U"sa" -P"password"'

3. 使用数据转换服务(DTS)导入导出数据

DTS是SQL Server中导入导出数据的核心,它除有具有SQL和命令行工具bcp相应的功能外,还可以灵活地通过VBScript、JScript等脚本语言对数据进行检验、净化和转换。

SQL Server为DTS提供了图形用户接口,用户可以使用图形界面导入导出数据,并对数据进行相应的处理。同时,DTS还以com组件的形式提供编程接口, 也就是说任何支持com组件的开发工具都可以利用com组件使用DTS所提供的功能。DTS在SQL Server中可以保存为不同的形式,可以是包的形式,也可以保存成Visual Basic源程序文件,这样只要在VB中编译便可以使用DTS com组件了。

DTS和其它数据导入导出方式最大的不同就是它可以在处理数据的过程中对每一行数据进行深度处理。以下是一段VBScript代码,这 段代码在处DTS理每一条记录时执行,DTSDestination表示目标记录,DTSSource表示源记录,在处理“婚姻状况”时,将源记录中的“ 婚姻状况”中的0或1转换成目标记录中“已婚”或“未婚”。

Function Main()
DTSDestination("姓名") = DTSSource("姓名")
DTSDestination("年龄") = DTSSource("年龄")
If DTSDestination("婚姻状况") = 1 Then
DTSDestination("婚姻状况") = "已婚"
Else
DTSDestination("婚姻状况") = "未婚"
End If
Main = DTSTransformStat_OK

End Function

上述的三种数据导入导出方法各有其利弊,它们之间的相互比较如图1如示。

http://soft.zdnet.com.cn/software_zone/2007/0911/500933.shtml

SQL Server数据导入导出技术概述与比较(1)

当我们建立一个数据库时,并且想将分散在各处的不同类型的数据库分类汇总在这个新建的 数据库中时,尤其是在进行数据检验、净化和转换时,将会面临很大的挑战。幸好SQL Server为我们提供了强大、丰富的数据导入导出功能,并且在导入导出的同时可以对数据进行灵活的处理。

在SQL Server中主要有三种方式导入导出数据:使用Transact-SQL对数据进行处理;调用命令行工具bcp处理数据;使用数据转换服务(DTS)对数据进行处理。这三种方法各有其特点,下面就它们的主要特点进行比较。

一、使用方式的比较

1. 使用Transact-SQL进行数据导入导出

我们很容易看出,Transact-SQL方法就是通过SQL语句方式将相同或不同类型的数据库中的数据互相导入导出或者汇集在一处的方 法。如果是在不同的SQL Server数据库之间进行数据导入导出,那将是非常容易做到的。一般可使用SELECT INTO FROM和INSERT INTO。使用 SELECT INTO FROM时INTO后跟的表必须存在,也就是说它的功能是在导数据之前先建立一个空表,然后再将源表中的数据导入到新建的空表中,这就相当于表的复制(并 不会复制表的索引等信息)。而INSERT INTO的功能是将源数据插入到已经存在的表中,可以使用它进行数据合并,如果要更新已经存在的记录,可以使用UPDATE。

SELECT * INTO table2 FROM table1        --table1和table2的表结构相同
INSERT INTO table2 SELECT * FROM table3 --table2和table3的表结构相同

当在异构数据库之间的进行数据导入导出时,情况会变得复杂得多。首先要解决的是如何打开非SQL Server数据库的问题。

在SQL Server中提供了两个函数可以根据各种类型数据库的OLE DB Provider打开并操作这些数据库,这两个函数是OPENDATASOURCE和OPENROWSET。它们的功能基本上相同,不同之处主要有两点。

(1) 调用方式不同。

OPENDATASOURCE的参数有两个,分别是OLE DB Provider和连接字符串。使用OPENDATASOURCE只相当于引用数据库或者是服务(对于SQL Server、Oracle等数据库来说)。要想引用其中的数据表或视图,必须在OPENDATASOURCE(...)后进行引用。

在SQL Server中通过OPENDATASOURCE查询Access数据库abc.mdb中的table1表

SELECT * FROM OPENDATASOURCE('Microsoft.Jet.OLEDB.4.0',
'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=abc.mdb;Persist Security
Info=False')...
table1

OPENROWSET相当于一个记录集,可以将直接当成一个表或视图使用。

在SQL Server中通过OPENROWSETE查询Access数据库abc.mdb中的table1表

SELECT * FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0', 'abc.mdb';
'admin';'','SELECT * FROM table1')
http://soft.zdnet.com.cn/software_zone/2007/0911/500929.shtml

标签:

【VFP】【雅奇MIS】FYI - Internet

我有很多时候的状态都是无意识的,没有明确生命方向感是我的一大特征。我总是在Internet上不断的google,不断找寻答案,然后再答案中我又产生新的迷茫和困惑,或者搜寻的结果让我更无法将自己从这种无意识的状态中拯救出来。

我前几天Google某个困惑问题(VFP Framework),其中有个结果是CSDN网站,然后又偶尔看到了一篇关于【雅奇MIS】软件公司已经成功走过15年的春秋,至今仍在发展的新闻。

作为软件狂人,我很早知道有过这个软件(雅奇MIS),我对国产的东西特别是软件类,尤其是 软件开发工具,一向很可怜,国人的思维和创造力是很令我无奈的,当时(很久以前)我试过一次【雅奇MIS】,大概不到两三分钟就失去耐心了,因为我装的试 用版不是能编译EXE的,我一点兴趣也没有了。

这次令我稍微再次感兴趣的原因有两个:一是这个软件公司居然还生存着?!(15年过去 CCED UCDOS 多少所谓的民族软件产品都成了尘土)这个小公司一直还在玩自己这个产品,有点另眼看待了;二是这个产品勾起了我对Windev的会议(我很久没有这么这么 想念Osi了),在我看过雅奇MIS软件的视频教学之后,我很自然地想起了Osi对WinDev的狂热(我有种冲动,当时我要是在2004-2005年用 这个玩意替代我用VFP去和Osi在Terra工作,应该是一件很愉快的事情)

雅奇的教学视频做的很仔细,也很全面,在认真学习了两天之后,我觉得雅奇的作者(规划)几乎和我希望用VFP完成的目标惊人一致,甚至每个细节都非常相近(选用MDB、内置Journal函数),它确实是我梦想中寻找的一个工具。

不过我得承认雅奇MIS不能用来玩更高深的,比如连ODBC都没有支持,不过想到它只有 380元人民币(原价是3880元,估计我很早试用的时候就是这个价 - 当时可能也是我没有兴趣的原因之一)已经和Windev几乎同出一辙的设计界面(图形流程),我只能觉得:很好,很强大

昨天我晚上又温习VFP,读了关于Cursor SPT SQL Offline online等技术,感觉VFP的确很专业,很惋惜微软已经放弃了VFP,但是我内心总有一个开发程序员的心结,这个心结在87年我上大学,91年工作 后,甚至在87年之前上高中的时候就已经和DBF Dbase无法分割了。

我知道现在的开发已经进入到Java时代, Web时代,我还是深爱着那些传统的技术语言,Foxbase, C。

股票今天大跌,我损失很重,不过我会觉定再次试用一下雅奇MIS,并且可能购买正版。

另外我也将延续我的VFP的道路,不管这是否是一条盲路,我会在我无意识的生命中走下去。


http://crazysm.blog.163.com/blog/static/39019920080309592781/

标签:

CoCreateInstance

函数功能描述:用指定的类标识符创建一个Com对象,用指定的类标识符创建一个未初始化的对象。当在本机中只创建一个对象时,可以调用 CoCreateInstance;在远程系统中创建一个对象时,可以调用CoCreateInstanceEx;创建多个同一CLSID的对象时, 可以参考 CoGetClassObject 函数。
函数原形:
STDAPI CoCreateInstance(
REFCLSID rclsid, //创建的Com对象的类标识符(CLSID)
LPUNKNOWN pUnkOuter, //指向接口IUnknown的指针
DWORD dwClsContext, //运行可执行代码的上下文
REFIID riid, //创建的Com对象的接口标识符
LPVOID * ppv //用来接收指向Com对象接口地址的指针变量
);
参数:
rclsid
[in] 用来唯一标识一个对象的CLSID(128位),需要用它来创建指定对象。
pUnkOuter
[in] 如果为NULL, 表明此对象不是聚合式对象一部分。如果不是NULL, 则指针指向一个聚合式对象的IUnknown接口。
dwClsContext
[in] 组件类别. 可使用CLSCTX枚举器中预定义的值.
riid
[in] 引用接口标识符,用来与对象通信。
ppv
[out] 用来接收指向接口地址的指针变量。如果函数调用成功,*ppv包括请求的接口指针。
返回值:
S_OK
指定的Com对象实例被成功创建。
REGDB_E_CLASSNOTREG
指定的类没有在注册表中注册. 也可能是指定的dwClsContext没有注册或注册表中的服务器类型损坏
CLASS_E_NOAGGREGATION
这个类不能创建为聚合型。
E_NOINTERFACE
指定的类没有实现请求的接口, 或者是IUnknown接口没有暴露请求的接口.
注释:
CoCreateInstance帮助者函数通过使用对象的CLSID,提供了一种便洁的方式与类对象连接,创建未初始化的实例,以及释放类对象。它封装了以下的功能:
CoGetClassObject(rclsid, dwClsContext, NULL, IID_IClassFactory, &pCF);
hresult = pCF->CreateInstance(pUnkOuter, riid, ppvObj);
pCF->Release();
当在本机中只创建一个对象时,调用CoCreateInstance是最方便的;如果要在远程系统中创建一个对象时,可以调用 CoCreateInstanceEx;创建多个同一CLSID的对象时, 可以参考 CoGetClassObject 函数;如果创建多个对象实例,可以获得类对象的IClassFactory 接口指针,并使用需要的方法,可以使用CoGetClassObject函数。
在CLSCTX枚举器中, 你可以指定用来管理对象的服务器类型. 这些常量可以是CLSCTX_INPROC_SERVER, CLSCTX_INPROC_HANDLER, CLSCTX_LOCAL_SERVER或是它们的任何组合. 常量CLSCTX_ALL被定义为这三个值的组合. 想获得更多的有关这些常量的用法,请参考CLSCTX.
实例:
if ( SUCCEEDED( CoInitialize(NULL) ) )
{
// 如果成功初始化COM库,则继续初始化并运行应用程序...
// 对于Win32应用程序, CoInitialize函数的pvReserved参数,必须为NULL. 此参数不能被用于32位COM,
// CoInitialize将会返回E_INVALIDARG,如果传递一个非NULL参数.
}
else
{
// 如果初始化COM库失败,则退出.
}
HRESULT hr;
IComObject *pRet;
hr = CoCreateInstance(CLSID_OFCOM,NULL,CLSCTX_INPROC_SERVER,IID_OFCOMOBJECT,
(PPVOID)&pRet);
if (SUCCEEDED(hr))
{
// 卸载不用的COM服务.
CoFreeUnusedLibraries();
}
else
...
...
pRet->Release();
CoUninitialize();

要求:
Windows NT/2000: 需要 Windows NT 3.1 或以后版本。
Windows 95/98: 需要 Windows 95 或以后版本。
头文件 : objbase.h.
库文件 : ole32.dll.
参看:
CoGetClassObject, IClassFactory::CreateInstance, CoCreateInstanceEx, CLSCTX, (实例创建帮助函数)Instance Creation Helper Functions

http://baike.baidu.com/view/1141927.html

标签:

wxWidgets研究目标

wxWidgets学习日志:

显示Splash窗口/已经OK.
显示URL,鼠标经过高亮,点击打开浏览器进入网址,不大好找资料,先不研究类库,偷个懒。

标签:

apache 防迅雷下载/盗链

今天研究了一下httpd.conf,成功禁止了迅雷的下载,迅雷的User-Agent是
  1. Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
,把这个User-Agent给deny掉就行了。

我分析了 栋力无限 主站2个G的IIS日志,发现只有迅雷用这个User-Agent。所以完全不必担心由此带来的兼容性问题。

目前 栋力无限音乐站http://www.dormforce.net/music)已经实现了防止迅雷的下载/盗链。

例如,在IE和Windows Media Player都可以正常播放 Star Wars Main Title

但是用迅雷下载却显示

Gigaget download failed

详细内容请看:http://initiative.yo2.cn/archives/260780

标签: ,

星期二, 三月 25, 2008

wxWidgets类列表(2.6.4)

管理窗口

这里有若干直接由窗口管理器(例如:MS WindowsMotif Window Manager)操纵的窗口类型。在wxWidgets中,framesdialogs是相似的,但是只有dialogs可能是模式的。

wxTopLevelWindow

任意的顶层窗口,dialogframe

wxDialog

对话框

wxFrame

通用的frame

wxMDIChildFrame

MDI(多文档界面)child frame

wxMDIParentFrame

MDI parent frame

wxMiniFrame

带有瘦标题栏的frame

wxSplashScreen

飞溅屏幕类

wxPropertySheetDialog

属性表对话框

wxTipWindow

在一个小窗口中显示文本

wxWizard

向导对话框

参见 公共对话框

其它窗口

这里有由wxWindow派生的各种类。

wxPanel

跟随当前用户设置而改变颜色的窗口

wxScrolledWindow

自动管理滚动条的窗口

wxGrid

表格窗口

wxSplitterWindow

可以被拆分为水平或垂直的窗口

wxStatusBar

frame上实现状态栏

wxToolBar

工具栏类

wxNotebook

笔记本类

wxListbook

类似于笔记本,但使用列表控件

wxChoicebook

类似于笔记本,但使用选择控件

wxSashWindow

包含四个可拖拽框格的窗口

wxSashLayoutWindow


wxVScrolledWindow

wxScrolledWindow一样,但支持可变高度的行

wxWizardPage

向导对话框中页面的基类

wxWizardPageSimple

向导对话框中的页面

公共对话框

公共对话框是在应用程序中被频繁使用的现成的对话框类。

wxDialog

公共对话框的基类

wxColourDialog

颜色选择对话框

wxDirDialog

目录选择对话框

wxFileDialog

文件选择对话框

wxFindReplaceDialog

文本搜索/替换对话框

wxMultiChoiceDialog

从列表获得一个或多个选择的对话框

wxSingleChoiceDialog

从列表中获得一个选择的对话框,并且返回一个字符串

wxTextEntryDialog

从用户那里获得一行文本的对话框

wxPasswordEntryDialog

从用户那里获得一个密码的对话框

wxFontDialog

字体选择对话框

wxPageSetupDialog

标准页面设置对话框

wxPrintDialog

标准打印对话框

wxProcessDialog

进程指示对话框

wxMessageDialog

简单的消息对话框

wxWizard

向导对话框

控件

典型的,提供与用户之间交互的小窗口。控件不是静态的,它们能够具有与之关联的validators

wxControl

控件的基类

wxButton

按钮控件,显示文本

wxBitmapButton

按钮控件,显示一张位图

wxToggleButton

一个按钮,当用户单击时保持按下(状态)

wxCalendarCtrl

显示一个完整的月历的控件

wxCheckBox

检查框控件

wxCheckBox


wxCheckListBox

每个条目左边都有一个检查框的列表框

wxChoice

选择控件(一个没有可编辑区的组合框)

wxComboBox

包含一个可编辑区的选择框

wxDatePickerCtrl

简单的日期选择控件

wxGauge

显示一个变化数量的控件,如剩余时间

wxGenericDirCtrl

显示一个目录树的控件

wxHtmlListBox

显示HTML内容的列表框

wxStaticBox

一个静态的或将相关控件在视觉上组合在一起的方框

wxListBox

单选或多选的字符串列表

wxListCtrl

显示字符串列表,和/或图标,加上一个多列报表视图

wxListView

一个简单的界面(wxListCtrl的报表视图外观)

wxTextCtrl

单行或多行文本编辑控件

wxTreeCtrl

树(层次)控件

wxScrollBar

滚动条控件

wxSpinButton

一个旋转或‘up-down’控件

wxSpinCtrl

一个旋转控件 例如:旋转按钮和文本控件

wxStaticText

一行或多行的不可编辑文本

wxStaticBitmap

显示一张位图的控件

wxRadioBox

一个单选按钮的组合

wxRadioButton

以相互排斥的方式和其它圆形按钮一起使用的圆形按钮

wxSlider

用户可拖拽的滑块

wxVListBox

支持可变行高的列表框

菜单

wxMenu

显示连续的菜单条目以供选择

wxMenuBar

包含连续菜单以一个frame的方式以供使用

wxMenuItem

表示单个菜单条目

窗口布局

有两种不同的窗口(特别是对话框)布局系统。一个是基于号称sizer的,它需要少量的定位,思考和计算,并且在所有平台下产生看起来一样的对话框。另一个是基于所谓的约束的,

虽然它仍然可用,但是已经被废弃了。

Sizer概述 描述基于sizer的布局

这些类是有关基于sizer布局的

wxSizer

抽象基类

wxGridSizer

在所有单元有着相同大小的网格中布局窗口的sizer

wFlexGridSizer

在一个可变网格中布局窗口的sizer

wxGridBagSizer

另一个网格sizer,让你指定一个条目的单元,且条目可以跨越行和/或列

wxBoxSizer

在一行或一列中布局窗口的sizer

wxStaticBoxSizer

wxBoxSizer相同,但是包含一个静态框的边框

约束概述 描述基于约束的布局

这些类是有关基于约束的窗口布局

wxIndividualLayoutConstraint

描述单个约束尺寸

wxLayoutConstraints

描述一个窗口类的约束

设备环境

概述

设备环境是一个能够在上面绘图的界面,并且提供一个允许传递不同的设备环境给你的绘图代码参数的抽象。

wxBufferedDC

双缓冲绘图的设备环境助手

wxBufferedPaintDC

OnPaint内部的双缓冲绘图设备环境助手

wxClientDC

OnPaint事件之外访问客户区的设备环境

wxPaintDC

OnPaint事件内部访问客户区的设备环境

wxWindowDC

访问非客户区的设备环境

wxScreenDC

访问整个屏幕的设备环境

wxDC

设备环境基类

wxMemoryDC

在位图上绘图的设备环境

wxMetafileDC

在元文件上绘图的设备环境

wxMirrorDC

允许简单映射的代理设备环境

wxPostScriptDC

PostScript文件上绘图的设备环境

wxPrinterDC

在打印机上绘图的设备环境

图形设备接口

位图概述

这里是在设备环境和窗口上绘图的相关类。

wxColour

描绘红,蓝和绿的颜色元素

wxDCClipper

包装设置操作并销毁剪切区域

wxBitmap

描绘一张位图

wxBrush

用于在设备环境上填充区域

wxBrushList

预定义刷子的列表

wxCursor

一个小的透明的描绘光标的位图

wxFont

描述字体

wxFontList

预定义字体的列表

wxIcon

一个小的透明的指派给帧的在设备环境上绘制位图,

wxImage

一个平台独立的图像类

wxImageList

一个图像列表,用于某些控件

wxMask

描绘一个掩码用于透明地绘制位图

wxPen

用于在设备环境上画线

wxPenList

预定义的画笔列表

wxPalette

描绘一个RGB值的索引表

wxRegion

描绘一个窗口或设备环境中简单或复杂的区域

wxRendererNative

抽象高水平的绘图原语

事件

概述

一个事件对象包含具体事件的信息。事件处理器(通常为成员函数)有一个单独的事件参数。

wxActivateEvent

一个窗口或应用程序的激活事件

wxCalendarEvent

用于wxCalendarCtrl

wxCalculateLayoutEvent

用于计算窗口布局

wxCloseEvent

一个关闭窗口或结束会话事件

wxCommandEvent

来自各种标准控件的事件

wxContextMenuEvent

当用户发出一个索引菜单命令时产生该事件

wxDateEvent

用于wxDatePickerCtrl

wxDialUpEvent

wxDialUpManager发出的事件

wxDropFilesEvent

一个撤销文件事件

wxEraseEvent

一个擦除背景事件

wxEvent

事件基类

wxFindDialogEvent

wxFindReplaceDialog发出的事件

wxFocusEvent

一个窗口聚焦事件

wxKeyEvent

一次击键事件

wxIconizeEvent

一个图标化/还原事件

wxIdleEvent

一个空闲事件

wxInitDialogEvent

一个对话框初始化事件

wxJoystickEvent

一个操纵杆事件

wxListEvent

一个列表控件事件

wxMaximizeEvent

一个最大化事件

wxMenuEvent

菜单事件

wxMouseCaptureChangedEvent

一个鼠标捕获变化事件

wxMouseEvent

鼠标事件

wxMoveEvent

移动事件

wxNotebookEvent

一个记事本控件事件

wxNotifyEvent

一个可以被禁止的通知事件

wxPaintEvent

绘画事件

wxProcessEvent

进程结束事件

wxQueryLayoutInfoEvent

用于查询布局信息

wxScrollEvent

来自滑块,独立的滚动条和旋转按钮的卷动事件

wxScrollWinEvent

来自滚动窗口的卷动事件

wxSizeEvent

一个大小化事件

wxSocketEvent

一个socket事件

wxSpinEvent

来自wxSpinButton的事件

wxSplitterEvent

来自wxSplitterWindow的事件

wxSysColourChangedEvent

一个系统颜色变化事件

wxTimerEvent

一个定时器到期事件

wxTreeEvent

树控件事件

wxUpdateUIEvent

用户界面更新事件

wxWindowCreateEvent

一个窗口建立事件

wxWindowDestroyEvent

一个窗口销毁事件

wxWizardEvent

一个向导事件

校验器

概述

这些窗口校验器用于过滤和校验用户输入。

wxValidator

基本的检验器类

wxTextValidator

文本控件检验器类

wxGenericValidator

通用控件检验器类

数据结构

这些是wxWidgets支持的数据结构类。

wxCmdLineParser

命令行分析器类

wxDateSpan

一个合理的时间间隔

wxDateTime

操纵日期/时间的类

wxArray

一个动态数组的实现

wxArrayString

一个保存wxString对象的高效容器

wxHaskMap

一个简单的哈希map的实现

wxHashSet

一个简单的哈希集合的实现

wxHaskTable

一个简单的哈希表的实现(不建议使用,建议使用wxHaskMap

wxList

一个简单的链表实现

wxLongLong

一种可移植的64位整型

wxNode

表示wxList中的一个节点

wxObject

大多数wxWidgets类的基类

wxPathList

帮助查找多个路径的类

wxPoint

一个点的表示

wxRect

表示一个矩形的类

wxRegEx

正则表达式支持

wxRegion

表示一个区域的类

wxString

一个字符串类

wxStringTokenizer

表示一种记号或单词列表的字符串类

wxRealPoint

一个用浮点数表示的点

wxSizer

一个size的表示

wxTimeSpan

一个时间间隔

wxURI

表示一个统一资源标识符

wxVariant

可保存任意类型的运行时刻可改变的类

运行时类信息系统

概述

wxWidgets支持运行时类信息的处理和指定类名的动态对象的建立。

wxClassInfo

保存运行时的类信息

wxObject

带有运行时信息类的基类

RTTI macros

处理运行时信息的宏

日志特征

概述

wxWidgets为消息日志提供了若干类和函数。详见wxLog概述。

wxLog

基本的日志类

wxLogStderr

记录消息到一个C STDIO

wxLogStream

记录消息到一个C++ iostream

wxLogTextCtrl

记录消息到一个wxTextCtrl

wxLogWindow

记录消息到一个日志frame

wxLogGui

GUI程序的默认日志目标

wxLogNull

临时禁止消息日志

wxLogChain

允许链接两个日志目标

wxLogPassThrough

允许过滤日志消息

wxStreamToTextRedirector

允许重定向coutwxTextCtrl的输出

Log functions

错误和警告日志函数

调试特征

概述

wxWidgets通过类,函数和宏支持一些应用程序的调试方式。

wxDebugContext

提供内存检查设施

Debugging macros

支持断言和检查的调试宏

WXDEBUG_NEW

使用该宏得到进一步的调试信息

wxDebugReport

在一个程序崩溃的情况下建立调试报告的基类

wxDebugReportCompress

建立压缩的调试报告的类

wxDebugReportUpload

通过HTTP上载压缩的调试报告的类

wxDebugReportPreview

预览一个调试报告内容的抽象基类

wxDebugReportPreviewStd

wxDebugReportPreview的标准实现

联网技术类

wxWidgets提供了它自己的基于联网的socket类。

wxDialUpManager

提供函数来检测网络连接状态并确定它

wxIPV4adress

描绘一个因特网地址

wxIPadress

描绘一个因特网地址

wxSocketBase

描绘一个socket基本对象

wxSocketClient

描绘一个socket客户端

wxSocketServer

描绘一个socket服务端

wxSocketEvent

一个socket事件

wxFTP

FTP协议类

wxHTTP

HTTP协议类

wxURL

描绘一个URL(统一资源定位符)

进程间通讯

概述

wxWidgets提供基于Windows DDE的简单的进程间通讯设施,而大部分平台则使用TCP

wxClientwxDDEClient

描绘一个客户

wxConnectionwxDDEConnection

描绘一个客户端与一个服务端的连接

wxServerwxDDEServer

描述一个服务端

文档/视图框架

概述

wxWidgets支持一种文档/视图框架,它为以文档为中心的应用程序提供了内部管理。

wxDocument

表示一个文档

wxView

表示一个视图

wxDocTemplate

管理一个文档与一个视图之间的关系

wxDocManager

管理应用程序中的文档与视图

wxDocChildFrame

一个显示文档视图的子框架

wxDocParentFrame

用于包含视图的父框架

打印框架

概述

实现一个打印和预览框架让提供文档打印设施变得相对简单。

wxPreviewFrame

显示一个打印预览的框架

wxPreviewCanvas

显示一个打印预览的画布

wxPreviewControlBar

一个打印预览的标准控制栏

wxPrintDialog

标准打印对话框

wxPageSetupDialog

标准的页面设置对话框

wxPrinter

表示打印机的类

wxPrinterDC

打印机的设备环境

wxPrintout

表示一个详细的打印输出的类

wxPrintPreview

表示一个打印预览的类

wxPrintData

表示将被打印的文档的信息

wxPrintDialogData

表示打印对话框的信息

wxPageSetupDialogData

表示页面设置对话框的信息

拖拽和剪切板类

拖拽和剪切板概述

wxDataObject

数据对象类

wxDataFormat

表示一个数据格式

wxTextDataObject

文本数据对象类

wxFileDataObject

文件数据对象类

wxBitmapDataObject

位图数据对象类

wxCustomDataObject

自定义数据对象类

wxClipboard

剪贴板类

wxDropTarget

拖拽目标类

wxFileDropTarget

文件拖拽目标类

wxTextDropTarget

文本拖拽目标类

wxDropSource

拖拽源类

文件相关类

wxWidgets有若干个小类来操作磁盘文件,更多细节见文件类概述。

wxFileName

操作文件名和属性

wxDir

列举文件/子目录的类

wxDirTraverser

wxDir一起递归的列举文件/子目录的类

wxFile

低级文件输入/输出类

wxFFile

另一个低级文件输入/输出类

wxTempFile

安全的替换一个存在文件的类

wxTextFile

操作以行数组方式表示的文本文件的类

wxStandardPaths

标准目录路径

流类

wxWidgets有它自己的流类集合,作为经常出现bug的标准流库的另外一个选择,并且提供了更加强大的功能。

wxStreamBase

流基类

wxStreamBuffer

流缓冲类

wxInputStream

输入流类

wxOutputStream

输出流类

wxCountingOutputStream

查询一个流的大小的流类

wxFilterInputStream

过滤输入流类

wxFilterOutputStream

过滤输出流类

wxBufferedInputStream

缓冲输入流类

wxBufferedOutputStream

缓冲输出流类

wxMemoryInputStream

内存输入流类

wxMemoryOuputStream

内存输出流类

wxDataInputStream