海's profileYunHai 个人空间PhotosBlogListsMore Tools Help

Windows Media Player

海 云

Interests
I am a programmer ^_^
There are no music lists on this space.
This person's network is empty (or maybe they're keeping it private).

YunHai 个人空间

June 05

某老板给员工上的第一课....标题为<为什么你还要请病假!>

一年里有365天你可以工作。
一年52个星期,你每星期休息两天,剩下261天工作。
你每天有16个小时不在工作,去掉170天,剩下91天。
你每天花30分钟喝咖啡,加起来每年23天,剩下68天。
每天午饭时间你花掉1小时,又用掉46天,还有22天。
通常你每年请两天事假,这样你的工作时间只有20天。
每年有5个节假日公司休息不上班,你只干了15天。
每年公司还慷慨地给你14天假期,算下来你就工作1天,
而这天你居然还要生病?!
因此:你不能生病!!!

快端午了

最近看到一个旧友让我更觉得两个人能相守到老不容易...
March 20

教你用7年预定70年幸福

如果天长地久意味着一列永不出轨的火车,下面有关婚姻生活的战略就像制定一张准确的运行时刻表。因为成功的婚姻并非源于机运,所谓的七年之痒也不是空穴来风。对那些已婚男人来说,他们需要计划——为了一年比一年过得更有价值,而不仅仅是等待幸福的日子。
 
  第1年 享受欢乐
  新婚是人生中最美妙的时刻,娶了这么可爱的女人,你还有什么心情搞7年计划。过了这年再说罢!
  第2年 制定家规
  婚姻中的男女总喜欢用各自的标准要求对方,但奇怪的是他们从未想过制定一个双方都了解和认可的规矩,矛盾也正是从这种互不知情的专制与抗争中产生。
  国有国法,家有家规。在一个幸福的家庭之中,规矩并不意味着限制,它是提示双方都应当承担的责任限度和对事物的相同理解。
  已婚男人经常会遇到这样的情况,当他责怪妻子某些事做得不好时,她或者立即以一种莫名其妙的理由反驳你或者躲在一边低声啜泣,而当他妻子唠叨他时,他也会马上反唇相讥。这种冲突实际表明了男女双方在对待相同事物上存在明显的分歧。
  为了避免这些根本不必要的矛盾,让未来的婚姻能够在一条秩序井然的道路上前行,已婚男人必须清醒地意识到家规的重要意义。婚姻的第2年,激情过后的你需要立即着手同妻子制定一套行之有效,双方认可的家庭内部章程。
  家规内容应当包括:
  1.各自应承担的家庭责任;
  2.家庭财产的合理运用和分配;
  3.对待亲友的态度;
  4.各自工作的安排。
第3年 确立户主
  一家之长的确立绝不是户口簿上第一页的争夺,对每一个已婚男人来说,它象征着一个家庭对内对外的处世态度以及夫妻双方在婚姻生活中的依赖倾向。不论是你,还是你妻子,目的都是要让婚姻有一个最坚实的承重点。
  现实婚姻中常常出现鲜明的夫妻主从关系,这种主从关系不仅代表了绝大多数婚姻的存在方式,也显示了家庭中确立户主的主要性。但是,这种主从关系不能简单地理解为谁说了算的问题。
  因为根据已有的婚姻调查报告,婚姻中男女的性格大多是以一种互补的形式出现,这也为主从关系的存在提供了可靠的基础。然而,家庭中真正的主从关系的形成并不是如此直接的互补可以轻易解决的,它仍然需要男女双方经过一段时间的共同生活,从相互了解中找到各自的位置。
  外向、勇敢等并不是确立户主的先决条件,关键在于谁能够并且愿意始终承担一个家庭的重大责任或者搞不完的琐事,谁在处事态度上更冷静更理智。户主的确立实际上也是一种家庭关系的确立,是男女双方保持平衡的重要前提。
  第4年 预约未来
  女人的浪漫从来不会因为年龄的增长而稍有消减,也不会因为婚姻的现实性而放弃,给女人一个远期的梦想并不需要男人付出什么,却能让她们感觉幸福,同时也能使她们更易于接受男人的过失和男人对婚姻供给的不足之处。
  女人是浪漫的宠物,她们也习惯在幻想的云朵上飘荡。如果你的精力财力还不允许你随意实现你给女人的承诺,你不妨试着向你的妻子描绘一下将来的美好蓝图,就像你未婚时向她描述婚礼将多么盛大,她的婚纱将多么漂亮。你可以告诉她你正在计划一次长达1个月的“二次蜜月”,告诉她银婚时你将包下王府饭店的水晶厅,告诉她你加薪后会找一个小工来帮她做家务,那样她就能和你一起坐在烛光下享用家庭晚宴……什么都可以,只要让她感觉浪漫,让她相信明天会更好。
  第5年 自我认识
  经过3、4年的洗礼,你已经没有理由推说自己不了解婚姻。如果你的期待还只能停留在不休的抱怨之中,你必须在这个时候重新认识自己。你不是儿童,这一切也不是儿戏,接受自己还是改造自己你需要给出一个肯定的答案。当然也包括你的妻子。
  是时候问自己一些现实的问题了:什么是幸福的婚姻?你认为理想的婚姻应当包括什么?你为你的婚姻做了什么?你对你的婚姻有哪些不满意?你是否努力使你的婚姻变得更加幸福?
  新娘是你选的,婚宴是你挑的地方,旅行结婚是你设计的线路,甚至做爱时该亮哪盏灯也是按你的要求安装的。你应该明白,是你自己跳上了北上的列车,寒冷的天气并不能成为你逃避的借口。你是一个成熟的已婚男人,你应当小心地呵护你的婚姻,自己为自己也为爱人幸福地去生活。
