2007年05月16日

 

开发国际化的软件要解决不同语言文化下的用户界面、字符串资源、日期货币显示格式等的本地化问题,除了日期货币等不同国度的显示格式外,都是关于维护一个资源集合的问题。集合中有针对不同语言文化的资源,包括用户界面的显示内容与方式,翻译好的字符串资源,声音资源,图片资源等。其实日期货币格式也是一个集合,只不过系统不是做为一个资源集合的方式来支持。

.NET为本地化工作提供了许多便利。例如Form有一个Bool属性Localizable,打开后可以为Form选择不同的Language属性,从而VisualStudio自动建立此Form在不同Language下的资源文件。当Form在设计期时,通过切换不同的Language属性就可以设计各个本地化版本。VisualStudio还可以识别不同Language下的规范命名,自动将各个Language版本的资源分别编译生成为一个个的Satlite Assembly,并生成在正确的资源搜索路径上,而且为Form生成的C#代码,可以在启动时根据线程环境选择正确的资源,并且在初始化界面元素时使用资源中的内容。基本代码如下:

System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Main));//自动完成根据环境加载正确资源,支持Fallback

resources.ApplyResources(this.button1, "button1");//所有的界面元素都会使用资源进行初始化

resources.ApplyResources(this, "$this");

 

而没有打开LocalizableForm初始化代码如下:

this.Text = "Form1";

 

.NETSDK中还提供了WinRes.exe,实现在VisualStudio之外对Form的各个本地化版本进行翻译、修改。

 

以上的内容在微软文档及参考书中都可以找到,但是如何实现软件中的非Form元素资源的本地化呢?

第一个想到的办法,就是在某个FormRES资源文件中加入其它类型资源,如字符串资源等,但由于这些RES都是由VisualStudio自动生成的,在下次修改Form再次生成时,手工加入的内容不会被保存。

第二个是使用VisualStudio为每个项目自动生成的一个Resource文件,位置在项目文件夹下Properties文件夹内,文件名是Resources.resx。但这如果加入其它语言文化的文件呢?手工方式可以是使用资源编译器编译手工编辑好的资源文件,再使用ALAssemblyLink)将其链接成一个SateliteAssembly。有没有自动办法呢?

在解决方案管理器中无法向Properties文件夹添加文件,所以只能手工添加,方法是先将Resources.resx复制重命名为类似Resources.zh-CN.resx的本地化资源文件名,再手工修改项目文件,如果使用C#就是扩展名为csproj的文件,加入以下内容:

<EmbeddedResource Include="Properties\Resources.zh-CN.resx">

<SubType>Designer</SubType>

</EmbeddedResource>

重新打开项目后,在解决方案管理器中就可以看到新加入的资源文件了,而且可以打开编辑。分别在Resources.resxResources.zh-CN.resx中添加一个字符串资源,内容分别为英文和中文,运行程序后可以看到已经根据环境显示本地化字符串了。而且支持Fallback,即删除Resources.zh-CN.resx中的对应字符串后,程序会自动显示Resources.resx中的内容。

 

这样就可以在程序开发过程中,根据默认的资源开展本地化工作,将内容保存到对应的资源文件中,VisualStudio会自动将规范命名的资源文件生成对应的SateliteAssembly

 

VisualStudio还为Resources.resx自动配置成生成StronglyTyped类,这个类只要生成一次就行了,使用它访问资源会自动支持多个SateliteAssembly

2006年06月29日

这两天写了一个小程序,试用一下BackgroundWorker这个控件,发现一些文档之外的东西。
程序好象MSDN中的示例一样,有两个按钮,一个进度条。一个按钮是Start,一个按钮是Cancel,分别调用BackgroundWorker的RunWorkerAsync和CancelAsync。
BackgroundWorker几个事件都有,代码如下:
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            int i = 0;
            while (i < 1000000)
            {
                if (this.backgroundWorker1.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                };
                i++;

                if (i % 1000 == 0)
                {
                    //System.Threading.Thread.Sleep(0);

                    backgroundWorker1.ReportProgress(i);
                };

            };
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
                this.progressBar1.Value = e.ProgressPercentage;
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
          
            if(e.Cancelled)
                MessageBox.Show("Canceled");
            else
                if(e.Error!=null)
                    MessageBox.Show("Error");
                else
                    MessageBox.Show("Finished");
        }

