RSS 2.0 Feed
2006-03 Entries
摘要:我成了C#和C++程序员的翻译... 最近有点烦,客户要求我们用C#实现一个关于串口通讯的模块,这事儿本身没那么麻烦,只是他们给了数年前写的几千行的C++代码,要我们给改成C#版的... 我不爽,于是我说,用VC.NET吧(还不是VS2005不能用C++/CLI),Refactor后重新编译一下基本上就能给.NET用了(整个项目是基于.NET/C#的),我也干过这事儿,貌似不难。但是客户坚持不要额外的DLL,项目又是用C#写的,ILMerge工具也不能合并用VC.NET写的DLL,再加上市场部的哥们已经和客户说了,“我们什么软件都写得出来”,所以... 理论上是可行的 从头到尾看了他们的代码一遍之后,发现虽然是VC6的Project,但并没有用到多少MFC里面的类(用到的也就是CString之类的,无视),阿门,上帝保佑,阿弥陀佛,大部分都是在直接调用标准库和Windows API。所以我觉得最起码理论上可行,不就是P/Invoke嘛,把内存给我对齐就行了,所以说,“没问题”。 程序员都是过于乐观的一族,对于这个缺点,我好像特别严重。答应下来之后,放下电话,我怀着内疚的心情看了看项目组的反应,不由的心生胆怯。不久后PM走到跟前,语重心长地说,“Vista Xia(我的无敌的英文名),这个任务就交给你了。下一个Release的Dead Line就快到了,赶紧啊。”(我靠连个Assistant都没有) 这是一个用C#语言编写的C++项目 这事儿比较特别,完全违反了公司惯用的流程,没有需求文档,没有HL和DL的设计文档,也没有测试用例(待会儿再说为什么)。公司的SQA不知道管不管这事儿,要管的话项目估计又要从CMM5直接跌到CMM1了。总而言之,我只是写了个TODO List,就开始了: 在.h文件中找到所有的struct定义,先改成C#的。用Marshal.SizeOf得到其大小,初步进行测试:看看它和在C/C++程序中用sizeof得到的大小是否相等 对应所有C++的类,先写出所有C#的类的原型,包括成员字段和函数。所有函数里先放着throw new NotImplementedException()这么一句,省得影响编译 对应于C++类的析构函数,在C#用Dispose Pattern实现 找到所有用到的Windows API和其他Native API,在C#用static extern方法声明之,然后对不确定都否工作的那些(比如参数里头有n个*的那种)进行初步测试 在.h文件找到所有宏定义的常量,改成const的字段;而宏方法只能直接改成普通方法(C#没有inline关键字)。对于用宏改了名字的类型,专门记载到一个文档中,以后直接用文本替换(ctrl+h)搞定之 把.cpp文件的代码逐步copy到C#代码中,一个函数一个函数的改。基于C/C++标准库和语言本身特性的代码语句,一般都可在FCL和C#中找到对应的做法,这也包括指针操作;至于API的调用,前面已经准备好了 编译通过,测试一下 走火入魔 这个模块并不是要实现什么很复杂的功能,不过,仅仅是声明API和结构体的C#代码就一千多行了(还不算注释)。我想99%的.NET程序员都没做过这种事情 — 我认为愿意写这种代码的人一定有受虐倾向,除我之外。 在Visual Studio,习惯了把所有代码写完,然后按F5就知道能不能跑了。然而我在这里忽略了一点,就是调试这个程序还需要相关硬件设备的配合(好像是客户开发的一些设备),需要连上我的机器安装,调试,才可运行。可客户在遥远的广东,公司里也不大可能有这种设备的,看来麻烦了。 客户那边的程序员看起来比我还要乐观,他似乎觉得只要程序写对了就应该能跑,不需要调试的。那么除了让代码能通过编译,我能做的就是再review几遍代码而已了。他大概不知道,在C#作P/Invoke时,编译器能做的工作是很有限的: 它不能检查API声明是否有效;也就是说,是否和API原型兼容 托管struct被Marshal到本机struct的时候能否对齐内存,只有在运行时才会抛出一个毫无提示作用的异常(比如会莫名其妙的抛出空引用异常) 有些托管struct,看起来很对,编译也能通过,但在运行时不给Marshal,我在前面的blog曾有提到。这是.NET 1.x的bug,2.0已经修好了 对于指针类型的函数参数,在C#有好多好多种声明的方法,比如StringBuilder、string、byte[]、int、uint、IntPtr、ref xxx、out xxx,等等。只要保证Marshal时是一个32位的值就可以了(对于32位系统)。这视乎具体情况而定,编译器不管这个细节(事实上编译器也管不了这个),要程序员自己处理。有时候会有点复杂,比如数组的元素又是子数组时,你只能选择IntPtr或者IntPtr[],并且需要手工copy内存 .NET的字符串在内存中总是以Unicode方式编码的,但对于API函数需要string的编码,有时取决于Windows版本,有时需要显式设定,有时又需要显式转换。同样,编译器也管不了这个细节 和C/C++编译器一样,这里也不会有越界检查和内存泄露等方面的保证 如履薄冰 且不说这些问题,就算编译器能帮上忙,对于这几千行几乎全部基于P/Invoke的代码,不测试就成功运行,也太NB了一点了吧。不过,这算是我的疏忽,早知本地无法测试(问题是客户也不早说),大概就不会答应这么做了,sigh。 不管怎么说,在反复review好几遍之后,我向VSS签入了代码,然后听天由命了(不过,我后来又给客户发了个mail说了一下risk,打个预防针先)。看来到客户现场调试已不可避免,嗯,不管了。 怎么说P/Invoke呢 毫无疑问,这是一个非常重要的技术,事实上,FCL本身也大量应用了P/Invoke,它是CLR的几种基础技术之一。但在我看来,这并不代表微软推荐你也把这项技术作为你的应用程序的基础。适当使用就可以了,比如偶尔需要调用一两个API的时候。而像我手上的这个东西,显然应该用VC.NET的(现在的C++/CLI),实现起来要容易得多,也可靠得多。 BTW,不知道是去年还是前年,有人居然用Javascript实现了Web上的《星际争霸》,着实很牛,但你不觉得它也属于走火入魔之列吗?现在流行用牛刀杀鸡,但不要用杀鸡刀宰牛啊!...[阅读全文]