第6年 加减乘除
  幸福本来是无法用尺度来衡量的,但是既然许多令人一筹莫展的问题都可以通过创造性地应用算术方法来加以解决,幸福也就有了变得同样神妙的充分理由。
  说到幸福,走过了6年婚姻的你可能会显得倦怠,甚至已经在依靠不时地念“忍”字来走进家门。但你
  知道吗?幸福从来不是旁人单纯地给予,也不可能留在不思进取的人的身边。你的婚姻完全可以幸福地延续下去,却因为你的惰性而失去积极的动力。假如你试试用“加减乘除”的方式对待婚姻,或许你会了解婚姻其实十分有趣。
  加法:从事新活动,开辟新天地。还记得你们的新婚之夜的感觉吗?那种第一次的感觉不是很令人兴奋和难忘吗?如果现在的婚姻模式让你感到厌倦,何不尝试一下新方式。如在早晨做爱,一起在午夜驾车兜风……所有你想做的事情都可以。
  化生活中的限制为机会。如果你觉得婚姻中的某些东西限制了你,而这东西又不能改变你完全可以化被动为主动。
  减法:放弃生活中已成为你负担的东西,终止你不再喜欢的东西。
  阿伦从结婚起就住在丈人家里,但他根本无法适应那里的生活方式和习惯,并且经常因为这个缘故同妻子争吵,后来甚至为此离异。其实他需要做的只是搬出去单住这么简单。
  乘法:扩大和他人及周围生活的交往和接触。
  婚姻的幸福取决于双方,但孤独的二人世界会产生许多意料不到的矛盾。这时最需要的是出去走走,而不是像新婚那样独处。外面的世界就像幸福婚姻的补氧器,外界的人和生活也能帮助你确定婚姻的真实存在。
  除法:将你或她的职责分作较易处理的几个部分,并将其中的某些部分委托他人代理。所谓“会生活”,在某种意义上意味着作出聪明的选择和必要的妥协。
  当你做了6年家务的妻子要求你做些家务时,拒绝并不是个好方法。你不如将洗衣服,收拾房间划在自己名下,然后请小时工来帮忙。那样你既讨好了妻子又承担了道义上的责任,代价不过是少抽几盒烟。
  将难以处理的问题化整为零,分别解决。婚姻中的问题并非一月形成,一次解决也不现实。如果婚姻中你承担的责任多得让你束手无策,不妨采取各个击破的方式加以解决。
  幸福的婚姻因人而异,通往幸福的道路也不相同。假如你面临的问题是懒怠、乏味或孤寂,通过加法和乘法会获益匪浅;假如你终日忙忙碌碌、疲惫不堪,则可以用减法和除法加以改善,最重要的一点是要行动起来。
  第7年 MPI—婚姻潜力调查
  尽管已经一起生活了7年,但你也许还不知道。你们婚姻的实际情况与其可望达到的理想状况之间仍然存在一个差距。如果你能够有效地开发出婚姻中潜在的幸福动力,你将不再为现在的状态烦恼。
  MPI又叫婚姻潜力调查,它是婚姻问题专家大卫和梅斯发明的。根据他们近50年的婚姻研究发现,90%的夫妻在家庭幸福方面都没有发挥出应有的潜力。这同时表明绝大多数破碎的婚姻都是由于缺乏更深层的相互了解才各奔东西。为此,大卫和梅斯精心设计了MPI,帮助测定及改善婚姻的现实状况。
  这项测验十分简单,只需夫妻双方各自就婚姻中10个基本方面的状况根据自己的感觉做出估计,打出分数。
  1.共同的目标和价值观念;
  2.为增进婚姻关系所做的努力;
  3.交流思想的技巧;
  4.感情与理解;
  5.建设性地对待夫妻间的冲突;
  6.对男女各方职责的一致看法;
  7.同心协力,配合默契;
  8.性生活的充实;
  9.钱财的使用安排;
  10.教育子女(对没有孩子的夫妻来说,在于怎样对家中问题商讨、决议)。
  有了这7年计划,加上有条不紊的坚决贯彻和实施,你的爱情一定能走得更远更远……
April 28

郭德纲相声之恶搞语录

1.女演员都跟导演睡觉了,我们男演员怎么办?
2.想上春晚就得没羞没臊 .
3.现在艺术家象雨后春笋一样涌现.
4.相声要是不搞笑,那才"搞笑"呢
5.传统相声本来还有一千多段,经过我们演员的努力,还剩两百多段
6.有一人坐一洋车,嘴里还喊:我想死你们啦!我想这人缺心眼啊,这样得人都能上春晚.我也能上啊
7.在电视台说相声好混,雇四百人给你鼓掌
8.我是一个艺术家,都一个多星期了
9.看京剧三岔口,有什么教育意义? 看杂技十六个人骑一自行车,有什么教育意义?你违反交通规则了知道吗? 我就不信了,为什么我们相声要有教育意义?
10.赵本山说大俗就是大雅,大雅就是大俗,所以二人转和昆曲是等效的.
11.现在有的相声,说的跟对口的新闻联播似的!
12.有的人还没会就红了,想回头再学吧,又怕耽误工夫挣钱!
13.有的人从来没当过厨子,非要拿鞋底子把鱼拍成肉松,这就是他们失败的原因
14.我们五千年文明古国,现在你好谢谢,对不起还要写在墙上教你说,这不是悲哀吗?
15.相声是传统文化,多听相声说明你爱国!
16.炸酱面你都不吃,忘了本了!
// 本文转自 C++Builder 研究 - http://www.ccrun.com/article.asp?i=1011&d=84dsoy
17.有人说要抛弃传统相声,这就值左右开弓一千四百个大嘴巴!
18.为了当宇航员,我要克服恐高症,于是我爬上了楼顶。
警察在下面喊:下来吧工头答应给钱了!
19.郭德刚:帝哥,我希望天下和平,天下百姓们安居乐业,国泰民安,没有战争,行吗,嗯? 上帝想了想,这难点儿,咱实话实说啊,我没那么大能力,真的真的,我也不跟你说别的,你换一样行吗?咱商量商量别的。 我一摸身上带了一张别人的相片,帝哥,你看看这个,这是我师兄弟,长得挺寒掺的,搞不上对象,你给他变漂亮点儿吧。 上帝:(想了想)还是说说世界和平那事儿吧(把相片撕了) 郭德刚:哎,你怎么把相片撕了,啊?你不同意归不同意,撕了干吗,我还留着避邪呢!
20.父亲:去跟你妈说,我已经知道真相了
~~~
儿子:妈妈,我已经知道真相了
妈妈:乖,妈妈给你五十块钱,别跟被人说去!
~~~~
儿子:爸爸,我已经知道真相了
爸爸:乖儿子,给你两百,别跟别人说!
~~~
儿子对邮局送信的说:我已经知道真相了
邮局送信的眼泪都下来了:来!儿子,爸爸抱抱!
21.上帝的秘书小三说:世界上有一个男人,天堂里就有一座钟.好人的转的慢,坏人的转的快! 郭德纲:那我朋友于谦的怎么没在啊? 小三:他那个上帝拿去当风扇用了
22.住的房子千疮百孔,一下雨算要了亲命了:外边小雨屋里中雨,外边大雨屋里暴雨,有时候雨实在太大了,全家人都上街上避雨去了。
23. 武大郎对倭瓜国的人说:以后床就不要叫床了,就叫他他密什么意思呢?就是说只要是我的孙子,都在踏踏实实的密着
22.现在说相声的,有百分之九十以前是厨子
24.王文林的父亲临死,王文林文他:爸爸,您还有什么话要说? 他爸爸写了一纸条,、然后就死了 王文林打开纸条一看,上面写着:你踩我氧气管子了!
25.您是我心中的世界第二伟人!那第一伟人是谁啊?西门庆!
26.郭德纲说:"有一个400多人的群口相声`````最后用洋车拉出一个逗哏演员来:"亲爱的观众朋友们,我想死你们了。咱们来说个反正话吧,我说一句,你们给翻过来---"大家新年好!"下边就喊:"新年好大家!""给大家拜年我很高兴"下边就说:"我很高兴给大家拜年."一鞠躬,完了.这简单啊,我说:"我来这个吧,导演.""
27.郭:营养液,不一定真有营养。张:哦,赝品。郭:张先生以前做生意做过这个,开过一个厂子卖鳖精,买一个王八熬汤兑水,装小瓶儿卖,干了十二年,一个王八没用了。
September 27