文档中说即使调用了CancelAsync,可能是在DoWork事件最后一次检测CancellationPending之后,这样DoWork仍将执行完毕。在执行上面的程序时,点击Cancel按钮很难使进度停下来,在第一次Start后停的概率大一些,在几次Start后,Cancel按钮基本不响应,不管怎么点,DoWork将是执行完毕,显示“Finished”对话框。
开始我想,是不是DoWork早就执行结束了,而ReportProgress是按消息方式执行了,所以虽然点击了Cancel按钮,实际最后一次CancellationPending检测已经过去,结果就是看到进度条不停地走到头,再显示“Finished”。
但是这个解释不完美,程序响应也不完美,大多数时候,在点击了Start之后,Cancel按钮就不响应了,这没有达到使用BackgroundWorker控件的目的,使用这个控件本来就是为了生成更好用户响应的程序。
于是我在DoWork事件中加入了注释的一行,对Sleep的调用。这时不管进度条走到哪儿,都可以点击Cancel按钮,而且Cancel按钮也始终有用户响应指示(当鼠标移动到它上面时),点击之后,DoWork事件被中止,程序结果提示“Cancel”对话框。
Thread.Sleep(0)起的作用是主动把时间片交给其它线程,在没有它时,也许操作系统觉得执行DoWork的线程更重要,所以会分较长的时间片给它,而用户界面线程的时间片被ProgressChanged事件占用完了,从而导致用户界面失去响应。
为进一步证实以上猜测,我将DoWork事件中的ReportProgress和Sleep同时注释掉,这时虽然看不到进度变化,但Cancel按钮有响应,而且可以将DoWork事件中止。
更准确的解释需要对Windows线程调度细节更进一步的了解。
另外还有一个需要注意的地方是,调用CancelAsync之后,在DoWork事件中可以进行CancellationPending的检测,但当运行到RunWorkerCompleted事件时,CancellationPending状态已经改变了。所以必须要在DoWork中对DoWorkEventArgs 的Cancelled进行设置,以便在RunWorkerCompleted中进行检测,判断是执行完毕还是中途取消了。

2006年05月06日

原来运行正常的软件今天出了问题,通过查看调试信息发现,最里层的错误信息是“加载类型库/DLL 时出错”,上网搜了一遍解决办法,未果,只能自己动手了。

这个软件出错的时候是使用System.Web.Mail进行邮件发送,而这个功能.Net Framework是调用CDO的COM控件实现的,根据出错信息可以猜到,可能是在加载包含此COM控件的文件时出了问题。那么是哪个文件呢?

使用.Net Reflector可以在System.Web.Mail命名空间中找到如下代码:

static CdoSysHelper()
{
      SmtpMail.CdoSysHelper._helper = new SmtpMail.LateBoundAccessHelper("CDO.Message");
}
CDO.Message就是COM控件的ProgID,可以在注册表中HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CDO.Message位置找到。
再通过CLSID找到包含控件的文件位置,在出错的机器上是
出错电脑的文件指向:
C:\Program Files\Common Files\Microsoft Shared\CDO\cdoex.dll,
而正常的电脑的文件指向:
C:\windows\system32\cdosys.dll
于是使用RegSvr32对原文件进行解除注册,并对正确文件进行注册后,一切恢复正常,命令如下:
RegSvr32 -u C:\Program Files\Common Files\Microsoft Shared\CDO\cdoex.dll
RegSvr32 C:\windows\system32\cdosys.dll
 
2005年07月08日

游戏有外挂,浏览器也有吗?

我把使用HTTP协议模拟浏览器动作的软件称为浏览器的外挂。

比如说有个网站,在登录之后可以访问一个表单,通过这个表单可以发送短消息。那么就可以编写一个winform外挂软件,实现自动登录网站,并将用户录入的内容通过HTTP提交,完成发送短消息的功能。

此类外挂软件有个很大的好处,就是如果软件被扩散出去,而你想使它失效时,只需改掉网站的登录密码即可。

我写过使用Websphere构建的网站的外挂,有时间把其中的经验分享一下。

2004年12月24日

 

配置Win2003的终端服务,使用Win2003的远程桌面

办公室有两台电脑,A17″液晶显示器,B15″CRT显示器,在使用远程桌面后,就可以只使用17″液晶了,感觉很爽。但为了达到这一目的,还是遇到了一些“坎坷”。

