In early days of August, I finished reading the book <<windows internals 4e>>: http://www.amazon.com/Microsoft-Windows-Internals-4th-Server/dp/0735619174

There’s a concept called dynamic volume. The volume type we commonly used are called basic volume. Basic volumes and dynamic volumes differ in ability to extend storage beyond one physical disk. The basic partitions are confined to one disk and their size is fixed. Dynamic volumes allow to adjust size and to add more free space either from the same disk or another physical disk.

I have done some experiment using dynamic volumes. I used VirtualBox for my virtual machine environment, since it can mount multiple disks.

dynamic_disks_1

By default, Windows choose basic volume types for installation. You can convert it manually in “Disk Management” component of MMC.

dynamic_disks_2

Then, you can create dynamic volumes of spanned, striped(RAID-0), mirror(RAID-1) and RAID-5 types. For Windows XP, only spanned and striped types are supported.

dynamic_disks_3

Here’s a screen-shot taken under Windows Server 2003:

dynamic_disks_4

Finally, here’s my colorful disk volumes:

dynamic_disks_5

今天来记录一下windows 中GDI (Graphics Device Interface) 的相关内容.

GDI 的目标是提供一种设备无关的绘图方式, 要支持不同的monitor 和graphics card.

1. WM_PAINT 消息

我们还是以以下的window procedure 代码为例:

这次关注的让然是WM_PAINT 消息的处理. 首先我们调用BeginPaint() 函数返回一个HDC 的handle. DC(Device Context) 可以理解成跟设备联系起来的, 可以在上面绘图的一个东西. 这里的设备一般指的是monitor, 但是也可以是printer. 得到这个handle 之后, 我们就可以在DC 上绘图了, DrawText() 函数就在HC 的正中间画了一个字符串. 最后就是EndPaint() 函数来说明绘图结束. 这里有2 个概念.

第一个概念叫做invalid region. 当一个被遮盖的窗口被重新显示的时候, 实际上要重画的只是被遮盖的部分. 而这些被遮盖的需要重画的部分就叫做invalid region. windows 会给窗口发WM_PAINT 消息来让窗口重画. 在代码中ps 变量(PAINTSTRUCT 结构)的rcPaint 域实际上包含了这个invalid region 的信息. 当我们调用BeginPaint() 的时候,实际上把这个invalid region 给validate 了. 不然的话, windows 检测到还有invalid region 没被重画就会不断的发送WM_PAINT 消息, CPU 会100% 的.

第二个概念叫做clipping region. 当我们调用BeginPaint() 的时候, 实际还把一个invalid region 转换成了一个clipping region. 什么意思呢? 就是之后的GDI 调用的绘图都会只限于这个clipping region 中. 如果某个GDI 调用画在了这个clipping region之外, 实际是不会有调用开销的, 这也是windows GDI 的一个优化. 虽然我觉得GDI 还是很慢的=v= (GDI+ 更慢…).

除了调用BeginPaint(), EndPaint() 来得到DC 外, 还可以通过调用GetDC() 这个API. 比如在处理一个鼠标消息, 可能需要在屏幕上画点什么的时候. 不过有这样几点需要注意: 1) BeginPaint(), EndPaint() 只能被用在WM_PAINT 消息中. 2) GetDC() 不会去把invalid region 给validate, 我们需要手动调用ValidateRect() 或ValidateRgn() 这2 个API. 完了之后, 记得调用ReleaseDC().

有的时候, 我们需要手动刷新一个window. 也有两种方法: 1) 调用InvalidateRect() 函数. 这种方法是post 一个WM_PAINT 消息到当前window 的message queue 中, 等待刷新. 一个message queue 中不会出现多个WM_PAINT 消息, windows 会自动合并. 2) 调用UpdateWindow() 函数来强制刷新window, 相当于send 一个WM_PAINT 消息来直接调用window procedure 中的处理代码.

2. GDI Objects