使用 typedef 抑制劣质代码

原文出处:Using typedef to Curb Miscreant Code

摘要:Typedef 声明有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法。不管怎样,使用 typedef 能为代码带来意想不到的好处,通过本文你可以学习用 typedef 避免缺欠,从而使代码更健壮。


  typedef 声明,简称 typedef,为现有类型创建一个新的名字。比如人们常常使用 typedef 来编写更美观和可读的代码。所谓美观,意指 typedef 能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性和以及未来的可维护性。本文下面将竭尽全力来揭示 typedef 强大功能以及如何避免一些常见的陷阱。

 如何创建平台无关的数据类型,隐藏笨拙且难以理解的语法? 
 
使用 typedefs 为现有类型创建同义字。

定义易于记忆的类型名
  typedef 使用最多的地方是创建易于记忆的类型名,用它来归档程序员的意图。类型出现在所声明的变量名字中,位于 ''typedef'' 关键字右边。例如:

typedef int size;

  此声明定义了一个 int 的同义字,名字为 size。注意 typedef 并不创建新的类型。它仅仅为现有类型添加一个同义字。你可以在任何需要 int 的上下文中使用 size:

void measure(size * psz); 
size array[4];
size len = file.getlength();
std::vector <size> vs; 

  typedef 还可以掩饰符合类型,如指针和数组。例如,你不用象下面这样重复定义有 81 个字符元素的数组:

char line[81];
char text[81];

定义一个 typedef,每当要用到相同类型和大小的数组时,可以这样:

typedef char Line[81]; 
Line text, secondline;
getline(text);

同样,可以象下面这样隐藏指针语法:

typedef char * pstr;
int mystrcmp(pstr, pstr);

  这里将带我们到达第一个 typedef 陷阱。标准函数 strcmp()有两个‘const char *’类型的参数。因此,它可能会误导人们象下面这样声明 mystrcmp():

int mystrcmp(const pstr, const pstr); 

  这是错误的,按照顺序,‘const pstr’被解释为‘char * const’(一个指向 char 的常量指针),而不是‘const char *’(指向常量 char 的指针)。这个问题很容易解决:

typedef const char * cpstr; 
int mystrcmp(cpstr, cpstr); // 现在是正确的

记住:不管什么时候,只要为指针声明 typedef,那么都要在最终的 typedef 名称中加一个 const,以使得该指针本身是常量,而不是对象。

代码简化
  上面讨论的 typedef 行为有点像 #define 宏,用其实际类型替代同义字。不同点是 typedef 在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换。例如:

typedef int (*PF) (const char *, const char *);

  这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int 类型的返回值。如果要使用下列形式的函数声明,那么上述这个 typedef 是不可或缺的:

PF Register(PF pf);

  Register() 的参数是一个 PF 类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。做一次深呼吸。下面我展示一下如果不用 typedef,我们是如何实现这个声明的:

int (*Register (int (*pf)(const char *, const char *))) 
(const char *, const char *); 

  很少有程序员理解它是什么意思,更不用说这种费解的代码所带来的出错风险了。显然,这里使用 typedef 不是一种特权,而是一种必需。持怀疑态度的人可能会问:“OK,有人还会写这样的代码吗?”,快速浏览一下揭示 signal()函数的头文件 <csinal>,一个有同样接口的函数。

typedef 和存储类关键字(storage class specifier)
  这种说法是不是有点令人惊讶,typedef 就像 auto,extern,mutable,static,和 register 一样,是一个存储类关键字。这并是说 typedef 会真正影响对象的存储特性;它只是说在语句构成上,typedef 声明看起来象 static,extern 等类型的变量声明。下面将带到第二个陷阱:

typedef register int FAST_COUNTER; // 错误

  编译通不过。问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置,在 typedef 声明中不能用 register(或任何其它存储类关键字)。

促进跨平台开发
  typedef 有另外一个重要的用途,那就是定义机器无关的类型,例如,你可以定义一个叫 REAL 的浮点类型,在目标机器上它可以i获得最高的精度:

typedef long double REAL; 

在不支持 long double 的机器上,该 typedef 看起来会是下面这样:

typedef double REAL; 

并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样: 、

typedef float REAL; 

  你不用对源代码做任何修改,便可以在每一种平台上编译这个使用 REAL 类型的应用程序。唯一要改的是 typedef 本身。在大多数情况下,甚至这个微小的变动完全都可以通过奇妙的条件编译来自动实现。不是吗? 标准库广泛地使用 typedef 来创建这样的平台无关类型:size_t,ptrdiff 和 fpos_t 就是其中的例子。此外,象 std::string 和 std::ofstream 这样的 typedef 还隐藏了长长的,难以理解的模板特化语法,例如:basic_string<char, char_traits<char>,allocator<char>> 和 basic_ofstream<char, char_traits<char>>。

 作者简介
  Danny Kalev 是一名通过认证的系统分析师,专攻 C++ 和形式语言理论的软件工程师。1997 年到 2000 年期间,他是 C++ 标准委员会成员。最近他以优异成绩完成了他在普通语言学研究方面的硕士论文。 业余时间他喜欢听古典音乐,阅读维多利亚时期的文学作品,研究 Hittite、Basque 和 Irish Gaelic 这样的自然语言。其它兴趣包括考古和地理。Danny 时常到一些 C++ 论坛并定期为不同的 C++ 网站和杂志撰写文章。他还在教育机构讲授程序设计语言和应用语言课程。

September 17

汇编语言的准备知识--给初次接触汇编者(4) 转载