基本设置网上都有了,就不说了。远程的机器必须启动终端服务并允许远程连接。首先是访问远程桌面的工具,我先使用的是管理工具中的远程桌面,结果找不到如何设置成全屏的办法,上网一搜发现附件中通讯菜单下有个远程桌面连接,搞定。

在远程桌面连接中设定将远程计算机的音频带入本地计算机,并在远程计算机中安装了声卡驱动,结果怎么也听不到声音,MediaPlayer一播放就提示声卡出错,而且声音的播放设备始终是Microsoft RDP 音频驱动程序,不是我安装的声卡驱动程序(CMI8738)。这个RDP迷惑了我,实际上它是“远程桌面协议”的意思,这个驱动程序就是远程桌面的声音驱动程序。我的声卡驱动安装是没有问题的,明白这一点时我已反复安装了N遍。

其实不能播放声音的原因是远程计算机的终端服务设置中禁用了音频映射。在终端服务配置程序中打开对应连接的属性对话框,在客户端设置选项卡中清除禁用音频映射的设置就好了。

安装SQLServer2005 Express B2

由于安装的.NetFramework2.0B1不正确,因此安装SSX时报错。之前使用的是同事在BT上下载的VS Express B1版本,Express工具一套,但里面的.NetFramework2.0B1版本的BuildNo不是MS网站上的B1版本。

换成MS网站上的B1版本后安装成功。

2004年12月17日

正在做一个小程序,可以将一些资料通过邮件上传到邮箱保存。现在163sina等都有1G以上的免费邮箱,这个程序也可以测试一下真实的容量。
使用
Windows CDO控件(如System.Web.Mail.SMTPMail)发送邮件时,如果使用Win2000SMTP服务,系统缓冲及出错文件在\INETPUB\MailRoot中。

CDO出现Exception时,信息很简单,但通过该异常的InnerException.InnerException.Message可以得到更多有用的信息。我就是通过查看这些信息,了解了程序出错的真正原因:SMTP服务器要求发送验证。

加入异步发信功能。

使用Delegate.BeginInvoke。这是由CompilerCLR实现的函数,调用时如果传入引用参数,必须保证该引用参数在函数调用期间未被其它线程改变。我的办法是每次传入的都是新创建的引用对象。

这种实现方法使用消息队列的方式,由CLR在线程池中分配线程执行特定函数。

在非UI线程需要访问UI时,使Control.Invoke/BeginInvoke。这相当于往UI线程的队列中插入一条消息,指定的函数将在UI线程的Context中执行。

让我好奇的是ThreadPool到底是如何运作的,当我在队列中放入N条消息时,真的会有N个线程来执行吗?于是我想在异步任务中加入显示当前线程ID的语句,经过一翻搜索,System.Diagnostics.Process.GetCurrentProcess.Threads属性,返回当前进程的所有线程。通过遍历所有线程,查到到ThreadStateRunning的线程并显示它的ID

通过加入多个任务发现,在任务非常短时(为方便测试,去除耗时的任务)多个任务由同一个线程执行,当任务耗时较长时,多个任务各由一个线程执行,当然我的机器只有一个CPU,多CPU环境的情况也许并不一样。

下面研究主线程如何知道队列中任务均已处理完毕,这样就可以避免任务未处理完就关闭程序。

通过ThreadPoolGetMaxThreads方法及GetAailableThreads可以得到最大线程数及可用线程数,当可用数等于最大数时,队列中应该就是空的。但我不知道CLR本身是否也使用这个ThreadPool做些什么,比如GC之类的操作。ThreadPool在一个进程中只有一个,当代码中访问ThreadPool类时创建ThreadPoolThreadPool所有成员均为静态成员,可以想象ThreadPool类有一个静态构造函数。

实际执行时可以看到每启用一个新线程ID,可用线程数就少一个。默认最大WorkThread25个,CompletionPortThread1000个。

如何访问异步队列,现在仍是个迷。

在我查找关于异步队列的过程中发现了这篇文章:

Programming the Thread Pool in the .NET Framework

 

源文档 <http://msdn.microsoft.com/library/en-us/dndotnet/html/progthrepool.asp?frame=true>

虽然没有解决如何了解异步队列状态的问题,但对于我之前的做法是一种印证。

 

2004年03月24日