posted @ | Feedback (34) | Filed Under [ Patterns and Practice P/Invoke ]

摘要:.NET 2.0提供了很多以“ToolStrip”开头和以“Strip”结尾的控件。它们的好处是显而易见的,你可以很方便的利用它们实现像Office 2003工具栏那样的界面,让你的软件看起来更“现代”一点。但也碰到了很多新的问题。在我看来,这些问题(当然,问题的原因可能源于已有的习惯)最终源于新的对象继承模型,它们不再都继承于System.Windows.Forms.Control基类,下面是我查到的一个架构图: ToolStrip : ScrollableControlMenuStripStatusStripToolStripDropDownToolStripDropDownMenuContextMenuStrip ToolStripItem : ComponentToolStripSeparatorToolStripButtonToolStripLabelToolStripStatusLabelToolStripDropDownItemToolStripMenuItemToolStripDropDownButtonToolStripSplitButtonToolStripControlHostToolStripTextBoxToolStripProgressBarToolStripComboBox 你可以看到它引入了一个新的顶级对象:ToolStripItem。而其中的像ToolStripButton这样的控件,都只是已有控件(比如Button)的Wrapper,而没有从Button类继承。当然,微软这么设计它自然是有合理原因的,但这带来了些问题,现在我觉得最不爽的有两个: 你无法通过重写Control的虚函数来得到自定义的效果 没了Dock和Anchor的支持,这样你得自己编写代码来让一个控件填满所在的容器 尤其是第一个问题,它基本上扼杀了我们为ToolStrip实现新的兼容的控件的能力。当然,你还有ToolStripControlHost可以继承,你可以把任何控件放到这个“Host”里面去,成为一个新的ToolStripItem,但是,它的Look & Feel和其他已有ToolStripItem并不一致(你可以试一下看看效果),这是我无法接受的。 就拿ComboBox来说吧,我手上的一个东西需要实现像Windows资源管理器地址栏那样的ComboBox,也就是其中的items需要缩进,并且需要带图标。你可以重写ComboBox的OnDrawItem虚方法来自己paint那些图标和文字,或者直接重写WndProc虚方法,在其中控制另一个TreeView控件来做到。但在ToolStripComboBox,这些虚方法都不存在。换句话说,要实现这种效果,你得几乎重写整个ToolStripComboBox?— 与其如此,我还不如放弃这些ToolStrips,或者选择第三方控件。 BTW,还是关于ToolStripComboBox,坦白的说,我觉得微软是曾考虑了这种需求的,ToolStripComboBox类有个这样的构造函数:public ToolStripComboBox (Control c);也许你可以在Control c里面写一些自己的东西,因为这里需要的是基础类Control。不过可惜的是,这个构造函数里面只有一行代码,throw new NotSupportedException。。。还有一个似乎有希望的地方是,这个控件提供了OnDropDown和OnDropDownClosed两个虚方法,不过,我还是找不到让事件取消的途径(动用无敌的Reflection?),这样TreeView会和已有的菜单一起弹出,很难看。还有些别的办法,比如让TreeView的Parent是ToolStrip,使让TreeView覆盖ComboBox的菜单等等,但我觉得这样的实现很难看(不过也算个办法)。 不知道各位看官还有其他好一点的办法没~ ...[阅读全文]