汇编语言的准备知识--给初次接触汇编者(4)
作者:lianzi2000 更新时间: 2005-04-30  
 
高级语言程序的汇编解析

在高级语言中,如C和PASCAL等等,我们不再直接对硬件资源进行操作,而是面向于问题的解决,这主要体现在数据抽象化和程序的结构化。例如我们用变量名来存取数据,而不再关心这个数据究竟在内存的什么地方。这样,对硬件资源的使用方式完全交给了编译器去处理。不过,一些基本的规则还是存在的,而且大多数编译器都遵循一些规范,这使得我们在阅读反汇编代码的时候日子好过一点。这里主要讲讲汇编代码中一些和高级语言对应的地方。

1. 普通变量。通常声明的变量是存放在内存中的。编译器把变量名和一个内存地址联系起来(这里要注意的是,所谓的“确定的地址”是对编译器而言在编译阶段算出的一个临时的地址。在连接成可执行文件并加载到内存中执行的时候要进行重定位等一系列调整,才生成一个实时的内存地址,不过这并不影响程序的逻辑,所以先不必太在意这些细节,只要知道所有的函数名字和变量名字都对应一个内存的地址就行了),所以变量名在汇编代码中就表现为一个有效地址,就是放在方括号中的操作数。例如,在C文件中声明:

int my_age;

这个整型的变量就存在一个特定的内存位置。语句 my_age= 32; 在反汇编代码中可能表现为:

mov word ptr [007E85DA], 20

所以在方括号中的有效地址对应的是变量名。又如:

char my_name[11] = "lianzi2000";

这样的说明也确定了一个地址,对应于my_name. 假设地址是007E85DC,则内存中[007E85DC]='l',[007E85DD]='i', etc. 对my_name的访问也就是对这地址处的数据访问。

指针变量其本身也同样对应一个地址,因为它本身也是一个变量。如:

char *your_name;

这时也确定变量"your_name"对应一个内存地址,假设为007E85F0. 语句your_name=my_name;很可能表现为:

mov [007E85F0], 007E85DC ;your_name的内容是my_name的地址。

2. 寄存器变量

在C和C++中允许说明寄存器变量。register int i; 指明i是寄存器存放的整型变量。通常,编译器都把寄存器变量放在esi和edi中。寄存器是在cpu内部的结构,对它的访问要比内存快得多,所以把频繁使用的变量放在寄存器中可以提高程序执行速度。

3. 数组

不管是多少维的数组,在内存中总是把所有的元素都连续存放,所以在内存中总是一维的。例如,int i_array[2][3]; 在内存确定了一个地址,从该地址开始的12个字节用来存贮该数组的元素。所以变量名i_array对应着该数组的起始地址,也即是指向数组的第一个元素。存放的顺序一般是i_array[0][0],[0][1],[0][2],[1][0],[1][1],[1][2] 即最右边的下标变化最快。当需要访问某个元素时,程序就会从多维索引值换算成一维索引,如访问i_array[1][1],换算成内存中的一维索引值就是1*3+1=4.这种换算可能在编译的时候就可以确定,也可能要到运行时才可以确定。无论如何,如果我们把i_array对应的地址装入一个通用寄存器作为基址,则对数组元素的访问就是一个计算有效地址的问题:

; i_array[1][1]=0x16

lea ebx,xxxxxxxx ;i_array 对应的地址装入ebx
mov edx,04 ;访问i_array[1][1],编译时就已经确定
mov word ptr [ebx+edx*2], 16 ;

当然,取决于不同的编译器和程序上下文,具体实现可能不同,但这种基本的形式是确定的。从这里也可以看到比例因子的作用(还记得比例因子的取值为1,2,4或8吗?),因为在目前的系统中简单变量总是占据1,2,4或者8个字节的长度,所以比例因子的存在为在内存中的查表操作提供了极大方便。

4. 结构和对象

结构和对象的成员在内存中也都连续存放,但有时为了在字边界或双字边界对齐,可能有些微调整,所以要确定对象的大小应该用sizeof操作符而不应该把成员的大小相加来计算。当我们声明一个结构变量或初始化一个对象时,这个结构变量和对象的名字也对应一个内存地址。举例说明:

struct tag_info_struct
{
int age;
int sex;
float height;
float weight;
} marry;

变量marry就对应一个内存地址。在这个地址开始,有足够多的字节(sizeof(marry))容纳所有的成员。每一个成员则对应一个相对于这个地址的偏移量。这里假设此结构中所有的成员都连续存放,则age的相对地址为0,sex为2, height 为4,weight为8。

; marry.sex=0;

lea ebx,xxxxxxxx ;marry 对应的内存地址
mov word ptr [ebx+2], 0
......

对象的情况基本相同。注意成员函数具体的实现在代码段中,在对象中存放的是一个指向该函数的指针。


5. 函数调用

一个函数在被定义时,也确定一个内存地址对应于函数名字。如:

long comb(int m, int n)
{
long temp;
.....

return temp;
}

这样,函数comb就对应一个内存地址。对它的调用表现为:

CALL xxxxxxxx ;comb对应的地址。这个函数需要两个整型参数,就通过堆栈来传递:

;lresult=comb(2,3);

push 3
push 2
call xxxxxxxx
mov dword ptr [yyyyyyyy], eax ;yyyyyyyy是长整型变量lresult的地址

这里请注意两点。第一,在C语言中,参数的压栈顺序是和参数顺序相反的,即后面的参数先压栈,所以先执行push 3. 第二,在我们讨论的32位系统中,如果不指明参数类型,缺省的情况就是压入32位双字。因此,两个push指令总共压入了两个双字,即8个字节的数据。然后执行call指令。call 指令又把返回地址,即下一条指令(mov dword ptr....)的32位地址压入,然后跳转到xxxxxxxx去执行。

在comb子程序入口处(xxxxxxxx),堆栈的状态是这样的:

03000000 (请回忆small endian 格式)
02000000
yyyyyyyy <--ESP 指向返回地址

前面讲过,子程序的标准起始代码是这样的:

push ebp ;保存原先的ebp
mov ebp, esp;建立框架指针
sub esp, XXX;给临时变量预留空间
.....

执行push ebp之后,堆栈如下:

03000000
02000000
yyyyyyyy
old ebp <---- esp 指向原来的ebp

执行mov ebp,esp之后,ebp 和esp 都指向原来的ebp. 然后sub esp, xxx 给临时变量留空间。这里,只有一个临时变量temp,是一个长整数,需要4个字节,所以xxx=4。这样就建立了这个子程序的框架:

