显示标签为“Windows技术”的博文。显示所有博文
显示标签为“Windows技术”的博文。显示所有博文

2009年4月1日星期三

Visual Studio Free E-book!!!

首先声明,这不是愚人节笑话。MS Press终于大方了一记,放出了一些免费的电子书。

其中有

Visual Studio 2008: Programming Microsoft LINQ


 

Introducing Microsoft Silverlight 2, Second Edition



Programming Microsoft ASP.NET 3.5


这三本时髦的重量级参考书。

你可以免费下载部分章节试读,如果觉得喜欢,注册一下就可以获得全部章节。

http://csna01.libredigital.com/?urvs5cn3s8



2009年2月28日星期六

Unicode, A programmer should know

首先说说这个标题,因为我最近非常迷星战,所以顺带也喜欢上了Yoda大师说话的腔调,以后可能会用的更多,情别见怪(反正也没人看)。

“曹操”为什么会变成“变巨”一文中,我提到了Unicode是多语言共存的解决方案,本文将详细介绍Unicode,以及为什么解决了各种令人头疼的编码问题。

那unicode解决了哪些问题呢?其实最主要的是这几个问题:

  1. 人们希望有一个单一的字符集,能包含这个星球上所有的字符
  2. 每个字符都有一个独立的编号,不会出现一个编号对应不同字符的事情
  3. 可以根据需要变换不同的编码方式,但是不会影响字符编号
那么看看unicode是怎么解决这些问题的呢?

Unicode 是一个国际标准,它主要包含了:
  • 超大的字符集,可以有1,000,000个字符,code space为[0x0-0x10FFFF]
  • 每个字符的code point编码方法
  • 一个标准的字符集
  • 一组字符属性的枚举值
  • 提供字符集的多种编码
有很多人把unicode 与utf8,utf16这些编码混为一谈,看完本文后,应该不会再存在这样的混淆了。

Unicode有其标准的书写格式。要表示一个字符的Unicode码,可以这样写:
U+0041 
这就是大写字母A的Unicode码,U+表示这是Unicode码,后面的十六进制数字是这个字符的unicode code point。比如hello的code point就是U+0048 U+0065 U+006C U+006C U+006F。可以看到,这些码值去掉前面的00,就跟ASCII码一样了,是的,unicode最大限度跟ASCII保持兼容。

Unicode的字符集可以说包括了这个星球上所有的字符。包括一些我们可能永远也不会遇到的字符。不过Unicode标准把一些最常用的字符放在0x0000-0xFFFF这个区间中,这样有些系统实现的时候就不需要用那么多空间来存放不常用的字符了。这个区间被称为BMP(Basic Multilingual Plane)。Unicode一共有17个Plane,有6个已经被占用,剩下的都是空的。如下表所示:

Plane RangeDescriptionAbbreviation
0 0000-FFFFBasic Multilingual PlaneBMP
1 10000-1FFFFSupplementary Multilingual PlaneSMP
2 20000-2FFFFSupplementary Ideographic PlaneSIP
3-13 30000-DFFFFcurrently unassigned
14 E0000-EFFFFSupplementary Special-purpose PlaneSSP
15 E0000-EFFFFSupplementary Private Use Area-A 
16 E0000-EFFFFSupplementary Private Use Area-B 

在BMP中的任何一个code point都用4位16进制数来表示。其他的plane中的code point用5-6位。

Unicode的编码问题是我们最关心的。Unicode允许使用不同的编码方式,也就是你平时看到的utf8, utf16之类的名字。他们的特点在于编码出来的值各不相同,但是解码之后,都对应到一个唯一的unicode code point。这样保证了unicode编码之间可以无损(在BMP中)转换以及映射(mapping)的唯一性。Unicode中把一个值的code point及其使用某种编码得到的码值称为一个映射(mapping)。

在unicode发展过程中,出现过多种编码。我这里就介绍几种主要的编码,在此过程中出现过两个组织,各自发展出一套映射方法,称为Unicode Transformation Format(UTF)和 Universal Character Set(UCS)。 后来两个组织合并在一起,UTF和UCS也随之合并了。简单的说它们之间的对应关系是UTF后面跟的是一个编码的位数,UCS后面跟的数字是编码的字节数(以八位作为一个字节),比如UTF-16和UCS2几乎是同一个东西。但实际上UCS2只实现了BMP内的字符编码,而UTF16则实现了所有unicode编码。但为了简单期间,我们在这里认为它们是一个东西。

