今天先休息一下,明天发照片。
自助行改为自驾游
真是计划没有变化快。
本来约好同行的两位突然告知不能同行,我们只有去退了火车票,改成我们自驾车出游,当然费用也要省下不少,时间也好安排。
在山溪谷戚老师的指导下配置了一些装备,这次准备还不充分,等回来再补充吧。
相关文章:
明天去广西桂林自助游
预计一个星期左右。
相关文章:
24 season 5 (13:00 -15:00) Episode 7,8

1:00 P.M.
Cummings explains to Jack and Logan that a man named Nathanson recruited him. They were acting as patriots to secure oil interests. Chloe tracks Nathanson but his line has been disconnected. The terrorists are now operating on their own. Jack believes Cummings’ story.
1:04 P.M.
Logan doesn’t know what to do next, but Jack verifies that CTU will take over. If Jack needs to disappear again, he doesn’t want his daughter to know that he is still alive. Logan asks Jack to serve the office of the President even though he is not Palmer. He wants to reinstate Jack, regardless of what political ramifications will occur. Their urgent need is to recover the nerve gas before millions are killed.
1:05 P.M.
One of Erwich’s men cannot figure out how to arm the canisters after Schaeffer locked them. He urges Erwich to forget about retaliation against the Americans because their true enemy is the Russians. Erwich warns him not to disobey orders.
1:06 P.M.
McGill’s sister Jenny calls him from a payphone asking for $500. He is annoyed, but she swears she is not using drugs anymore. McGill won’t let her come to CTU to get the money because he is embarrassed of her. Jenny threatens to hurt herself, so McGill agrees to meet her in a parking lot across from the office.
1:08 P.M.
CTU learns that the canisters are useless without the detonation codes.
Erwich gets a call from a man named Jacob Rossler, who has been asked to help him reconfigure the activation device. Rossler needs Erwich to cut into the canisters in order to get the identification keys so that he can set up new remote triggers.
1:09 P.M.
Buchanan lets Audrey know that President Logan has reinstated Jack with full autonomy and access. Chloe gets a tap on Erwich’s phone and she plays back his conversation with Rossler. Edgar traces the call to a landline in downtown Los Angeles.
1:10 P.M.
Buchanan calls Jack, who is in a helicopter, with the location of Rossler. Rossler is a software programmer who was indicted for high security code infiltration. Jack asks for schematics of the building and for Curtis to meet him there with a small team.
1:11 P.M.
Chloe alerts Buchanan that the security system in Rossler’s building uses a proprietary technology that is hard to hack into. She wants Spenser to help because he has more experience with it than her and Edgar combined. Edgar thinks this is a bad idea because Spenser had been spying on them. Buchanan relents on the condition that Spenser wears a security wristband.
Jack calls Audrey. He asks her to call his daughter Kim and bring her to CTU. He wants to tell her that he is alive in person.
1:12 P.M.
Erwich enters an auto body shop and threatens to kill a mechanic unless he helps them cut some metal.
1:18 P.M.
Spenser is brought of out holding. He tries to thank Chloe for having him reinstated, but she says that she only needs his help with hacking. She does not forgive him.
Although Martha is still agitated with her husband, she doesn’t want to fight. She thinks he will need her because things are going to get rough for him. She slaps him, cautioning, “Don’t you ever do that to me again.”
1:19 P.M.
Novick comes in, and Logan apologizes to both him and Martha. The President wants to discuss the Cummings problem with Martha present. Novick thinks they can cover up Cummings’ involvement in Palmer’s murder to protect the credibility of the office of the President. Martha is appalled, and she insists that the American public doesn’t like being lied to. Logan agrees and asks her to help draw up a press statement for him.
1:21 P.M.
With the mechanic ready, Rossler gives Erwich instructions on how to cut the canister.
1:22 P.M.
Curtis and his team meet Jack’s chopper on the roof of Rossler’s building. Chloe radios to Jack as Spenser monitors the men guarding Rossler. Jack wants Spenser to shut down the security cameras so that he can enter, but the cameras can only be disrupted for thirty seconds because they will reboot themselves.
1:24 P.M.
Erwich gives the mechanic directions to cut the metal but doesn’t tell him what is inside when the man questions him. Erwich recalls how his wife is in a prison and his son is being held in a state-run facility. “This is about justice, about ending tyranny,” Erwich says, explaining that the government is his enemy. The mechanic begins slicing the canister.
1:32 P.M.
The mechanic opens the canister so that Erwich can make out the LED numbers of the ID keys.
1:33 P.M.
Spenser overrides the security cameras at Rossler’s building so that Jack and Curtis can raid the premises. A guard alerts Rossler that there might be a breach with a warning to remain in his office.
1:34 P.M.
As the elevator reaches the penthouse, Chloe lets Jack know that there are men with automatic weapons on the floor. Jack and Curtis come out firing when the door opens. Curtis is hit in his flak jacket, so Jack proceeds to Rossler’s office.
1:35 P.M.
Chloe tells Jack that Rossler is armed, and Jack quickly shoots him in the leg. Rossler tosses his handgun. Jack orders his backup teams to come in. Chloe says that there is someone else in the penthouse, so Curtis covers Rossler.
1:36 P.M.
Jack finds a young girl with bruises hiding in the bedroom. In her Russian accent, she says that her name is Inessa and Rossler was keeping her captive. Jack approaches her gently and leads her out of the room, promising that he will help her. He tells the medic not to give Rossler any pain medication for his wound.
1:37 P.M.
Jack handles the interrogation while CTU listens in, but Rossler is unyielding. Jack even has Curtis step on Rossler’s wound. Rossler demands full immunity, his assets sent to a foreign country and Inessa to be given to him. Jack says no, and he has Curtis inflict pain on the man some more.
1:38 P.M.
McGill radios Jack and orders him to accept the deal. McGill tells Buchanan to make sure that Jack follows the plan while he steps out.
1:39 P.M.
Jack begrudgingly tells Rossler that he will be granted his conditions. Rossler wants the agreement signed by the Attorney General. He will only talk when he sees the document.
1:44 P.M.
McGill goes outside and meets up with his sister Jenny. He hands her the number of a doctor who can help her. Suddenly, a guy jumps out and punches McGill, taking his wallet. Jenny apologizes to her brother and leaves with the man.
1:45 P.M.
Chloe is uncomfortable as she directs Spenser to hand his work over to Edgar. Spenser wants to keep working, but Chloe is adamant that his reinstatement is over now that Rossler is in custody. Spenser is escorted out by guards. Edgar tries to console Chloe for doing the right thing. “Shut up, Edgar,” she snaps.
1:46 P.M.
Rossler tells Jack that he only first talked to Erwich a half hour ago. He was put in touch with him by a Russian associate who is a commander in the resistance army. Erwich will be calling him back with the identification keys in the canisters so that he can reprogram the trigger with an updated chip. Rossler is going to deliver the chip to him at a location Erwich decides. Rossler won’t turn over the chip to Jack until he sees the paperwork granting his freedom.
1:49 P.M.
Jack calls Buchanan, who says that the Attorney General will be sending the papers in a few minutes. CTU hasn’t found much on Inessa, who is fifteen and was listed as missing from Kiev. Jack tells Rossler that he can’t take Inessa. Rossler calls it a deal breaker.
1:54 P.M.
Martha thinks Logan should take responsibility for Cummings’ actions and offer swift punishment. Logan is happy to work with his wife again. Novick calls and urgently asks the President to meet him in the hallway.
1:55 P.M.
As the Secret Service cuts the strap that Cummings has hanged himself with, Logan and Martha enter, horrified.
1:56 P.M.
Jack calls Audrey to find out if she contacted Kim. Audrey promises to try her again.
1:57 P.M.
Inessa asks Jack when she can return home. He carefully explains that he needs her to go with Rossler, but that agents will follow her and take her away from him. Jack swears that he won’t let anything happen to her, but Inessa doesn’t trust him. “You are no different from Rossler,” she says.
1:58 P.M.
After the mechanic finishes cutting open the last canister, Erwich calmly shoots him. One of the men inputs all the codes into a PDA. Erwich orders them to load all the canisters back into the truck. Erwich calls Rossler’s number from his sat phone. Jack has Rossler answer, and Erwich sends him the codes over text message. Rossler says he can have the chip ready in ten minutes. Erwich will call him back with a location.
1:59 P.M.
Jack has Rossler wired, and he takes Inessa to leave. Suddenly, Inessa pulls a gun from her shirt and plugs Rossler with two shots. Jack lunges for the gun, but Rossler is already dead. With Erwich calling back shortly, they have no other link to the nerve gas.
相关文章:
昆明西华园
同学论坛重新开放
为了方便极少数同学使用,同学论坛重新开放,论坛程序也升级到 PHPWind_GBK_4.3.2 免费版。
知道的就去看看吧,地址就不公开了。
相关文章:
[转贴]元宵节相关
元宵节简介
每年农历的正月十五日,春节刚过,迎来的就是中国的传统节日–元宵节。
正月是农历的元月,古人称夜为“宵”,所以称正月十五为元宵节。正月十五日是一年中第一个月圆之夜,也是一元复始,大地回春的夜晚,人们对此加以庆祝,也是庆贺新春的延续。元宵节又称为“上元节”。
按中国民间的传统,在这天上皓月高悬的夜晚,人们要点起彩灯万盏,以示庆贺。出门赏月、燃灯放焰、喜猜灯谜、共吃元宵,合家团聚、同庆佳节,其乐融融。
元宵节也称灯节,元宵燃灯的风俗起自汉朝,到了唐代,赏灯活动更加兴盛,皇宫里、街道上处处挂灯,还要建立高大的灯轮、灯楼和灯树,唐朝大诗人卢照邻曾在《十五夜观灯》中这样描述元宵节燃灯的盛况“接汉疑星落,依楼似月悬。”
宋代更重视元宵节,赏灯活动更加热闹,赏灯活动要进行5天,灯的样式也更丰富。明代要连续赏灯10天,这是中国最长的灯节了。清代赏灯活动虽然只有3天,但是赏灯活动规模很大,盛况空前,除燃灯之外,还放烟花助兴。
“猜灯谜”又叫“打灯谜”,是元宵节后增的一项活动,出现在宋朝。南宋时,首都临安每逢元宵节时制迷,猜谜的人众多。开始时是好事者把谜语写在纸条上,贴在五光十色的彩灯上供人猜。因为谜语能启迪智慧又饶有兴趣,所以流传过程中深受社会各阶层的欢迎。
民间过元宵节吃元宵的习俗。元宵由糯米制成,或实心,或带馅。馅有豆沙、白糖、山楂、各类果料等,食用时煮、煎、蒸、炸皆可。起初,人们把这种食物叫“浮圆子”,后来又叫“汤团”或“汤圆”,这些名称“团圆”字音相近,取团圆之意,象征全家人团团圆圆,和睦幸福,人们也以此怀念离别的亲人,寄托了对未来生活的美好愿望。
一些地方的元宵节还有“走百病”的习俗,又称“烤百病”“散百病”,参与者多为妇女,他们结伴而行或走墙边,或过桥,走郊外,目的是驱病除灾。
随着时间的推移,元宵节的活动越来越多,不少地方节庆时增加了耍龙灯、耍狮子、踩高跷、划旱船扭秧歌、打太平鼓等传统民俗表演。这个传承已有两千多年的传统节日,不仅盛行于海峡两岸,就是在海外华人的聚居区也年年欢庆不衰。
元宵节的由来与传说
元宵节是中国的传统节日,早在2000多年前的西汉就有了,元宵赏灯始于东汉明帝时期,明帝提倡佛教,听说佛教有正月十五日僧人观佛舍利,点灯敬佛的做法,就命令这一天夜晚在皇宫和寺庙里点灯敬佛,令士族庶民都挂灯。以后这种佛教礼仪节日逐渐形成民间盛大的节日。该节经历了由宫廷到民间,由中原到全国的发展过程。
在汉文帝时,已下令将正月十五定为元宵节。汉武帝时,“太一神”的祭祀活动定在正月十五。(太一:主宰宇宙一切之神)。司马迁创建“太初历”时,就已将元宵节确定为重大节日。
另有一说是元宵燃灯的习俗起源于道教的“三元说”;正月十五日为上元节,七月十五日为中元节,十月十五日为下元节。主管上、中、下三元的分别为天、地、人三官,天官喜乐,故上元节要燃灯。
元宵节的节期与节俗活动,是随历史的发展而延长、扩展的。就节期长短而言,汉代才一天,到唐代已为三天,宋代则长达五天,明代更是自初八点灯,一直到正月十七的夜里才落灯,整整十天。与春节相接,白昼为市,热闹非凡,夜间燃灯,蔚为壮观。特别是那精巧、多彩的灯火,更使其成为春节期间娱乐活动的高潮。至清代,又增加了舞龙、舞狮、跑旱船、踩高跷、扭秧歌等“百戏”内容,只是节期缩短为四到五天。
关于元宵节的来历,民间还有几种有趣的传说:
关于灯的传说
传说在很久以前,凶禽猛兽很多,四处伤害人和牲畜,人们就组织起来去打它们, 有一只神鸟困为迷路而降落人间,却意外的被不知情的猎人给射死了。天帝知道后十分震怒,立即传旨,下令让天兵于正月十五日到人间放火,把人间的人畜财产通通烧死。 天帝的女儿心地善良,不忍心看百姓无辜受难,就冒着生命的危险,偷偷驾着祥云来到人间,把这个消息告诉了人们。众人听说了这个消息,有如头上响了一个焦雷。吓得不知如何是好,过了好久,才有个老人家想出个法子,他说:「在正月十四、十五、十六日这三天,每户人家都在家里张灯结彩、点响爆竹、 燃放烟火。这样一来,天帝就会以为人们都被烧死了」。
大家听了都点头称 是,便分头准备去了。到了正月十五这天晚上,天帝往下一看,发觉人间一片红光,响声震天,连续三个夜晚都是如此,以为是大火燃烧的火焰,以中大快。人们就这样保住了自己的生命及财产。为了纪念这次成功,从此每到正月十五,家家户户都悬挂灯笼,放烟火来纪念这个日子。
汉文帝时为纪念“平吕”而设
另一个传说是元宵节是汉文帝时为纪念“平吕”而设。汉高祖刘邦死后,吕后之子刘盈登基为汉惠帝。惠帝生性懦弱,优柔寡断,大权渐渐落再吕后手中.汉惠帝病死后吕后独揽朝政把刘氏天下变成了吕氏天下,朝中老臣,刘氏宗室深感愤慨,但都惧怕吕后残暴而敢怒不敢言.
吕后病死后,诸吕惶惶不安害怕遭到伤害和排挤。于是,在上将军吕禄家中秘密集合,共谋作乱之事,以便彻底夺取刘氏江山。
此事传至刘氏宗室齐王刘囊耳中,刘囊为保刘氏江山,决定起兵讨伐诸吕随后与开国老臣周勃,陈平取得联系,设计解除了吕禄,“诸吕之乱”终于被彻底平定.
平乱之后,众臣拥立刘邦的第二个儿子刘恒登基,称汉文帝.文帝深感太平盛世来之不易,便把平息“诸吕之乱”的正月十五,定为与民同乐日,京城里家家张灯结彩,以示庆祝。从此,正月十五便成了一个普天同庆的民间节日——“闹元宵”。
东方朔与元宵姑娘
这一则传说与吃元宵的习俗有关:相传汉武帝有个宠臣名叫东方朔,他善良又风趣。有一天冬天,下了几天大雪 ,东方朔就到御花园去给武帝折梅花。刚进园门,就发现有个宫女泪流满面准备投井。东方朔慌忙上前搭救,并问明她要自杀的原因。原来,这个宫女名叫元宵,家里还有双亲及一个妹妹。自从她进宫以后,就再也无缘和家人见面。每年到了腊尽春来的时节,就比平常更加的思念家人。觉得不能在双亲跟前尽孝,不如一死了之。东方朔听了她的遭遇,深感同情,就向她保证,一定设法让她和家人团聚。
一天,东方朔出宫在长安街上摆了一个占卜摊。不少人都争着向他占卜求卦。不料,每个人所占所求,都是“正月十六火焚身”的签语。一时之间,长安里起了很大恐慌。人们纷纷求问解灾的办法。东方朔就说:“正月十三日傍晚,火神君会派一位赤衣神女下凡查访,她就是奉旨烧长安的使者,我把抄录的偈语给你们,可让当今天子想想办法。”说完,便扔下一张红帖,扬长而去。老百姓拿起红帖,赶紧送 到皇宫去禀报皇上。
汉武帝接过来一看,只见上面写着:“长安在劫,火焚帝阙,十五天火,焰红宵夜”,他心中大惊,连忙请来了足智多谋的东方朔。东方朔假意的想了一想,就说:“听说火神君最爱吃汤圆,宫中的元宵不是经常给你做汤圆吗?十五晚上可让元宵做好汤圆。万岁焚香上供,传令京都家家都做汤圆,一齐敬奉火神君。再传谕臣民一起在十五晚上挂灯,满城点鞭炮、放烟火,好像满城大火,这样就可以瞒过玉帝了。 此外,通知城外百姓,十五晚上进城观灯,杂在人群中消灾解难”。武帝听后,十分高兴,就传旨照东方朔的办法去做。
到了正月十五日长安城里张灯结彩,游人熙来攘往,热闹非常。宫女元宵的父母也带着妹妹进城观灯。当他们看到写有“元宵”字样的大宫灯时,惊喜的高喊:“元宵!元宵!”, 元宵听到喊声,终于和家里的亲人团聚了。
如此热闹了一夜,长安城果然平安无事。汉武帝大喜,便下令以后每到正月十五都做汤圆供火神君,正月十五照样全城挂灯放烟火。因为元宵做的汤圆最好,人们就把汤圆叫元宵,这天叫做元宵节。
元宵节习俗
元宵节是中国的传统节日,所以全国各地都过,大部分地区的习俗是差不多的,但各地也还是有自己的特点。
吃元宵
正月十五吃元宵,“元宵”作为食品,在我国也由来已久。宋代,民间即流行一种元宵节吃 的新奇食品。这种食品,最早叫“ 浮元子”后称“元宵” ,生意人还美其名曰“元宝” 。元宵即”汤圆”以白糖、玫瑰、芝麻、豆沙、黄桂、核桃仁、果仁、枣泥等为馅,用糯米粉包成圆形,可荤可素,风味各异。可汤煮、油炸、蒸食,有团圆美满之意。陕西的汤圆不是包的,而是在糯米粉中”滚”成的,或煮司或油炸,热热火火,团团圆圆。
观 灯
汉明帝永平年间(公元58–75),因明帝提倡佛法,适逢蔡愔从印度求得佛法归来,称印度摩喝陀国每逢正月十五,僧众云集瞻仰佛舍利,是参佛的吉日良辰。汉明帝为了弘扬佛法,下令正月十五夜在宫中和寺院”燃灯表佛”。此后,元宵放灯的习俗就由原来只在宫廷中举行而流传到民间。即每到正月十五,无论士族还是庶民都要挂灯,城乡通宵灯火辉煌。
元宵放灯的习俗,在唐代发展成为盛况空前的灯市,当时的京城长安已是拥有百万人口的世界最大都市,社会富庶。在皇帝的亲自倡导下,元宵灯节办得越来越豪华。中唐以后,已发展成为全民性的狂欢节。唐玄宗(公元685–762)时的开元盛世,长安的灯市规模很大,燃灯五万盏,花灯花样繁多,皇帝命人做巨型的灯楼,广达20间,高150尺,金光璀璨,极为壮观。
宋代,元宵灯会无论在规模和灯饰的奇幻精美都胜过唐代,而且活动更为民间化,民族特色更强。以后历代的元宵灯会不断发展,灯节的时间也越来越长,。唐代的灯会是”上元前后各一日”,宋代又在十六之后加了两日,明代则延长到由初八到十八整整十天。
到了清代,满族入主中原,宫廷不再办灯会,民间的灯会却仍然壮观。日期缩短为五天,一直延续到今天。
灯在台湾民间具有光明与添丁的涵义,点燃灯火有照亮前程之意,且台语灯与丁谐音代表生男孩,因此往昔元宵节妇女都会刻意在灯下游走,希望“钻灯脚生卵葩”(就是钻到灯下游走,好生男孩)。
中国的情人节
元宵节也是一个浪漫的节日,元宵灯会在封建的传统社会中,也给未婚男女相识提供了一个机会,传统社会的年轻女孩不允许出外自由活动,但是过节却可以结伴出来游玩,元宵节赏花灯正好是一个交谊的机会,未婚男女借着赏花灯也顺便可以为自己物色对象。元宵灯节期间,又是男女青年与情人相会的时机。
在台湾,还有未婚女性在元宵夜偷摘葱或菜将会嫁到好丈夫的传统习俗,俗称:“偷挽葱,嫁好尪”、“偷挽菜,嫁好婿”,希望婚姻美满的女孩,要在元宵之夜到菜园里偷摘葱或青菜,期待未来家庭幸福,唐代的灯市还出现乐舞百戏表演,成千上万的宫女,民间少女在灯火下载歌载舞,叫做行歌、踏歌。
欧阳修(生查子)云:去年元夜时,花市灯如书;月上柳梢头,人约黄昏后。 辛弃疾(青玉案)写道:众里寻它千百度,暮然回首,那人却在灯火阑珊处。就是描述元宵夜的情境,而传统戏曲陈三和五娘是在元宵节赏花灯进相遇而一见钟情,乐昌公文与徐德言在元宵夜破镜重圆,《春灯谜》中宇文彦和影娘在元宵订情。所以说元宵节也是中国的“情人节”。
走百病
元宵节除了庆祝活动外,还有信仰性的活动。那就是”走百病”又称”烤百病”"散百病”参与者多为妇女,他们结伴而行或走墙边,或过桥过走郊外,目的是驱病除灾。
随着时间的推移,元宵节的活动越来越多,不少地方节庆时增加了耍龙灯、耍狮子、踩高跷、划旱船扭秧歌、打太平鼓等活动。
正月十五元宵节还有一些鲜为人知已经失传的民间活动,这里列举二三。
祭门、祭户古代有“七祭”,这是其中的两种。祭祀的方法很简单,把杨树支插在门户上方,在盛有豆粥的碗里插上一双筷子,或者直接将酒肉放在门前。
逐鼠
这项活动主要是对养蚕人家所说的。因为老鼠常在夜里把蚕大片大片地吃掉,人们听说正月十五用米粥喂老鼠,它就可以不吃蚕了。于是,这些人家在正月十五熬上一大锅粘糊糊的粥,有的还在上面盖上一层肉,将粥用碗盛好,放到老鼠出没的顶棚、墙角、边放嘴里还边念念有词,诅咒老鼠再吃蚕宝宝就不得好死。
迎紫姑
紫姑是民间传说中一个善良、贫穷的姑娘。正月十五,紫姑因穷困而死。百姓们同情她、怀念她、有些地方便出现了“正月十五迎紫姑”的风俗。每到这一天夜晚,人们用稻草、布头等扎成真人大小的紫姑肖像。妇女们纷纷站到紫姑常做活的厕所、猪圈和厨房旁边迎接她,象对待亲姐妹一样,拉着她的手,跟她说着贴心话,流着眼泪安慰她,情景十分生动,真实的反映了劳苦民众善良、忠厚、同情弱者的思想感情。
相关文章:
同学论坛停了
二○○四年同学聚会建立的同学论坛,因为平时访问量太少,已经关闭了。
相关文章:
delphi函数参考手册
名称 类型 说明
abort 函数 引起放弃的意外处理
abs 函数 绝对值函数
addexitproc 函数 将一过程添加到运行时库的结束过程表中
addr 函数 返回指定对象的地址
adjustlinebreaks 函数 将给定字符串的行分隔符调整为cr/lf序列
align 属性 使控件位于窗口某部分
alignment 属性 控件标签的文字位置
allocmem 函数 在堆栈上分配给定大小的块
allowgrayed 属性 允许一个灰度选择
ansicomparestr 函数 比较字符串(区分大小写)
ansicomparetext 函数 比较字符串(不区分大小写)
ansilowercase 函数 将字符转换为小写
ansiuppercase 函数 将字符转换为大写
append 函数 以附加的方式打开已有的文件
arctan 函数 余切函数
assignfile 函数 给文件变量赋一外部文件名
assigned 函数 测试函数或过程变量是否为空
autosize 属性 自动控制标签的大小
backgrounddi2001.jpg 属性 背景色
beginthread 函数 以适当的方式建立用于内存管理的线程
bevelinner 属性 控件方框的内框方式
bevelouter 属性 控件方框的外框方式
bevelwidth 属性 控件方框的外框宽度
blockread 函数 读一个或多个记录到变量中
blockwrite 函数 从变量中写一个或多个记录
borderstyle 属性 边界类型
borderwidth 属性 边界宽度
break 命令 终止for、while、repeat循环语句
brush 属性 画刷
caption 属性 标签文字的内容
changefileext 函数 改变文件的后缀
chdir 函数 改变当前目录
checked 属性 确定复选框选中状态
chr 函数 返回指定序数的字符
closefile 命令 关闭打开的文件
color 属性 标签的颜色
columns 属性 显示的列数
comparestr 函数 比较字符串(区分大小写)
concat 函数 合并字符串
continue 命令 继续for、while、repeat的下一个循环
copy 函数 返回一字符串的子串
cos 函数 余弦函数
ctl3d 属性 是否具有3d效果
cursor 属性 鼠标指针移入后的形状
date 函数 返回当前的日期
datetimetofiledate 函数 将delphi的日期格式转换为dos的日期格式
datetimetostr 函数 将日期时间格式转换为字符串
datetimetostring 函数 将日期时间格式转换为字符串
datetostr 函数 将日期格式转换为字符串
dayofweek 函数 返回星期的数值
dec 函数 递减变量值
decodedate 函数 将日期格式分解为年月日
decodetime 函数 将时间格式分解为时、分、秒、毫秒
delete 函数 从字符串中删除子串
deletefile 命令 删除文件
diskfree 函数 返回剩余磁盘空间的大小
disksize 函数 返回指定磁盘的容量
dispose 函数 释放动态变量所占的空间
disposestr 函数 释放字符串在堆栈中的内存空间
ditherbackgrounddi2001.jpg?使背景色的色彩加重或减少50%
dragcursor 属性 当鼠标按下时光标的形状
dragmode 属性 按动的作用方式
dropdowncount 属性 容许的显示数据项的数目
editmask 属性 编辑模式
enabled 属性 是否使标签呈现打开状态
encodedate 函数 将年月日合成为日期格式
encodetime 函数 将时、分、秒、毫秒合成为时间格式
endmargin 属性 末尾边缘
eof 函数 对有类型或无类型文件测试是否到文件尾
eoln 函数 返回文本文件的行结束状态
erase 命令 删除外部文件
exceptaddr 函数 返回引起当前意外的地址
exclude 函数 从集合中删除一些元素
exceptobject 函数 返回当前意外的索引
exit 命令 立即从当前的语句块中退出
exp 函数 指数函数
expandfilename 函数 返回包含绝对路径的字符串
extendedselect 属性 是否允许存在选择模式,true时,multiselect才有意义
extractfiledir 函数 返回驱动器和路径
extractfileext 函数 返回文件的后缀
extractfilename 函数 返回文件名
extractfilepath 函数 返回指定文件的路径
fileage 函数 返回文件已存在的时间
fileclose 命令 关闭指定的文件
filecreate 命令 用指定的文件名建立新文件
filedatetodatetime 函数 将dos的日期格式转换为delphi的日期格式
fileexists 函数 检查文件是否存在
filegatattr 函数 返回文件的属性
filegetdate 函数 返回文件的dos日期时间标记
fileopen 命令 用指定的存取模式打开指定的文件
filepos 函数 返回文件的当前指针位置
fileread 命令 从指定的文件读取
filesearch 命令 在目录中搜索指定的文件
fileseek 函数 改变文件的指针
filesetattr 函数 设置文件属性
filesetdate 函数 设置文件的dos日期时间标记
filesize 函数 返回当前文件的大小
filewrite 函数 对指定的文件做写操作
fillchar 函数 用指定的值填充连续字节的数
findclose 命令 终止findfirst/findnext序列
findfirst 命令 对指定的文件名及属性搜索目录
findnext 命令 返回与文件名及属性匹配的下一入口
floattodecimal 函数 将浮点数转换为十进制数
floattostrf 函数 将浮点数转换为字符串
floattostr 函数 将浮点数转换为字符串
floattotext 函数 将给定的浮点数转换为十进制数
floattotextfmt 函数 将给定的浮点数转换为十进制数
flush 函数 将缓冲区的内容刷新到输出的文本文件中
fmtloadstr 函数 从程序的资源字符串表中装载字符串
fmtstr 函数 格式化一系列的参数,其结果以参数result返回
font 属性 设置字体
format 函数 格式化一系列的参数并返回pascal字符串
formatbuf 函数 格式化一系列的参数
formatdatetime 函数 用指定的格式来格式化日期和时间
formatfloat 函数 指定浮点数格式
frac 函数 返回参数的小数部分
freemem 函数 按给定大小释放动态变量所占的空间
getdir 返回指定驱动器的当前目录
getheapstatus 返回内存管理器的当前状态
getmem 建立一指定大小的动态变量,并将指针指向该处
getmemorymanager 返回内存管理器的入口点
glyph 函数 按钮上的图象
halt 停止程序的执行并返回到操作系统
hi 返回参数的高地址位
high 返回参数的上限值
hint 属性 提示信息
int 返回参数的整数部分
include 添加元素到集合中
insert 在字符串中插入子串
inttohex 将整型数转换为十六进制数
inttostr 将整型数转换为字符串
ioresult 返回最新的i/o操作完成状态
isvalidident 测试字符串是否为有效的标识符
items 属性 默认显示的节点
kind 属性 摆放样式
largechange 属性 最大改变值
layout 属性 图象布局
length 函数 返回字符串的动态长度
lines 属性 缺省显示内容
ln 函数 自然对数函数
lo 函数 返回参数的低地址位
loadstr 函数 从应用程序的可执行文件中装载字符资源
lowercase 函数 将给定的字符串变为小写
low 函数 返回参数的下限值
max 属性 最大值
maxlength 属性 最大长度
min 属性 最小值
mkdir 命令 建立一子目录
move 函数 从源到目标复制字节
multiselect 属性 允许同时选择几个数据项
name 属性 控件的名字
new 函数 建立新的动态变量并设置一指针变量指向他
newstr 函数 在堆栈上分配新的字符串
now 函数 返回当前的日期和时间
odd 测试参数是否为奇数
onactivate 事件 焦点移到窗体上时触发
onclick 事件 单击窗体空白区域触发
ondblclick 事件 双击窗体空白区域触发
onclosequery 事件 使用者试图关闭窗体触发
onclose 事件 窗体关闭后才触发
oncreate 事件 窗体第一次创建时触发
ondeactivate 事件 用户切换到另一应用程序触发
ondragdrop 事件 鼠标拖放操作结束时触发
ondragover 事件 有其他控件从他上面移过触发
onmousedown 事件 按下鼠标键时触发
onmouseup 事件 释放鼠标键时触发
onmousemove 事件 移动鼠标时触发
onhide 事件 隐藏窗体时触发
onkeydown 事件 按下键盘某键时触发
onkeypress 事件 按下键盘上的单个字符键时触发
onkeyup 事件 释放键盘上的某键时触发
onpaint 事件 窗体上有新部分暴露出来触发
onresize 事件 重新调整窗体大小触发
onshow 事件 在窗体实际显示之前瞬间触发
ord 返回序数类的序数
outlinestyle 属性 类型
outofmemoryerror 引起outofmemory意外
pageindex 属性 页索引
pages 属性 页
paramcount 函数 返回在命令行上传递给程序的参数数量
paramstr 函数 返回指定的命令行参数
pen 属性 画刷设置
pi 函数 返回圆周率pi
picture 属性 显示图象
pictureclosed 属性 设置closed位图
pictureleaf 属性 设置leaf位图
pictureminus 属性 设置minus位图
pictureopen 属性 设置open位图
pictureplus 属性 设置plus位图
pos 函数 在字符串中搜索子串
pred 函数 返回先前的参数
random 函数 返回一随机函数
randomize 函数 用一随机数初始化内置的随机数生成器
read 函数 对有格式的文件,读一文件组件到变量中;
对文本文件,读一个或多个值到一个或多个变量中
readln 函数 执行read过程,然后跳到文件下一行
readonly 属性 只读属性
reallocmem 函数 分配一动态变量
rename 函数 重命名外部文件
renamefile 函数 对文件重命名
reset 函数 打开已有的文件
rewrite 函数 建立并打开一新的文件
rmdir 函数 删除空的子目录
round 函数 将实数值舍入为整型值
runerror 函数 停止程序的执行
scrollbars 属性 滚动条状态
seek 函数 将文件的当前指针移动到指定的组件上
seekeof 函数 返回文件的文件结束状态
seekeoln 函数 返回文件的行结束状态
selectedcolor 属性 选中颜色
setmemorymanager 函数 设置内存管理器的入口点
settextbuf 函数 给文本文件指定i/o缓冲区
shape 属性 显示的形状
showexception 函数 显示意外消息与地址
sin 函数 正弦函数
sizeof 函数 返回参数所占的字节数
smallchange 属性 最小改变值
sorted 属性 是否允许排序
sqr 函数 平方函数
sqrt 函数 平方根函数
startmargin 属性 开始边缘
state 属性 控件当前状态
str 函数 将数值转换为字符串
stralloc 函数 给以null结束的字符串分配最大长度-1的缓冲区
strbufsize 函数 返回存储在由stralloc分配的字符缓冲区的最大字符数
strcat 函数 将一字符串附加到另一字符串尾并返回合并的字符串
strcomp 函数 比较两个字符串
strcopy 函数 将一个字符串复制到另一个字符串中
strdispose 函数 释放堆栈上的字符串
strecopy 函数 将一字符串复制到另一个字符串并返回结果字符串尾部的指针
strend 函数 返回指向字符串尾部的指针
stretch 属性 自动适应控件的大小
strfmt 函数 格式化一系列的参数
stricomp 函数 比较两个字符串(不区分大小写)
stringtowidechar 函数 将ansi字符串转换为unicode字符串
strlcat 函数 将一字符串中的字符附加到另一字符串尾并返回合并的字符串
strlcomp 函数 以最大长度比较两个字符串
strlcopy 函数 将一个字符串中的字符复制到另一个字符串中
strlen 函数 返回字符串中的字符数
strlfmt 函数 格式化一系列的参数,其结果中包含有指向目标缓冲区的指针
strlicomp 函数 以最大长度比较两个字符串(不区分大小写)
strlower 函数 将字符串中的字符转换为小写
strmove 函数 将一个字符串中的字符复制到另一个字符串中
strnew 函数 在堆栈上分配一个字符串
strpas 函数 将以null结束的字符串转换为pascal类的字符串
strpcopy 函数 将pascal类的字符串复制为以null结束的字符串
strplcopy 函数 从pascal类的最大长度字符串复制为以null结束的字符串
strpos 函数 返回一个字符串在另一个字符串中首次出现指针
strrscan 函数 返回字符串中最后出现字符的指针
strscan 函数 返回字符串中出现首字符的指针
strtodate 函数 将字符串转换为日期格式
strtodatetime 函数 将字符串转换为日期/时间格式
strtofloat 函数 将给定的字符串转换为浮点数
strtoint 函数 将字符串转换为整型
strtointdef 函数 将字符串转换为整型或默认值
strtotime 函数 将字符串转换为时间格式
strupper 函数 将字符串中的字符转换为大写
style 属性 类型选择
suce 函数 返回后继的参数
swap 函数 交换参数的高低地址位
tabs 属性 标记每一项的内容
tabindex 属性 标记索引
text 属性 显示的文本
texttofloat 函数 将字符串(以null结束的格式)转换为浮点数
time 函数 返回当前的时间
timetostr 函数 将时间格式转换为字符串
trim 函数 从给定的字符串中删除前导和尾部的空格及控制字符
trimleft 函数 从给定的字符串中删除首部的空格及控制字符
trimright 函数 从给定的字符串中删除尾部的空格及控制字符
trunc 函数 将实型值截取为整型值
truncate 函数 截去当前文件位置后的内容
unselectedcolor 属性 未选中颜色
upcase 将字符转换为大写
uppercase 将给定的字符串变为大写
val 函数 将字符串转换为整型值
vararraycreate 函数 以给定的界限和维数建立变体数组
vararraydimcount 函数 返回给定变体的维数
vararrayhighbound 函数 返回给定变体数组维数的上界
vararraylock 函数 锁定给定的变体数组
vararraylowbound 函数 返回给定变体数组维数的下界
vararrayof 函数 返回指定变体的数组元素
vararrayredim 函数 通过改变上限来调整变体的大小
vararrayunlock 函数 解锁指定的变体数组
varastype 函数 将变体转换为指定的类型
varcase 函数 将变体转换为指定的类型并保存他
varclear 函数 清除指定的变体
varcopy 函数 将指定的变体复制为指定的变体
varformdatetime 函数 返回包含日期时间的变体
varisarray 函数 测试变体是否为数组
varisempty 函数 测试变体是否为unassigned
varisnull 函数 测试变体是否为null
vartodatetime 函数 将给定的变体转换为日期时间
vartype 函数 将变体转换为指定的类型并保存他
visible 属性 控件的可见性
wantreturns 属性 为true时,按回车键产生一个回车符;
为false时,按下ctrl+enter才产生回车符
write 命令 对有格式的文件,写一变量到文件组件中;
对文本文件,写一个或多个值到文件中
writeln 命令 执行write过程,然后输出一行结束标志
widecharlentostring 函数 将ansi字符串转换为unicode字符串
widecharlentostrwar 函数 将unicode字符串转换为ansi字符串变量
widechartostring 函数 将unicode字符串转换为ansi字符串
widechartostrvar 函数 将unicode字符串转换为ansi字符串变量
相关文章:
Delphi控件杂项
Delphi 插件(Plug-ins)创建、调试与使用应用程序扩展
有没有使用过Adobe Photoshop?如果用过,你就会对插件的概念比较熟悉。
对外行人来说,插件仅仅是从外部提供给应用程序的代码块而已(举个例子来说,在
一个DLL中)。一个插件和一个普通DLL之间的差异在于插件具有扩展父应用程序功能
的能力。例如,Photoshop本身并不具备进行大量的图像处理功能。插件的加入使其获
得了产生诸如模糊、斑点,以及其他所有风格的奇怪效果,而其中任何一项功能都不
是父应用程序自身所具有的。
对于图像处理程序来说这很不错,可是为什么要花偌大的力气去完成支持插件的商业
应用程序呢?假设,我们举个例子,你的应用程序要产生一些报表。你的客户肯定会
一直要求更新或者增加新的报表。你可以使用一个诸如Report Smith的外部报表生成
器,这是个不怎么样的解决方案,需要发布附加的文件,要对用户进行额外的培训,
等等。你也可以使用QuickReport,不过这会使你身处版本控制的噩梦之中——如果每
改变一次字体你就要Rebuild你的应用程序的话。
然而,只要你把报表做到插件中,你就可以使用它。需要一个新的报表吗?
没问题,只要安装一个DLL,下次应用程序启动时就会看见它了。另外一个例子是处理
来自外部设备(比如条形码扫描器)的数据的应用程序,为了给用户更多的选择,你
不得不支持半打的各种设备。通过将每种设备接口处理例程写成插件,不用对父应用
程序作任何变动就可以获得最大程度的可伸缩性。
入门
在开始写代码之前最重要的事情就是搞清楚你的应用程序到底需要扩展哪些功
能。这是因为插件是通过一个特定的接口与父应用程序交互的,而这个接口将根据你的
需要来定义。在本文中,我们将建立3个插件,以便展示插件与父应用程序相交互的几
种方式。
我们将把插件制作成DLL。不过,在做这项工作之前,我们得先制作一个外壳程
序来载入和测试它们。图1显示的是加载了第一个插件以后的测试程序。第一个插件没有
完成什么大不了的功能,实际上,它所做的只是返回一个描述自己的字符串。不过,它
证明了很重要的一点——不管有没有插件应用程序都可以正常运行。如果没有插件,它
就不会出现在已安装的插件列表中,但是应用程序仍然可以正常的行使功能。
我们的插件外壳程序与普通应用程序之间的唯一不同就在于工程源文件中出现
在uses子句中的Sharemem单元和加载插件文件的代码。任何在自身与子DLL之间传递字符
串参数的应用程序都需要Sharemem单元,它是DelphiMM.dll(Delphi提供该文件)的接
口。要测试这个外壳,需要将DelphiMM.dll文件从DelphiBin目录复制到path环境变量
所包含的路径或者应用程序所在目录中。发布最终版本时也需要同时分发该文件。
插件通过LoadPlugins过程载入到这个测试外壳中,这个过程在主窗口的
FormCreate事件中调用,见图2。该过程使用FindFirst和FindNext函数在应用程序所在
目录中查找插件文件。找到一个文件以后,就使用图3所示的LoadPlugins过程将其载入。
{ 在应用程序目录下查找插件文件 }
procedure TfrmMain.LoadPlugins;
var
sr: TSearchRec;
path: string;
Found: Integer;
begin
path := ExtractFilePath(Application.Exename);
try
Found := FindFirst(path + cPLUGIN_MASK, 0, sr);
while Found = 0 do begin
LoadPlugin(sr);
Found := FindNext(sr);
end;
finally
FindClose(sr);
end;
end;
{ 加载指定的插件 DLL. }
procedure TfrmMain.LoadPlugin(sr: TSearchRec);
var
Description: string;
LibHandle: Integer;
DescribeProc: TPluginDescribe;
begin
LibHandle := LoadLibrary(Pchar(sr.Name));
if LibHandle <> 0 then
begin
DescribeProc := GetProcAddress(LibHandle,
cPLUGIN_DESCRIBE);
if Assigned(DescribeProc) then
begin
DescribeProc(Description);
memPlugins.Lines.Add(Description);
end
else
begin
MessageDlg('File “' + sr.Name + '” is not a valid plug-in.',
mtInformation, [mbOK], 0);
end;
end
else
MessageDlg('An error occurred loading the plug-in “' +
sr.Name + '”.', mtError, [mbOK], 0);
end;
LoadPlugin方法展示了插件机制的核心。首先,插件被写成DLL。其次,通过
LoadLibrary API它被动态的加载。一旦DLL被加载,我们就需要一个访问它所包含的过
程和函数的途径。API调用GetProcAddress提供这种机制,它返回一个指向所需例程的
指针。在我们这个简单的演示中,插件仅仅包含一个名为DescribePlugin的过程,由常数
cPLUGIN_DESCRIBE指定(过程名的大小写非常重要,传递到GetProcAddress的名称必须
与包含在DLL中的例程名称完全一致)。如果在DLL中没有找到请求的例程,
GetProcAddree将返回nil,这样就允许使用Assigned函数测定返回值。
为了以一种易用的方式存储指向一个函数的指针,有必要为用到的变量创建一个
特定的类型。注意,GetProcAddress的返回值被存储在一个变量中,DescribeProc,属于
TpluginDescribe类型。下面是它的声明:
type
TPluginDescribe = procedure(var Desc: string); stdcall;
由于过程存在于DLL内部,它通过标准调用转换编译所有导出例程,因此需要使
用stdcall指示字。这个过程使用一个var参数,当过程返回的时候它包含插件的描述。
要调用刚刚获得的过程,只需要使用保存地址的变量作为过程名,后面跟上任何
参数。就我们的例子而言,声明:
DescribeProc(Description)
将会调用在插件中获得的描述过程,并且用描述插件功能的字符串填充Description变量。
构造插件
我们已经创建好了父应用程序,现在该轮到创建我们希望加载的插件了。插件
文件是一个标准的Delphi DLL,所以我们从Delphi IDE中创建一个新DLL工程,保存它。
由于导出的插件函数将用到字符串参数,所以要在工程的uses子句中把Sharemen单元放
在最前面。图4列出的就是我们这个简单插件的工程源文件。
uses
Sharemem, SysUtils, Classes,
main in 'main.pas';
{$E plg.}
exports
DescribePlugin;
begin
end.
虽然插件是一个DLL文件,但是没有必要一定要给它一个.DLL的扩展名。实际上,
一个原因就足以让我们有理由改变扩展名:当父应用程序寻找要加载的文件时,新的扩展
名可以作为特定的文件掩模。通过使用别的扩展名(我们的例子使用了*.plg),你可以
在一定程度上确信应用程序只会载入相应的文件。编译指示字$X可以实现这个改变,也可
以通过Project Options对话框的Application页来设置扩展名。
第一个例子插件的代码是很简单的。图5显示了包含在一个新单元中的代码。注
意,DescribePlugin原型与外壳应用程序中的TpluginDescribe类型相一致,使用附加的
export保留字指定该过程将被导出。被导出的过程名称也将会出现在主工程源代码的
exports段中(在图4中列出)。
unit main;
interface
procedure DescribePlugin(var Desc: string);
export; stdcall;
implementation
procedure DescribePlugin(var Desc: string);
begin
Desc := 'Test plugin v1.00';
end;
end.
在测试这个插件之前,要先把它复制到主应用程序的路径下。最简单的办法就是
在主目录的子目录下创建插件,然后把输出路径设置为主路径(Project Options对话框
的Directories/Conditionals也可以作这个设置)。
调试
现在介绍一下Delphi 3中一个较好的功能:从IDE中调试DLL的能力。在DLL工程
中可以通过Run paramaters对话框指定某程序为宿主应用程序,这就是指向将调用DLL的
应用程序的路径(在我们这个例子中,就是刚刚创建的测试外壳程序)。然后你就可以
在DLL代码中设置断点并且按F9运行它——就像在一个普通应用程序中做的那样。Delphi
会运行指定的宿主程序,并且,通过编译带有调试信息的DLL,把你指引到DLL代码内的
断点处。
—
Delphi 插件(Plug-ins)创建、调试与使用应用程序扩展(续)
关键词:Delphi控件杂项
延伸父应用
这个简单的插件不错,不过它不能做什么有用的事情。第二个例子就是纠正这个问题。
这个插件的目标就是在父应用程序的主菜单中加入一个项目。这个菜单项目,当被单击
时,就会执行插件内的一些代码。图6显示外壳程序的改进版,两个插件都已经加载。在
这个版本的外壳程序中,一个名为Plug-in的新菜单项目,被添加到主菜单中。插件会在
运行时加入一个菜单项。
为了实现这个目的,我们必须在插件DLL中定义第二个接口。现有的DLL只导出了一个过
程,DescribePlugin。第二个插件将声明一个叫做InitPlugin的过程。不过,在这个过程
可以在主应用程序中看到以前,必须修改LoadPlugin来配合它。
图7所示的代码展示了改进的过程。
procedure TfrmMain.LoadPlugin(sr: TSearchRec);
var
Description: string;
LibHandle: Integer;
DescribeProc: TPluginDescribe;
InitProc: TPluginInit;
begin
LibHandle := LoadLibrary(Pchar(sr.Name));
if LibHandle <> 0 then
begin
// 查找 DescribePlugin.
DescribeProc := GetProcAddress(LibHandle,
cPLUGIN_DESCRIBE);
if Assigned(DescribeProc) then
begin
// 调用 DescribePlugin.
DescribeProc(Description);
memPlugins.Lines.Add(Description);
// 查找 InitPlugin.
InitProc := GetProcAddress(LibHandle, cPLUGIN_INIT);
if Assigned(InitProc) then
begin
// 调用 InitPlugin.
InitProc(mnuMain);
end;
end
else
begin
MessageDlg('File “' + sr.Name + '” is not a valid plugin.',
mtInformation, [mbOK], 0);
end;
end
else
begin
MessageDlg('An error occurred loading the plugin “' +
sr.Name + '”.', mtInformation, [mbOK], 0);
end;
end;
如你所见,当GetProcAddress第一次查找调用描述过程之后,又调用了一次
GetProcAddress。这一次,我们要寻找的是常量cPLUGIN_INIT,定义如下:
const
cPLUGIN_INIT = 'InitPlugin';
返回值存储在TpluginInit类型的变量中,定义如下:
type
TPluginInit = procedure(ParentMenu: TMainMenu); stdcall;
当InitPlugin方法被执行时,父应用程序的主菜单被当作一个参数传递给它。这个过程
可以按照自己的意愿修改菜单。由于所有GetProcAddress的返回值都用assigned测试,
新版本的LoadPlugin过程仍然会加载不包含InitPlugin过程的第一个插件。在这个过程
中第一次调用寻找DescribePlugin方法会通过,第二次寻找InitPlugin会无响应失败。
现在新的接口已经定义好了,可以为新的InitPlugin方法编写代码了。像原先一样,
新插件的实现代码存在于一个单独的单元中。图8显示了修改过的包含InitPlugin方法
的main.pas。
unit main;
interface
uses Dialogs, Menus;
type
THolder = class
public
procedure ClickHandler(Sender: TObject);
end;
procedure DescribePlugin(var Desc: string);
export; stdcall;
procedure InitPlugin(ParentMenu: TMainMenu);
export; stdcall;
var
Holder: THolder;
implementation
procedure DescribePlugin(var Desc: string);
begin
Desc := 'Test plugin 2 – Menu test';
end;
procedure InitPlugin(ParentMenu: TMainMenu);
var
i: TMenuItem;
begin
// 创建新菜单项.
i := NewItem('Plugin &Test', scNone, False, True,
Holder.ClickHandler, 0, 'mnuTest');
ParentMenu.Items[1].Add(i);
end;
procedure THolder.ClickHandler;
begin
ShowMessage('Clicked!');
end;
initialization
Holder := THolder.Create;
finalization
Holder.Free;
end.
很明显,对原始插件的第一个改变就是增加了InitPlugin过程。像原先一样,带有
export关键字的原型被加入到单元顶端的列表中,过程名也被加入到工程源代码的
exports子句列表中。这个过程使用NewItem函数创建一个新的菜单项,返回值是TmenuItem
对象。新菜单项通过下列语句被加入到应用程序主菜单中:
ParentMenu.Items[1].Add(I);
在测试外壳主菜单上的Items[1]是菜单项Plug-in,所以这个语句在Plugin菜单条上添
加一个叫Plug-in Test的菜单项。
为了处理对新菜单项的响应,作为它的第五个参数,NewItem可以接受一个TNotifyEvent
类型的过程,这个过程将在菜单项被点击时调用。不幸的是,按照定义,这种类型的过程
是一个对象方法,然而在我们的插件中并没有对象。如果我们想用通常的指针来指向函数,
那么得到的将只会是Delphi编译器的抱怨。所以,唯一的解决办法就是创建一个处理菜单
点击的对象。这就是Tholder类的用处。它只有一个方法,是一个叫做ClickHandler的过程。
一个叫做Holder的全局变量,在修改过的main.pas的var段中被声明为Tholder类型,并且
在单元的initialization段中被创建。现在我们就有一个对象了,我们可以拿它的方法
(Holder.ClickHandler)当作NewItem函数的参数。
搞了这一通,ClickHandler除了显示一个“Clicked!”消息对话框以外什么以没干。也
许这不怎么有趣,不过它仍然证明了一点:插件DLL成功的修改了父应用的主菜单,表现
了它的新用途。并且如同第一个例子一样,不管这个插件在不在应用程序都能执行。
由于我们创建了一个对象来处理菜单点击,那么在不再需要这个插件时,就要释放这个
对象。修改后的单元中会在finalization段中处理这件事情。Finalization端时与
initialization段相对应的,如果前面有一个initialization段,那么在应用程序终止时
finalization段一定会得到执行。把下面的语句
Holder.Free
加到finalization段中,以确保Holder对象会被正确的释放。
显而易见,虽然这个插件只是修改了外壳应用的主菜单,但是它可以轻易地操纵传递
到InitPlugin过程中的任何其他对象。如果有必要,插件也可以打开自己的对话框,向
列表框(List boxes)和树状视图(tree views)中添加项目,或者在画布(canvas)
中绘画。
事件驱动的插件
到现在为止我们所描述的技术可以产生一种通用的扩展应用程序的方法。通过增加新菜
单、窗体和对话框,就可以实现全新的功能而不必对父应用做任何修改。不过仍然有一个
限制:这只是一种单侧(one-sided)机制。正如所看到的,系统依赖用户的某些操作才
能启动插件代码,比如点击菜单或者类似的动作。代码运行起来以后,又要依靠另外一个
用户动作来停止它,例如,关闭插件可能已经打开的窗体。克服这种缺陷的一种可行的方
法就是使插件可以响应父应用中的动作——模仿在Delphi中工作地很好的事件驱动编程模
型的确有效。
在最后一个例子插件中,我们将创建一种机制,插件可以藉此响应父应用中产生的事件。
通常情况下,可以通过判定需要触发哪些事件、在父应用中为每个事件创建一个Tlist对象
来实现。然后每个Tlist对象都被传递到插件的初始化过程中,如果插件想在某个事件中
执行动作,它就把负责执行的函数地址加入到对应的TList中。父应用在适当的时刻循环
这些函数指针的列表,按次序调用每个函数。通过这种方法,就为多个插件在同一事件中
执行动作提供了可能。
应用程序产生的事件完全依赖于程序已确定的功能。例如,一个TCP/IP网络应用程序可能
希望通过TclientSocket的onRead事件通知插件数据抵达,而一个图形应用程序可能对调
色板的变化更感兴趣。
为了说明事件驱动的插件应答的概念,我们将创建一个用于限制主窗口最小尺寸的插件。
这个例子有点儿造作,因为把这个功能做到应用程序里边会比这简单的多。不过这个例子
的优点在语容易编码而且易于理解,而这正是本文想要做到的。
很明显,我们要做的第一件事情就是决定到底要产生哪些事件。在本例中,答案很简单:
要限制一个应用程序窗口的尺寸,有必要捕获并且修改Windows消息WM_GETMINMAXSINFO。
因此,要创建一个完成这项功能的插件,我们必须捕获这个消息并且在这个消息处理器中
调用插件例程。这就是我们要创建的事件。
接下来我们要创建一个TList来处理这个事件。在主窗体的initialization段中将会创建
lstMinMax对象,然后,创建一个消息处理器来捕获Windows消息WM_GETMINMAXINFO。图9中
的代码显示了这个消息处理器。
{ 捕获 WM_GETMINMAXINFO. 为每个消息调用插件例程. }
procedure TfrmMain.MinMaxInfo(var msg: TMessage);
var
m: PMinMaxInfo; file://在 Windows.pas 中定义.
i: Integer;
begin
m := pointer(msg.Lparam);
for i := 0 to lstMinMax.count -1 do begin
TResizeProc(lstMinMax)(m.ptMinTrackSize.x,
m.ptMinTrackSize.y);
end;
end;
外壳应用的LoadPlugin过程必须再次修改以便调用初始化例程。这个新初始化函数把
我们的TList当作参数接受,在其中加入修改消息参数的函数地址。图10显示了LoadPlugin
过程的最终版本,它可以执行到目前为止所讨论的全部几个插件的初始化工作。
{ 加载指定的插件DLL. }
procedure TfrmMain.LoadPlugin(sr: TSearchRec);
var
Description: string;
LibHandle: Integer;
DescribeProc: TPluginDescribe;
InitProc: TPluginInit;
InitEvents:TInitPluginEvents;
begin
LibHandle:=LoadLibrary(Pchar(sr.Name));
ifLibHandle<>0then
begin
//查找DescribePlugin.
DescribeProc:=GetProcAddress(LibHandle, cPLUGIN_DESCRIBE);
if Assigned(DescribeProc) then
begin
// 调用 DescribePlugin.
DescribeProc(Description);
memPlugins.Lines.Add(Description);
file://查找InitPlugin.
InitProc := GetProcAddress(LibHandle, cPLUGIN_INIT);
if Assigned(InitProc) then
begin
file://调用InitPlugin.
InitProc(mnuMain);
end;
// 为第三方插件查找 InitPluginEvents
InitEvents := GetProcAddress(LibHandle,
cPLUGIN_INITEVENTS);
if Assigned(InitEvents) then
begin
// 调用 InitPlugin.
InitEvents(lstMinMax);
end;
end
else
begin
MessageDlg('File “' + sr.Name +
'” is not a valid plugin.',
mtInformation, [mbOK], 0);
end;
end
else
begin
MessageDlg('An error occurred loading the plugin “' +
sr.Name + '”.', mtInformation, [mbOK], 0);
end;
end;
图 10: LoadPlugin 的最终版本
最后一步是创建插件自身。如同前面的几个例子,插件展示一个标志自身的描述过程。
它也带有一个初始化例程,在本例中只是接受一个TList作为参数。最后,它还包含一个
没有引出(Export)的历程,叫做AlterMinTrackSize,它将修改传递给它的数值。图11
显示了最终插件的完整代码。
unit main;
interface
uses Dialogs, Menus, classes;
procedure DescribePlugin(var Desc: string);
export; stdcall;
procedure InitPluginEvents(lstResize: TList);
export; stdcall;
procedure AlterMinTrackSize(var x, y: Integer); stdcall;
implementation
procedure DescribePlugin(var Desc: string);
begin
Desc := 'Test plugin 3 – MinMax';
end;
procedure InitPluginEvents(lstResize: TList);
begin
lstResize.Add(@AlterMinTrackSize);
end;
procedure AlterMinTrackSize(var x, y: Integer);
begin
x := 270;
y := 220;
end;
end.
InitPluginEvents过程是这个插件的初始化例程。它接受一个TList作为参数。这个
TList就是在父应用程序中创建的保存相应函数地址的列表。下面的语句:
lstResize.Add(@AlterMinTrackSize);
把AlterMinTrackSize函数的地址加入到了这个列表中。它被声明为类型stdcall以便
与其他过程相配,不过用不着export指示字。由于函数被直接通过它的地址调用,所以也
就没有必要按照通常的方式把它从DLL中引出。
所以,事件序列如下所列:
1、 在应用程序初始化时,创建一个TList对象。
2、 在启动时这个列表被传递到插件的初始化过程InitPluginEvents中。
3、 插件过程把一个过程的地址加入到列表中。
4、 每次窗口大小改变时所产生的Windows消息WM_GETMINMAXINFO被我们的应用程序所捕获。
5、 该消息被我们的消息处理器TfrmMain.MainMaxInfo所处理,见图10。
6、 消息处理器遍历列表并调用它所包含的函数,把当前的X和Y最小窗口尺寸作为参数传递
。要注意,TList类只是存储指针,所以如果想用保存的地址做些什么事情的话,我们必须
把指针转换成所需要的类型——在本例中,要转换成TresizeProc。
TResizeProc = procedure (var x, y: Integer); stdcall;
7、 插件过程AlterMinTrackSize(列表中的指针所指向的),接受X和Y值作为可变的var
参数并且修改它们。
8、 控制权返回到父应用的消息处理器,按照最小窗口尺寸的新值继续运行下去。
9、 应用程序退出时TList会在主代码的finalization段被释放。
结论
使用该体系结构时,可能利用Delphi提供的package功能是个不错的主意。在通常情况
下,我不是一个分割运行时模块的狂热爱好者,但是当你认为任一包含大量代码的Delphi
DLL超过200KB时,它就开始变得有意义了。
这篇文章应该还是有些用处的,至少它可以让你思考一些程序设计方面的问题,比如
如何让它变得更加灵活。我知道如果我在以前的应用程序中使用一些这种技术的话,我就
可以省掉在修改程序方面的好多工作。我并不想把插件作为一种通用的解决方案。很明显,
有些情况下额外的复杂度无法验证其正确性,或者应用程序压根儿就不打算把自身搞成
几块可扩展的单元。还有一些其它的方法也可以达成同样的效果。Delphi自身提供了一个
接口来创作能集成到IDE中的模块,比起我所说明的技术这种方法更加面向对象(或者说
更“干净”),而我也确信你可以在自己的应用中模仿这一技术。在运行时加载Delphi包
也不是做不到的。探索一下这种可能性吧。