读了一篇DNJ Online上的一篇文章《Designing applications with ‘Whidbey’》,其中有Tim AndersonKeith Short的访问,里面谈到:


1、Microsoft对企业开发和应用程序设计及架构方面的战略,代号为“Whitehorse”。


2、为什么不使用UML做为建模语言:因为它的长处在于可视化的设计和描述系统结构,不具备精确描述细节模型的能力,且扩展能力差。使用它不如使用一种精简专用的新模型语言,为描述不同的领域专门设计的模型语言,如Deploy模型,Web Service模型,Class模型。引用了Martin Fowler的两篇Blog1&2。指出了MDA的一些缺陷,及Microsoft对Model Driven的理解和实现方法。


3.Visual Studio的Enterprise Templates功能强大,是通向Patterns of Solution结构的第一步。但较难掌握。一篇MSDN中的相关文章


4.今后的开发工具将重视设计模式的支持。


5.可以对微软的设计器进行扩展或编写新的设计器实现新的模型。


6.Visio对数据模型的支持是使用ORM。


其中涉及以下几个新的术语:


1.Object Role Modelling(ORM)


2.Digital Systems Initiative(DSI):to unite design,deployment and operation on the Windows platform.


3.System Definition Model:an XML Schema for distributed systems used by Microsoft Logical Infrastructure Designer.

2004年03月04日

在这篇文章中可以了解到微软后续开发平台的发展情况。在看这篇文章时要记住Whidbey包括哪些内容。除了几种高级语言、IDE外,还有新版的.NetFramework(包括CLR和FCL),作者在文章中把对IDE的增强功能的介绍融入到对应的语言和.NetFramework的内容中了,作为读者应该能分得清哪些是IDE的增强,免得有C#支持重构了此类的误解。


主要的分为以下几个方面。


一、下一代.NetFramework


包括CLR,及建立其上的ASP.Net、ADO.Net等技术,微软在.Net中支持的几种高级语言的变化实际上也只是对这些高级语言的用户提供了访问CLR中功能的语言接口。还有ClickOnce应用程序发布技术及一些新的用于WinForm或ASP.Net的控件。


二、高级语言的增强


C#、J#、C++.Net、VB.Net均有所增强,当然有些如上一条所示只是为已有的CLR功能或是新增的CLR功能提供了接口,而另外一些是通过编译器魔法实现的。如:匿名过程等。在.Net中语言的增强可以采用以上两种方式,甚至有的两种方式都实现了。比如文章中说C++将支持CLR的泛型技术,而C++ Template则是由编译器实现的。


C#的增强我是欢迎的,而C++则越来越象是大杂烩了,我不知道出于什么原因我会使用C++.Net。


VC的几个改进包括


1.支持生成“纯”IL,没有NativeCode可以使软件更安全,不会被种上病毒或木马,因为IL都是通过验证才执行的,其中包括完整性检查,这保证了文件未经第三方的修改,当然如果文件本身就是做为一个木马编写的,那就没办法了。不过最起码无法自我复制在其它文件上了。


2.支持Profile Guided Optimizations (POGO)技术


3.支持生成64位Intel或AMD平台代码。当然这应该是NativeCode的选项了。


4.新版本的STL支持CLR托管代码开发。


5.新版的安全性增强的CRT(C运行时库,不是显示器;-))。


 


VB的改进包括:运算符重载,无符号数据类型,partial type,泛型。其它谈的都是IDE的改进,给我的感觉是VB好象是只老狗,玩不出什么新花样了。语言的改进只是在象C#靠拢,与其用从VB6过渡到VB.Net,不如直接学C#。


很早就有人说VB是一种受了污染的Basic语言,我的感觉是VB身上打满的补丁,即使手工再好,也不如一件新衣服。


C#的改进包括:泛型(由CLR支持),迭代器(Iterators),匿名方法(我在Java程序中看到过),partial type,重构(由IDE支持)。


J#我不感兴趣,跳过。


三、IDE的增强


这方面的内容被混杂在语言及.NetFramework内容之中。


四、对Office与SQLServer的API进行封装,以支持使用.Net技术进行开发。这部分功能我很期待。


五、企业开发


Design for operation、配置管理、MSBuild。


上面的都是Whidbey的内容,而Orcas文章中只是泛泛而谈,毕竟比较遥远(与Longhorn同时推出),变数较大。