UTF-16/UCS2
最直观的编码方式就是一一映射。一个unicode码对应一个UTF-16编码。比如上面所说的hello,其编码为00 48 00 65 00 6C 00 6C 00 6F。但是问题马上来了,在大端法(big endian)和小端法(little-endian)的机器上编码得到的串的顺序不一样。在大端法的机器上,同样hello经过编码得到的是48 00 65 00 6C 00 6C 00 6F 00。而48 00也是一个合法的unicode code point,因此把在大端法机器上保存的文件拿到小端法机器上读就会解析成完全不同的东西。人们于是想到用最前面的两个字节来标明字节顺序。这两个字同样是unicode码,U+FEFF,一般被称为BOM(Byte Order Mark)。FE FF表明大端法,FF FE表明小端法。

UTF-16使用2个字节来进行编码,因此只能编码到BMP的code point。对于其他的plane,UTF-16采用一种额外的编码方式,达到3,4字节的编码。但是一般情况下,UTF16指的就是2字节的编码。这样的编码非常方便,所有的字符都占用一个unsigned short,对于传统的c字符串函数就非常方便。

在windows上,为了方便utf16被直接称为unicode编码,与ANSI对应。你在使用notepade时可以保存成unicode,指的便是utf16。

下图显示了在linux上使用utf16编码



比较下在windows下使用utf16(unicode)编码,注意到BOM了吗?




UTF-8
UTF-8可以说是用得最广泛的编码方式了,很多Linux的发布版都使用UTF-8作为其系统编码。UTF-8的产生原因可能是由于有人觉得UTF-16太占用空间了。在美国英语系统下,每一个字符都有一个00,字符串大小凭空增加了一倍。于是人们研究出UTF-8这个可以兼容ASCII的变长编码方式。UTF8编码出来的字符占1-4字节。

具体占位如下图所示:


对于在U+0000到U+007F区间(是不是很熟,对了,就是ASCII码的区间)内字符,使用1个字节来编码,因此完全兼容ASCII码,而且省空间。

[U+0080, U+07FF] 区间内的字符,基本上是欧洲各国字符集,使用2个字节来编码。
图中的yyy是code point中的高位字节,由于最多到7,因此只要3个bit就可以编码。
图中的xxxxxxxx是code point中的低位字节,从00到FF,需要用的所有8个bit,在编码时,把低位的6个bit与10结合作为一个字节,剩下的2个bit与yyy,110结合作为一个字节。

[U+0800, U+FFFF] 区间内的字符,包括中文,日文,韩文等都使用3个byte来编码。详细的编码规则在图中已经解释的很清楚了,我这里不再重复。

下图显示了在linux使用utf8编码,于utf16的截图相比,utf8对曹操这两个中文各用了3个byte进行编码。



比较下在windows上使用utf8编码,首先是字节顺序不同。然后你还可以看到utf8的BOM,0xEF 0xBB 0xBF。这BOM不仅和系统,还和使用的软件有关。notepad无论什么编码都会加上BOM。



UTF32/UCS4
这是最浪费空间的一种编码方式,它也是一一映射,但是UTF32映射了所有的unicode code point。对于每一个code point,UTF32如其名字所示,使用32bit也就是4个字节来进行编码。比如A=00 00 00 41。

很多程序使用utf32作为内部编码,在保存的时候它可以无损的转换成其他编码。而且因为现在的硬件总线的关系,这种编码的寻址和读写更快。比如Apache的Xerce2 就是这么做得。



还有其他很多编码,比如国标的GB18030。只要符合unicode标准,你也可以创造自己的编码。


讲了这么多,最初的问题也算回答得差不多了。Jeffrey Richter[1], Joey Spolsky[2], Charles Petzold[3]等人都在各自的著作中强调unicode对现代编程的重要性。虽然我人微言轻,但是我还是想附和一句:unicode以及编码的知识应该是每一个专业的程序员需要了解的知识。现在,停止ANSI程序,开始用unicode编写你的正式程序吧。




注1:《Programming Applications for Microsoft Windows》, chapter 2, Jeffrey Richter
注2:《Joel on software》, Charpter 4, Joel Spolsky
注3:《Programming windows》,chapter 2, Charles Petzold

2008年11月7日星期五

File not found: ieframe.dll

When using Visual Basic 6 with Internet Explorer 7 installed, I encountered such a problem "File not found: c:\windows\system32\ieframe.dll\1" when loading a project.

Usually, the file does exist. Then why we get this problem? Well, the simplest answer is that some control referenced by your project has been replaced by ieframe.dll. I can not tell more since I am barely familiar with VB. Or to be frankly, I am neither not knowledgable of VB nor like it.