posted @ | Feedback (9) | Filed Under [ Patterns and Practice ]

摘要:昨日Yinkui告诉我,微软的Virtual Earth出新功能了,这不: 你可以用四个方向键移动那个小车,嗯,实在是太酷了! Windows Live Local - Virtual Earth:http://preview.local.live.com/MSDN Channel 9的采访录像:http://channel9.msdn.com/Showpost.aspx?postid=166518...[阅读全文]

posted @ | Feedback (6) |

摘要:昨天在尝试使用System.ComponentModel.BackgroundWorker时,发现这个类的行为和我预料的大不一样,可以说是惊喜。原来以为这个类只是一个线程的简单包装,用多线程模拟了异步调用而已;但是看下面的这段代码: Thread.CurrentThread.Name = "Main Thread";backgroundWorker1.RunWorkerAsync(); ... private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e){    int i = 0;    while (i++ < 100)    {        backgroundWorker1.ReportProgress(i);        Thread.Sleep(50);    }} private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e){    this.Text = e.ProgressPercentage + "% - " + Thread.CurrentThread.Name;} private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){    this.Text = "DONE - " + Thread.CurrentThread.Name;} 毫无疑问,_DoWork方法是运行在另一个不同线程之上的(很容易验证这一点,这也符合BackgroundWorker的设计),而这个方法又调用了backgroundWorker1.ReportProgress方法,触发了ProgressChanged事件。在通常的异步实现,_ProgressChanged方法应该运行于事件触发者相同的线程中;但在这里,它运行于主线程(名为Main Thread的线程)。_RunWorkerCompleted方法也是一样。 在我看来,这个行为非常特别,实际上它也非常有用。这样_ProgressChanged方法体中操作UI控件的代码都无需使用Control.Invoke包装了,让程序的编写大为简化。而我真正感兴趣的是这个类究竟是怎么实现的,我用Reflector打开它的源码之后,原来关键在于它用到了一个名为AsyncOperation的类(System.ComponentModel.AsyncOperation)。 AsyncOperation类有个Post方法,可以用来把一个委托(作为方法指针/列表)提交给另一个线程执行。继续反编译下去,又查到了System.Threading.SynchronizationContext类。不过具体怎么实现是无从得知了,因为追踪到最后,停在了一个[MethodImpl(MethodImplOptions.InternalCall)]的方法,它由CLR本身实现。(我个人猜测,其中很可能利用了Windows API:Get/SetThreadContext,和结构体CONTEXT,改掉了线程上下文。) 退一步说,它怎么实现的并不是那么重要,重要的是我们可以用这个AsyncOperation类实现自己的BackgroundWorker。这里是我写的和上面代码基本等价的实现: AsyncOperation asyncOperation;SendOrPostCallback progressReporter;Thread workerThread; public MainForm(){    InitializeComponent();     asyncOperation = AsyncOperationManager.CreateOperation(null);    progressReporter = new SendOrPostCallback(ReportProgress);    workerThread = new Thread(new ThreadStart(WorkOnWorkerThread));} private void MainForm_Load(object sender, EventArgs e){    Thread.CurrentThread.Name = "Main Thread";    workerThread.Name = "Worker Thread";    workerThread.IsBackground = true;    workerThread.Start();} void ReportProgress(object obj){    this.Text = obj.ToString() + "% - " + Thread.CurrentThread.Name;} void WorkOnWorkerThread(){    int i = 0;    while......[阅读全文]

posted @ | Feedback (4) | Filed Under [ Inside CLR ]