2007年03月27日

响应KeyPress事件

    /*全角字符从的unicode编码从65281~65374   
      半角字符从的unicode编码从               33~126   
     * 差值65248
      空格比较特殊,全角为       12288,半角为       32 
     
*/
    
public char FullCodeToHalfCode(char c)
    {
        
//得到c的编码
        byte[] bytes = System.Text.Encoding.Unicode.GetBytes(c.ToString());

        int H = Convert.ToInt32(bytes[1]);
        
int L = Convert.ToInt32(bytes[0]);

        //得到unicode编码
        int value = H * 256 + L;

        //是全角
        if (value >= 65281 && value <= 65374)
        {
            
int halfvalue = value - 65248;//65248是全半角间的差值。
            byte halfL = Convert.ToByte(halfvalue);

            bytes[0= halfL;
            bytes[
1= 0;
        }
        
else if (value == 12288)
        {
            
int halfvalue = 32;
            
byte halfL = Convert.ToByte(halfvalue);

            bytes[0= halfL;
            bytes[
1= 0;
        }
        
else
        {
            
return c;
        }

        //将bytes转换成字符
        string ret = System.Text.Encoding.Unicode.GetString(bytes);

        return Convert.ToChar(ret);
    }

 

C#中文乱码解决:UTF8 转 UNICODE

XML文件可以采用多种编码,但是经过不同的编码后对于中文会出现乱码问题,比如“骞垮憡涓戦椈”,对于此问题的解决如下:

static void Main()
      {
         string utf8String = "骞垮憡涓戦椈";

         // Create two different encodings.
         Encoding utf8= Encoding.UTF8;
         Encoding defaultCode= Encoding.Default;

         // Convert the string into a byte[].
         byte[] utf8Bytes = default.GetBytes(utf8String );

         // Perform the conversion from one encoding to the other.
         byte[] defaultBytes = Encoding.Convert(utf8, defaultCode, utf8Bytes );
           
         // Convert the new byte[] into a char[] and then into a string.
         // This is a slightly different approach to converting to illustrate
         // the use of GetCharCount/GetChars.
         char[] defaultChars = new char[defaultCode.GetCharCount(defaultBytes , 0, defaultBytes .Length)];
         defaultCode.GetChars(defaultBytes , 0, defaultBytes .Length, defaultChars , 0);
         string defaultString = new string(defaultChars );

         // Display the strings created before and after the conversion.
         Console.WriteLine("Original string: {0}", utf8String);
         Console.WriteLine("Ascii converted string: {0}", defaultString);

//或者如下:
         byte[] buffer1 = Encoding.Default.GetBytes(utf8String );
         byte[] buffer2 = Encoding.Convert(Encoding.UTF8, Encoding.Default, buffer1, 0, buffer1.Length);
         string strBuffer = Encoding.Default.GetString(buffer2, 0, buffer2.Length);
      }

2007年03月14日

浅谈C#托管程序中的资源释放问题

便于对文章的开展,需要先明确两个概念。
第一个就是很多人用.Net写程序,会谈到托管这个概念。那么.Net所指的资源托管到底是什么意思,是相对于所有资源,还是只限于某一方面资源?很多人对此不是很了解,其实.Net所指的托管只是针对内存这一个方面,并不是对于所有的资源;因此对于Stream,数据库的连接,GDI+的相关对象,还有Com对象等等,这些资源并不是受到.Net管理而统称为非托管资源。而对于内存的释放和回收,系统提供了GC-Garbage Collector,而至于其他资源则需要手动进行释放。

那么第二个概念就是什么是垃圾,通过我以前的文章,会了解到.Net类型分为两大类,一个就是值类型,另一个就是引用类型。前者是分配在栈上,并不需要GC回收;后者是分配在堆上,因此它的内存释放和回收需要通过GC来完成。GC的全称为“Garbage Collector,顾名思义就是垃圾回收器,那么只有被称为垃圾的对象才能被GC回收。也就是说,一个引用类型对象所占用的内存需要被GC回收,需要先成为垃圾。那么.Net如何判定一个引用类型对象是垃圾呢,.Net的判断很简单,只要判定此对象或者其包含的子对象没有任何引用是有效的,那么系统就认为它是垃圾。

明确了这两个基本概念,接下来说说GC的运作方式以及其的功能。内存的释放和回收需要伴随着程序的运行,因此系统为GC安排了独立的线程。那么GC的工作大致是,查询内存中对象是否成为垃圾,然后对垃圾进行释放和回收。那么对于GC对于内存回收采取了一定的优先算法进行轮循回收内存资源。其次,对于内存中的垃圾分为两种,一种是需要调用对象的析构函数,另一种是不需要调用的。GC对于前者的回收需要通过两步完成,第一步是调用对象的析构函数,第二步是回收内存,但是要注意这两步不是在GC一次轮循完成,即需要两次轮循;相对于后者,则只是回收内存而已。

很明显得知,对于某个具体的资源,无法确切知道,对象析构函数什么时候被调用,以及GC什么时候会去释放和回收它所占用的内存。那么对于从CC++之类语言转换过来的程序员来说,这里需要转变观念。

那么对于程序资源来说,我们应该做些什么,以及如何去做,才能使程序效率最高,同时占用资源能尽快的释放。前面也说了,资源分为两种,托管的内存资源,这是不需要我们操心的,系统已经为我们进行管理了;那么对于非托管的资源,这里再重申一下,就是Stream,数据库的连接,GDI+的相关对象,还有Com对象等等这些资源,需要我们手动去释放。

如何去释放,应该把这些操作放到哪里比较好呢。.Net提供了三种方法,也是最常见的三种,大致如下:
<!–[if !supportLists]–>1.  <!–[endif]–>析构函数;
<!–[if !supportLists]–>2.  <!–[endif]–>继承IDisposable接口,实现Dispose方法;
<!–[if !supportLists]–>3.  <!–[endif]–>提供Close方法。

经过前面的介绍,可以知道析构函数只能被GC来调用的,那么无法确定它什么时候被调用,因此用它作为资源的释放并不是很合理,因为资源释放不及时;但是为了防止资源泄漏,毕竟它会被GC调用,因此析构函数可以作为一个补救方法。而CloseDispose这两种方法的区别在于,调用完了对象的Close方法后,此对象有可能被重新进行使用;而Dispose方法来说,此对象所占有的资源需要被标记为无用了,也就是此对象被销毁了,不能再被使用。例如,常见SqlConnection这个类,当调用完Close方法后,可以通过Open重新打开数据库连接,当彻底不用这个对象了就可以调用Dispose方法来标记此对象无用,等待GC回收。明白了这两种方法的意思后,大家在往自己的类中添加的接口时候,不要歪曲了这两者意思。

接下来说说这三个函数的调用时机,我用几个试验结果来进行说明,可能会使大家的印象更深。
首先是这三种方法的实现,大致如下:

    ///<summary>

    /// The class to show three disposal function

    ///</summary>

    public class DisposeClass:IDisposable

    {

        public void Close()

        {

            Debug.WriteLine( "Close called!" );

        }

        ~DisposeClass()

        {

            Debug.WriteLine( "Destructor called!" );

        }

        #region IDisposable Members

        public void Dispose()

        {

            // TODO:  Add DisposeClass.Dispose implementation

            Debug.WriteLine( "Dispose called!" );

        }

        #endregion

    }

对于Close来说不属于真正意义上的释放,除了注意它需要显示被调用外,我在此对它不多说了。而对于析构函数而言,不是在对象离开作用域后立刻被执行,只有在关闭进程或者调用GC.Collect方法的时候才被调用,参看如下的代码运行结果。

        private void Create()

        {

            DisposeClass myClass = new DisposeClass();

        }

        private void CallGC()

        {

            GC.Collect();

        }

        // Show destructor

        Create();

        Debug.WriteLine( "After created!" );

        CallGC();

运行的结果为:

After created!

Destructor called!

显然在出了Create函数外,myClass对象的析构函数没有被立刻调用,而是等显示调用GC.Collect才被调用。

对于Dispose来说,也需要显示的调用,但是对于继承了IDisposable的类型对象可以使用using这个关键字,这样对象的Dispose方法在出了using范围后会被自动调用。例如:

    using( DisposeClass myClass = new DisposeClass() )

    {

        //other operation here

    }

如上运行的结果如下:
Dispose called!

那么对于如上DisposeClass类型的Dispose实现来说,事实上GC还需要调用对象的析构函数,按照前面的GC流程来说,GC对于需要调用析构函数的对象来说,至少经过两个步骤,即首先调用对象的析构函数,其次回收内存。也就是说,按照上面所写的Dispose函数,虽说被执行了,但是GC还是需要执行析构函数,那么一个完整的Dispose函数,应该通过调用GC.SuppressFinalize(this )来告诉GC,让它不用再调用对象的析构函数中。那么改写后的DisposeClass如下:

    ///<summary>

    /// The class to show three disposal function

    ///</summary>

    public class DisposeClass:IDisposable

    {

        public void Close()

        {

            Debug.WriteLine( "Close called!" );

        }

        ~DisposeClass()

        {

            Debug.WriteLine( "Destructor called!" );

        }

        #region IDisposable Members

        public void Dispose()

        {

            // TODO:  Add DisposeClass.Dispose implementation

            Debug.WriteLine( "Dispose called!" );

            GC.SuppressFinalize( this );

        }

        #endregion

    }

通过如下的代码进行测试。

        private void Run()

        {

            using( DisposeClass myClass = new DisposeClass() )

            {

                //other operation here

            }

        }

        private void CallGC()

        {

            GC.Collect();

        }

        // Show destructor

        Run();

        Debug.WriteLine( "After Run!" );

        CallGC();

运行的结果如下:

Dispose called!

After Run!

显然对象的析构函数没有被调用。通过如上的实验以及文字说明,大家会得到如下的一个对比表格。

析构函数

Dispose方法

Close方法

意义

销毁对象 销毁对象 关闭对象资源

调用方式

不能被显示调用,会被GC调用 需要显示调用
或者通过using语句
需要显示调用

调用时机

不确定 确定,在显示调用或者离开using程序块 确定,在显示调用时

那么在定义一个类型的时候,是否一定要给出这三个函数地实现呢。

我的建议大致如下。
<!–[if !supportLists]–>1.<!–[endif]–>提供析构函数,避免资源未被释放,主要是指非内存资源;
<!–[if !supportLists]–>2.<!–[endif]–>对于DisposeClose方法来说,需要看所定义的类型所使用的资源(参看前面所说),而决定是否去定义这两个函数;
<!–[if !supportLists]–>3.<!–[endif]–>在实现Dispose方法的时候,一定要加上“GC.SuppressFinalize( this )”语句,避免再让GC调用对象的析构函数。

C#程序所使用的内存是受托管的,但不意味着滥用,好地编程习惯有利于提高代码的质量以及程序的运行效率。

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1023352

2007年03月13日

   很多年以后,我无比想念桃花林里那个妖精。我知道,作为一个“佛”,这很不应该。我应该把一切都忘掉,把所有的爱和恨,悲和喜,功业和理想,都忘掉。
  但我清楚,就算我把自己也忘了,当那朵红霞拂过我的窗前,我还是会想起三千年前,那张美丽的脸,那双透明的眼睛,那银铃一般的歌声。
  “我吃了你好不好?”
  “不好。”
  “为什么?”
  “我还没洗澡呢。”
  她咯咯地笑,粉红色的长裙轻轻摆动,象一朵美丽的红霞。
  (一)
  孙悟空有几百年没来看我了。西天路上,我这徒弟曾无数次救过我的命。我们没想到那是游戏,我们如此投入,抱头痛哭,相对嘻笑,但直到结局才明白,一切原来都是虚幻。取经路上的一切山,一切水,一切妖魔鬼怪,都是如来设的障眼法。
  “这么多年了,你还在生气?”三百年前,我问他。
  他长叹,“你知道我最后悔的是什么事?”
  “是什么?”
  “我真后悔认识你,师父。”
  这句话是我多年以前对他说过的,那时他还是一只猴子。
  猴子和人的差别有多大?孙悟空说:“只隔一张纸。”
  “你把这张纸揭开,里面有一个秘密。”三千年前,那只猴子躺在树下微笑着说。
  我把封印揭开。
  轰隆隆一声巨响,霎那间天崩地裂,一道金光从山谷里升腾而起,那只猴子一飞冲天,坐在云端大笑。
  “哈哈哈,你救了我,但我决定要杀了你,你有什么话说?”
  “我真后悔认识你,猴子。”
  他没有杀我,他成了我的徒弟。
  很多年之后我知道那也是如来的安排,他不愿意看一个人演的戏,那不够精彩。
  女妖桃儿在一个美丽的夜里俘虏了我,她挥了挥手,我就动弹不得。
  “你要带我到哪里去?”我挣扎着问,天上有一轮瓦蓝瓦蓝的月亮。
  她重重打了我一耳光,“不许说话!再罗嗦我杀了你!”
  我们在林间飞翔,昆虫唧唧鸣叫,树叶轻轻飘动,她身上散发出淡淡的香气,我心里有一点害怕,但更多的是惆怅,这一切,多象我少年时常做的那个梦呵。
  “和尚,你死了么?”她忽然问我。
  “还没有,活着多好啊,你死了我都不会死。”我笑着说。
  她转回身,劈面又打了我一个耳光,半边脸热辣辣地疼,“不许你说话,你还敢说?!”
  “阿弥陀佛,善哉善哉。”
  “啪!”又一耳光。
   “阿弥陀佛!!”我大怒,“善哉善哉!!”
  “啪!”
  “阿弥陀佛阿弥陀佛!!!!”
  “啪!啪!”
  “阿弥陀佛阿弥陀佛阿弥陀佛阿弥陀佛阿弥陀佛阿弥陀佛阿弥陀佛………”
  她咯咯地笑了,“我从来没见过你这样的倔和尚,好吧,我不打你了,但你也不许罗嗦。”
  这是第一天,她打了我五个耳光,但最后我赢了。
  (二)
  成佛后,世界一片寂静。把灯点燃,把灯熄灭,世界一片光明,所有的经文都是帮助你忘却的。忘却过去,忘却自己,忘却经文本身。
  又见离欲,常处空闲,深修禅定,得五神通。
  又见菩萨,安禅合掌,以千万偈,赞诸法王。
  复见菩萨,智深志固,能问诸佛,闻悉受持。
  又见佛子,…………
  我轻轻地念着,感觉自己渐渐走远,歌声在星斗间缥缈散尽,人间花谢花开、悲欢聚散,都象是耳后的微风。
  有一段时间,我以为我真的已经忘记了一切。我安静而幸福,恒河的沙子堆成了高山,但我一粒也不要。
  “师父!”
  我抬起头来,眼前是一只猴子。
  “你找谁?”我问他。
  “师父,你不记得了?我是悟空啊。”
  “悟…空…,悟空?”
  记忆的海水漫卷而来,拍打着光阴的墙壁。那些模糊的记忆渐渐清晰,我看见眼前的猴子眼含热泪。
  “师父啊~!”他号啕大哭,“这就是我们一直追求的幸福生活吗?”
  走出桃花林时,我们两个都没有说话。过了很久,他忽然问我:“师父,什么叫作幸福?”
  “幸福只是一种感觉,也许到了西天,我们就会明白。”
  “那么,你现在幸福吗?”他歪着猴头看我。
  我的眼圈一下子红了,“操你娘!闭嘴!”我粗鲁地说。
  “操你娘”这三个字是我教妖精桃儿的。
  在桃林深处,有一座美丽的花园。青青的草地上落满了花瓣。
  “到家喽!”桃儿长出一口气,把我重重地扔到地上,“你可真重,倔和尚。”
  我不理她,嘴角渗出丝丝血迹,被她打的。
  “你不理我吗,倔和尚?”
  我把脸也转过去。
  “你敢不理我,不怕我打你?”她威胁我。
  我哼了一声。
  “对了,你不怕打。”她自言自语,“那么我骂你了啊!”
  “哼!”
  “骂你什么呢?你们人类是怎么骂的?”
  “操你娘!”我直视着她。她要再敢打我,我就跟她拼了,我想。
  “操你娘,操你娘,嘻嘻,真好玩。”
  西天路上有无数妖精,但从来没见过象她这么傻的。
  “你把我捉来,想干什么?”
  她绕着我走来走去,“我姐姐说吃了你的肉会长生不老,你有那么好吗,倔和尚?”
  “我从来不洗澡,我的肉又臭又硬,吃了毒死你!”我恶狠狠地说。
  她突然从背后扑过来,在我胳膊上狠狠咬了一口,鲜血直流。
  “哎哟哎哟~~”我疼得大叫,“操你娘!”
  (三)
  传说中有一种法术,叫作“回梦”,施了这种法术,你就可以沿着梦里的路,回到从前。
  “三藏,你为何来?”如来在莲座上问。
  我磕头,“我想请师尊传我回梦之术。”
  “你要回到哪里?”
  “回到取经路上。”
  他笑了,“你取过经么?”
  我的头在地上重重地顿了两下,血流了出来,殷红灿烂,象一朵盛开的桃花。
  “你入了魔道了,三藏!你何曾取过经?!”如来大喝。
  我愣住了,我看见自己的一生象一幅长轴的画卷,在眼前慢慢翻开。我看见我从一个女人的身体里钻出来,对着世界大声哭泣;看见自己慢慢站立,蹒跚地走路,咿呀学语;看见自己在五岳山苦读修行;看见自己渐渐老去,一些人围着我的身体流泪;看见我的灵魂脱离了躯壳,在白云中慢慢升腾,所有的阳光都照在我身上,我成了佛。
  “你取过经么?”
  ………
  我心里空空荡荡,摇摇欲倒。
  “来,跟我走,我带你回去。”如来身旁的童子对我说。
  “你说什么?”我蓦地睁开眼睛。
  “跟我走!我带你回去!”妖精桃儿抓着我的衣领。
  “我不去!”我拼命挣扎,“你这该死的妖精,我一定会让我徒弟杀了你!”
  桃儿叹了口气,“由得你吧,不过我告诉你,我姐姐就要来了。你~,你保重吧。”
  我成佛后,不再是当年那个固执冲动的和尚,我知道,我眼里看到的一切都是幻影,来一阵风,一切就会无影无踪。三千年,多少王国毁灭,多少城市荒芜,俗世在沧桑之后容颜更改,不留痕迹。不管你执着或者放弃,最终的结局都一样,色身化身,尽归虚空。
  妖精的洞窟里群魔乱舞,桃树精开怀大笑。
  “长生不老!”她喊道。
  “长生不老!长生不老!长生不老!”千百个小妖怪同声附和。
  我被锁在柱上,茫然地看着这群愚昧的生灵。是的,我即将死去,但他们也决不会长生,早死或者晚死,在佛的眼里,没有分别。
  桃儿远远地站着,静静地望着我。
  如果我不是和尚,我一定会由衷地赞叹她的美丽,她象一朵盛开的桃花,芬芳明艳,整座洞窟都因她而加倍明亮。她既不是脓水也不是骷髅,佛经也不总是正确,我想。
  “把唐三藏洗剥干净了,抬到蒸笼上去!”桃树精喊道。
  “嗨!嗨!嗨!”小妖们欢呼雀跃。
  桃儿的身体剧烈地抖动了一下,她满面通红。“瞧她高兴的!”我恨恨地想。
  两个妖怪架起我来就往外走,山洞里崎岖不平,我的头在石壁上撞了一下,剧烈地疼痛。
  “等一等!”桃儿突然说。
  她一把抓住我的胳膊,脸上浮现笑容,“姐姐,这些粗货肯定洗不干净,我去吧!”
  “好!”桃树妖说,“这次你立了大功,你想吃哪块肉,你自己来挑!”
  “我要吃他的心!”桃儿咬着嘴唇说。她的牙齿闪了一下,象是人世间最珍贵的玉石。
  (四)
  我少年时经常会作同一个梦:在一望无边的草原上,我骑着白马飞快地奔跑,一个害羞的少女把脸埋在我的背后,双手紧紧地抱着我的腰,我表情幸福而又悲伤,一片宁静中,少女抬起头来,在我耳边喃喃地说:如果,如果……。
  我睁开眼,门外响起第一声晨钟,这个时候我总是无比伤感。
  “你是个情种,”玄苦师父摸着我的光头说,“你不该到这里来。”
  “我该去哪里?”
  玄苦师父长久地摇头。
  直到今天我也不知道我该去哪里。成佛很难,但我成了;做梦很容易,但几千年来,我始终不能走回到那个梦里。
  桃儿没有带我去河边,她拉着我的手,象月光一样飞向桃林深处,那里雾气蒙蒙。 “你想独吞唐僧肉吗?”我冷冷地问。
  “嘘…,不要说话!”她没有回头,带着我掠过一株株桃树,粉红色的纱巾在风中飞扬,轻轻拂过我的双眼。
  夜色深深,我们在无边寂静中不停地穿行。黑暗里,有一些东西正在我心中轻轻蠕动,慢慢成长。我张开双臂,在空中轻盈漂浮,脑海里一片迷茫,我看见那匹久违的梦中白马正在长鸣,脚下青草无边……
  “和尚,我们到了!”
  我睁开眼,发现身处一个群山环抱的山谷,一条小河正从我身边潺潺流去。
  “你为什么要救我?”
  “你以为我是救你啊,和尚?我是要吃你,别做梦了!”她笑嘻嘻地说。
  妖怪就是妖怪,我双手合什,“阿弥陀佛。”
  “和尚,你说我漂亮吗?”她忽然问我。
  我的脸刷的红了,我转过身去,装作什么也没听见,低头喃喃吟诵:无上甚深微妙法,百千万劫难遭遇。
  我今见闻得受持,愿解如来真实意。
  ………
  “你在骗自己呢,和尚,”桃儿咯咯地笑,“在山洞里你就一直盯着我看–你的脸都红了。”
  玄苦师父一直夸我有定力,打坐的时候,他经常会在我耳边大吼一声。耳朵嗡嗡作响,但我的身体纹丝不动。他总是满意的摸摸我的头,起身离去。这个游戏,我们玩了三十年,直到他死。
  那一刻我忽然想起玄苦师父,羞愧难当。
  “你觉得我漂亮就好,”她幽幽地说,“我叫桃儿,是我姐姐身上的一朵桃花–不知道你会不会记得住?”
  (五)
  我功德圆满的那一天,诸天神佛云集灵山,仙乐飘飘,天花飞舞。当一切沉寂之后,如来问我:“你是谁?你从哪里来?要到何处去?”
  所有的目光都直视着我。我躬身作答:“我本无名,我本无形,我就是空,我无来处,亦无去处。”
  “那么,你都忘了吗?”
  我低头不语。
  “为什么不说话?”
  我抬起头来,对着如来大声说:“本来未曾有过,又何须忘记?!”
  如来大笑,神佛们啧啧赞叹,天花从空中纷纷撒落。
  但我记忆的闸门却在那一刻汹涌打开,生命中的每个人、每件事、每句话、每个喜怒忧乐的表情,都如此清晰和美丽,猛烈地摇动着我最深处的灵魂。
  桃林里响起一阵歌声:天路遥,人世远,凝眸处沧海桑田。
  为谁痛哭,为谁嘻笑,任光阴凋尽朱颜。
  哪个出将入相,哪个成佛登仙,到头来或为黄土,或为轻烟。
  且去世外垂钓,手有青青竹竿。
  莫问卿卿何处去,回头看见桃花仙………
  尾音袅袅,散入青云,我看见桃儿提着竹篮远远走来,她嘻嘻地笑,长发在朝霞中飘飘飞扬,身上洒满阳光……
  桃儿敲敲我的光头,笑,“吃饭了!还在看着美女发呆!”
  我红着脸低下头。
  从那一刻起,我再也没有恨过她,虽然她损折了我的修行,虽然她打过我,虽然,她几乎让我堕入魔道。佛说,爱恨痴嗔是人生痛苦的根源,但如果没有那些爱,那些恨,那些痴情,那些含泪的微笑,人生该是多么乏味啊。
  “知不知道我为什么要救你?”
  “为什么?”
  “因为只有你才敢顶撞我,而且,从来都不正眼看我。”桃儿撅起小嘴。
  “谁让你打我的。”我笑着说,心里涌上一阵悲哀。我知道,自己再也不是个和尚了,我坚守的清规戒律都已经崩溃,在那个阳光明媚的清晨,在那个妖精迷人的微笑里。
  “你吃肉吗?”
  “不吃。”
  “喝酒吗?”
  “不喝。”
  我坐在那里,不知道该生气还是该高兴,过了很久,我无声地哭了。
  (六)
  “你为什么要去取经?”
  “为了拯救众生啊。”
  “你徒弟那么厉害,让他一个人去不行吗?”
   我从没想过这个问题。
  “你徒弟去西天,只要翻一个跟斗,但你这样走过去,要走上好几百年,”桃儿夸张地比划了一下,“等你把经取回来,众生早死光了。”
  我呆住了。桃儿摸了一下我的光头,叹气,“连自己的命都差点保不住,还拯救众生,你呀,真是个傻和尚。”
  很多年以后,我终于明白,所谓西天取经,所谓普渡众生,只不过是如来跟我开的一个玩笑。那些尘封千年的往事一一浮现,一张美丽的脸在光阴深处闪烁,我看见她在千年里始终不改地向我微笑。让我浑身颤栗。
  一切事情都有它的因果,痛苦是因为执著,快乐来自于放弃。千年里我不断地想,是什么样的缘起,造就了我和那个妖精的生死悲欢?
  “我吃了你好不好?”她突然问我。
  “不好。”
  “为什么?”
  “我还没洗澡呢。”
  她愣了一下,随即咯咯的笑起来,粉红色的长裙轻轻摆动,象一朵美丽的红霞。“我才不舍得吃你,和尚,我很喜欢你呢。”
  桃儿从来不羞于表达她的情感,在她的世界里,爱和恨,生和死,都那么简单。
  “你哪里都不许去,”她站在一棵桃树下对我说,“我要和你在一起。”
  我的心剧烈地跳动,桃儿象个孩子一样无邪地看着我,这目光穿越了光阴和生死,让我在千年后泪落如雨。
  我的眼泪飘落人间,人间涌起洪水滔滔。
  “你相信有来生吗?”我在河里洗澡,桃儿远远地问。
  “相–信–!”
  她飞快地跑到岸边,脸色涨红,“来生你还见不见我?”
  我反问她:“那你还打不打我?”
  她前仰后合地笑,“不打了不打了,你这个小心眼的和尚!”
  我点头,“那我就见你,你要是还打我,我就永远都不理你了。”
  她扑通一声跳进水里,紧紧搂着我的脖子,“你要说话算话!”她说,泪水叭嗒叭嗒地落在我的头上。
  “’操你娘’的’操’是什么意思?”一片黑暗中,她贴在我的耳边问。
  “就是这样。”我拿起她的手,比了一下形状。
  “我要!”
  “什么?”
  她把脸紧紧地贴在我的脸上,“我要,”她轻轻地说,脸象火一般烫。
  …………
  那个夜里,春雨落满人间,无数朵桃花悄悄开放,光阴的枝头洒满了生命的甘露。
  “我真幸福,”
  “我也是。”
  我紧紧地拥抱她,心中无限喜悦。这就是涅磐,我在心里喃喃自语,幸福的、死亡一般的涅磐。
  (七)
  孙悟空是个诗人,我指的是他的生活态度。从本质上,他是一只浪漫多情的猴子,对世界无比温柔,但看上去却象个暴徒。他成佛的那一刻眼含热泪,浑身颤抖。如来笑着问:“你这顽皮的猴子,哭什么?”
  当时诸天神佛都在,悟空突然放声大哭,谁都劝不住。
  只有我知道,那才是真实的孙悟空,一只软弱的、自卑的猴子,一只渴望爱情的猴子。
  三百年前,他在我的墙上题了一首诗:有缘未必相逢,无情莫向翠微。人间一堕十劫,犹记桃花未归。
  我知道他说的是什么。
  幸福,是的,可望不可及的幸福。
  桃树精找到我们的时候,桃儿正在梳头。她妩媚地望着我,头发长长地垂下来,我轻轻地吻着她,小心地把一朵桃花插在她的耳边。
  “我美吗?”
  “嗯。”
  我们相视微笑,心中无限甜蜜。
  千年后,那瞬间的微笑让我无比疼痛,往事就象最锋利的宝刀,一刀刀刺穿我的胸膛。
  如果可能,我愿意用我不死的生命,用尽千千万万年光阴,来换取当年桃树下那深情的微笑,哪怕只有一刹那,一秒钟。
  两个妖精翻翻滚滚地斗了起来,风声呼啸,木叶飞扬,鲜血一滴滴溅到我身上,我双手合什,浑身颤抖着祈祷,一些从未有过的恐惧滚滚而来,反复搓弄着我的心灵。
  突然一声巨响,桃儿象断线的风筝一样跌了出去,鲜血飞溅,染红了脚下的草地。
  桃树精挥剑直劈,“小贱人,今天我要杀了你!”
  桃儿象个孩子一样无助地躺在那里,脸上满是鲜血,她痴痴地望着我,目光中含着重重的情感。我猛扑过去,紧紧抱住她,抱紧她,用尽我全身的气力。桃树精的利剑就在眼前,但我不怕,我愿意就这样死去,我想。
  当然,这是个游戏。有一双眼睛一直在俯视着这片土地。
  在最危机的关头,如来佛和孙悟空同时出现在我的面前。
  如来招了招手,桃树精就立刻倾倒、软化、分解,弹指之间,那个美丽凶狠的妖精就不见了,象风一样无影无踪。
  我如大梦初醒,生死之间,一切都那么突兀。我揉揉眼睛,看见面前金光闪闪的如来,他双手合什,慈祥地微笑,“善哉善哉,三藏,恭喜你过得此劫,西天不远,趁早上路吧。”
  桃儿紧紧地握着我的手,“不要!”她虚弱但坚定地说,“我不要你去,我不要你离开我!”
  她头发散乱,脸色苍白,两行清泪滑过她秀美的脸庞,鬓边的桃花血迹斑斑,那是一个芬芳的梦。
  我痴痴地看着她,胸中波涛汹涌,她再也不是那个法力高强的妖精,而是一个需要我去疼、去爱、去温柔拥抱的姑娘。一种从未有过的情感在我心中迅速升腾,象烈火一样灼热着我的灵魂。
  “师尊!”我突然双膝跪倒,大声地说:“我不去取经!我不想成仙成佛,也不想长生不老!我要留下来当一个平凡的人!”
  有的人一生只会勇敢一次,为了一个人或者一份真情。多年来我无数次想起我那天的宣言,我知道,在那一刻,我最接近幸福。
  如来脸色阴沉,大喝:“小小情欲,就让你意丧魂消,你还是不是我的弟子?!”他迅疾无伦的伸出手,在我头上重重敲了一下,“你还不悟?!”
  桃儿从地上艰难地爬进我的怀里,我紧紧地抱住她,不肯放手。我们目光相对,彼此都感到无限欣慰。
  如来大怒,“心魔不除,你就是妖孽!”
  我大声回答:“弟子今日愿为妖孽!哪怕堕入苦海万劫不复,也在所不惜!”
  如来怒不可遏,右手高高举起,“那你们就一起灭亡吧!”
  霹雳裂天而来,山岳摇动,江河倒流。桃儿突然勇敢地扑上前去,挡在我的身前。
  “你不要杀他,”她看着如来,“我一定会还你一个忠心耿耿的和尚!”
  她拿起宝剑,一剑刺进自己的胸膛,鲜血象桃花一样绽放在她胸前。
  “不!桃儿,不!”我大叫,一把抱住她,胸口如同千万把刀剑同时刺入,无比的疼痛。
  桃儿缓慢地转过身来,看着我泪如雨下。“和尚,你告诉我,真的有来生吗?”
  雷声在我空空的心中流响,往事如火花闪耀,我的眼泪终于决堤,在脸上滚滚流淌,“有!真的有!”我哭着说。
  “那么,来生你还要我吗?”
  我拼命地点头,在风雨里大声呼喊,“我要你!要你!!”
  桃儿用尽最后一点力气扑进我的怀里,锋利的宝剑刺穿了她的身体。她紧紧搂住我的脖子,“和尚,你要说话算话!”她说,最后一滴泪水叭嗒叭嗒地落在我的头上。
  (八)
  “师父!”一个声音在外面叫。
  “不要吵,我很累,我要睡觉。”我喃喃地说。
  我看见自己在天空飞翔,云图不停变幻,流星纷纷坠落,一颗蓝色的星球在眉间缓缓滑过。白云深处是谁的目光?让我在光阴的梦里如此悲伤。
  “师父,你醒醒啊!”悟空哭着说。
  我倏地睁开眼,看见一只痛哭流涕的猴子,他两眼通红,鼻涕和眼泪把他的脸搞得一蹋糊涂。
  我拍拍他的手,“你为什么伤心,猴子?”
  悟空扑过来,伏在我胸口号啕大哭,“师父啊,我可怜的师父啊………”
  桃儿呢?
  没有桃儿。
  你骗我!
  师父,是你自己在骗自己。根本就没有桃儿,也没有那片桃林。
  也许悟空是对的,但枝头那朵桃花为什么哭得如此伤心?
  如果没有路,你就停下来歇息。如果没有忧愁,你就真诚地微笑。但幸福,如果没有幸福,我还拿什么活着?
  悟空突然停下脚步,转过身来看着我。
  “你幸福吗?”三千年前,我幽幽地问道。
  他笑了。他哭了。一颗晶莹的眼泪凋谢在无邪的笑容里。
  “师父,你终于醒了。”
  “我已经醒了,你呢?”
  “我还在那个梦里。”
  那是多么悠长的一个梦啊。
  三千年。雪山融为江河,沧海凝固成岩石,桃花开过,人间又是春天。岁月的四壁题写着不朽的传奇,总有一些让人心潮难平。
  “我吃了你好不好?”
  “不好。”
  “为什么?”
  “我还没洗澡呢。”
  她咯咯地笑,粉红色的长裙轻轻摆动,象一朵美丽的红霞。
  “有一句话我一直想对你说。”
  “你想说什么?”如来微笑着问。
  西天的红霞轻轻拂过双眼,我看见那张美丽的脸在千千万万年光阴之外向我深情微笑,在生命的彼岸向我频频招手。
  “操你娘。”我轻轻地对如来说。

WMI 的一个实现

作者:Paul Li

翻译:Abbey



原文出处:Code Project:Windows Management Instrumentation (WMI) Implementation

源代码下载:wmi.zip(45KB)


介绍

  这是我在继上一篇文章"My Explorer"之后关于Windows Management Instrumentation(Windows管理规范)的又一新作。我将向你展示一些技巧,让你可以在远程地访问网络中其他计算机的操作系统、服务、当前运行着的进程等等信息,当然前提是你必须得拥有这些计算机的管理员权限。同时我也将向你展示如何利用WMI来启动或者停止服务、终止进程、创建进程。这是程序的主界面:

                      

开始

  在这个WMI应用程序里,我创建了一个包含了四个用户控制的库WMIControlLibrary。这四个用户控制分别是Explorer,SystemInfo,Services与Processes。每个控制都有其特定的功用。以下是对每个控制作用的一个简单描述:

  • Explorer控制     我把我那个"My Explorer"转换成了一个用户控制,它还是用来显示你系统上的驱动器、目录、文件等信息。
  • SystemInfo 控制* 这个控制用来显示操作系统与硬件数据及清单等信息。
  • Services 控制*   这个控制用来显示系统当前运行着的服务。
  • Process 控制*    这个控制用来显示系统当前运行着的进程。

(*注意:这个控制可以用来监控本地或者网络上的远程系统。)

上述的每个控制都引用了System.Management命名空间,以保证它们能访问各自特定的系统信息。

控制的状态事件

  这其中的一些控制需要点时间才能从系统获取相关的信息,因此我在每个控制中都实现了一个事件UpdateStatus(string e)。这样每个控制就可以更新主应用程序窗体的状态条,用户也能很清楚地知道控制正在干什么了。

//控制内部的代码
//声明一个Status的事件委托类型
public delegate void Status(string e);

//声明了一个更新状态的事件

public event Status UpdateStatus;

//更新状态条
UpdateStatus("Hello world.");

//主程序代码
//用参数中的字符串刷新状态条的显示文本
private void refreshStatusBar(string stringStatus)
{
    //更新状态条
    statusBarStatus.Text = stringStatus;
}

Explorer 控制

  在Explorer控制内部,我使用了WMI的Win32_LogicalDisk类来访问所有本地的及网络映射的驱动器。要访问驱动器的相关信息,我得先使用一个ManagementObjectSearcher对象来获取一个包含了我所需驱动器信息的ManagementOjbectCollection对象(译注:原文用的是class,我认为不准确,因此改译为对象)。之后,我们就可以自由支配所有这些驱动器的信息。(比如驱动器名、类型、卷标、标识等等)。你也可以只查询剩余空间低于1MByte的驱动器的信息,对此只需要改变ManagementObjectSearcher参数而已:

//译注:这句就是查询剩余空间低于1MByte的SQL语句,用在ManagementObjectSearcher的构造时。
//是不是很象一般数据库编程里用的SQL语句啊?
Select * From Win32_LogicalDisk Where FreeSpace < 1000000

//取得驱动器集
ManagementObjectSearcher query =                        new ManagementObjectSearcher ("SELECT * From Win32_LogicalDisk "); 

ManagementObjectCollection queryCollection = query.Get(); 

//遍历每个对象,以获取每个驱动器的信息
foreach ( ManagementObject mo in queryCollection)
{
    switch (int.Parse( mo["DriveType"].ToString()))
    {
        case Removable: //可移动驱动器
            imageIndex = 5;
            selectIndex = 5;
            break; 

        case LocalDisk: //本地驱动器
            imageIndex = 6;
            selectIndex = 6;
            break; 

        case CD: //CD-ROM驱动器
            imageIndex = 7;
            selectIndex = 7;
            break; 

        case Network: //网络驱动器
            imageIndex = 8;
            selectIndex = 8;
            break; 

        default: //缺省:文件夹
            imageIndex = 2;
            selectIndex = 3;
            break;
    } 

    //获取驱动器名
    Console.WriteLine("Drive: " + mo["Name"].ToString());
} 

SystemInfo 控制

  SystemInfo控制用于显示你的本地计算机或者远程计算机上一些不同类型的信息。它首先定义一个ConnectionOptions对象,并设置好该对象的UserName与Password属性,准备用此来建立一个WMI的连接。之后再以该ConnectionOptions对象为参数,使用本地或远程计算机的主机名创建一个ManagementScope对象。

//建立远程计算机连接
ConnectionOptions co = new ConnectionOptions();

co.Username = textUserID.Text;
co.Password = textPassword.Text;

//将管理范围确定为该远程计算机
System.Management.ManagementScope ms = new System.Management.ManagementScope
					("\\\\" + stringHostName + "\\root\\cimv2", co); 

  现在我们就要准备通过创建一个ObjectQuery 成员对象来访问这个系统上的信息了。我们需要利用这个ObjectQuery对象和之前的那个ManagementScope对象来创建一个ManagementObjectSearcher对象。然后再调用该ManagementObjectSearcher对象的Get()方法来执行ObjectQuery对象定义的那个查询命令,并将查询结果返回到一个ManagementObject对象集中。

//查询操作系统信息
oq = new System.Management.ObjectQuery("SELECT * FROM Win32_OperatingSystem");

query = new ManagementObjectSearcher(ms,oq);

queryCollection = query.Get();

foreach ( ManagementObject mo in queryCollection)
{
    //在树中创建一个操作系统的子结点
    createChildNode(nodeCollection, "Operating System: " + mo["Caption"]);
    createChildNode(nodeCollection, "Version: " + mo["Version"]);
    createChildNode(nodeCollection, "Manufacturer : " + mo["Manufacturer"]);
    createChildNode(nodeCollection, "Computer Name : " + mo["csname"]);
    createChildNode(nodeCollection, "Windows Directory : " + mo["WindowsDirectory"]);
}

  如果你只关心本地主机的信息,那你可以不用创建ConnectionOption,ManagementScope,与ObjectQuery这些对象。你只需要用SQL查询语句串创建一个ManagementObjectSearcher对象,然后直接调用该对象的Get()方法,就能以一个ManagementObjectCollection对象的形式返回本地主机的信息了。

ManagementObjectSearcher query = new ManagementObjectSearcher("SELECT * From Win32_OperatingSystem");
ManagementObjectCollection queryCollection = query.Get();

  SystemInfo控制也用于显示计算机相关的其他信息:系统制造商,处理器,BIOS,时区,内存、网络连接、显卡等等。用于查询这些信息的代码只是在SQL查询语句和返回属性上不同而已,所以为了减少篇幅我就不把代码写出来了。具体的代码你可以看下载包里的内容。

Service 控制

Service控制使用了这样的一个查询来返回系统中所有服务的信息:

SELECT * FROM Win32_Service

  为了能启动或者停止一个服务,我为ListView动态地创建了一个弹出式菜单(上下文菜单)。你在列表的某个项上单击鼠标右键时,一个启动或停止服务(依赖于服务的当前运行状态)的菜单就会弹出。当菜单项被点击后,我需要利用这样的查询语句获得该服务的ManagementObject对象:

SELECT * FROM Win32_Service WHERE Name = ''ServiceName''

  接着我就可以通过调用ManagementObject.InvokeMethod()方法来启动或者停止该服务了。InvokeMethod()方法的第一个参数是一个Observer。我传递一个ManagementOperationObserver对象给这个方法,来管理这些异步操作,以及相应的异步事件与信息。通过检查返回的completionHandlerObj.ReturnObject的returnValue属性,我们就可以确定操作是否成功了。

/// 
/// List view的鼠标右击事件导致动态上下文菜单的生成
/// 

private void listViewServices_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
    System.Windows.Forms.ListView listViewObject = (System.Windows.Forms.ListView) sender;
    ContextMenu mnuContextMenu = new ContextMenu();
    MenuItem menuItem = new MenuItem();
    ManagementObjectCollection queryCollection;

    //是否是鼠标右键单击
    if (e.Button == System.Windows.Forms.MouseButtons.Right)
    {
        //取得服务的名称
        ServiceName = listViewObject.GetItemAt(e.X, e.Y).Text;

        //取得列表项
        ServiceItem = listViewObject.GetItemAt(e.X,e.Y);

        //创建弹出式菜单
        listViewObject.ContextMenu = mnuContextMenu;

        try
        {
            //取得特定的服务对象
            queryCollection = getServiceCollection("SELECT * FROM Win32_Service Where Name =
		''" + ServiceName + "''");
            foreach ( ManagementObject mo in queryCollection)
            {
                //据服务的当前状态创建相应的菜单
                if (mo["Started"].Equals(true))
                {
                    menuItem.Text = "Stop";

                    //设置动作Action属性
                    ServiceAction = "StopService";
                }
                else
                {
                    menuItem.Text = "Start";

                    ServiceAction = "StartService";
                }
                mnuContextMenu.MenuItems.Add(menuItem);

                // 给菜单项挂上事件处理函数
                menuItem.Click  += new System.EventHandler(this.menuItem_Click);
            }
        }
        catch (Exception e1)
        {
            MessageBox.Show("Error: " + e1);
        }
    }
}

/// 
/// List view上下文菜单的事件响应函数
///
///
///
private void menuItem_Click(object sender, System.EventArgs e)
{
    ManagementObjectCollection queryCollection;
    ListViewItem lvItem;

    //设置一个异步回调函数的句柄
    ManagementOperationObserver observer = new ManagementOperationObserver();
    completionHandler.MyHandler completionHandlerObj = new completionHandler.MyHandler();
    observer.ObjectReady += new ObjectReadyEventHandler(completionHandlerObj.Done);

    //获得特定的服务对象
    queryCollection = getServiceCollection("Select * from Win32_Service Where Name =''" +
                           ServiceName + "''");

    //更新状态条
    updateStatus("Starting/Stopping service..."); 

    foreach ( ManagementObject mo in queryCollection)
    {
        //启动或者停止服务
        mo.InvokeMethod(observer, ServiceAction, null);
    }
    //等待,直到invoke调用完成或者超时5秒后 

    int intCount = 0;  

    while(!completionHandlerObj.IsComplete)
    {
        if
        (intCount >  10)
        {
            MessageBox.Show("Terminate process timed out.", "Terminate Process Status");
            break;
        }

        //等待0.5秒
        System.Threading.Thread.Sleep(500); 

        //增加计数器
        intCount++;
    } 

    //检查是否成功执行了
    if (completionHandlerObj.ReturnObject.Properties["returnValue"].Value.ToString() == "0")
    {
        //成功
        lvItem = ServiceItem;

        if (ServiceAction == "StartService")
            lvItem.SubItems[2].Text = "Started";
        else
            lvItem.SubItems[2].Text = "Stop";
    }
    else
    {
        //错误信息
        string stringAction;

        if (ServiceAction == "StartService")
            stringAction = "start";
        else
            stringAction = "stop";

        MessageBox.Show("Failed to " + stringAction + " service " + ServiceName + ".",
                                        "Start/Stop Service Failure");
    }

    //清除对象
    ServiceName = "";
    ServiceAction = "";
    ServiceItem = null;

    //更新状态条
    updateStatus("Ready");
    this.Update();
}

//----------------------------------
// 完整的处理器
//----------------------------------
using System;
using System.Management;

namespace completionHandler
{
    /// 
    /// MyHandler类在InvokeMethod()调用完成后处理通知
    ///
    public class MyHandler
    {
        private bool isComplete = false;
        private ManagementBaseObject returnObject;

        /// 
        /// 当InvokeMethod完成后触发Done事件
        ///
        public void Done(object sender, ObjectReadyEventArgs e)
        {
            isComplete = true;
            returnObject = e.NewObject;
        }

        /// 
        /// 取IsComplete属性
        ///
        public bool IsComplete
        {
            get
            {
                return isComplete;
            }
        }

        /// 
        /// 属性允许访问主函数里的返回结果
        ///
        public ManagementBaseObject ReturnObject
        {
            get
            {
                return returnObject;
            }
        }

    }
}

Process 控制

  Process控制显示系统中运行着的进程,启动进程的用户,CPU使用率,内存的使用情况。要获得进程的用户信息,需要调用GetOwner(User, Domain)方法,其中的User 与Domain是传出参数。我们如何才能从InvokeMethod()调用中取回这些传出型参数呢?这实际取决于我们是如何实现这个InvokeMethod()方法的。如果我们不需要管理异步操作,那么我们只需要传递一个string数组给InvokeMethod()以获取传出的参数值。否则,我们就无需给InvokeMethod()传递任何的参数了,而是从completionHandlerObj.ReturnObject属性中取回传出的参数值。

//-------------------------------------------------
//在不使用observer对象的情况下获取进程用户信息
//--------------------------------------------------
//为InvokeMethod()方法准备参数表

string[] methodArgs = {"", ""}; 

//获取进程用户信息
mo.InvokeMethod("GetOwner", methodArgs); 

//methodArgs[0] 进程用户
//methodArgs[1] 进程的域 

//-----------------------------------------------
//在使用observer对象的情况下获取进程用户信息
//-----------------------------------------------
mo.InvokeMethod(observer,"GetOwner", null);

while (!completionHandlerObj.IsComplete)
{
    System.Threading.Thread.Sleep(500);
} 

if (completionHandlerObj.ReturnObject["returnValue"].
    ToString() == "0") 

    structProcess.stringUserName = completionHandlerObj.
        ReturnObject.Properties["User"].Value.ToString();
else
    structProcess.stringUserName = "";

终止进程

  终止一个特定的进程与启动或停止一个服务类似。首先还是要取得选中进程对应的 ManagementObject 对象,然后通过调用InvokeMethod(observer, "Terminate", null) 来杀死一个进程。

//设置一个异步回调的句柄
ManagementOperationObserver observer = new ManagementOperationObserver();
completionHandler.MyHandler completionHandlerObj = new completionHandler.MyHandler();
observer.ObjectReady  += new ObjectReadyEventHandler(completionHandlerObj.Done);

//获取进程的ManagementObject
queryCollection = getProcessCollection("Select * from Win32_Process Where ProcessID =
				''" + ProcessID + "''");

//更新状态条
updateStatus("Invoking terminate process");

foreach ( ManagementObject mo in queryCollection)
{
    //启动或者停止服务(译注:作者真懒?)
    mo.InvokeMethod(observer, "Terminate", null);
}

//等待,直到invoke调用完成或者超时5秒后 

int intCount = 0;
while (!completionHandlerObj.IsComplete)
{
    if (intCount == 10)
    {
        MessageBox.Show("Terminate process timed out.", "Terminate Process Status");
        break;
    }
    //等待0.5秒
    System.Threading.Thread.Sleep(500); 

    //增加计数器
    intCount++;
} 

if (intCount != 10)
{
    //InvokeMethod尚未超时
    if (completionHandlerObj.ReturnObject.Properties["returnValue"].Value.ToString() == "0")
    {
        lvItem = ProcessItem;
        lvItem.Remove();
    }
    else
    {
        MessageBox.Show("Error terminating process.",
            "Terminate Process");
    }
}

创建进程

  要创建一个进程,我们需要调用ManagementClass 的InvokeMethod ()方法。我们可以这么创建一个ManagementClass对象:

ManagementClass processClass = New ManagementClass(ms,path,null);

  其中的ms是一个ManagementScope对象,path是一个ManagementPath对象。ManagementScope对应了一个管理操作对应的范围。ManagementPath则提供了一个对Win32_Process进行解析与创建的封装。在调用ManagementClass.InvokeMethod(observer, methodName, inParameters)之前,我们还需要做点其他的准备。我们得把四个传入参数封装到一个object数组里。

uint32 Create(string CommandLine,
				string CurrentDirectory,
				Win32_ProcessStartup ProcessStartupInformation,
			    uint32* ProcessId);

参数说明

  • CommandLine – [传入] 要执行的命令行。如果有必要,系统会自动在末尾追加一个null字符来截断该串,表示真正要执行的文件。
  • CurrentDirectory – [传入] 子进程的当前驱动器与当前目录。这个串必须保证当前目录能解析到一个已知的路径。用户可以定义一个绝对的或相对的路径作为当前的工作目录。如果该参数为null,新创建的进程就会使用父进程的同一路径。这样做是主要是为了保证操作系统外壳能确定应用程序启动的初始驱动器和工作目录。
  • ProcessStartupInformation – [传入] 这是一个Windows进程的启动配置,请参见Win32_ProcessStartup.
  • ProcessId – [传出] 一个全局的用于标识进程的标识符。这个值的生存期自进程创建时起,至进程终结时止。
//为InvokeMethod()准备参数
object[] methodArgs = {stringCommandLine, null, null, 0};

//执行这个方法
processClass.InvokeMethod (observer, "Create", methodArgs);

  下面是创建进程的实现代码。我编写了一个CreateProcess()函数接受一个传入的命令行字符串stringCommandLine作为参数。当你调用CreateProcess("Calc.exe")时,就意味着创建了一个新的计算器的进程。就这么简单。

/// 
/// 在一个本地或者远程机器上调用Create方法
///
///
private void CreateProcess(string stringCommandLine)
{
    //设置一个异步回调的句柄
    ManagementOperationObserver observer = new ManagementOperationObserver();
    completionHandler.MyHandler completionHandlerObj = new completionHandler.MyHandler();
    observer.ObjectReady  += new ObjectReadyEventHandler(completionHandlerObj.Done);

    string stringMachineName = "";

    //连接到远程计算机
    ConnectionOptions co = new ConnectionOptions();

    if (radioMachine.Checked == true)
    {
        stringMachineName = "localhost";
    }
    else
    {
        stringMachineName = textIP.Text;
    }

    if (stringMachineName.Trim().Length == 0)
    {
        MessageBox.Show("Must enter machine IP address or name.");
        return;
    }

    //获取用户名与密码
    if (textUserID.Text.Trim().Length   > 0)
    {
        co.Username = textUserID.Text;
        co.Password = textPassword.Text;
    }

    //获取指向机器的接入点
    System.Management.ManagementScope ms = new System.Management.ManagementScope("\\\\" +
	        stringMachineName + "\\root\\cimv2", co);      

  //获取进程的路径
    ManagementPath path = new ManagementPath( "Win32_Process");

    //获取将要被调用的进程的对象
    ManagementClass processClass = new ManagementClass(ms,path,null);

    //更新状态条
    updateStatus("Create process " + stringCommandLine + ".");

    //为方法准备参数
    object[] methodArgs = {stringCommandLine, null, null, 0};

    //执行方法
    processClass.InvokeMethod (observer, "Create", methodArgs);

    //等待,直到invoke调用完成或者超时5秒后
    int intCount = 0;
    while (!completionHandlerObj.IsComplete)
    {
        if (intCount > 10)
        {
            MessageBox.Show("Create process timed out.", "Terminate Process Status");
            break;
        }
         //等待0.5秒
        System.Threading.Thread.Sleep(500); 

         //增加计数器
        intCount++;
    } 

    if (intCount != 10)
    {
        //InvokeMethod尚未超时
        //检查是否出现错误
        if (completionHandlerObj.ReturnObject.Properties["returnValue"].Value.ToString() == "0")
        {
            //刷新进程列表
            this.Refresh();
        }
        else
        {
            MessageBox.Show("Error creating new process.",
                "Create New Process");
        }
    }

    //更新状态条
    updateStatus("Ready");
    this.Update();
}

总结

  编写这个演示用的WMI应用程序,增加了我不少的经验。这只展示了WMI很小一部分的功能。我想有了我给出的注释,代码还容易理解吧。

你可以使用WMI完成下列工作:

  • 控制硬件与软件
  • 监控事件
  • 就某个事件运行一个脚本
  • 就某个事件发出一封Email

MSDN 中关于WMI的内容:Windows Management Instrumentation



Paul Li 是在纽约的 Dell 专业服务的首席软件安全顾问

用户不喜欢反应慢的程序。在执行耗时较长的操作时,使用多线程是明智之举,它可以提高程序 UI 的响应速度,使得一切运行显得更为快速。在 Windows 中进行多线程编程曾经是 C++ 开发人员的专属特权,但是现在,可以使用所有兼容 Microsoft .NET 的语言来编写。

不过Windows 窗体体系结构对线程使用制定了严格的规则。如果只是编写单线程应用程序,则没必要知道这些规则,这是因为单线程的代码不可能违反这些规则。然而,一旦采用多线程,就需要理解 Windows 窗体中最重要的一条线程规则:除了极少数的例外情况,否则都不要在它的创建线程以外的线程中使用控件的任何成员。本规则的例外情况有文档说明,但这样的情况非常少。这适用于其类派生自 System.Windows.Forms.Control 的任何对象,其中几乎包括 UI 中的所有元素。所有的 UI 元素(包括表单本身)都是从 Control 类派生的对象。此外,这条规则的结果是一个被包含的控件(如,包含在一个表单中的按钮)必须与包含它控件位处于同一个线程中。也就是说,一个窗口中的所有控件属于同一个 UI 线程。实际中,大部分 Windows 窗体应用程序最终都只有一个线程,所有 UI 活动都发生在这个线程上。这个线程通常称为 UI 线程。这意味着您不能调用用户界面中任意控件上的任何方法,除非在该方法的文档说明中指出可以调用。该规则的例外情况(总有文档记录)非常少而且它们之间关系也不大。请注意,以下代码是非法的:

        private Thread myThread;

        private void Form1_Load(object sender, EventArgs e)

        {

            myThread = new Thread(new ThreadStart(RunsOnWorkerThread));

            myThread.Start();

        }

        private void RunsOnWorkerThread()

        {

            label1.Text = "myThread线程调用UI控件";

    }

如果您在 .NET Framework 1.0 版本中尝试运行这段代码,也许会侥幸运行成功,或者初看起来是如此。这就是多线程错误中的主要问题,即它们并不会立即显现出来。甚至当出现了一些错误时,在第一次演示程序之前一切看起来也都很正常。但不要搞错我刚才显示的这段代码明显违反了规则,并且可以预见,任何抱希望于“试运行时良好,应该就没有问题”的人在即将到来的调试期是会付出沉重代价的。

 

下面我们来看看有哪些方法可以解决这一问题。

 

 

一、System.Windows.Forms.MethodInvoker 类型是一个系统定义的委托,用于调用不带参数的方法。

 

        private Thread myThread;

        private void Form1_Load(object sender, EventArgs e)

        {

            myThread = new Thread(new ThreadStart(RunsOnWorkerThread));

            myThread.Start();

        }

        private void RunsOnWorkerThread()

        {

            MethodInvoker mi = new MethodInvoker(SetControlsProp);

            BeginInvoke(mi);

        }

        private void SetControlsProp()

        {

            label1.Text = "myThread线程调用UI控件";

        }

二、直接用System.EventHandle(可带参数)

        private Thread myThread;

        private void Form1_Load(object sender, EventArgs e)

        {

            myThread = new Thread(new ThreadStart(RunsOnWorkerThread));

            myThread.Start();

        }

        private void RunsOnWorkerThread()

        {

            //DoSomethingSlow();

            string pList = "myThread线程调用UI控件";

            label1.BeginInvoke(new System.EventHandler(UpdateUI), pList);

        }

        //直接用System.EventHandler,没有必要自定义委托

        private void UpdateUI(object o, System.EventArgs e)

        {

           //UI线程设置label1属性

            label1.Text = o.ToString() + "成功!";

        }

 

 

三、包装 Control.Invoke

 

虽然第二个方法中的代码解决了这个问题,但它相当繁琐。如果辅助线程希望在结束时提供更多的反馈信息,而不是简单地给出“Finished!”消息,则 BeginInvoke 过于复杂的使用方法会令人生畏。为了传达其他消息,例如“正在处理”、“一切顺利”等等,需要设法向 UpdateUI 函数传递一个参数。可能还需要添加一个进度栏以提高反馈能力。这么多次调用 BeginInvoke 可能导致辅助线程受该代码支配。这样不仅会造成不便,而且考虑到辅助线程与 UI 的协调性,这样设计也不好。对这些进行分析之后,我们认为包装函数可以解决这两个问题。

        private Thread myThread;

        private void Form1_Load(object sender, EventArgs e)

        {

            myThread = new Thread(new ThreadStart(RunsOnWorkerThread));

            myThread.Start();

        }

        private void RunsOnWorkerThread()

        {

            ////DoSomethingSlow();

            for (int i = 0; i < 100; i++)

            {

                ShowProgress( Convert.ToString(i)+"%", i);

                Thread.Sleep(100);

            }

        }

        public void ShowProgress(string msg, int percentDone)

        {

            // Wrap the parameters in some EventArgs-derived custom class:

            System.EventArgs e = new MyProgressEvents(msg, percentDone);

            object[] pList = { this, e };

 

            BeginInvoke(new MyProgressEventsHandler(UpdateUI), pList);

        }

        private delegate void MyProgressEventsHandler(object sender, MyProgressEvents e);

        private void UpdateUI(object sender, MyProgressEvents e)

        {

            lblStatus.Text = e.Msg;

            myProgressControl.Value = e.PercentDone;

        }

    public class MyProgressEvents : EventArgs

    {

        public string Msg;

        public int PercentDone;

        public MyProgressEvents(string msg, int per)

        {

            Msg = msg;

            PercentDone = per;

        }

 }

ShowProgress 方法对将调用引向正确线程的工作进行封装。这意味着辅助线程代码不再担心需要过多关注 UI 细节,而只要定期调用 ShowProgress 即可。

如果我提供一个设计为可从任何线程调用的公共方法,则完全有可能某人会从 UI 线程调用这个方法。在这种情况下,没必要调用 BeginInvoke,因为我已经处于正确的线程中。调用 Invoke 完全是浪费时间和资源,不如直接调用适当的方法。为了避免这种情况,Control 类将公开一个称为 InvokeRequired 的属性。这是“只限 UI 线程”规则的另一个例外。它可从任何线程读取,如果调用线程是 UI 线程,则返回假,其他线程则返回真。这意味着我可以按以下方式修改包装:

        public void ShowProgress(string msg, int percentDone)

        {

            if (InvokeRequired)

            {

                // As before

                //…

            }

            else

            {

                // We’re already on the UI thread just

                // call straight through.

                UpdateUI(this, new MyProgressEvents(msg,PercentDone));

            }

        }

参考:

http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/softwaredev/misMultithreading.mspx?mfr=true

 

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=715064