So the simplest way is to revert the reference from ieframe.dll to its ancestor. After google it, I find its ancestor is shdocvw.dll. It's in the same directory. Then register it:

regsvr32 c:\windows\system32\shdocvw.dll

Try to launch VB6.exe and load your project again, does the annoy error message disppear?

2008年11月5日星期三

pipe console program output to clipboard

I am making some demostration these days. I need run some console programs, get their output and put them into a nice slice as example. The trivial way is to redirect the output to a plain text file then open it, Ctrl+A to select all and copy, switch to PowerPoint, paste, which is exactly what I am doing.  Some days past, I feel I can't stand it anymore. A simple idea just jumped out of my mind, why not make a small tool the can read data from pipe and copy to clipboard? So I can do my jod like this(assume the program is called cmd2cb.exe):

dir|cmd2cb
type readme.txt|cmd2cb

thus, all output of dir command are read by cmd2cb and copy to clipboard. Then I simply switch to PowerPoint and Ctrl+V. Everything's done. Small tool, but cool!

When I share this tool to my colleages, it turns out that I am not the only one needs it.
So I decides to put it to my box. If you need, just download here. cmd2cb.exe (9k)
Chinese user can download from namipan(纳米盘): cmd2cb.exe (9k)

*This file can only be used in windows, console and after the pipe symbol and no warranty.
*If you're using Vista, you can use Clip.exe shipped with Vista.

2008年9月25日星期四

Track Active Item in Solution Explorer

我现在所在的项目规模很大,在solution中的源文件就有几千个。平时编辑的时候要全部展开是不可能的,特别是对于我这样的新进员工,一下子接触到这么大的Project还真有点摸不着头脑。

这几天我发现一个问题;当我在编辑某个文件的时候,我很希望看到Solution Explorer同时帮我定位到那个文件,这样我可以看到该文件在项目中的哪个项目的哪个子目录之下,这有助于我了解程序的逻辑结构。同时也因为这个项目中的文件的物理结构和逻辑结构完全对应不上,使我没法通过物理结构直接映射到逻辑结构(也就是在Solution Explorer中的树结构)。因此,Track Active Item这个功能对我而言很有必要了。然而,当默认安装好VS2005之后,这个功能是没有的。

于是我很愚蠢的花了3个多小时时间,研究了VS2005的宏编程,写了一个宏,当我选中某个文件的时候,Solution Explorer会自动定位到那个文件,必要时会自动展开。还洋洋得意的把这个宏发给同事。结果,同事告诉我,在VS2005中早已有这样的设置,我这是在重新发明轮子的傻事。

VS2005中的设置在Tools->Option里面,如下图



另外,这里也附上我写的宏,以纪念这次愚蠢的行动。


Public Sub LocateFileInSolutionExplorer()

' Get the UIHierarchy
Dim oUih As UIHierarchy = DTE.ToolWindows.SolutionExplorer

' Check if there is solution exists
If (oUih.UIHierarchyItems.Count = 0) Then
Return
End If

' Get the top level item in UIHierarchy.(solution)
Dim oUihSln As UIHierarchyItem = oUih.UIHierarchyItems.Item(1)

' Reverse finding corressponding parent until solution node.
Dim oItem As Object = DTE.ActiveDocument.ProjectItem
Dim oPath As String = oItem.Name

While Not (TypeOf oItem Is Project)
oItem = oItem.Collection.Parent
oPath = oItem.Name & "\" & oPath
End While

'Construct item path
oPath = oUihSln.Name & "\" & oPath
'Select this file
oUih.GetItem(oPath).Select(vsUISelectionType.vsUISelectionTypeSelect)
'Activate the solution window so the selected file is hilighted
DTE.Windows.Item(EnvDTE.Constants.vsWindowKindSolutionExplorer).Activate()

End Sub




这个事情给我的教训是,当遇到问题时,首先问问周围的人,然后Google一下,再去Usenet问一下,最后才自己动手。除非是想学习某样技术,否则没有必要浪费时间重新发明轮子。

2008年7月8日星期二

windows绘图概述

这两天在开发的时候遇到好多绘图的问题,发现自己在绘图这块实在又是门外汉一个。于是翻出MSDN仔细研究了一下,搞懂了很多概念。我觉得这对于windows编程来说是非常重要的概念,因此仔细的把MSDN上相关的信息全部读了一遍,同时做了翻译和笔记。我贴在下面,希望对跟我一样的朋友有所帮助。
另有PDF版本可以从我的namipan下载->WindowsPaintingAndDrawing.pdf

Windows 绘图详解