现在来看一下DC 的使用. 一个DC 可以有很多的属性. 有以下5 种: Bitmap, Brush, Font, Pen, Region. Bitmap 可以理解成DC 中的画布(canvas), 所有画在DC 上的元素实际都是画在Bitmap 这个属性中的. Brush 表示的是绘图的背景属性, 可以是单色, 渐变色, 或是一个图案(pattern). Font 自然是字体, 包括大小, 颜色, 粗细等. Pen 表示的是画笔的属性, 包括颜色, 粗细, 线段起始点, 拐点的一些绘画属性. Region 表示一个区域, 可以理解为Bitmap 这个canvas 上的一个clipping region.

我们通过SelectObject() 这个API 来设置这些属性. 以Brush 为例, 可以使用系统与定义的Brush:

也可以使用自定义的Brush:

其它4 种属性的调用相似. 只是最后自定义的属性, 使用完了一定要记得调用DeleteObject() 这个API 来删除, GDI 的object 也是会泄漏(leak) 的.

另外, 有一个logic object 的概念. 当我们调用CreateXXX() 函数的时候, 得到的GDI object 实际已经跟特定的DC 相关联了, 但是有时候, 我们并不要实际的GDI object, 而只是需要一个包含这些信息的一个数据结构. 于是我们就可以使用所谓的logic object. 以Font 为例. 在GDI 中CreateFont() 这个API 可以说是最麻烦的一个API 了, 光参数就有14 个. 相应的, 还有另外一个API 叫做CreateFontIndirect(), 它的参数是一个LOGFONT 结构, 其中的域对应了CreateFont() 的14 个参数, 所以可以用这两个API 完成同样的工作. 其它四种GDI object 也有类似的logic object.

3. GDI 坐标映射

到目前为止, 我们并没有设置过GDI 中的任何坐标. 所以我们到底是在用什么单位(unit) 来绘图的呢? 在GDI 中, 默认的坐标映射模式是MM_TEXT. 需要指出的是, 我们在GDI 函数中传入的数字都是逻辑坐标(logic coordinates), 而GDI 会根据当前的映射模式来重新加算设备相关的视点坐标(viewport coordinates).

其它的映射模式包括: MM_LOMETRIC, MM_HIMETRIC, MM_LOENGLISH, MM_HIENGLISH, MM_TWIPS, MM_ISOTROPIC, MM_ANISOTROPIC. 前5 个映射模式只是逻辑坐标的不同, 而x 轴和y 轴坐标都是等比例映射缩放的, 后两种映射模式允许非等比例的坐标映射. 具体的映射规则请查阅MSDN, 因为非常非常的麻烦, 丸子只给出一个映射的计算公式, 其中(xWinOrg, yWinOrg) 是逻辑坐标的原点, (xViewOrg, yViewOrg) 是视点坐标的原点:

  • xViewport = (xWindow – xWinOrg) * xViewExt/xWinExt + xViewOrg
  • yViewport = (yWindow – yWinOrg) * yViewExt/yWinExt + yViewOrg

这些数值当然不需要手动计算, GDI 主要提供了这样5 个函数来进行坐标映射的操作: SetMapMode(), SetWindowOrgEx(), SettWindowExtEx(), SetViewportOrgEx(), SetViewportExtEx().

根据丸子的经验, 为了计算坐标更容易, 一般只需要调用SetViewportOrgEx() 来设置视点坐标就可以了, 其它API 函数基本可以不用的.

4. DIB和DDB

DIB(Device-Independent Bitmap) 设备独立的位图, DDB(Device-Dependent Bitmap) 设备相关的位图.

什么意思呢? 简单来说, DIB 就是存在硬盘上的bitmap 文件, DDB 就是要在一个DC 上画bitmap 的时候, 一个HBITMAP 的handle 在windows 内存中表示的bitmap. 这里有个很搞笑的事情就是: windows 的GDI 函数中, 并没有提供从文件中读取DIB 的API 函数(Gdiplus 中有), 于是我们要手动写把DIB 转换成DDB 的函数, 这就需要我们了解DIB 的文件结构.