03000000
02000000
yyyyyyyy
old ebp <---- 当前ebp指向这里
temp

所以子程序可以用[ebp+8]取得第一参数(m),用[ebp+C]来取得第二参数(n),以此类推。临时变量则都在ebp下面,如这里的temp就对应于[ebp-4].

子程序执行到最后,要返回temp的值:

mov eax,[ebp-04]
然后执行相反的操作以撤销框架:

mov esp,ebp ;这时esp 和ebp都指向old ebp,临时变量已经被撤销
pop ebp ;撤销框架指针,恢复原ebp.

这是esp指向返回地址。紧接的retn指令返回主程序:

retn 4

该指令从堆栈弹出返回地址装入EIP,从而返回到主程序去执行call后面的指令。同时调整esp(esp=esp+4*2),从而撤销参数,使堆栈恢复到调用子程序以前的状态,这就是堆栈的平衡。调用子程序前后总是应该维持堆栈的平衡。从这里也可以看到,临时变量temp已经随着子程序的返回而消失,所以试图返回一个指向临时变量的指针是非法的。

为了更好地支持高级语言,INTEL还提供了指令Enter 和Leave 来自动完成框架的建立和撤销。Enter 接受两个操作数,第一个指明给临时变量预留的字节数,第二个是子程序嵌套调用层数,一般都为0。enter xxx,0 相当于:

push ebp
mov ebp,esp
sub esp,xxx

leave 则相当于:

mov esp,ebp
pop ebp

=============================================================
好啦,我的学习心得讲完了,谢谢各位的抬举。教程是不敢当的,因为我也是个大菜鸟。如果这些东东能使你们的学习轻松一些,进步快一些,本菜鸟就很开心了。

汇编语言的准备知识--给初次接触汇编者(3) 转载

汇编语言的准备知识--给初次接触汇编者(3)
作者:lianzi2000 更新时间: 2005-04-30  
 
“汇编语言”作为一门语言,对应于高级语言的编译器,我们需要一个“汇编器”来把汇编语言原文件汇编成机器可执行的代码。高级的汇编器如MASM, TASM等等为我们写汇编程序提供了很多类似于高级语言的特征,比如结构化、抽象等。在这样的环境中编写的汇编程序,有很大一部分是面向汇编器的伪指令,已经类同于高级语言。现在的汇编环境已经如此高级,即使全部用汇编语言来编写windows的应用程序也是可行的,但这不是汇编语言的长处。汇编语言的长处在于编写高效且需要对机器硬件精确控制的程序。而且我想这里的人学习汇编的目的多半是为了在破解时看懂反汇编代码,很少有人真的要拿汇编语言编程序吧?(汗......)

好了,言归正传。大多数汇编语言书都是面向汇编语言编程的,我的帖是面向机器和反汇编的,希望能起到相辅相成的作用。有了前面两篇的基础,汇编语言书上对大多数指令的介绍应该能够看懂、理解了。这里再讲一讲一些常见而操作比较复杂的指令。我这里讲的都是机器的硬指令,不针对任何汇编器。

无条件转移指令jmp:

这种跳转指令有三种方式:短(short),近(near)和远(far)。短是指要跳至的目标地址与当前地址前后相差不超过128字节。近是指跳转的目标地址与当前地址在用一个段内,即CS的值不变,只改变EIP的值。远指跳到另一个代码段去执行,CS/EIP都要改变。短和近在编码上有所不同,在汇编指令中一般很少显式指定,只要写 jmp 目标地址,几乎任何汇编器都会根据目标地址的距离采用适当的编码。远转移在32位系统中很少见到,原因前面已经讲过,由于有足够的线性空间,一个程序很少需要两个代码段,就连用到的系统模块也被映射到同一个地址空间。

jmp的操作数自然是目标地址,这个指令支持直接寻址和间接寻址。间接寻址又可分为寄存器间接寻址和内存间接寻址。举例如下(32位系统):

jmp 8E347D60 ;直接寻址段内跳转
jmp EBX ;寄存器间接寻址:只能段内跳转
jmp dword ptr [EBX] ;内存间接寻址,段内跳转
jmp dword ptr [00903DEC] ;同上
jmp fward ptr [00903DF0] ;内存间接寻址,段间跳转

解释:
在32位系统中,完整目标地址由16位段选择子和32位偏移量组成。因为寄存器的宽度是32位,因此寄存器间接寻址只能给出32位偏移量,所以只能是段内近转移。在内存间接寻址时,指令后面是方括号内的有效地址,在这个地址上存放跳转的目标地址。比如,在[00903DEC]处有如下数据:7C 82 59 00 A7 01 85 65 9F 01

内存字节是连续存放的,如何确定取多少作为目标地址呢?dword ptr 指明该有效地址指明的是双字,所以取
0059827C作段内跳转。反之,fward ptr 指明后面的有效地址是指向48位完全地址,所以取19F:658501A7 做远跳转。

注意:在保护模式下,如果段间转移涉及优先级的变化,则有一系列复杂的保护检查,现在可不加理会。将来等各位功力提升以后可以自己去学习。

条件转移指令jxx:只能作段内转移,且只支持直接寻址。

=========================================
调用指令CALL:

Call的寻址方式与jmp基本相同,但为了从子程序返回,该指令在跳转以前会把紧接着它的下一条指令的地址压进堆栈。如果是段内调用(目标地址是32位偏移量),则压入的也只是一个偏移量。如果是段间调用(目标地址是48位全地址),则也压入下一条指令的完全地址。同样,如果段间转移涉及优先级的变化,则有一系列复杂的保护检查。

与之对应retn/retf指令则从子程序返回。它从堆栈上取得返回地址(是call指令压进去的)并跳到该地址执行。retn取32位偏移量作段内返回,retf取48位全地址作段间返回。retn/f 还可以跟一个立即数作为操作数,该数实际上是从堆栈上传给子程序的参数的个数(以字计)返回后自动把堆栈指针esp加上指定的数*2,从而丢弃堆栈中的参数。这里具体的细节留待下一篇讲述。

虽然call和ret设计为一起工作,但它们之间没有必然的联系。就是说,如果你直接用push指令向堆栈中压入一个数,然后执行ret,他同样会把你压入的数作为返回地址,而跳到那里去执行。这种非正常的流程转移可以被用作反跟踪手段。

==========================================
中断指令INT n