几乎所有的windows程序都会在屏幕上绘图,但是由于多任务和多窗口,为了使绘制图形平滑,漂亮,应用程序使用windows作为其主要输出设备,而不是屏幕。

系统提供了与窗口一致的Device context。应用程序就使用DC把输出定向到目标窗口。


什么时候在窗口中绘图?

一个应用程序在很多时候都会在窗口中绘图。比如,第一次创建窗口的时候,当改变了窗口大小的时候,当把一个窗口从别的窗口后面拉出来的时候,当最大最小化窗口的时候,当打开文件显示内容的时候,当滚动,改变或者选择了一部分数据进行显示的时候等等。


系统会管理类似移动窗口和改变大小的操作,如果一个操作影响了窗口的内容(如果屏幕上只有一个窗口,把这个窗口在屏幕范围内不断拖动,不会引发重绘),系统会把这部分被影响了的部分标记出来,以备后面绘图时使用,然后,发送WM_PAINT消息给窗口函数。这个消息告诉了应用程序哪些地方必须被重绘,使得应用程序能够实现有效的重绘。


同样的,应用程序也可以标记需要重绘的区域,一旦标记,也会导致发送WM_PAINT消息。如果一个操作需要立刻得到回应,应用程序可以在操作的同时进行绘图,这样就不必等到下一次WM_PAINT事实上WM_PAINT产生的条件就是有部分窗口失效了。如果不出现这种情况,就不会产生WM_PAINT


在任何情况下,只要窗口创建了,应用程序就可以在里面绘图。为了能够绘图,应用程序首先应该获得一个设备描述表的句柄。在理想情况下,应用程序的大多数绘图在WM_PAINT中完成。在处理该消息的时候,通过调用BeginPaint就能获得DC的句柄。如果应用程序在其他时候进行绘图,可以通过GetDC或者GetDCEx来得到DC的句柄。


WM_PAINT 消息

当窗口的Client Area发生改变的时候,系统给应用程序发送WM_PAINT消息。系统仅在消息队列中没有其他消息的时候发送该消息给应用程序。一般的用法是在WM_PAINT中调用BeginPaint获得DC,然后进行任何GDI绘图,最后通过EndPaint释放DC


beginPaint返回之前,系统为指定的窗口准备DC。它会为DC设置裁剪区域,这块区域就是需要重绘的区域。任何在此区域之外的绘制都会被裁减掉。


beginPaint结束之前,系统还会发送WM_NCPAINTWM_ERASEBKGND两个消息给应用程序。这些消息告诉应用程序绘制Non-Client Area和窗口背景。Nonclient Area也是window的一部分,其包括title barsystem menuscrollbar。大多数应用程序依赖DefWindowProc来绘制这些区域,因此一般会把WM_NCPAINT发送给DefWindowProcWindow的背景是用颜色或者笔刷在任何绘制操作之前填充出来的。背景会盖住任何之前存在的图片或者窗口后面的屏幕。如果一个windowwindow class定义了背景笔刷,DefWindowProc用这个笔刷自动绘制背景。


BeginPaint会填充一个PAINTSTRUCT结构。该结构中包含需要绘制的区域的大小等信息。应用程序可以利用这个信息,(存放在rcpaint中)来确定需要绘制的地方。如果需要输出的内容很简单,也可以不管这个信息,直接输出,让window自己来裁剪。


BeginPaint会把需要更新的区域设为NULL,以防连续导致发送WM_PAINT消息。即没有需要更新的区域就不会发送该消息。如果一个应用程序在WM_PAINT中没有调用BeginPaint或者清除更新区域,则系统会不断发送WM_PAINT,直到更新区域为空为止。在任何情况下,应用程序都必须在离开WM_PAINT之前清空更新区域。


绘制结束,应该调用EndPaintEndPaint释放DC,使得其他window可以使用。如果之前caretBeginPaint隐藏,EndPaint会显示出来。