bitmap, 位图, 就是把一张图的所有颜色信息按照pixel 来存储, 所以我们可以把一个DIB 读到内存中, 并把这些按pixel 存储的信息放在一个数组中. 注意, 这里不仅仅是bmp 图片, jpg, png 等其它图片格式, 如果要画到GDI 的DC 上, 都要经过这写操作步骤. 这些步骤搞定之后, 我们可以调用SetDIBitsToDevice() 函数来把这些数组中的信息画到一个DC 上去, 注意倒数第二个参数:

调用SetDIBitsToDevice() 只是画DIB 的一种方法, 第二种是把一个DIB 转换成一个DDB, 然后就可以调用windows 的Bitblt() 函数来更高效的绘制bitmap 了. 之所以说更高效, 是因为SetDIBitsToDevice() 是一个一个pixel 画的, 而DDB 是已经包含跟DC 相关的bitmap 信息的了. 可以调用CreateDIBitmap() 函数来生产一个DDB, 嗯.. 你没看错, 名字和用处居然不一致. 然而这种方法的缺点是, 不能按pixel 来访问bitmap 的信息了.

于是, 我们有第三种方法, 调用CreateDIBSection() 函数. 这个函数既能转换DIB 到DDB, 又能提供按pixel 访问bitmap 信息的方法. 缺点是, 很难用, 这个API 函数返回的虽然是一个HBITMAP 的handle, 但是跟CreateDIBitmap() 返回的handle 不一样, 有诸多需要特别注意的地方. 所以丸子也不推荐使用= =.

总结一下就是, 对于一张不大的bitmap, 且要精心按pixel 操作的话, 使用SetDIBitsToDevice(). 对于比较大的bitmap, 且要被画多次, 那么使用CreateDIBitmap(), 因为DDB 的绘制会比较快速. 当然, CreateDIBSection() 提供了前两者的好处, 问题就是比较难用.

以上.

我终于读完了Charles Petzold 的这本圣书: http://www.amazon.com/Programming-Windows%C2%AE-Fifth-Microsoft/dp/157231995X

丸子觉得这本书虽然很多东西已经有些过时了, 比如palette, mci 之类的, 但是有2 个方面讲的非常的深入: 一个是windows 的message 机制, 还有一个是GDI 的绘图部分. 今天闲来讲一下windows 的message 机制.

1. 入口函数

先看一段最最最入门的代码, 这边写的代码都是纯C 代码:

跟console 的程序不同, windows 的窗口程序都是以WinMain 函数作为入口的. 不过这只是一个编译选项而已, 可以指定/entry 参数重设, 当然我们一般都用默认的. 第一个参数hInstance 表示的是这个程序实例的句柄(handle), 我们可以用它来load 内嵌的resource, 第二个参数在win98 以后的版本都不会用到不讲. 第三个参数是传入的命令行参数. 第四个也是运行参数, 控制的是窗口初始化时的现实模式, 最大化, 最小化还是其它什么, 这个参数可以在windows shortcut 的property 里设置, 也可以在CreateProcess() 和ShellExecute() 等API 函数里指定.

2. Unicode 编码

接下来是一个Unicode 的问题, 上面的代码用到的其实是ANSI 编码的API, 从windows nt 开始, Unicode 已经是windows 的内建编码了, 所以Unicode 版本的程序在windows nt 之后的版本会运行的更快一些. 然而windows nt 之前的os 就不一定能运行了. 还是看代码:

区别有这样几个: WinMain–>wWinMain, PSTR–>PWSTR, MessageBoxA–>MessageBoxW, “Hello”–>L”Hello”. 其实就是把函数改成Unicode 版本, 字符串改成宽字符. 我们也可以写一个ANSI 和Unicode 通用的版本:

_tWinMain 实际上是一个宏定义(macro)(注意下划线), 它会根据编译器是否定义了UNICODE 这个宏来分别预处理(preprocess) 成WinMain 和wWinMain, 同样的情况适用于PTSTR, TEXT, MessageBox 宏. 嗯.. 你没看错, MessageBox 实际是一个宏, 具体参阅windows sdk 的头文件.

3. Windows 消息机制

接下来看一个稍微复杂一些的代码:

