http://blog.joycode.com/sumtec/articles/25292.aspx
.NET CF杂谈系列(二)—— 拿起你的苍蝇拍
2004年6月22日 10:50 - (阅读:1950;评论:2)
.NET CF 1.0 里面有好多的臭虫啊!简直就是多不胜数——为了引起你的注意,我只好进行一定的夸大了。事实上有多少的错误大家可以看看.NET CF 1.0 SP2(pre-release)里面显示的Defect Fix List就知道了:
Defect Fix List:
- Fixed crash when SQLClient or SQLServerCe is installed.
- DataRowCollection.Find() method throws wrong exception
- Fixed erroneous parsing of HttpWebRequest response stream
- Form does not get the focus when all controls are removed from the form
- InputPanel Button disappears from MainMenu on Pocket PC 2002
- InputPanel's EnabledChanged Event fires inspite of setting the enabled property to the same value.
- IrDAClient.DiscoverDevices device names garbled
- Memory leak in the NumericUpDown, DomainUpDown, ComboBox and TextBox controls
- Native Exception occurs when using the get accessor for Control.Capture
- Navigation bar flickers when new form is being loaded.
- ObjectDisposedException occurs when DataBinding a control to a form shown via Show()
- OK button eventually goes away when re-using recycled form for ShowDialog()
- Setting the InputPanel.Enabled property when the form is closing throws an exception
- SIP button does not go away when the toolbar control is removed from a form that does not have a menu
- The Focus method does not work correctly for the NumericUpDown and DomainUpDown controls
- The Text property of a ListView column is reset to “” when removing the column from the ListView and then adding it back.
- ContextMenu: Fixed TreeView issues on Pocket PC 2003
- ContextMenu: Fixed side effects on controls like Scrollbar and Label
- ContextMenu: erroneous functionality when bound to ListBox
- ContextMenu: Showing a context menu on Pocket PC for a textbox unselects the selected text in the textbox
- ContextMenu: Empty ContextMenu on Pocket PC should not pop up
- Web services: After repeatedly canceling asynchronous web request, all web service calls fail
- conversion of 0.0F to a string and results in a memory leak
- Web services: An XmlException is thrown when deserializing a qualified name that has no prefix and there is no default namespace defined
- Web services: Incorrect handling of null headers in SOAP message
这里还不包括1.0里面已经修改了的部分。上面这对文字有点苍白,看了也不会有什么感性的认识,下面我就具体讲一下某些我遇到的、并且觉得经常都会遇到的Bug给大家说一下。说这些Bug的时候我是基于没有任何补丁的1.0版本,因为你无法确定目标硬件上面的版本是否打过补丁,所以我会建议你尽可能假设是在没有打过补丁的机器上面运行。(和PC机上面的操作不一样,PDA概念上就是一个硬件,通过硬件型号来区分操作系统的版本号,所以你没有办法辨别补丁的情况。当然,你可以一台一台的去研究,但是在你没有获得这个硬件的时候,你几乎没有办法从型号上知道补丁的情况。甚至同一个公开型号的不同批次,不定情况都很可能不一样。当然,如果那样的话,内部Rom上面记载的内部型号会不一样的。)
首先我想告诉大家的是一个TextBox上面的Bug,这个Bug在上面给出的List里面有提到,也就是说在SP2里面已经修正了。这个Bug是怎么回事呢?就是如果你在TextBox里面选中了一段文字,然后在上面弹出一个菜单,你就会发现刚才选中了的文字就消失掉了。这意味着如果你想在TextBox里面实现Copy&Paste,那没门!因为在菜单弹出的时候,TextBox的SelectionLength已经是0了。这个问题能够解决吗?可以,打SP2。“不不,我的意思是如果不打补丁能不能够……”答案是不行的。.NET CF里面一个可恶的地方是它在很多地方都是Ground up rebuild,所以有很多东西和.NET Framework完全不一样,包括机制上面。我告诉各位,再.NET CF里面,你没有办法自己重写一个WndProc来处理消息,也不是所有的空间都有Key/Mouse的事件的——TextBox就没有Mouse事件,所以你想通过Mouse事件首先把选中文字的范围得到,然后再弹出菜单也是不可能的。
另外一个Bug可能我经常遇到,而大家则不会经常遇到,可能是因为我的设计比较少见。这个问题出在Microsoft.WindowsCE.Forms.dll里面的InputPanel上面,这个控件上面的问题非常的多!首先就是,如果一个InputPanel里面的EnabledChanged事件被连接了,比如在Form1里面有一个InputPanel1_EnabledChanged(object sender, EventArgs e)这样的函数连接到该事件上面,那么这个事件完全有可能在Form1已经被Destroy的情况下仍然激发。这一激发就有可能引起严重的问题,比如该函数内部有一个if (Form1.Visible)这样的语句,那么就会引发ObjectDisposed异常。为了解决这个问题,我们不得不在Form1的Dispose函数里面去掉这一个EnabledChanged事件的连接。观察InputPanel里面的代码,我们很容易得知,实际上InputPanel的作用只是通知系统将SIP的消息转到内部的一个_SIPWnProc上面进行处理,如果不手动解除这一消息转发过程,则无论EnabledChanged所连接的对象或者上一次显示出来的窗口是否已经Disposed,都会引发这一事件的。当然,按照常规来说,一个Form上面都有一个InputPanel,每一个Form都是用自己InputPanel,那就应该没有问题了——因为Form在Dispose的时候会依次调用上面每一个子控件的Dispose函数。很不幸的是,上面这一句话几乎每一段都是错的!
首先在.NET CF里面,Control并不会在Dispose里面自动调用每一个控件的Dispose,因此你不能够保证每一个子控件都会在父控件Dispose的时候立即被通知。也就是说有可能出现Form1已经被Dispose了,但是Form1上面的InputPanel仍然在工作,甚至在引发EnabledChanged。注意上面这一句话只是有可能,一般大家的运气都会足够的好,不会遇到这样的问题。另外一个错误的地方是,如果每一个Form上面都有一个InputPanel,并不能够解决问题,甚至有可能带来一些其他的问题。由于实际上SIP(就是右下角的选择语言输入法的按钮以及整个语言输入系统)只有一个,所以当SIP的状态发生改变的时候,所有Form上面的InputPanel都有可能会得到通知,并引发EnabledChanged事件。此外,多个InputPanel有可能会扰乱焦点系统,这个问题如果不是有人告诉你的话,你一定会郁闷好几天也找不出问题来。比如说你有Form1和Form2,分别都有一个InputPanel,首先激活的是Form1,然后显示的是Form2。现在你希望在某一个TextBox在获得焦点的时候将SIP显示出来,方便用户直接输入(不需要用户在点击一下右下角的按钮来探出输入区),这个时候问题就出来了。你会发现无论你输入什么都不会进入刚才用户点击的TextBox上面,按照分析,TextBox上面获得了焦点才会引发那个会显示SIP的事件,现在看到SIP弹出来了那就应该是获得了焦点了。可是文字就是不会显示上去,怎么回事呢?原来在SIP被弹出来的时候会引发一些EnabledChanged事件,MS不知道在背后做了一些什么小动作,反正就是有可能将焦点转移到别的Form上面,因此就会无法输入文字。
解决的办法有两个:1、用户说明书里面要求用户在点击该文本框的时候“长按”;2、一个程序只用一个唯一的InputPanel控件,并且每个Form在Dispose的时候去掉与EnabledChanged事件的关联。
看到这里也许会希望我给出一个代码作为这个Bug的例子,但是我非常抱歉得告诉大家,这不是很现实,因为实际上要引发这个问题并不容易,代码的长度不会短。我想可能各位如果自己做实验的话很可能会得到一个没有这个Bug的结论,原因是我这里的描述简化了一些步骤,比如需要通过动态加载Assembly,然后二次进入等等,要完整的描述可能会非常困难。并且对于这个问题我并没有做过完整的测试,只得到了一个“充分条件”,有可能在一些其他的情况下面也会遇到这个问题。所以这里还是忠告大家采信我的结论,并尽可能用上面的方法2来避免这个问题,这是我多个月的实践结果。这样的问题有时候是很难调试出来的,避免这个问题可能会为你节省不少的时间精力。
最后一个Bug不是.NET CF本身的问题,应该是ToolHelp的问题。在.NET CF里面是没有办法调试动态加载的代码的,也就是说整个动态加载的代码就是一个黑箱。在你打算用动态加载代码的技术之前,请首先谨慎考虑,因为调试这里面的问题会变得非常的困难。如果您最后还是决定要使用动态加载代码的技术,那么这里有几个忠告:
1、在动态调用之前,首先通过直接运行的方式来进行调试。
2、如果在动态调用的时候出现了异常,首先考虑再次通过直接运行的方式来进行调试。
3、要切记动态调用的时候可能会和直接运行的时候有很多不一样的地方,并且有一些功能可能有问题,例如可能异常机制会出现问题——包括:
? a、可能截获不到异常
? b、可能截获到异常,但是有问题:
???? i、可能不是处于调用堆栈顶层的try...catch截获到了,而是底层的try...catch截获到了
???? ii、可能正确截获到了异常,但是在catch块内想通过MessageBox或者ShowModal的方式来显示异常信息,但结果却发现完全不会显示出来,或者显示出来之后就立刻执行下一句话(以非Modal的形式显示)
???? iii、可能处于处于堆栈顶层的和底层的try...catch同时截获到异常
??? (对于异常处理过程本身出现异常,这个我无法给出预言,只能够让你做好心理准备,遇到这样的问题不要不知所措)
4、尽可能避免Singleton模式,这种模式在动态代码加载的方式里面是危险的,如果你确定要使用Singleton模式,那么一定要仔细考虑清楚整个的状态转换过程,尤其是可能引用的一些外部控件。有可能你引用的控件已经被Dispose了,但是你没有办法通过IsDisposed函数来确定——没有这个函数。如果你这是一个控件,并且采用this.Parent = xxx之类的方式不停的变化父控件,甚至有可能因为父控件被Dispose之后一段时间后,由GC把这个你这个Singleton的空间给Dispose了,也就是说有可能随时引发ObjectDisposed异常。因此建议您一定要好好的设计Dispose函数里面的代码,并且提供判断是否已经Disposed并提供新的实例的能力。
5、事实上还有好多其他的模式是危险的,比如说FlyWeight模式或者ProtoType模式。因为你可能没有考虑到一个情况——有可能整个加载的模块会退出,然后重新进入。如果真的没有考虑,那么就可能出现下列情况之一:
? a、有可能因为每次进入都会进行初始化,结果引起多次产生同一个含义的实例
? b、有可能因为你在.cctor(静态构造函数)里面进行初始化来避免多次初始化的问题,但是有可能出现上一次退出造成部分对象被Dispose而你却不知道,第二次进入使用的时候引发ObjectDisposed异常。
? c、在b的条件底下还有一种可能,就是你忘了有二次进入的可能性,在退出的时候手动注销了这些对象,结果第二次进入的时候由于不会再次调用.cctor,所以没有初始化,得到NullReferenceException。
? d、其实还有很多其他的问题,比如可能你的这个动态代码为不同的应用提供相似的服务,所以可能有些东西在不同的条件下需要有不同的初始化,等等。
6、永远记住重入的问题。不一定采用什么模式才会引起这个问题的,有时候不是你采用了什么模式,而是MS的空间采用了什么模式。如果你有什么很莫名其妙的,在动态加载模式调不出来,但是在直接运行状态下却没有任何问题的话,那么首先考虑可能是重新进入引起的问题,或者进入了模块A再进入模块B这样引起的问题。
下一次也许会谈一下优化的问题。
Defect Fix List:
- Fixed crash when SQLClient or SQLServerCe is installed.
- DataRowCollection.Find() method throws wrong exception
- Fixed erroneous parsing of HttpWebRequest response stream
- Form does not get the focus when all controls are removed from the form
- InputPanel Button disappears from MainMenu on Pocket PC 2002
- InputPanel's EnabledChanged Event fires inspite of setting the enabled property to the same value.
- IrDAClient.DiscoverDevices device names garbled
- Memory leak in the NumericUpDown, DomainUpDown, ComboBox and TextBox controls
- Native Exception occurs when using the get accessor for Control.Capture
- Navigation bar flickers when new form is being loaded.
- ObjectDisposedException occurs when DataBinding a control to a form shown via Show()
- OK button eventually goes away when re-using recycled form for ShowDialog()
- Setting the InputPanel.Enabled property when the form is closing throws an exception
- SIP button does not go away when the toolbar control is removed from a form that does not have a menu
- The Focus method does not work correctly for the NumericUpDown and DomainUpDown controls
- The Text property of a ListView column is reset to “” when removing the column from the ListView and then adding it back.
- ContextMenu: Fixed TreeView issues on Pocket PC 2003
- ContextMenu: Fixed side effects on controls like Scrollbar and Label
- ContextMenu: erroneous functionality when bound to ListBox
- ContextMenu: Showing a context menu on Pocket PC for a textbox unselects the selected text in the textbox
- ContextMenu: Empty ContextMenu on Pocket PC should not pop up
- Web services: After repeatedly canceling asynchronous web request, all web service calls fail
- conversion of 0.0F to a string and results in a memory leak
- Web services: An XmlException is thrown when deserializing a qualified name that has no prefix and there is no default namespace defined
- Web services: Incorrect handling of null headers in SOAP message
这里还不包括1.0里面已经修改了的部分。上面这对文字有点苍白,看了也不会有什么感性的认识,下面我就具体讲一下某些我遇到的、并且觉得经常都会遇到的Bug给大家说一下。说这些Bug的时候我是基于没有任何补丁的1.0版本,因为你无法确定目标硬件上面的版本是否打过补丁,所以我会建议你尽可能假设是在没有打过补丁的机器上面运行。(和PC机上面的操作不一样,PDA概念上就是一个硬件,通过硬件型号来区分操作系统的版本号,所以你没有办法辨别补丁的情况。当然,你可以一台一台的去研究,但是在你没有获得这个硬件的时候,你几乎没有办法从型号上知道补丁的情况。甚至同一个公开型号的不同批次,不定情况都很可能不一样。当然,如果那样的话,内部Rom上面记载的内部型号会不一样的。)
首先我想告诉大家的是一个TextBox上面的Bug,这个Bug在上面给出的List里面有提到,也就是说在SP2里面已经修正了。这个Bug是怎么回事呢?就是如果你在TextBox里面选中了一段文字,然后在上面弹出一个菜单,你就会发现刚才选中了的文字就消失掉了。这意味着如果你想在TextBox里面实现Copy&Paste,那没门!因为在菜单弹出的时候,TextBox的SelectionLength已经是0了。这个问题能够解决吗?可以,打SP2。“不不,我的意思是如果不打补丁能不能够……”答案是不行的。.NET CF里面一个可恶的地方是它在很多地方都是Ground up rebuild,所以有很多东西和.NET Framework完全不一样,包括机制上面。我告诉各位,再.NET CF里面,你没有办法自己重写一个WndProc来处理消息,也不是所有的空间都有Key/Mouse的事件的——TextBox就没有Mouse事件,所以你想通过Mouse事件首先把选中文字的范围得到,然后再弹出菜单也是不可能的。
另外一个Bug可能我经常遇到,而大家则不会经常遇到,可能是因为我的设计比较少见。这个问题出在Microsoft.WindowsCE.Forms.dll里面的InputPanel上面,这个控件上面的问题非常的多!首先就是,如果一个InputPanel里面的EnabledChanged事件被连接了,比如在Form1里面有一个InputPanel1_EnabledChanged(object sender, EventArgs e)这样的函数连接到该事件上面,那么这个事件完全有可能在Form1已经被Destroy的情况下仍然激发。这一激发就有可能引起严重的问题,比如该函数内部有一个if (Form1.Visible)这样的语句,那么就会引发ObjectDisposed异常。为了解决这个问题,我们不得不在Form1的Dispose函数里面去掉这一个EnabledChanged事件的连接。观察InputPanel里面的代码,我们很容易得知,实际上InputPanel的作用只是通知系统将SIP的消息转到内部的一个_SIPWnProc上面进行处理,如果不手动解除这一消息转发过程,则无论EnabledChanged所连接的对象或者上一次显示出来的窗口是否已经Disposed,都会引发这一事件的。当然,按照常规来说,一个Form上面都有一个InputPanel,每一个Form都是用自己InputPanel,那就应该没有问题了——因为Form在Dispose的时候会依次调用上面每一个子控件的Dispose函数。很不幸的是,上面这一句话几乎每一段都是错的!
首先在.NET CF里面,Control并不会在Dispose里面自动调用每一个控件的Dispose,因此你不能够保证每一个子控件都会在父控件Dispose的时候立即被通知。也就是说有可能出现Form1已经被Dispose了,但是Form1上面的InputPanel仍然在工作,甚至在引发EnabledChanged。注意上面这一句话只是有可能,一般大家的运气都会足够的好,不会遇到这样的问题。另外一个错误的地方是,如果每一个Form上面都有一个InputPanel,并不能够解决问题,甚至有可能带来一些其他的问题。由于实际上SIP(就是右下角的选择语言输入法的按钮以及整个语言输入系统)只有一个,所以当SIP的状态发生改变的时候,所有Form上面的InputPanel都有可能会得到通知,并引发EnabledChanged事件。此外,多个InputPanel有可能会扰乱焦点系统,这个问题如果不是有人告诉你的话,你一定会郁闷好几天也找不出问题来。比如说你有Form1和Form2,分别都有一个InputPanel,首先激活的是Form1,然后显示的是Form2。现在你希望在某一个TextBox在获得焦点的时候将SIP显示出来,方便用户直接输入(不需要用户在点击一下右下角的按钮来探出输入区),这个时候问题就出来了。你会发现无论你输入什么都不会进入刚才用户点击的TextBox上面,按照分析,TextBox上面获得了焦点才会引发那个会显示SIP的事件,现在看到SIP弹出来了那就应该是获得了焦点了。可是文字就是不会显示上去,怎么回事呢?原来在SIP被弹出来的时候会引发一些EnabledChanged事件,MS不知道在背后做了一些什么小动作,反正就是有可能将焦点转移到别的Form上面,因此就会无法输入文字。
解决的办法有两个:1、用户说明书里面要求用户在点击该文本框的时候“长按”;2、一个程序只用一个唯一的InputPanel控件,并且每个Form在Dispose的时候去掉与EnabledChanged事件的关联。
看到这里也许会希望我给出一个代码作为这个Bug的例子,但是我非常抱歉得告诉大家,这不是很现实,因为实际上要引发这个问题并不容易,代码的长度不会短。我想可能各位如果自己做实验的话很可能会得到一个没有这个Bug的结论,原因是我这里的描述简化了一些步骤,比如需要通过动态加载Assembly,然后二次进入等等,要完整的描述可能会非常困难。并且对于这个问题我并没有做过完整的测试,只得到了一个“充分条件”,有可能在一些其他的情况下面也会遇到这个问题。所以这里还是忠告大家采信我的结论,并尽可能用上面的方法2来避免这个问题,这是我多个月的实践结果。这样的问题有时候是很难调试出来的,避免这个问题可能会为你节省不少的时间精力。
最后一个Bug不是.NET CF本身的问题,应该是ToolHelp的问题。在.NET CF里面是没有办法调试动态加载的代码的,也就是说整个动态加载的代码就是一个黑箱。在你打算用动态加载代码的技术之前,请首先谨慎考虑,因为调试这里面的问题会变得非常的困难。如果您最后还是决定要使用动态加载代码的技术,那么这里有几个忠告:
1、在动态调用之前,首先通过直接运行的方式来进行调试。
2、如果在动态调用的时候出现了异常,首先考虑再次通过直接运行的方式来进行调试。
3、要切记动态调用的时候可能会和直接运行的时候有很多不一样的地方,并且有一些功能可能有问题,例如可能异常机制会出现问题——包括:
? a、可能截获不到异常
? b、可能截获到异常,但是有问题:
???? i、可能不是处于调用堆栈顶层的try...catch截获到了,而是底层的try...catch截获到了
???? ii、可能正确截获到了异常,但是在catch块内想通过MessageBox或者ShowModal的方式来显示异常信息,但结果却发现完全不会显示出来,或者显示出来之后就立刻执行下一句话(以非Modal的形式显示)
???? iii、可能处于处于堆栈顶层的和底层的try...catch同时截获到异常
??? (对于异常处理过程本身出现异常,这个我无法给出预言,只能够让你做好心理准备,遇到这样的问题不要不知所措)
4、尽可能避免Singleton模式,这种模式在动态代码加载的方式里面是危险的,如果你确定要使用Singleton模式,那么一定要仔细考虑清楚整个的状态转换过程,尤其是可能引用的一些外部控件。有可能你引用的控件已经被Dispose了,但是你没有办法通过IsDisposed函数来确定——没有这个函数。如果你这是一个控件,并且采用this.Parent = xxx之类的方式不停的变化父控件,甚至有可能因为父控件被Dispose之后一段时间后,由GC把这个你这个Singleton的空间给Dispose了,也就是说有可能随时引发ObjectDisposed异常。因此建议您一定要好好的设计Dispose函数里面的代码,并且提供判断是否已经Disposed并提供新的实例的能力。
5、事实上还有好多其他的模式是危险的,比如说FlyWeight模式或者ProtoType模式。因为你可能没有考虑到一个情况——有可能整个加载的模块会退出,然后重新进入。如果真的没有考虑,那么就可能出现下列情况之一:
? a、有可能因为每次进入都会进行初始化,结果引起多次产生同一个含义的实例
? b、有可能因为你在.cctor(静态构造函数)里面进行初始化来避免多次初始化的问题,但是有可能出现上一次退出造成部分对象被Dispose而你却不知道,第二次进入使用的时候引发ObjectDisposed异常。
? c、在b的条件底下还有一种可能,就是你忘了有二次进入的可能性,在退出的时候手动注销了这些对象,结果第二次进入的时候由于不会再次调用.cctor,所以没有初始化,得到NullReferenceException。
? d、其实还有很多其他的问题,比如可能你的这个动态代码为不同的应用提供相似的服务,所以可能有些东西在不同的条件下需要有不同的初始化,等等。
6、永远记住重入的问题。不一定采用什么模式才会引起这个问题的,有时候不是你采用了什么模式,而是MS的空间采用了什么模式。如果你有什么很莫名其妙的,在动态加载模式调不出来,但是在直接运行状态下却没有任何问题的话,那么首先考虑可能是重新进入引起的问题,或者进入了模块A再进入模块B这样引起的问题。
下一次也许会谈一下优化的问题。