更新区域(The Update Region

更新区域是window的一部分,这一部分已经过期或者无效了,需要重绘。系统依靠更新区域向应用程序发送WM_PAINT消息。系统只会把无效的区域加到更新区域中去。


当系统发现有一部分需要重绘的时候,它把这部分加入到更新区域中,但不会立刻要求重绘。只有等到系统把消息队列中的消息处理完之后,系统才会检查更新区域,如果更新区域不是空的,则发送WM_PAINT消息。


应用程序可以自己设置更新区域。比如读取一个文件,然后设置更新区域,这样在之后的WM_PAINT中就可以显示文件内容了。但一般来说,应用程序不应该在数据改变的时候绘制,而应该把所有的绘制工作放到WM_PAINT中去。


更新区域的有效化和无效化( Invalidating and Validating Update Region)

应用程序可以通过调用InvalidateRectInvalidateRgn来无效化一块区域,并设置更新区域。这两个函数把指定的矩形和区域加入到更新区域中。与之前加入进去的区域结合起来。


这两个函数不会导致WM_PAINT消息。Window在处理其他消息的时候,系统不断积累无效区域,直到消息队列中不再有任何消息。


ValidateRectValidateRgn两个函数的作用则相反,用来有效化一块区域,并把这块区域从Update Region中去掉。


获得更新区域

使用GetUpdateRectGetUpdateRgn两个函数来获得当前的更新区域。前者获得最小的能全部包含更新区域的矩形,后者返回更新区域。这两个函数用来获得当前的更新区域来精确定位输出。


BeginPaint也会获得包含全部更新区域的最小矩形。该矩形就在PAINTSTRUCT中的rcPaint。因为BeginPaint是更新区域全部有效话,因此在这之后调用GetUpdateRectGetUpdateRgn返回的都是空区域。


同步和异步绘图

大多数利用WM_PAINT来实现的绘图都是异步的。即在window的部分失效之后有一个短暂的等待,才能进行绘图。在这个短暂的等待中,system从消息队列中获取其他消息,并进行处理。因为system认为WM_PAINT是个低优先级的消息。


但有时需要同步绘图,也就是在窗口的部分失效之后立刻就绘图。比如在创建主窗口之后立刻把它绘制出来等等。基本上来说,那些需要立刻绘图的都是需要和用户交互的部分,同步绘图可以保证不影响交互的性能。


UpdateWindowRedrawWindow两个用来同步绘图。如果更新区域非空,UpdateWindow能够立刻发送一个WM_PAINT消息给应用程序。RedrawWindow同样发送WM_PAINT消息给window,但是它给绘制更多的自由,比如是否要绘制nonclient区域、背景,以及是否不管更新区域非空也能发送WM_PAINT消息等等。这两个函数无论消息队列中有多少其他消息,他们都能直接发送WM_PAINTwindow


任何会消耗一定时间的绘图都应该使用异步绘图,免得程序被block住。一个程序如果经常是一小部分window无效化,最好把这些部分合起来,在一次WM_PAINT中进行处理。


不通过WM_PAINT来绘图

尽管大多数时候应用程序在WM_PAINT处理绘图,但是有时候不通过WM_PAINT而直接进行绘图可能效率更高。这一般都是用于需要立刻得到反馈的情况下,比如用户选择了一部文字,拖曳或放大缩小一个对象时。这些情况下,应用程序通常在键盘和鼠标操作中进行绘图。


不在WM_PAINT中绘图时,应用程序通过GetDCGetDCEx两个函数获得窗口的设备描述表(记住,不是client区域,而是整个窗口)。结束绘制使用ReleaseDC释放DC


当不使用WM_PAINT的时候,应用程序使用一种技巧来进行“可恢复”的绘制。比如在反选一些文字的时候,仅仅只要对那一块区域反色即可,当取消反选的时候,只要再次反色即可。


Window区域

除了更新区域,每一个window都有一个可视区域,即用户能看到的所有区域。当window的大小改变时,或者被其他Windows挡住时,或重新显示时,都要绘制这部分区域。用户没法直接改变这个区域,但是系统自动使用这个区域来表示该windowDC中的裁剪区域



裁剪区域决定了系统的那些部分允许绘图,当应用程序通过BeginPaintGetDCGetDCEx获得DC之后,系统为这些DC都设置了裁剪区域。应用程序可以通过SetWindowRgn, SelectClipPath and SelectClipRgn等函数来设置裁剪区域。


WS_CLIPCHILDRENWS_CLIPSIBLINGS这两个样式告诉系统怎么来计算window的可视区域。如果一个窗口有其中一个样式,窗口就会把子窗口或者兄弟窗口(具有相同Parent的窗口)的可视区域排除在外。


Window背景

Window背景是在进行绘图之前对client区域填充的颜色或者笔刷。背景覆盖了屏幕上窗口区域中的任何东西,擦除了以前的图像,防止之后的绘图颜色混合。


系统可以自己绘制背景,也可以在调用BeginPaint的时候给应用程序发送一个WM_ERASEBKGND消息把绘制的机会让给应用程序。如果由DefWindowProc来处理,则系统使用创建窗口时指定的笔刷进行背景绘制。如果笔刷无效或者window class没有定义背景笔刷,则设置PAINTSTRUCT结构中的fErase为非空,告诉应用程序,由应用程序来负责背景绘制。


如果应用程序处理WM_ERASEBKGND,则需要使用WPARAM来进行绘图。WPARAM包含一个窗口的DC句柄(是的,已经帮你获得了)。在绘制完毕之后,应用程序需要返回一个非空的值。这样BeginPaint就不会把fErase设置为非空值了。(从这里可以看出,WM_ERASEBKGND是通过SendMessage发送的,所以在这个函数中处理要快!)即使window class的背景笔刷已经被定义好了,应用程序还是能够处理WM_ERASEBKGND消息的。



改变window大小

当用户用鼠标拖拉window边框时,通过系统菜单选择最大最小化时或者使用SetWindowPos函数时,系统就会改变window大小。当window改变大小时,系统假设之前显示在外的内容没有被影响到,不需要重绘,系统仅仅使新暴露出来的那部分窗口失效,这样可以在处理WM_PAINT时节省时间。在这种情况下,即使window的大小减小,系统也不产生WM_PAINT

但是有些窗口需要不时的重绘,比如一个钟表应用程序,每次大小改变,都需要重绘。当横向,纵向的任意部分或者两者都发生改变时,为了强制应用程序重绘整个client区域,应用程序必须设置CS_VREDRAWCS_HREDRAW这两个样式。有了这两个样式,任何改变大小的操作都会引起重绘。



Nonclient区域

当任何一部分Nonclient区域,比如title barmenubarwindow frame需要重绘时,系统会给应用程序发送一个WM_NCPAINT息。系统也可以发送其他消息来更新一部分Nonclient区域。例如,当一个窗口被激活或者非激活时,系统发送WM_NCACTIVATE来更新Title Bar。一般来说,对普通window最好不要处理这些消息(带NC的),因为应用程序必须处理所有要求处理的Nonclient区域。

但是如果一个应用程序想要自定义Nonclient区域,则必须处理这些消息。应用程式必须使用窗口的DC来进行绘制。应用程序可以通过GetWindowDCGetDCEx来获得窗口DC。当绘制结束,必须使用ReleaseDC来释放DC

系统也会为Non-clinet区域维护一个更新区域。当一个应用程序收到WM_NCPAINT消息的时候,wParam指向一个包含一个指定了更新区域的Rgn handle。应用程序可以使用这个句柄使更新区域和DC的裁剪区域组合起来。当获得DC是,window不会自动组合更新区域,除非使用GetDCEx并且同时指定这个更新区域的handleDCX_INTERSECTRGN标志。如果应用程序不组合更新区域,则只有超出window以外的绘制操作会被裁减掉。无论是否使用该更新区域,应用程序不需要负责清空更新区域。

如果应用程序要处理WM_NCACTIVATE消息,在处理完毕之后需要返回TRUE来告诉系统完成被激活窗口的更新工作。当应用程式收到WM_NCACTIVATE时如果是最小化情况下,应用程序应该把该消息转发给DefWindowProc,在这种情况下,这个默认的函数会重绘Taskbar上面的这个icon

子窗口更新区域

子窗口是具有WS_CHILD或者WS_CHILDWINDOW样式的窗口。和一般窗口一样,子窗口通过WM_PAINT来绘图。子窗口也维护一个更新区域,应用程序和系统都可以通过设置该更新区域来产生WM_PAINT消息。

子窗口的更新和显示区域受到父窗口的影响,其他样式的窗口则不会。系统常常设置父窗口的更新区域的同时设置子窗口的更新区域,使父窗口收到WM_PAINT消息的同时子窗口也能收到WM_PAINT消息。系统把子窗口的位置限制在父窗口的client区域,超出这个区域就会被裁减掉。

无论何时,只要父窗口的更新区域包含了子窗口的一部分,系统就会为子窗口设置更新区域。此时,系统先向父窗口发送WM_PAINT消息,然后向子窗口发送消息让子窗口可以恢复被父窗口覆盖的内容。

但是如果只有子窗口设置了更新区域,系统不会给父窗口也设置。在无效化子窗口时,系统不会给父窗口发WM_PAINT(因为被覆盖住了,根本没有必要)。同样的,如果使被子窗口覆盖住的父窗口的部分无效化,系统也不会给父窗口发送WM_PAINT的。在这种情况下,无论子窗口还是父窗口都不会收到WM_PAINT消息。

应用程序如果设置了WS_CLIPCHILDREN这个样式的话,当父窗口的更新区域被设置的时候,子窗口的更新区域不会被设置。任何在子窗口下面的绘图全部被裁减掉,因此继续给子窗口发送WM_PAINT消息也是没有必要的了。

子窗口的更新和可视区域也受到兄弟窗口的影响。如果两个窗口重叠,则两个窗口都会收到WM_PAINT消息。他们受到WM_PAINT消息的顺序与z-index相反,即最上面的(z-order最高)的收到WM_PAINT消息最晚。

应用程序可以设置WS_CLIPSIBLING来避免兄弟窗口的绘制重叠。设置了这个,高z-order的窗口部分就被下面的窗口裁减掉了。

2008年3月17日星期一

Visual Studio 2008,我还没有准备好

昨天从MS网站下载了一个Webcast,是关于VS 2008的新特性介绍的。本来是为了写会议记要用来交作业的,所以也不打算仔细看。但是后来慢慢被吸引了,最后发现VS 2008竟然让我感到我离技术的浪尖越来越远了。

在VS 2008中,.Net Framework 已经升级到了3.5。

在VS 2008中推出了一种和编程语言整合的简单数据访问语言LINQ(Language INtegrated Query),用LINQ可以很简单的访问数据库,进行增加,删除和修改操作。它的思想和OR Mapping很相近。把Table映射成对象后,所有的操作都是对对象进行的,然后通过后台的序列化更新到数据库。

MS也终于把VSTO整合到了VS中去,这个开发Office所用的组件从通房大丫鬟变成小妾了。在VS2008中可以直接创建基于VISIO,WORD,EXCEL等的项目。

VS2008还增强了HTML/CSS编辑器,B/S项目现在已经成为主流,MS在这方面的支持实在很到位。HTML编辑器增加了和Dreamweaver一样的上下屏编辑窗口,上屏代码,下屏直接预览效果。CSS编辑器也非常强大,以前写CSS要靠开发人员的经验,手写CSS,还要进行调试也比较麻烦。在VS2008中,有一个傻瓜化的CSS编辑器,点击下鼠标一个漂亮的效果就出来了。不过在我看来,这个功能很鸡肋。假设我们在一个注重版权的公司里面。做HTML和CSS往往是UI设计师的工作。我很难想象公司会为这些人配置一套VS2008。要知道一套VS2008 Team Edition的价格可以抵得上N个UI设计师了。

WPF, WCF, WWF的完美支持。MS一直希望加强B/S系统的可操作性和表现力。但是要把瘦客户端程序做成和富客户端程序一样效果,光从HTML和CSS来说还是很困难的。于是MS就推出了WPF,一个基于XAML的图形库。不过老实说,我觉得还不如Adobe推出的基于Flex的解决方案好。WWF是基于Workflow的东东,我不懂,也没有兴趣。

VS2008的重定向功能,在VS2008中,用户可以随意的创建基于各种版本的.Net Framework的项目,其实也就是三个2.0,3.0和3.5。确定了.Net Framework的版本之后,支持的项目也会变化,乃至动态提示功能也会只对应的那个.Net Framework。我觉得MS在兼容性上做的的确很好。

不过说了这么多,这些都不是我需要的。我做的是系统编程,用的是C++,这个看起来快要落伍的东西。但是我相信C++有它自己的一片天地,我也有我的。只是发现VS离我越来越远,有些感慨罢了。

2007年8月1日星期三

删不掉的目录

今天学到了一招,觉得非常有用。那就是--“Windows上删不掉的目录”。

首先看看我这里的一个目录:c:\tomcat.6.0.14. 嘿嘿,看起来很正常是不是?我们来打开看看。当我双击它,想打开看个究竟时立刻出现了一个错误“c:\tomcat6.0.14.引用了一个不可用的位置......”


当你想删除它时,windows提示无法删除文件!
够布尔比的目录了。看又不能看,删又不能删。那这个文件夹到底怎么产生的呢?真相只有一个,那就是--这个文件夹不叫这个名字。

开始->运行里面打开cmd,在c:\根目录下打
c:\rmdir tomcat.6.0.14..\

你就会发现这个目录被轻易的删除了。原来这个目录真正的名字是tomcat.6.0.14..\ 注意最后的.\ 这个在windows里面被隐藏起来了。做GUI的程序员没有把这部分显示出来,因此在资源管理器里面看出来的就是tomcat.6.0.14. 因为前面有这么多点了,最后一个点大家也不会特别注意,因此就起到了掩人耳目的作用。

创建这类目录也很容易,在命令行里面打
c:\mkdir tomcat.6.0.14..\
那么看到这里,聪明人就知道怎么利用这个漏洞了,在里面放点不想让别人看到的图片啊之类的东西(嘿嘿,某人很猥琐的笑笑),或者放点木马,病毒之类的,统统没法被杀毒软件查出来。这个目录还无法显示大小,一般人根本不会注意到自己的硬盘上被划分出去这样一块空间。其实最先找到这个bug的还是黑客们,他们通过这个在目标机器上放程序或者在肉鸡上面创建这样的目录来存放中间文件。

不过奉劝大家一声,看过了就算了,不要做坏事情啊,要遭雷劈的!(假如做坏事,也不要放在C盘根目录下面这么明显的地方)

2007年7月26日星期四

ATL8的COM服务器实现(一)

在现阶段,ATL仍然是实现COM组件的一个重要手段。在几年的发展中,ATL出现了两个重要的版本-ATL3和ATL8。虽然ATL8已经出来好几年,但是由于我一直重用着以前用ATL3开发的老代码,因此对ATL8没有什么太多的研究。这次借着新项目的机会,能够正式使用到Visual Studio 8进行开发,我也因此得以好好的研究了一下ATL8的实现。虽然现在是.net大行其道的时代,但我这个守旧的人还是希望能记录下这个快要被遗忘的技术的研究心得。


COM服务器的种类

Win32版本的COM有两种服务器

  • inporc server --进程中服务器(动态链接库,DLL等形式)
  • out-of-proc server --进程外服务器(exe程序,进程外是对客户进程而言)
其中out-of-proc server有几个特例,一个是DCOM,可以叫做Remote Server,一种是系统服务,也就是service。

对于InProc-Server,我没有过多的研究其在ATL8中的实现,毕竟相当简单。这里主要对out-of-proc Server的实现进行研究。研究对象也是一般的EXE程序,而不是service。

COM服务器的职责

COM服务器有三个基本职责
  • 注册和反注册服务器
  • 暴露类厂(实现IClassFactory),使SCM能够访问
  • 服务器生命周期管理
进程外服务器简述

进程外服务器就是一个exe程序。如果在命令行上面指定 regserver或unregserver就能要求该服务器进行注册和反注册。注册成功就会在注册表中出现该服务器所包含的所有接口的ProgID和ClSID。ATL实现了这个简单的功能。

ATL同样是调用标准COM辅助函数CoRegisterClassObject把类厂和每个类的IUnknown暴露给SCM,这样客户就可以通过SCM来使用组件提供的接口。在Server结束生命周期之前,调用
CoRevokeClassObject把组件从SCM中注销。

对于生命周期管理,由于是exe程序,所以它可以自己管理自己的生命周期。当服务器检测到已经没有任何客户在引用它内部对象时,它可以选择是否退出运行(ATL中默认是exe退出运行)。


服务器与COM对象

一个服务器在程序中是什么呢?事实上没有具体的对象可以代表它。ATL使用OO编程,因此为服务器定义了一个类,其最高层父类是CAtlModule。它是个全局变量,因此它有着和exe程序本身相同的生命周期,因此由他管理COM对象再合理不过。

一个COM对象可以通过CoCreateInstance创建。它有自己的类厂,该类厂实现IClassFactory接口。
注:有些对象是不能被CoCreateInstance创建的,只能通过其他对象间接的创建。

那么服务器如何与COM对象打交道呢?
ATL从一开始的版本就提供了一个叫Object Map的结构来存放服务器内COM对象的信息。CAtlModule(简单起见,就把它当作服务器吧,这样容易理解一点)利用Object Map来做要求每个类自己注册和反注册,创建类厂,提供一些辅助函数来进行初始化和结束前的工作。

下一章 详细介绍Object Map




2007年5月15日星期二

windows server 2003 中DCOM 权限设置

Windows2003加强了DCOM的权限管理,其设置和window2000中稍有不同。

首先打开 注册表,在HKLM\Software\Microsoft\Ole下面加上两个DWORD类型的键
ActivationFailureLoggingLevel (1)
CallFailureLoggingLevel(1)
这两个键可以把失败的登录尝试记录下来。比如你用xxx尝试登录,结果得到800706ba的错误

然后运行DCOMCNFG,在My Computer上面右键,然后选择Default Property标签,Default Authentication Level 选择None,Default Impersonation Level选择Impersonate.
这里很奇怪,就算选择别的选项,关掉再打开,又会回到这两个选项。

然后选择 COM Security标签,在Access Permission , Launch and Run Permission 里面把刚才失败的用户名加上,并赋予 Remote Access, Remote Launch 的权限。

然后最重要的,在防火墙里面把RPC服务的端口135打开。打开端口似乎需要重启计算机,否则用netstat -a -b 查看一下是否真的打开。

当然,这个方法只能运用于信任度高的局域网内,否则就危险了。