好长好长的代码呀.. 运行的效果是, 现实一个窗口, 中间有一行字, 启动时还有音效.

我们要显示一个window(指的是程序窗口), 大概要做两件事, 第一是注册这个window 的class, 第二是用这个class 来create 这个window. 分别对应的API 就是RegisterClass() 和CreateWindow().

调用RegisterClass() 的时候, 我们指定这个class 的名字, 样式(style), 实例句柄(instance handle), 以及最最重要的window procedure 的回调函数(callback function)地址, 我们将通过这个回调函数来处理对于属于这个window class 所创建出的window 的消息处理.

RegisterClass() 并不创建实际的window, 而只是创建了一个window 的类别, 比如button 就是一个windows 内建的window class. 我们调用CreateWindow() 来创建一个具体的可现实的window. 可以看到, 这个API 的参数里有一个叫做window style, 这个东西很容易跟之前的class style 搞混. 怎么区分呢? 记住class style 指定的是所有这个class 的window 都会有的style, 比如一个button, 它总是可以click 的, 这就是一个class style. 而window style 则是在class style 的基础上各个window 定制的style, 比如不同的button 可以有不同的观感(look and feel), 这就是要给window style).

之后, 终于讲到windows 的message 机制了. 每个windows 的程序都会有一个message queue(消息队列), 这个queue 保存了从windows 系统或是其它程序发给这个程序的各种message. windows 可以通过这种方法来实现各个应用程序之间的交互. 有了这个message queue 之后, windows 又定义了叫做message loop(消息循环) 的机制来从这个queue 里拿出消息并进行处理. GetMessage() 这个API 就是用来实现这个功能的, 而代码中GetMessage() 之外的while 循环就叫做message loop.

注意, 这边GetMessage() 拿到的message 是针对整个程序来说的, 并不针对某个特别的窗口. 当GetMessage() 收到的是WM_QUIT 消息的时候返回0, 发生错误的时候返回-1, 其它情况下返回其它非0 值. 所以在代码中我们根据它的返回值是否为0 来判断要不要结束message loop. 在GetMessage() 的输出参数中, 会返回得到的message 数据, 即一个MSG 结构. 在message loop 中, 又有2 个API 调用.

TranslateMessage() 的作用是用来转换键盘消息. 当我们按下一个键的时候, 会产生WM_KEYDOWN 或WM_SYSKEYDOWN 消息, 放开按键的时候则会产生WM_KEYUP 或WM_SYSKEYUP 消息. WM_SYS 开头的消息是系统消息, 一般是Alt 跟其它键的组合键, 比如Alt+Tab, 这个没什么值得讨论的. 在WM_KEYDOWN 的时候, 我们有时要根据shift 是否也被按了来进行大小写字母的判断, 但这不是很好的做法. 比如, caps lock 的状态怎么判断? 如果输入的是中文呢(中文没有对应的virtual key code)? 所以TranslateMessage() 这个API 会帮我们做把所谓keystroke message 转换成character message, 即如果接受到WM_KEYDOWN 消息且shift 被按下又能够组合成一个字符(character), 那么另外一个WM_CHAR 消息会被插入到当前的message queue 中, 紧接在WM_KEYDOWN 消息之后. windows 在处理键盘消息的时候, 其实有一个system message queue. 为什么要有这个东西, 而不把每次的按键直接加到当前窗口的message queue 里呢? 因为无法判断下一个键盘消息到底要发给哪个窗口. 比如要是按了Alt+Tab 怎么办呢?

然后是DispatchMessage() 这个API. 它的作用是把得到的message 传回给windows 系统, 让windows 根据MSG 结构里的信息, 把这个消息dispatch 到相应的window procedure(关于window procedure 之后会讲). 这里其实很神奇, 在我们自己的程序里, 居然要手动dispatch 系统的消息.