在保护模式下,这个指令必定会被操作系统截获。在一般的PE程序中,这个指令已经不太见到了,而在DOS时代,中断是调用操作系统和BIOS的重要途径。现在的程序可以文质彬彬地用名字来调用windows功能,如 call user32!getwindowtexta。从程序角度看,INT指令把当前的标志寄存器先压入堆栈,然后把下一条指令的完全地址也压入堆栈,最后根据操作数n来检索“中断描述符表”,试图转移到相应的中断服务程序去执行。通常,中断服务程序都是操作系统的核心代码,必然会涉及到优先级转换和保护性检查、堆栈切换等等,细节可以看一些高级的教程。

与之相应的中断返回指令IRET做相反的操作。它从堆栈上取得返回地址,并用来设置CS:EIP,然后从堆栈中弹出标志寄存器。注意,堆栈上的标志寄存器值可能已经被中断服务程序所改变,通常是进位标志C, 用来表示功能是否正常完成。同样的,IRET也不一定非要和INT指令对应,你可以自己在堆栈上压入标志和地址,然后执行IRET来实现流程转移。实际上,多任务操作系统常用此伎俩来实现任务转换。

广义的中断是一个很大的话题,有兴趣可以去查阅系统设计的书籍。

============================================
装入全指针指令LDS,LES,LFS,LGS,LSS

这些指令有两个操作数。第一个是一个通用寄存器,第二个操作数是一个有效地址。指令从该地址取得48位全指针,将选择符装入相应的段寄存器,而将32位偏移量装入指定的通用寄存器。注意在内存中,指针的存放形式总是32位偏移量在前面,16位选择符在后面。装入指针以后,就可以用DS:[ESI]这样的形式来访问指针指向的数据了。

============================================
字符串操作指令

这里包括CMPS,SCAS,LODS,STOS,MOVS,INS和OUTS等。这些指令有一个共同的特点,就是没有显式的操作数,而由硬件规定使用DS:[ESI]指向源字符串,用ES:[EDI]指向目的字符串,用AL/AX/EAX做暂存。这是硬件规定的,所以在使用这些指令之前一定要设好相应的指针。
这里每一个指令都有3种宽度形式,如CMPSB(字节比较)、CMPSW(字比较)、CMPSD(双字比较)等。
CMPSB:比较源字符串和目标字符串的第一个字符。若相等则Z标志置1。若不等则Z标志置0。指令执行完后,ESI 和EDI都自动加1,指向源/目标串的下一个字符。如果用CMPSW,则比较一个字,ESI/EDI自动加2以指向下一个字。
如果用CMPSD,则比较一个双字,ESI/EDI自动加4以指向下一个双字。(在这一点上这些指令都一样,不再赘述)
SCAB/W/D 把AL/AX/EAX中的数值与目标串中的一个字符/字/双字比较。
LODSB/W/D 把源字符串中的一个字符/字/双字送入AL/AX/EAX
STOSB/W/D 把AL/AX/EAX中的直送入目标字符串中
MOVSB/W/D 把源字符串中的字符/字/双字复制到目标字符串
INSB/W/D 从指定的端口读入字符/字/双字到目标字符串中,端口号码由DX寄存器指定。
OUTSB/W/D 把源字符串中的字符/字/双字送到指定的端口,端口号码由DX寄存器指定。

串操作指令经常和重复前缀REP和循环指令LOOP结合使用以完成对整个字符串的操作。而REP前缀和LOOP指令都有硬件规定用ECX做循环计数器。举例:

LDS ESI,SRC_STR_PTR
LES EDI,DST_STR_PTR
MOV ECX,200
REP MOVSD

上面的代码从SRC_STR拷贝200个双字到DST_STR. 细节是:REP前缀先检查ECX是否为0,若否则执行一次MOVSD,ECX自动减1,然后执行第二轮检查、执行......直到发现ECX=0便不再执行MOVSD,结束重复而执行下面的指令。


LDS ESI,SRC_STR_PTR
MOV ECX,100
LOOP1:
LODSW
.... (deal with value in AX)

LOOP LOOP1
.....

从SRC_STR处理100个字。同样,LOOP指令先判断ECX是否为零,来决定是否循环。每循环一轮ECX自动减1。

REP和LOOP 都可以加上条件,变成REPZ/REPNZ 和 LOOPZ/LOOPNZ. 这是除了ECX外,还用检查零标志Z. REPZ 和LOOPZ在Z为1时继续循环,否则退出循环,即使ECX不为0。REPNZ/LOOPNZ则相反。

汇编语言的准备知识--给初次接触汇编者(2) 转载

汇编语言的准备知识--给初次接触汇编者(2)
作者:lianzi2000 更新时间: 2005-04-30  
 
汇编指令的操作数可以是内存中的数据, 如何让程序从内存中正确取得所需要的数据就是对内存的寻址.

INTEL 的CPU 可以工作在两种寻址模式:实模式和保护模式. 前者已经过时,就不讲了, WINDOWS 现在是32位保护模式的系统, PE 文件就基本是运行在一个32位线性地址空间, 所以这里就只介绍32位线性空间的寻址方式.

其实线性地址的概念是很直观的, 就想象一系列字节排成一长队,第一个字节编号为0, 第二个编号位1, .... 一直到4294967295(十六进制FFFFFFFF,这是32位二进制数所能表达的最大值了). 这已经有4GB的容量! 足够容纳一个程序所有的代码和数据. 当然, 这并不表示你的机器有那么多内存. 物理内存的管理和分配是很复杂的内容, 初学者不必在意, 总之, 从程序本身的角度看, 就好象是在那么大的内存中.

在INTEL系统中, 内存地址总是由"段选择符:有效地址"的方式给出.段选择符(SELECTOR)存放在某一个段寄存器中, 有效地址则可由不同的方式给出. 段选择符通过检索段描述符确定段的起始地址, 长度(又称段限制), 粒度, 存取权限, 访问性质等. 先不用深究这些, 只要知道段选择符可以确定段的性质就行了. 一旦由选择符确定了段, 有效地址相对于段的基地址开始算. 比如由选择符1A7选择的数据段, 其基地址是400000, 把1A7 装入DS中, 就确定使用该数据段. DS:0 就指向线性地址400000. DS:1F5278 就指向线性地址5E5278. 我们在一般情况下, 看不到也不需要看到段的起始地址, 只需要关心在该段中的有效地址就行了. 在32位系统中, 有效地址也是由32位数字表示, 就是说, 只要有一个段就足以涵盖4GB线性地址空间, 为什么还要有不同的段选择符呢? 正如前面所说的, 这是为了对数据进行不同性质的访问. 非法的访问将产生异常中断, 而这正是保护模式的核心内容, 是构造优先级和多任务系统的基础. 这里有涉及到很多深层的东西, 初学者先可不必理会.