接下来来看window procedure 的回调函数WndProc. 这个函数提供了对于各种发送给窗口的对于处理方法. 这个回调函数有4 个参数: 第一个参数是一个HWND 的handle, 由于多个的window 可以指定同一个window procedure, 可以用这个handle 来区分. 第二个参数是收到的message 的类型. 第三第四个参数是对于当前收到message 的参数, 比如对于WM_KEYDOWN 消息, wParam 就是key 的virtual code, lParam 则是其它一切附加信息. 代码中我们处理了3 个消息: WM_CREATE 是window 在被创建时收到的消息, 我们播放了一段音频. WM_PAINT 是window 要被重画时收到的消息, 我们简单画了一个字符串(关于GDI 相关的内容, 下次笔记再讲). WM_DESTROY 消息则是window 被销毁的时候收到的消息, 我们让整个程序退出. 对于其它我们不感兴趣的消息, 我们简单的把它们扔给DefWindowProc() 来做默认处理.

现在应该对windows 的消息机制有一定了解了, 让我们来看看以上程序中, 当我们按了Alt+F4 的系统热键发生了些什么吧. 我们的WinProc 首先会有到一个WM_SYSCOMMAND 消息, 并传给DefWindowProc() 处理. DefWindowProc() 的默认处理方法就是发一个WM_CLOSE 消息给这个window. 又一次, 我们的WinProc 吧这个消息传给DefWindowProc(). DefWindowProc() 的默认处理方法是调用DestoryWindow(), 而DestroyWindow() 会给这个window 发一个WM_DESTROY 消息. 这次终于有自定义处理代码了, PostQuitMessage() 简单的发送一个WM_QUIT 消息给当前的程序. WM_QUIT 消息导致message loop 的退出(GetMessage() 返回0), 于是整个程序结束. PostQuitMessage() 的参数表示exit code, 会被存在MSG 结构的wParam 域里.

以上说了message loop 中的消息处理. 这里消息叫做queued message, 但是还有一类叫做nonqueued message, 即它们是不会进入message loop, 而是直接通过上面的window procedure 函数调用的. 有两个函数: SendMessage() 和PostMessage(), 它们分别对应queued 和nonqueued. 调用SendMessage() 产生一个nonqueued message, 结果就是直接调用window procedure 函数, 且函数返回了, SendMessage() 才会返回, 它是同步的. 调用PostMessage() 产生一个queued message, 并把这个message 加到message queue 中, 之后通过程序的GetMessage() 来polling 处理, PostMessage() 是直接返回的, 不需要等到message 被处理完, 它是异步的. 一般, 通过windows API 间接发送的消息都是nonqueued message, 比如代码中的UpdateWindow() 会把WM_PAINT 作为参数直接调用window procedure 回调函数来强制刷新.

另外要提到作为timer 使用的WM_TIMER 消息. 这是一个queued message, 虽然用来计时, 但却不是那么的准确. 如果WM_TIMER 的前一次消息处理时间太长的话, 会把多个WM_TIMER 合并在一起. 因此, 需要更加准确的计时的话, 我们需要调用Thread 的相关API 函数.

4. 子控件相关

稍微加少下子控件的概念, 所谓的子控件, 就是window上的控件, 真废话=v=. 这里要说的其实没多少东西, 只是一笔带过.

当子控件被点击的时候, 子控件就会给父控件发送一个WM_COMMAND 消息. 当一个push button 被点击, radio button 被选中, check button 被选中都是这个消息. 注意, 是从子控件发送给父控件, 也就是说WM_COMMAND 消息的处理是在父控件的window procedure 回调函数中.

当我们用CreateWindow() 创建window 的时候, 上面的子控件是不具备Tab 遍历功能的, 我们添加对于Tab 键的处理. 但是如果它是通过CreateDialog() 或DialogBox() API 函数创建的话, 只要子控件指定了WS_TABSTOP 的window style 的flag, 就会自动添加Tab 键的遍历功能.

当我们要拿子控件的属性的时候, 我们其实也是通过windows 的message 来实现的. MFC 类库中把这些message 都封装成了宏(macro) 来方便使用. 比如我们要那一个check button 的选中状态, 则可以发送一个BM_GETCHECK 消息.

关于子控件的自定义有3 种方法或者说概念, 从最简单的说起:

a) Owner Draw:
这种方法一般用来定制子控件的外观. 举个例子, 如果要owner-draw 一个push button 的外观, 那么这个button 的style 里要有BS_OWNERDRAW 这个flag. window procedure 回调函数遇到owner-draw 的控件时, 会收到一个WM_DRAWITEM 的消息, 你要做的就是在这个消息处理中把button 画出来.

b) Sub-classing:
这种方法一般用来定制子控件的行为. 还是以push button 为例, button 的点击的默认行为是发送一个WM_COMMAND 消息给父窗口, 那么我们怎么修改这个默认行为呢? 由于button 这类系统与定义的控件的window procedure 都是在windows 系统中写死的, 我们只能想办法替换这个window procedure 回调函数. 具体的方法是调用SetWindowLong() 这个API, 并传入DWL_DLGPROC 这个flag.

c) Custom Control:
不用说了, 最全面的方法. 如果你要完全定义一个全新的子控件. 你需要完全自己coding 所有的代码, 调用RegisterClass(), CreateWindow(), 实现window procedure 回调函数. 很大的工程那…

5. Dialog 对话框

Dialog 可以分成2 类, modal 和modeless(注意拼写=v=). 具体概念我就不解释了.

一个modal 的dialog 调用DialogBox() 函数创建, 一个modeless 的dialog 调用CreateDialog() 创建. 跟CreateWindow() 的调用一样, 我们也要把一个所谓的dialog procedure 当作参数传进去. 一下是这个procedure 的signature:

对比window procedure:

dialog precedure 虽然返回值是一个INT_PTR类型, 但通常情况下返回的是TRUE 或这FALSE, 特殊值的返回请参阅MSDN. 除了返回值意外, 这两个回调函数似乎没什么不一样, 但实际上并不那么简单. 一个dialog 是一个window, 而它对应的window procedure 是包含在windows 系统中的, 在那个window procedure 中, 会调用到我们的dialog procedure.

dialog 不会收到WM_CREATE 消息, 但是会收到对应的WM_INITDIALOG 消息, 想来是windows 系统在WM_CREATE 消息中重新发的消息吧. 如此一来便可以完成一些dialog 特有的初始化操作. 当我们要用代码销毁一个dialog 的时候, modal 和modeless 这2 中dialog 的方法不同. modal dialog 调用EndDialog(), 而modeless dialog 调用DestroyWindow().

最后, 书上有这样一句话: Unlike messages to modal dialog boxes and message boxes, messages to modeless dialog boxes come through your program’s message queue. The message queue must be altered to pass these messages to the dialog box window procedure. 然后说我们要调用IsDialogMessage() 这个API 来传递message. 经过实践, 上面那句话的意思其实是说: modeless dialog 跟window 一样, 默认不支持Tab 键的遍历等键盘消息处理, 但是调用了这个IsDialogMessage() 之后, 所有标记过WS_TABSTOP 的子控件就都支持了. MSDN 上还说, 传入的window 句柄不一定要是dialog, window 也可以. 通过这种方法, 我们通过CreateWindow() 出来的window 也毫不费力的实现Tab 的遍历了. 于是最终, 我们的message loop 的代码大概是这样的(假设hDlgModeless 是一个modeless 的dialog 的handle):

如果我们有多个modeless 的dialog, 这种写法岂不是要死人? M$ 的knowledge base 上有篇文章可以参考: How To Use One IsDialogMessage() Call for Many Modeless Dialogs

以上. 请继续期待下次GDI 的笔记.

WPF(Windows Presentation Foundation), WCF(Windows Communication Foundation), WF(Windows Workflow Foundation) 是.NET 3.0 中新加入的类库. 这里会介绍一下WCF 和WF 的大概应用和架构, 关于WPF, 由于丸子也还没能完全得到那个点, 所以可能就 不介绍了, 还需要在多看看砖头.

进入正题, 今天讲的是WCF. 这个东西是用来创建分布式系统的.