有效地址的计算方式是: 基址+间址*比例因子+偏移量. 这些量都是指段内的相对于段起始地址的量度, 和段的起始地址没有关系. 比如, 基址=100000, 间址=400, 比例因子=4, 偏移量=20000, 则有效地址为:

100000+400*4+20000=100000+1000+20000=121000. 对应的线性地址是400000+121000=521000. (注意, 都是十六进制数).

基址可以放在任何32位通用寄存器中, 间址也可以放在除ESP外的任何一个通用寄存器中. 比例因子可以是1, 2, 4 或8. 偏移量是立即数. 如: [EBP+EDX*8+200]就是一个有效的有效地址表达式. 当然, 多数情况下用不着这么复杂, 间址,比例因子和偏移量不一定要出现.

内存的基本单位是字节(BYTE). 每个字节是8个二进制位, 所以每个字节能表示的最大的数是11111111, 即十进制的255. 一般来说, 用十六进制比较方便, 因为每4个二进制位刚好等于1个十六进制位, 11111111b = 0xFF. 内存中的字节是连续存放的, 两个字节构成一个字(WORD), 两个字构成一个双字(DWORD). 在INTEL架构中, 采用small endian格式, 即在内存中,高位字节在低位字节后面. 举例说明:十六进制数803E7D0C, 每两位是一个字节, 在内存中的形式是: 0C 7D 3E 80. 在32位寄存器中则是正常形式,如在EAX就是803E7D0C. 当我们的形式地址指向这个数的时候,实际上是指向第一个字节,即0C. 我们可以指定访问长度是字节, 字或者双字. 假设DS:[EDX]指向第一个字节0C:

mov AL, byte ptr DS:[EDX] ;把字节0C存入AL
mov AX, word ptr DS:[EDX] ;把字7D0C存入AX
mov EAX, dword ptr DS:[EDX] ;把双字803E7D0C存入EAX

在段的属性中,有一个就是缺省访问宽度.如果缺省访问宽度为双字(在32位系统中经常如此),那么要进行字节或字的访问,就必须用byte/word ptr显式地指明.

缺省段选择:如果指令中只有作为段内偏移的有效地址,而没有指明在哪一个段里的时候,有如下规则:

如果用ebp和esp作为基址或间址,则认为是在SS确定的段中;
其他情况,都认为是在DS确定的段中。

如果想打破这个规则,就必须使用段超越前缀。举例如下:

mov eax, dword ptr [edx] ;缺省使用DS,把DS:[EDX]指向的双字送入eax
mov ebx, dword ptr ES:[EDX] ;使用ES:段超越前缀,把ES:[EDX]指向的双字送入ebx

堆栈:

堆栈是一种数据结构,严格地应该叫做“栈”。“堆”是另一种类似但不同的结构。SS 和 ESP 是INTEL对栈这种数据结构的硬件支持。push/pop指令是专门针对栈结构的特定操作。SS指定一个段为栈段,ESP则指出当前的栈顶。push xxx 指令作如下操作:

把ESP的值减去4;
把xxx存入SS:[ESP]指向的内存单元。

这样,esp的值减小了4,并且SS:[ESP]指向新压入的xxx. 所以栈是“倒着长”的,从高地址向低地址方向扩展。pop yyy 指令做相反的操作,把SS:[ESP]指向的双字送到yyy指定的寄存器或内存单元,然后把esp的值加上4。这时,认为该值已被弹出,不再在栈上了,因为它虽然还暂时存在在原来的栈顶位置,但下一个push操作就会把它覆盖。因此,在栈段中地址低于esp的内存单元中的数据均被认为是未定义的。

最后,有一个要注意的事实是,汇编语言是面向机器的,指令和机器码基本上是一一对应的,所以它们的实现取决于硬件.有些看似合理的指令实际上是不存在的,比如:

mov DS:[edx], ds:[ecx] ;内存单元之间不能直接传送
mov DS, 1A7 ;段寄存器不能直接由立即数赋值
mov EIP, 3D4E7 ;不能对指令指针直接操作.

汇编语言的准备知识--给初次接触汇编者(1) 转载

汇编语言的准备知识--给初次接触汇编者(1)
作者:lianzi2000 更新时间: 2005-04-30  
 