首先介绍一下SOA(Service-Oriented Architecture) 的概念. SOA 实际上是一个基于service 的分布式架构. 每个service 对外提供一定的功能接口. 当我们要做一个大的软件的时候, 只要把这些service 提供的接口组合一下就可以了. 那这些service 之间, 或者我们client 程序之间怎么交互呢? 这里就是SOA 实现的问题了. 比如比较流行的有Web Service, Windows 下比较老的DCOM 和MSMQ. 当然还有WCF. Web Service, WCF 代表的是一种SOA的技术或者说实现, 而它们的通信协议(protocol) 是另外一个概念. 比如Web Service 用的可以是SOAP 或是RESTful 协议. WCF 的好处就是, 把以前的一些技术都整合到了一起, 包括DCOM, MSMQ, .NET remoting, Web Service 等.

要建立一个WCF 的应用程序, 基本的组件有三个: a) service assembly, b) service host, c) client. client 端和server 端通信是通过WCF 中的所谓ABC: Address, Binding, Contract. Address 表示的是service 的地址, Binding 表示的是service 的协议, 编码等, Contract 表示的是WCF 对外暴露出的方法.

service assembly 的实现, 主要就是在Contract 这块. 我们用接口(interface) 来定义contract, 然后再用一个具体的类来实现它. 比如:

然后把以上代码编译成一个dll, 就算完成了.

service host 的稍微复杂一些. 主要用到了System.ServiceModel.ServiceHost 这个WCF 中的类来实现application 级的service host. 大概的代码可能是这样:

在代码中, 我们在ServiceHost 类的constructor 中传入了MyService 类型作为所要host 的service. 注意, 要调用这个constructor 的话, MyService 类必须有一个不带参数的constructor, 不然运行时会抛错的那. 当然也可以用一个service 的实例来初始化ServiceHost, 但是比较麻烦, 丸子也没研究过.
以上的代码显然是缺少Address 和Binding 这两快内容的, 我们要在app.config 的配置文件中指定. 配置文件大概是这样的:

好处一串… 先来看<service>这个tag, 它对应的就是contract 的实现类, 用name 属性来指定. 然后是<endpoint>这个tag, 它对应的是单个的contract 的接口. 在运行时, ServiceHost 会进行这样的检查: 根据代码中的MyService 类找到app.config 中对应的<service>的tag, 这个tag 中的name 属性默认就是类名, 但是也可以指定为一个friend name, 不过具体做法未知=v=. 然后分别比对实现类和config 文件中的接口定义是否一致, 如果不一致的话会抛运行时错误. 注意: 我们在所有的<endpoint>中的address 属性都为空, 其实是利用了下面的<baseAddresses>中的baseAddress 属性, 这是一个基地址.

另外还有一个MEX(Metadata Exchange) 的概念, 在config 文件中, 对应一个<endpoint>和<behavior>的tag. 简单来说, 这个东西就是用来生产client 端的代理(proxy) 代码的. 它提供了对于metadata 请求的响应. 当运行这个service host 的时候, 我们可以用browser 来访问mex 这个<endpoint>中的address, 会看到关于这个service 的metadata 信息, 以及如何生产client 端代码的提示. 一旦我们生产了client 端的代码, 如果不希望这些metadata 信息再被发现(discover) 的话, 可以删掉这两个tag.

注意, 如果是vista或者win7 的话, service host 程序必须以管理员权限运行.

最后就是client 端的写法了. client 端怎样通过WCF 的框架来调用远程的service 呢? 我们需要一个proxy 的类, 这个类通过在service host 得到的metadata 信息, 来封装对于service 的远程调用. vs2008 提供了wizard 来实现这个繁冗的过程, 请自由的在项目节点上右键点击add service reference 菜单项. 在出现的对话框中, 会看到service 的列表, 而每个service 包含多个service contract, 而每个service contract 又包含多个operation contract. 我们要生成的proxy 类的代码是service 级别的, 对于每个service, 所有的代码都生成在一个namespace 中. 一旦生产了代码, 我们的client 端代码将是非常简单的:

WCF 还提供了异步调用和自定义数据类型的支持, 这些就请自行查阅MSDN 吧.