汇编语言和CPU以及内存,端口等硬件知识是连在一起的. 这也是为什么汇编语言没有通用性的原因. 下面简单讲讲基本知识(针对INTEL x86及其兼容机) ============================ x86汇编语言的指令,其操作对象是CPU上的寄存器,系统内存,或者立即数. 有些指令表面上没有操作数, 或者看上去缺少操作数, 其实该指令有内定的操作对象, 比如push指令, 一定是对SS:ESP指定的内存操作, 而cdq的操作对象一定是eax / edx. 在汇编语言中,寄存器用名字来访问. CPU 寄存器有好几类, 分别有不同的用处: 1. 通用寄存器: EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP(这个虽然通用,但很少被用做除了堆栈指针外的用途) 这些32位可以被用作多种用途,但每一个都有"专长". EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器. EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址. ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器. EDX是...(忘了..哈哈)但它总是被用来放整数除法产生的余数. 这4个寄存器的低16位可以被单独访问,分别用AX,BX,CX和DX. AX又可以单独访问低8位(AL)和高8位(AH), BX,CX,DX也类似. 函数的返回值经常被放在EAX中. ESI/EDI分别叫做"源/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串. EBP是"基址指针"(BASE POINTER), 它最经常被用作高级语言函数调用的"框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码: push ebp ;保存当前ebp mov ebp,esp ;EBP设为当前堆栈指针 sub esp, xxx ;预留xxx字节给函数临时变量. ... 这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可. ESP 专门用作堆栈指针. 2. 段寄存器: CS(Code Segment,代码段) 指定当前执行的代码段. EIP (Instruction pointer, 指令指针)则指向该段中一个具体的指令. CS:EIP指向哪个指令, CPU 就执行它. 一般只能用jmp, ret, jnz, call 等指令来改变程序流程,而不能直接对它们赋值. DS(DATA SEGMENT, 数据段) 指定一个数据段. 注意:在当前的计算机系统中, 代码和数据没有本质差别, 都是一串二进制数, 区别只在于你如何用它. 例如, CS 制定的段总是被用作代码, 一般不能通过CS指定的地址去修改该段. 然而,你可以为同一个段申请一个数据段描述符"别名"而通过DS来访问/修改. 自修改代码的程序常如此做. ES,FS,GS 是辅助的段寄存器, 指定附加的数据段. SS(STACK SEGMENT)指定当前堆栈段. ESP 则指出该段中当前的堆栈顶. 所有push/pop 系列指令都只对SS:ESP指出的地址进行操作. 3. 标志寄存器(EFLAGS): 该寄存器有32位,组合了各个系统标志. EFLAGS一般不作为整体访问, 而只对单一的标志位感兴趣. 常用的标志有: 进位标志C(CARRY), 在加法产生进位或减法有借位时置1, 否则为0. 零标志Z(ZERO), 若运算结果为0则置1, 否则为0 符号位S(SIGN), 若运算结果的最高位置1, 则该位也置1. 溢出标志O(OVERFLOW), 若(带符号)运算结果超出可表示范围, 则置1. JXX 系列指令就是根据这些标志来决定是否要跳转, 从而实现条件分枝. 要注意,很多JXX 指令是等价的, 对应相同的机器码. 例如, JE 和JZ 是一样的,都是当Z=1是跳转. 只有JMP 是无条件跳转. JXX 指令分为两组, 分别用于无符号操作和带符号操作. JXX 后面的"XX" 有如下字母: 无符号操作: 带符号操作: A = "ABOVE", 表示"高于" G = "GREATER", 表示"大于" B = "BELOW", 表示"低于" L = "LESS", 表示"小于" C = "CARRY", 表示"进位"或"借位" O = "OVERFLOW", 表示"溢出" S = "SIGN", 表示"负" 通用符号: E = "EQUAL" 表示"等于", 等价于Z (ZERO) N = "NOT" 表示"非", 即标志没有置位. 如JNZ "如果Z没有置位则跳转" Z = "ZERO", 与E同. 如果仔细想一想,就会发现 JA = JNBE, JAE = JNB, JBE = JNA, JG = JNLE, JGE= JNL, JL= JNGE, .... 4. 端口 端口是直接和外部设备通讯的地方。外设接入系统后,系统就会把外设的数据接口映射到特定的端口地址空间,这样,从该端口读入数据就是从外设读入数据,而向外设写入数据就是向端口写入数据。当然这一切都必须遵循外设的工作方式。端口的地址空间与内存地址空间无关,系统总共提供对64K个8位端口的访问,编号0-65535. 相邻的8位端口可以组成成一个16位端口,相邻的16位端口可以组成一个32位端口。端口输入输出由指令IN,OUT,INS和OUTS实现,具体可参考汇编语言书籍。
May 26

[转载] Visual C#如何使用Active X组件

滁州供电局调度所 马金虎
Active X组件是充分利用OLE和Active X技术的自定义组件,微软公司积极鼓励把Active X作为一个与应用程序无关的自定义组件。从本质上说,Active X组件是一个Active X服务器,他能够提供所有的OLE功能和服务,可视化编辑、拖放和OLE自动化。ActiveX控件有属性(Propertiy)、方法(method)、事件(Event)三个元素构成,他一般是以"DLL"扩展名的形式出现的。这时细心的读者可能已经注意到在.Net FrameWork SDK中的类库中的类库文件也是以"DLL"扩展名的形式出现的。那么这二种"DLL"是一种类型的么?答案是否定的。这是因为这些Net FrameWork SDK中的类库是由Common Language Runtime编译生成的,这些由Common Language Runtime编译生成的的代码就是所谓的受管代码(Managed Code),而Active X组件不是由Common Language Runtime生成的,而是由譬如:Visual Basic等语言生成的,这些代码是非受管代码(Unmanaged Code)。非受管代码是不能直接被.Net框架中的程序开发语言直接使用的,即Active X组件不能直接在Visual C#和Visual Basic .Net等程序中使用。而要经过一定的转换,把非受管代码转换成受管代码,这样才可以使用。本文就来探讨一下Visual C#中如何进行Active X组件编程。本文将按照以下顺序来介绍:首先利用Visual Basic 6.0构建一个Active X组件,然后转换这个Active X组件成可以被Visual C#使用的组件,最后在Visual C#中使用此组件。
一. 程序设计和运行的基本环境:
(1).视窗2000服务器版
(2)..Net FrameWork SDK Beta 2 版
(3).Visual Basic 6.0
二.用Visual Basic 6.0构建一个Active X组件:
(1).首先打开Visual Basic 6.0,在"文件"菜单中,选择"新建工程",出现一个对话框,在对话框中选择"Active X DLL",具体如下图:
 
 
图1:新建Active X组件
 
(2).然后在"工具"菜单中,选择"添加过程",命名过程名称为"show"。具体如下图:
 
 
图2:在组件中创建一个过程
 
然后在此过程中加入以下代码:
Public Function show ( )
MsgBox ( "这是在Visual C#中使用Active X!" )
End Function
(3).接着选择"工程"菜单中的"工程属性",把刚才新建的过程重命名为"mydll"。如下图所示:
 
 
图3:改变工程文件名称
 
(4).生成Active X组件,产生"mydll.dll"文件:
这时选择"文件"菜单中的"生成mydll.dll",就会在指定的目录下面产生一个Active X组件,名称为"mydll.dll"。具体操作如下图:
 
 
图4:产生Active X组件
 
此Active X组件的内容十分简单,只有一个show()方法。
三.把非受管代码的Active X组件转换成受管代码的类库:
.Net框架提供了一个转换的程序,通过此程序可以把非受管代码转换为受管代码,这个程序的名称为"tlbimp.exe"。通过下面的命令就可以完成转换:
tlbimp mydll.dll /out:my.dll
此时会产生一个"my.dll"的类库,这个类库中定义了一个命名空间为"my",在此命名空间中定义了一个类"Class1",这个类有一个方法叫"show"的方法。
四.使用编译成受管代码的Active X组件:
其实这样说已经不十分准确了,因为Active X组件不可能是受管代码,此时的Active X组件已经变成了可以被.Net开发语言使用的类库了。这样说主要是为了更方便理解。有了上面的介绍,可以较方面的完成下列的程序。
using my ;
using System ; 
public class Activex 
{
public static void Main ( ) 
{
Class1 s = new Class1 ( ) ;
s.show ( ) ;
}
}
下图是编译后程序的运行界面:
 
 
图5:程序运行界面
 
五.总结:
Actvie X组件是一个庞大的家族,拥有很多功能强大的组件,充分利用这些Active X组件资源,不仅是对以前的资源的利用,而且有时也能够解决许多依靠现有的技术,不便于或者难以解决的问题。其实Active X组件在Visual C#中的使用方法和其在Visual Basic .Net中,以及其他.Net开发语言使用的方法是大同小异的。

 
Photo 1 of 5