Monday, March 30, 2009

Windows编程 WIN32 API入门三 (MESSAGE, STYLE 和 GDI) (预稿)

前面写的两篇介绍了Windows API编程中最基础的部分. 其中第二篇中的概念比较多, 也比较难理解, 我迟迟没有修改第二篇, 是因为它的确很难修改, 而且修改了也很难说清一些东西, 只能先靠大家悟性去理解了.

这星期都忙于bin32的三个软件的开发, 周末了, 终于有空来写第三篇. 在这一篇里, 主要展示下Window的常见消息.
我会通过一个自定义的Window Class来说明, 这个class 就是 control_spliter, 其源码可以在下面的地址下载http://www.bin32.com/bbs/viewthread.php?tid=47.

集成control_spliter

还记得上一篇里面的Win32 Basic工程么? 我们在这里, 建立一个相似的工程, 名字叫Sample_Spliter, 接下来我们把control_spliter的源码放进去, 然后在Sample_Spliter.cpp的头部加上
#include "control_Spliter.h"
把stdafx.h里面的#define WIN32_LEAN_AND_MEAN删除.
然后在control_Spliter .cpp 头部加上#include "stdafx.h"

这样整个工程就可以顺利编译了.
但是编译了之后什么都没有变化, 是的, 因为我们什么都没有创建.
我们先在Sample_Spliter.cpp上部声明几个全局变量
HWND HWndEdit1;
HWND HWndEdit2;
HWND hWndSpliter;
然后在WndProc这个函数的主switch语句里面加入两个case;


case WM_CREATE: {
initial_Control_Spliter(hInst);
HWndEdit1 = CreateWindow(TEXT("edit"), TEXT("EDIT1"), WS_CHILD|WS_VISIBLE|ES_MULTILINE, 0, 0, 0, 0, hWnd, NULL, hInst, NULL);
HWndEdit2 = CreateWindow(TEXT("edit"), TEXT("EDIT2"), WS_CHILD|WS_VISIBLE|ES_MULTILINE, 0, 0, 0, 0, hWnd, NULL, hInst, NULL);

CONTROL_SPLITER_INFO Info;
Info.Horizenter = TRUE;
Info.hWndFirst = HWndEdit1;
Info.hWndSecond = HWndEdit2;
Info.SpliterWidth = 4;
Info.Style = 0;
Info.FirstLength = 0;
Info.SecondLength = 0;
Info.Ratio = 1;
Info.CommonLength = 0;
hWndSpliter = CreateWindow(WC_SPLITER, TEXT(""), WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, NULL, hInst, &Info);

} break;
case WM_SIZE: {
WORD X = LOWORD(lParam);
WORD Y = HIWORD(lParam);
MoveWindow(hWndSpliter, 0, 0, X, Y , TRUE);
} break;



我来简单解释下这两个消息:


WM_CREATE:正像它名字说解释的, 表示窗口正在创建的消息.


我们在这个消息里使用CreateWindow创建了两个EDIT, 然后再创建了一个Spliter.


WM_SIZE:表示窗口被重新调整了大小, 从代码上看, 貌似我们通过lParam取得了窗口了X值和Y值, 然后MoveWindow应该是把Spliter移动在0,0点, 宽为X, 高为Y. 是的, 事实完全就是这样, 当窗口接受到WM_SIZE消息时, lParam(它是一个DWORD)就包含了窗体”客户区”的高度和宽度值, MoveWindow可以用来移动窗口, 这里被移动的是子窗口, 将它移动到完全覆盖父窗体的”客户区”的位置.


来我们看看效果~



哎呀, 中间一个小棍棍把两个缺钙的Edit分开了, 鼠标点点, 中间的小棍棍还能动~ (至于这两个小Edit为什么缺钙, 以后再说)




常见WINDOW STYLE



你可能已经发现, 我们在使用CreateWindow 函数时, 第三个参数都包含, WS_CHILD | WS_VISIBLE.

CreateWindow的第三个参数是Window的STYLE (“WS_”):


WS_CHILD:


表示这是一个子窗体, 此时CreateWindow倒数第四个参数表示这个Window的父窗体, 如果父窗体是NULL, 函数会失败.


WS_VISIBLE:


表示窗体是否可见, 大多数时候我们创建Window时并不需要所有Window都显示, 比如播放器的播放列表.


另外创建Edit的时候还使用了ES_MULTILINE:


这是一个Edit专用的STYLE,(“ES_”) 它表示这个Edit可以接受多行输入.


|:


或操作符 (如果你已经忘了), &, ^, ~和|在这里使用非常广泛. 毕竟你需要把一堆信息通过一个参数传进去. 如果你仍然不理解这个, 你可能需要仔细回顾位操作的用途.




接下来我给出几个非常常见的通用Window STYLE, 具体的可以查阅MSDN, 专用STYLE也因该查阅MSDN.


WS_CHILD, WS_VISIBLE: 最常用的两个STYLE, 已经说过了.


WS_ OVERLAPPEDWINDOW: 独立窗口的典型STYLE.


WS_BORDER: 带有边框的, 通常那边框可能不是你想像的那么好看.


WS_DISABLED: 不可使用, 通常表现为”灰掉”.


WS_VSCROLL, WS_HSCROLL: 水平滚轮和上线滚轮, 不过通常这个滚轮不太好控制.




Extended Style

至今我们都在用CreateWindow创建窗口, 其实我们还可以使用CreateWindowEx, 两者的差别只是CreateWindowEx多了一个参数: Extended Style, Extended Style很常用, 我简单说说常用的WS_EX_:


WS_EX_ACCEPTFILES: 原来下载软件的文件拖拽的窗口就是用的它~.

WS_EX_LAYERED: 你是否觉得那些程序的半透明的或者渐变透明的窗口都很神奇? 其实他们只是使用了layered窗口的特性而已. 不过layered窗口使用其他会不太一样, 使用这个属性之前, 要好好研究下layered窗口才行.


WS_EX_TOOLWINDOW: 你是否觉得你的程序的标题边框太大, 这个属性可以帮你解决这个问题. 不过这个属性的实际用途是提供一个能够浮动的小工具栏窗口.


WS_EX_TOPMOST: 置顶窗口. 想挡住别人, 用它吧~




Spliter的实现


举完这么多STYLE, 我们还是回到消息上, 我们来自习看看我们的Spliter.


它能够接收鼠标按下, 和拖动的消息. 拖动的时候自动把两个小窗体大小调整, 我们来看看control_Spliter里面有什么.


VOID Redraw(…), 貌似是一个重绘函数.


LRESULT CALLBACK proc_Control_Spliter(…), 消息处理循环, 应该是处理spliter这个Window的消息的.


BOOL initial_Control_Spliter(…), 初始化函数.


initial_Control_Spliter很简单, 注册了一个Window的class, 我们知道, Window必须选注册才能使用, 所以我们得先运行这个函数.


Redraw是一个利用DC重绘的函数, 也就是传说的GDI绘图.




GDI



GDI是什么, 怎么实现了的. 这不重要, 对我们来说重要的如何使用它, 我现在给出一个可能让你觉得很失望的解释.

GDI绘图就是使用一堆普通的C/C++函数, 唯一有点区别的就是被绘图的对象是一个特殊句柄HDC.


我们来看看Redraw这个函数, 它有两个参数, HDC和CONTROL_SPLITER_INFO, CONTROL_SPLITER_INFO是一个特殊的自定义结构, 它在control_Splite.h里面定义, 它用来保存这个Spliter的信息.


VOID Redraw(HDC hdc, CONTROL_SPLITER_INFO * SpliterInfo ) {


    RECT Temp;//这是一个特殊的结构, 它有4个LONG值, 分别表示一个矩形的左上点和右下点的X,Y值.


    // 一堆计算操作


    FillRect(hdc, &Temp,CreateSolidBrush(RGB(240, 240, 240)));


    // 填充一个矩形, 位置在Temp里, 颜色是RGB:240, 240, 240


    // 又是一堆计算操作


    FrameRect(hdc, &Temp,CreateSolidBrush(RGB(180, 180, 180)));


    // 画一个矩形的边框, 位置在Temp里, 颜色是RGB:180, 180, 180


}





你可能已经猜出来了, 那两个Edit中间的小棍棍是利用两个矩形画出来的. 是的, 这就是传说中的GDI. 那接下来一个问题, 那个HDC是哪里来的呢?




其实只要介绍两个函数:


GetDC(HWND) :取得一个窗口的HDC, 得到的HDC是这个窗口的”客户区”, 这个名词之前有提到, 它表示这个窗口除了边框外, 中间的区域.


取得这个HDC之后, 就可以在这个HDC里面使用GDI函数画图了.


ReleaseDC(HWND, HDC) :事实上, 里面使用GDI函数在HDC上画图, 实际被画图的内容并没有被显示出来, 你需要调用ReleaseDC这个单数让被绘制的效果立即显示, 如果你不调用这个函数, 绘制的效果会过一段时间后显示, 不过这可能会引起一些问题.



WM_PAINT事件, 当有这个事件发生时, 你需要调用


BeginPaint(HWND, &PAINTSTRUCT), EndPaint(HWND, &PAINTSTRUCT);这两个函数, 函数中的PAINTSTRUCT结构里面已经有了HDC, 不需要调用GetDC, 和ReleaseDC.



重要的GDI结构:

RECT, POINT
. RECT刚才已经说到, 它表示矩形, POINT表示一个点.


GDI的函数库里面有足够多的函数可以进行对RECT和POINT操作, 如坐标映射, 矩形的交, 并运算, 判断点是否在坐标等等. 你不需要自己写函数对其运算.




重要的GDI功能:




  • 绘制点, 线条, 矩形, 椭圆, 多边形, 弦, 饼图.


  • 输出文字, 输出图片.


  • 图片切片, 变形等处理.


  • 笔刷, 颜色笔刷, 或者图片笔刷等等..



可以说GDI能够做所有的绘图相关的事情, 只是有的简单, 有的麻烦.



常见的消息和鼠标函数



介绍完GDI, 我们再回来看看最后一个函数: proc_Control_Spliter, 我们要做的就是根据这个函数, 介绍下常见的消息和鼠标函数.

WM_CREATE: 我们又看见它了, 它首先调用了malloc分配一块内存,


然后调用了这句话:


*SpliterInfo = *(CONTROL_SPLITER_INFO *)(((CREATESTRUCT *)lParam)->lpCreateParams);


还是否记得我们在WndProc的WM_CERATE里面创建了一个CONTROL_SPLITER_INFO, 然后把它作为CreateWindow的最后一个参数传了进去, 这句话就可以把这个结构弄出来, 并且赋值给* SpliterInfo. 通过这个手段, 我们可以为自己创建的Class提供足够的显示数据.


接下来运行了:


SetWindowLong(hWnd, GWL_USERDATA, (LONG)SpliterInfo);


这句和下面我们会看到的GetWindowLong(hWnd, GWL_USERDATA);是相对应了, 其目的就是把SpliterInfo的地址设为这个窗口的” USERDATA”, 这个”USERDATA”会由操作系统保存, 之后调用GetWindowLong就可以将其取出, 是不是很方便啊~


接下来是两个SetParent, 把那两个Edit设为自己的子窗口.


WM_DESTROY: 窗体要被”扔掉”的消息, 在这个消息里面我们把USERDATA的内存给free掉了.


WM_PAINT: 绘图消息, 当然是使用我们的Redraw函数绘图了.


WM_MOUSEMOVE:鼠标移动的消息, 在这里,我们经过了一系列复杂的运算, 最后调用了两个MoveWindow把那两个Edit放到合适的地方.


最后我们运行了下面这句话:


hdc = GetDC(hWnd);


Redraw(hdc, SpliterInfo);


ReleaseDC(hWnd, hdc);


其实MoveWindow最后一个参数就是是否对窗口进行重绘, 如果为TRUE, 则被Move的窗口会接收到WM_PAINT消息.


但是我们这里还是主动调用了绘图函数, 为什么呢?


消息队列及优先级


这涉及到Window的消息队列问题,每个Window都有自己的消息队列, 记录已经接受到的消息, 但是消息队列里面的消息是有优先级的, 
WM_MOUSEMOVE的优先级远比WM_PAINT高, 换句话说, 如果WM_MOUSEMOVE不断产生, WM_PAINT可能是一直不被发出的. 所以我们这里在WM_MOUSEMOVE里面主动调用了GDI函数.


WM_LBUTTONDOWN, WM_LBUTTONUP: 从字面上看, 是”鼠标左键被按下, 和按起”.


我们在这两个消息中运行了SetCapture和ReleaseCapture两个函数, 前者表示”从现在开始, 鼠标事件一直发生在我身上”, 换句话说, 就算鼠标离开了窗口的范围, WM_MOUSEMOVE消息也会不断产生. ReleaseCapture则解除这个功能.


WM_SIZE: 窗口被重新调整大小消息, 我们这里进行了一堆复杂的操作后, 强制进行了重绘.



其他常见消息:


你可以主动向窗口发送消息, (SendMessage函数).

利用这个函数, 你既可以实现一些通知功能, 也可以实现信息获取功能.下面给出几个非常常用的消息, 你可以从中理解这一点.


WM_CLOSE: 和WM_DESTROY不一样, 这个消息只表示你点击了窗口右上角的叉, 不过默认它会调用WM_DESTROY.


WM_GETTEXT: 取得Window的TEXT, 一般你只需要发送这个消息, 而不需要自己处理这个消息.


WM_ENABLE: 设定Window是否有效, 和GETTEXT一样, 一般不需要自己处理.

Monday, March 23, 2009

Rebar Controls 使用需要注意点.

由于需要, 今天在程序中使用了 Rebar 这个控件.
和以往一样, MSDN ... 不过今天却遇到了点问题.

按理说Rebar也算是一个比较灵活的控件, 不过MSDN只给出了一个Sample ... 更让人郁闷的是这个Sample竟然是有错误的... 我汗... 汗了一个下午... 汗的我感冒都快好了...

它给出的示例代码叫"Creating a Rebar Control”功能很单一, 创建一个Rebar上面放了一个ToolBar一个Combobox

它使用下面这句话取到ToolBar按钮大小
DWORD dwBtnSize = SendMessage(hwndToolbar, TB_GETBUTTONSIZE, 0,0);

然后执行了以下操作.
rbBand.cyChild = LOWORD(dwBtnSize);
rbBand.cxMinChild = NUMBUTTONS * HIWORD(dwBtnSize);
rbBand.cyMinChild = LOWORD(dwBtnSize);


很显然, HIWORD和LOWORD用反了... 所以创出来的都是畸形..

这是一个问题, 另一个问题是:
如果你直接写一个WinMain, 然后运行这个示例代码. 你会发现执行
SendMessage(hwndRebar, RB_INSERTBAND, (WPARAM)-1, (LPARAM)&rbBand);
的时候总是失败. 也没有LastError, 很是郁闷.

经过仅两个小时的仔细研究, 发现要定义Windows版本在0501以上才行.
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501


最后来张截图, Rebar用在这里了

Thursday, March 19, 2009

Windows编程 WIN32 API入门二 (HANDLE, INSTANCE, Window, 和简单窗口程序)

今天首先我会介绍基本的数据类型, 以及说明什么是HANDLE和INSTANCE.
之后我会以一个小程序作为例子, 大致说明Windows编程都干了什么. 并在此期间穿插这介绍Window的概率和Windows程序运行的机制.
我认为这部分相当的枯燥, 也是Windows编程入门最难的地方. 我会仔细并努力将每个要点都点到. 大家看完后懂不懂, 就看大家造化了.
值得一提的是, 看完这篇文章后, 可能会发现:虽然明白怎么回事, 但是还是写不出属于自己的程序, 这是正常的.

HANDLE, INSTANCE

DWORD
MS似乎钟爱使用DWORD这个数据类型. 这里的DWORD和IA32汇编编程的DWORD不一样. 它其实是一个unsigned long.  在80x86环境下unsigned long是4字节. 所以DWORD是32位无符号整数.

HANDLE, INSTANCE
前者中文叫句柄, 后者叫实例. 如果你直接被告知这两个东东是用来识别各种对象的, 你可能还是不知道为什么要使用它和什么时候需要使用它. 所以, 我们需要从操作系统的角度来理解它们.
操作系统需要管理很多东西, 各种窗口, 各种按钮, 各种设备, 各种xxx.  操作系统需要识别他们, 就像给每个人一个名字.于是有个人想到:不如让他们每人拿着一个数字, 而且每人的数字都不一样不就行了. 这就是HANDLE.
不过又有问题出现了, 就拿设备来说, 分类太多了, 都不知道以后会有什么样的新设备出现. 于是程序员想, 干脆就别分类了. 所以你会发现, 一个窗口拿着一个HANDLE, 一个ICON(图标) 也拿着一个HANDLE, 一个用来输出输入的设备也拿着一个HANDLE. 到处都是HANDLE. 想知道你系统中有多少HANDLE? 也许你会在任务管理器中发现它.
接着接着… 程序员看着一堆HANDLE觉得头大, 突然想到了typedef. 于是, 窗口的HANDLE变成HWND, ICON的变成HICON, 菜单的变成HMENU. 等等. 其实他们都是HANDLE, 只是看上去似乎有点区别.
INSTANCE其实也是一种HANDLE, 但是它识别的东西有点特殊. 你暂且可以认为它是用来识别各种进程的HANDLE. 一般来说, 你仅会在载入”资源文件”, 和使用”dll”的时候使用它. 换句话说, 一个相同的exe, 如果你同时打开两个. 那么这两个具有不同的INSTANCE.

Window

也许之前你看了许多, 还是没觉得Windows编程有啥特别. 不过看来这一篇, 相信你会发现不一样的地方.
这里说的Window可不是一个打开的窗体, 而是Window. 算了, 还是让我们先来看一个窗体.
我知道这是一个窗体, 可是他有多少个Window呢, 我大致数了下, 大概20个. 下面这个图中的红框框全部是Window, 我没画全, 因为我不想让这张图变得连我都不认识.


是的, 每个按钮, 每个控件, 都是一个Window. 这点一定要理解, 我认为这是Windows设计相当独到的地方. 也是很难理解的地方 ,我在这会给予比较简要的说明. 不过会比较枯燥.
为了给予区别, 在下面我会使用”窗口”这个词来代表我们通常所说的窗口, 而使用Window 在指窗口里面的各种Window. (虽然Window的翻译就是窗口.)
每个Window都有自己独立的属性, 比较重要的有:
CLASS (window 类)
比如按钮, 他们的CLASS叫做”button”.
值得一提的是, 每个”窗口”包含了很多window, 但是其自身也是一个window, 而且拥有自定义的CLASS名称.
STYLE (样式)
比如说按钮, 有带只图标的, 有只带文字的. 有正在显示的, 有正在隐藏的 … 怎么控制呢? 拿STYLE. 另外, 一个window如果想成为”窗口”, 需要拥有特定的STYLE才行.
TEXT(内容)
在不同的window 类里面有着不同的意义, 比如在按钮里面, 这个就表示按钮要显示的字, 在”窗口”上, 这个代表”窗口”的标题.
PARENT(父窗体)
一个”窗口”里面拥有很多Window, 但是它自身也是一个Window. 后者就是前者的PARENT. 相对的, 就有CHILD(子窗体)的概率, 每个Window都可以拥有CHILD. (如果一个Window没有PARENT, 那么它一定是一个”窗口”)
MENU (菜单)
虽然名字叫菜单, 但是他仅仅在当前的window是一个”窗口”的时候才表示窗口的菜单. 如果当前的window是一个按钮, MENU则表示这个按钮被按下后所发出的命令.
PROC (消息处理函数)
严格上说, 这个可以是window CLASS的属性, 不过每个window可以拥有单独是处理函数来处理各种各样的事件.
如果你不理解上面所说的东西, 可以先暂时跳过. 这个东西的理解需要一定时间. 你可以在下一段落中慢慢理解.
但是你必须明白, 每个按钮, 每个控件啊什么的, 都是一个Window. 这是基础.

Windows程序的结构

第一个带窗口的WIN32程序.
接下来我会带大家分析一个带窗口的WIN32程序.
使用VS2008创建一个”Win32 Project”, 我起名叫"Win32 Basic". 什么配置都不要改, 创建完了之后编译运行, 能够看到下面的窗口.


接下来我会给大家分析这个示例代码的重要部分, 并借此给大家说明Windows程序的结构.
我跳过的部分建议你不必太关心, 他们大多数只是在实现一些细节的功能. 你暂时大可跳过.
入口:
如果你还在找main的话, 那么很遗憾的说, 没有main, 整个程序的入口在这里.
int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
_tWinMain, 看上去也是一种main, 不过参数不太一样, 不过别担心, 你不需要主动调用这个函数. 真正调用它的是操作系统, 它会为你填好参数. 除了hInstance之外, 其他参数在这里你也无需关心. (_t作为前缀, 还记得上一篇中介绍的UNICODE吧).
注册CLASS
接着程序运行了MyRegisterClass()这个自定义函数. Window有个重要的属性是CLASS, 但是每个独立的窗口都必须拥有一个CLASS, 这个CLASS得自己创建. 操作系统提供了”注册窗体类”的功能, 对CLASS进行注册之后, 就可以使用了. 这个函数注册了一个名字为szWindowClass的CLASS (注意, szWindowClass不是CLASS的名字, 而是szWindowClass这个变量存的内容, szWindowClass已经通过LoadString这个系统函数得到了一个字符串.)  我们来看看这个函数干了什么.
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex; //定义一个结构, 这个结构要用来注册CLASS用的.
    wcex.cbSize = sizeof(WNDCLASSEX); //初始化结构
    wcex.style        = CS_HREDRAW | CS_VREDRAW; //定义CLASS的STYLE, Window有STYLE, 其实CLASS也有的.
    wcex.lpfnWndProc    = WndProc; //所有”这个CLASS的Window”的消息处理函数都是WndProc (这个是函数指针, 很多C语言的书都没有提及)
    wcex.cbClsExtra    = 0;//忽略
    wcex.cbWndExtra    = 0;//忽略
    wcex.hInstance        = hInstance;//这个CLASS所属于的Instance, 一般来说, 别的Instance是不能使用的
    wcex.hIcon        = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32BASIC)); //定义所有”这个CLASS的Window”的图标,它有可能是exe程序的图标
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW); //定义所有”这个CLASS的Window”的鼠标指针
    wcex.hbrBackground    = (HBRUSH)(COLOR_WINDOW+1); //定义所有”这个CLASS的Window”的背景颜色
    wcex.lpszMenuName    = MAKEINTRESOURCE(IDC_WIN32BASIC); //定义所有”这个CLASS的Window”的菜单
    wcex.lpszClassName    = szWindowClass; //这个CLASS的名字
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); //定义所有”这个CLASS的Window”的小图标
    return RegisterClassEx(&wcex); //告诉操作系统关于这个CLASS的信息, 今后就可以使用它了.
}
创建Window
没错, 就在InitInstance这个自定义函数里. 这个函数最重要的是运行了下面的一个函数. 我们伟大的CreateWindow :
CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
这个函数非常重要和常用. 所以这里我会对这个函数的参数进行简单说明.
HWND CreateWindow(
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam
);

LPCTSTR是一个宏+typedef, 最终会等价于TCHAR*
HWND, HMENU, HINSTANCE都是HANDLE, 分别代表了窗口句柄, 菜单句柄, Instance
LPVOID 等价于 void *
lpClassName: Window的CLASS名, 在现在的例子中, 这个CLASS名已经在MyRegisterClass()函数中注册过.
lpWindowName: Window的 TEXT, 在这里表示”窗体”的标题
dwStyle: Window的STYLE值, 你可以在MSDN中找到很多STYLE值, 它们中的大多数是可以同时使用的, 同时使用的方法就是使用”或”运算. 如 WS_CHILD | WS_VISIBLE. 这里的WS_ OVERLAPPEDWINDOW是一个普通的独立”窗体”所需的STYLE. (事实上, 它也是多个STYLE的组合).
x, y, nWidth, nHeight: 就像他们的名字一样, 它代表了Window的位置. 如果它拥有父窗体, x, y则代表其在父窗体中的位置. CW_USEDEFAULT则是一个特殊的数值, 组合使用它可达到很多效果, 这里CW_USEDEFAULT, 0, CW_USEDEFAULT, 0组合能够让系统选择合适的位置和大小显示窗体.
hWndParent: 父窗体的句柄, 这里用NULL, 代表他是一个独立显示的”窗口”
hMenu: Window的MENU句柄. 这里使用NULL. (不过我们可以看到这个主窗体是有菜单的, 其实这是因为窗体的CLASS已经定义了MENU的属性)
hInstance: Instance的句柄, 程序入口_tWinMain的第一个参数就是它. 注意Instance都是由操作系统分配的, 你能做的只是在该使用它的时候使用它.
lpParam: 这是一个void指针, 它能够提供一些额外的数据. 很多Windows的函数都有类似的参数, 很显然这是一个成功的设计. 在这里不需要提供额外的信息, 则使用NULL. 至于如何通过它提供额外的信息, 你可以查阅MSDN.
接下来运行了ShowWindow();UpdateWindow(); 功能就是显示窗体和刷新窗体.

消息循环
函数的_tWinMain函数的末尾运行了以下代码.
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}
你并不需要看懂每句话, 你只要明白这个循环的功能是:
接收每个消息, 并且调用对应的Window的消息处理函数处理这个消息. 要理解这点, 你需要理解Windows程序的运行机制.

Windows程序的消息处理机制

这个名字是我自己起的, 如果你去网上搜索, 可能一无所获.
大家可能习惯于C程序的那种逻辑. 从main开始, 一句一句执行知道退出. 大家是否想过, 我们平时运行的带窗口的应用程序是否是这样, 显然不是.
事实上, 纯ANSI C程序运行的原理应该是下面这个过程.

  1. 操作系统载入程序到内存.
  2. 操作系统找到程序入口: main函数.
  3. 操作系统运行main函数.
  4. 操作系统发现main函数运行完了, 清理程序信息, 释放内存.

我想说的就是一句话, 操作系统在控制你的程序. 你的程序是不是可运行的程序, 是操作系统说的算. 你的程序要运行, 怎么运行, 也是操作系统说的算.
Windows程序是有着特殊的结构的, 这就是传说中的PE结构. 这也就是为什么上面那个示例程序入口不再是main, 而变成WinMain. 操作系统找到WinMain后会逐步运行. 一般在WinMain中会有个消息循环, 就是上面讲到的循环函数. 里面调用了GetMessage之类的系统函数. 其作用就是不断接收系统消息, 并且经过一定处理后, 让操作系统调用对应的Window的PROC函数.说白了, 就是下面的过程.
创建了一个窗口, 里面N多Window, 这些Window会产生各种各样的事件, 操作系统会记下来.存在一个叫”消息列队”的地方.
while(给我一个系统消息) {
    操作系统, 你给我把这个消息告诉属于它的Window去. (实质是调用这个Window的消息处理函数PROC)
}

你可能会想, 为什么操作系统不直接告诉属于它的Window. 我的回答是:每个程序默认只有一个线程. 所以它每次只能处理一个消息. 调用一个函数. 另外, 主函数需要控制所以的Window以及他们的消息, 对吧.

消息处理函数.

WndProc函数就是刚才建立的Window的PROC(”消息处理函数”). 之前说到PROC可以在Window的CLASS中定义. 我想你已经猜到了, 在运行MyRegisterClass()的时候, 已经告诉操作系统这个CLASS的所有Window都使用WndProc这个函数.
这个函数很是重要, 其实今后Windows编程, 界面部分主要就是由这个函数处理.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {    switch (message)
    {
    case WM_COMMAND:
        //….
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
LRESULT和CALLBACK分别定义函数的返回值和类型. 如果没听过函数也没有关系, 照猫画虎就行.
这个函数有4个参数:
HWND hWnd: 消息所属的Window, 要知道, 可能不同的窗体会使用相同的函数, 所以必须给予区别.
UINT message: 消息类型, 仅仅是一个数.
WPARAM wParam, LPARAM lParam: 消息类容, 通常都是通过这两个参数传过来. 前者是一个64位的数, 后者是一个指针. 不同的消息类型会以不同的方式使用这两个参数, 有些甚至不用这两个参数.
Window的消息类型有很多, WM_COMMAND, WM_DESTROY都是. 这些通常以宏的方式在各种头文件中定义, 你可以在MSDN中查到它的资料.
DefWindowProc 默认消息处理函数:
首先要意识到一点, 那就是Window的消息非常多, 你可能只关系你感兴趣的几个. 其他的Windows提供了默认的处理方案, 这就是DefWindowProc所干的事.

Sunday, March 15, 2009

Windows编程 WIN32 API入门一 (概述, UNICODE 和 数据类型)

大学上了几年,懂一点Windows编程. 常有人问入门的问题, 发现自己在做重复劳动, 于是想写几个文章, 介绍一下基础的东西. 就算编程之余一点消遣~

我大概讲如下几个最入门的东西, 复杂一点的, 我也不敢讲, 大学几年学的东西比别人稍稍快那么一点, 却误导了一批又一批同学. 况且编程这东西, 入了门也就容易了. 应该是分为4篇, 也有可能5 篇. 我会精简篇幅. 概况通用的东西, 细节方面点到为之. 如果能看懂, 就算不会写BinPlayer, 也至少知道个大概了.如果哪里讲的不清楚, 可以给我发邮件, 也可以在这里回复.

  1. 概述 UNICODE 和数据类型
  2. HANDLE, INSTANCE, Window, 和创建窗口.
  3. MESSAGE, 鼠标, 键盘, 文件操作.
  4. COMMON CONTROL, GDI, 资源文件.

对于参考资料上:我只给大家推荐两本书.

1. Windows via C/C++ - Jeffrey Richter
这本书我强烈推荐. 不过这本书不会讲最最基础的东西. 比如我在这讲的东西几乎都没讲, 除了UNICODE.

2. Programming Windows, 5th Edition - Charles Petzold
这本书很出名, 但是最新版是在98年出的. 这么多年过去了, 里面有很多东西都是有问题的. 不过里面的东西比较基础.

概述

我就不大谈特谈Windows编程的历史,未来什么什么的了.

WIN32 API是Windows应用编程的最底层.当然我这里所说的WIN32开发也包括COM编程.
编出来的程序就是大家常见的exe,dll之类.可以原生地被Windows操作系统调用运行,不需要辅助程序.

WIN32 API编程有时候又叫SDK编程,也有叫Windows API编程.它和大家常用的.NET有着本质的区别.跟MFC和VB6之类的也有一定的区别.这个技术在十多年前是Windows编程的基础,后来又有了VC,VB,Delphi等其他技术.如今大学计算机专业中, 知道和掌握这种技术的人越来越少了.

优势上讲:
WIN32 API编程原生地调用系统API,如果排除一些特殊情况, 使用WIN32 API编程出来的程序的运行效率是最高的.

另外,Windows SDK 提供了完全的WIN32 API的C和C++开发所需的头文件和库文件.

当然也有劣势:
入门困难这个是最大的问题.其次是界面上开发略显复杂.
但是只要了解一点里面的机制,再拥有了MSDN文档,可以说一切就受你控制了. 大多数时候MSDN给的说明都很全面,但是有些地方还是有问题的,这时候用搜索引擎之类的查一下就OK.

Windows API到底是个啥, 怎么才能用?
简单明了的说. 操作系统提供了一系列函数和功能.
你如果要C/C++语言调用这些函数, 就必须要有这些提供函数信息的.h文件和.lib文件. 这些都包含Windows SDK中. VS2008已经包含了精简的Windows SDK.

这些函数的具体信息可以在MSDN中找到.
如最基本的函数CreateWindow. 就可以在下面的网页中找到相应信息.
http://msdn.microsoft.com/en-us/library/ms632679.aspx
(注, 看不懂没关系. 用起来会比看上去的要简单)

第一个程序,命令行的hello world.
是的,命令行的hello world.
使用VS2008建立新的Win32 Console工程, 在向导中注明创建空的功能, 然后创建一个main.cpp文件.(如果你不知道这步如何做, 那么请尝试去做. 如果尝试了还是不会做, 你还是不要继续看了 ) 输入并且运行以下代码.

#include 
#include
int _tmain () {
wprintf(TEXT("Hello World"));
return 0;
}


看上去和C语言的Hello World几乎一样,只是函数变了.



其实原来的C语言的程序在这里也完全可以编译和运行,但是这里为什么要使用这些函数呢?如果你有这么想,那我觉得你还是有前途的.如果没有,你还是回去考研吧,只有高学历才能救的了你.



回答,是因为UNICODE



UNICODE



UNICODE是什么应该多少知道一点.



下面我们简述Windows 下的UNICODE.



Windows NT开始, Windows 的API就通过UNICODE实现.而之前的Windows 9x是使用ANSI码实现. 这就是为什么Windows XP只要下载语言包就能够更换语言, 而Windows 98则不行.



不过WIN32 的API是同时支持ANSI模式和UNICODE模式的.不过在NT上, ANSI模式的函数是通过转换编码后调用UNICODE模式函数来实现功能, 而在9x中反之. 所以现在而言, 编程中使用UNICODE既能够保证字符的显示, 又能够稍稍提高效率.



如果你想知道这里的UNICODE使用的是那种编码的话, 那么我可以告诉你这里使用的是UTF-16. 不是在网络中常用的是UTF-8. UTF-8有UTF-8的优势, 比如在显示英文时比较小, 同时能兼容ANSI. 但是UTF-8长度是不固定的, 而UTF-16中长度是固定的2字节, 16位.



在C/C++中, 一个 char 是一个字节, 不过Windows编程中的UNICODE字符是WHCAR, 为两个字节. 相对应的, C语言中的printf输出打印char字符, 这里就得使用wprintf输出WHCAR字符. 不过值得说明的是, WCHAR本身是这样定义的.

typedef wchar_t WCHAR;





如何定义WCHAR字符串呢?



WCHAR hello[16] = L"hello world";

这里的L是一个宏, VS能够在编译的时候将其编译成WCHAR字符.


也许你已经注意到在刚才hello world中使用了TEXT,而没有使用L.解释这个之前, 我先隆重介绍一下 TCHAR.


TCHAR 是什么呢, 也是一个typedef.



#ifdef _UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif


如果定义了_UNICODE, 那么TCHAR就是16位的, 否则就是8位的.



这个_UNICODE不需要自己在代码中定义, VS的工程属性里面有一个叫Character Set, VS2008在默认情况下是Use Unicode Character Set, 这样在编译的时候, 编译参数就带有/D "_UNICODE".



上面曾经介绍道CreateWindow这个API. 其实CreateWindow并不是一个函数, 而是一个宏. 它使用类似上面的方法来对应CreateWindowA (ANSI) 和CreateWindowW (UNICODE)两个函数.



几乎所以的常用API都是这样: 一个xxxA, 一个xxxW. 一般来说, 两者只有在字符串上的参数有区别.有些C/C++的标准库的函数则使用了_t 作为前缀 (比如 _tmain, 对应的就是 main 和 wmain. 另外, 那个Hello World程序里面的 wprintf最好写成 _tprintf)



使用TCHAR比使用WCHAR有什么好处呢?



你如果想更换字符集, 只要随时去掉或者添加_UNICODE参数即可.

因为系统API会跟着变为A版和W版, 字符也应该跟着变.


于是也就有了TEXT() 这个宏 (就像TEXT("HELLO") ). 它会在UNICODE的时候变为L (L"HELLO"),在非UNICODE的时候则变为空气~ ("HELLO").


(不过遗憾的是, 并不是所有函数都有A版和W版, 有些被MS认为已经过时的函数就没有W版)



如何在不改变数据的情况下转化WCHAR和CHAR呢?


首先,强转是不行的,也就是(WCHAR) CHAR 是不行的.


其次要知道, ANSI 模式下, 不同语言的字符可能拥有一样的表达方式. 英文语言环境系统下打开中文的WinRAR这样的软件看到的是乱码. 就是这个原因.



MS提供了两个函数, MultiByteToWideChar 和WideCharToMultiByte. 我在这里给出两个函数的详细说明.

当然我还是鼓励大家自己看MSDN, 因为如果走上WIN32和COM编程的路, MSDN可能是天天要看的.



使用示例:WideCharToMultiByte(CP_ACP , NULL, WCHAR *, -1, CHAR *, 1024, NULL, FALSE);

第一个参数CP_ACP是一个UINT(这是一个宏, 代表unsigned int), 因为ANSI模式的编码是各种各样的. 这个参数就告诉他是使用哪种编码. 不过这里的CP_ACP意义特殊, 表示使用当前系统的语言选项. 这个在Windows “控制面板”上的”区域和语言选项”上的最后一页可以设定. 另外, GB2312是936.


第二个参数一般是NULL, 有特殊需求是有用.


第三个是被转换UNICODE字符的指针.


第四个是被转换UNICODE字符的长度, 如果是-1, 系统会自动检查.


第五个是转换后ANSI字符串的目标地址. 注意, 这里必须要事先分配好内存, 比如使用数组. 光声明个CHAR * 然后传过来是新手常见的错误. (事实上, 如果你是可以这么做的, 但是需要第六个参数配合).


第六个是第五个参数的缓存大小. ( 如果是0, 这个函数会返回需要的长度. 这样就可以做所谓的 ”Safe Operation” ,很多Windows 函数都支持这种功能 )


第七八个参数是设定默认字符使用的, 一般设为NULL, 和FALSE.



相比这个就简单一些, MultiByteToWideChar(CP_ACP, NULL, CHAR *, -1, WCHAR *, 1024); 参数也基本一致.



其他:



使用_t前缀的宏是常见的.不过不是唯一的.

有些函数:如C语言中string.h里面的常见函数:strcmp, strcpy等,在这里就对应lstrcmp, lstrcpy. 不过这些函数是作为系统函数存在. 他们的A版不是string.h里面的函数. 换句话说, 在非UNICODE的时候lstrcmp会调用lstrcmpA 而不会调用strcmp.


Windows.h是Windows编程中的核心头文件.


tchar.h则是提供UNICODE和非UNICODE的字符宏的主要文件之一.




Windows 数据类型和变量命名.





命名方式


这是一个永恒的话题. 也许每个老师都愿意和你探讨探讨. 不过自从我看到下面这句话我就再也不考虑这个问题了. 虽然讲的不是一个东西.


Should array indices start at 0 or 1? My compromise of 0.5 was rejected without. - Stan Kelly-Bootle.


Windows在编写的时候大量使用了匈牙利命名法, 这个几乎在每个Windows API中都多少能看出来点. 不过这并不意味着你也必须使用, 你当然可以使用骆驼命名法和帕斯卡命名法. 比较他不影响你的程序.



数据类型


会影响你程序的是数据类型的宏. 让然, 我这里就把typedef也当为宏.


也许你已经注意到了, 我使用CHAR 来表示 char. CHAR是一个宏.


不仅是CHAR , 常见的数据和类型都有这样的宏.


简单罗列一下


VOID, BOOL, TRUE, FALSE


UINT, INT, ULONG, LONG, LONGLONG,


CHAR, WCHAR, BYTE


HANDLE, HRESULT, HWND, HMODULE 等


为什么要使用这个呢?


要知道不同的编译器, 在对待不同的数据类型是不一样的.同样的使用ANSI C写的代码, 在不同的CPU上, 和不同的编译器上是不一样的. (不过很多人认为与操作系统也有关, 据我说知, 是无关的). 这经常造成功能出现偏差. 举个简单的例子, 早期的int 被作为16位处理, 而现在int 在一般被处理为4字节. 但是INT定义为32位__int32. 另外, 使用BOOL和TRUE, FALSE似乎能帮助你把程序从C++移植到C~.



这里面值得一提的有以下几点.


TRUE: TRUE是1, 所以while (TRUE == 2) { 我是不会运行的; }. 请尽量使用FALSE作为比较对象.


LONGLONG: 有的编译器认识 long long, 甚至long long long. LONGLONG这里是__int64. 这足以应付常见的大数了. 在COM编程中大量使用LONGLONG,它已经能够达到很高的精度了.


BYTE: 不要默认char 是一字节. BYTE才是一字节.


HANDLE, HRESULT, HWND, HMODULE 这样的类型是Windows编程特有的, 他们大多数和DWORD有着某种关系, 但是不要将他们混用. 编译器默认是不会认为他们等价的.



总结和建议:



使用UNICODE编程.

使用TCHAR, TEXT()定义字符串, 使用_tprintf(), lstrcpy()这样的函数.


尽量不要直接使用xxxW和xxxA这样的函数.


使用BOOL, TRUE, INT, LONGLONG 等代替原来的数据类型.

Tuesday, March 10, 2009

虚拟主机 图片不能外链的手动解决方案

很多虚拟主机商不允许图片外链.
对于正儿八经地做网站的人来说,主机商这样的做的好处大于坏处.

但是偶尔也会遇到让人郁闷的问题:
比如网站总得有LOGO吧~ 比如88*31的那种用来交换链接的LOGO.是必须得外链的.

怎么解决这个问题呢?开始我也觉得很麻烦...后来想到了一个办法:实在是太遂了...
然后在网站根目录下建立了一个logo.gif的文件夹.
然后在文件夹里面建立index.php
因为几乎所有的服务器在接受"http://xxxxxxx/logo.gif"和"http://xxxxxxx/logo.gif/"时的反馈是一样的.
所以只要使用以下的代码.
<?php
header("content-type:image/gif");
$img=imagecreatefromgif("图片路径");
ImageGif($img);
ImageDestroy($img);
?>

当然还有一种更加遂的方法,借用pizzsa等网站来存图片
首先把logo.gif上传到pizzsa.
里面使用类似下面的代码把地址专项另外pizzsa的logo.gif图片地址.
<?php
header("Location:图片完整地址");
?>

好了... 一切工作了...

这样做还有一个有点,就是图片可以通过HTTP的参数随时改变.甚至可以通过浏览器灵活改变.(比如发现是火狐,就使用新的APNG格式)

这样做的局限是创建一个图片比较麻烦.所以只适合特定的图片.比如LOGO.
此技术已经在bin32.com全面推广....

Friday, March 6, 2009

使用Windows自带组件简析XML (C++, COM, MSXML)

BinPlayer需要使用播放列表,在研究了过多个格式的播放列表后,设计了一个基于xml的播放列表:BPLS.
在BinPlayer build 14 中应该就可以看到. 简单构架XML, UTF-8编码, 精简命名.

剩下一个问题就是解析它了,曾经有3个方案摆在我面前,我选择了第三者.
1. 自己编写代码解析UTF-8和XML.
2. 使用第三方组件.
3. 使用Windows自身组件MSXML.

如果翻翻CSDN,大多数结论都是前两个,不知道是为什么.不排除大多数人是使用"不择手段"的思想.
MSXML是Windows核心组件之一.不过不是唯一的XML解析组件.后来还有xmlite,不过xmlite从Vista开始才被Windows自带.

MSXML里面的东西也很多.常用的是DOM和SAX2.这里使用的是DOM.(注意,他们都是COM组件).

整体流程大致:
1. 初始化组件.
2. Load XML. (IXMLDOMDocument::load);
3. 查询Node.
关键函数:
IXMLDOMNode::selectSingleNode
IXMLDOMNode::selectNodes
这里要使用一个查询语句查询,就是XPath表达式(XPath expression),你可以把它理解为SQL.

整体过程很简单,下面是我开始测试bpls时候使用的测试代码.
doActionsWithNode函数用来显示节点的"t"属性.(说到这里,如果搞不清什么叫节点和属性,最好先去看看资料.MSDN里面也有,写得也很全)
pXMLDom->selectSingleNode(TEXT("//BPF[2][@t|@a|@s|@b|@e]"), &pNode);
这句的意思就是查询拥有t,a,s,b,e属性的第二个BPF节点.
pXMLDom->selectNodes(TEXT("//BPF[@t|@a|@s|@b|@e]"), &pNodes);
这句意思是查询所以的拥有t,a,s,b,e属性的BPF节点.
这个代码简单改改就可以适合很多应用.


#include <tchar.h>
#include <stdio.h>
#include <windows.h>
#include <objbase.h>
#include <msxml2.h>

BOOL doActionsWithNode (IXMLDOMNode *pNode) {
if (NULL == pNode) return FALSE;
BSTR bstr = NULL;
IXMLDOMNamedNodeMap * pNodeMap;
IXMLDOMNode * pNodeTemp;
pNode->get_attributes(&pNodeMap);
if (NULL == pNodeMap) return FALSE;
// read attributes
pNodeMap->getNamedItem(TEXT("t"), &pNodeTemp);
if (NULL != pNodeTemp) {
pNodeTemp->get_text(&bstr);
wprintf(TEXT("t %s\n"), bstr);
SysFreeString(bstr);
pNodeTemp->Release();
}
// freeMap
pNodeMap->Release();
return TRUE;
}

int _tmain(int argc, _TCHAR* argv[])
{
IXMLDOMDocument *pXMLDom=NULL;
IXMLDOMNodeList *pNodes=NULL;
IXMLDOMNode *pNode=NULL;
BSTR bstr = NULL;
VARIANT_BOOL status;
VARIANT var;
long length;
HRESULT hr;

//Initialize COM
CoInitialize(NULL);

//Initialize DOM
hr = CoCreateInstance(__uuidof(DOMDocument30),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IXMLDOMDocument),
(void**)&pXMLDom);
if (FAILED(hr)) return 0;
pXMLDom->put_async(VARIANT_FALSE);
pXMLDom->put_validateOnParse(VARIANT_FALSE);
pXMLDom->put_resolveExternals(VARIANT_FALSE);

//prepare a string to load XML
VariantInit(&var);
V_BSTR(&var) = SysAllocString(TEXT("C:\\bpls.bpls"));
V_VT(&var) = VT_BSTR;

//Load XML
pXMLDom->load(var, &status);
SysFreeString(V_BSTR(&var));
if (status!=VARIANT_TRUE) {
return 0;
}

// Query a single node.
pXMLDom->selectSingleNode(TEXT("//BPF[2][@t|@a|@s|@b|@e]"), &pNode);
if (NULL != pNode) {
doActionsWithNode(pNode);
pNode->Release();
}

// Query a node-set.
pXMLDom->selectNodes(TEXT("//BPF[@t|@a|@s|@b|@e]"), &pNodes);
if (NULL != pNodes) {
pNodes->get_length(&length);
for (long i=0; i
pNodes->get_item(i, &pNode);
if (NULL != pNode) {
doActionsWithNode(pNode);
pNode->Release();
}
}
pNodes->Release();
}

//Release
pXMLDom->Release();
CoUninitialize();
return 0;
}

Thursday, March 5, 2009

Windows 时间函数, 进程间共享内存, Mutex 等. (C/C++)

开学第二周,老师把操作系统的5个实验全部布置了.
我看老师那么积极,我这两天也积极地把这5个实验都写好了.

Windows这边没什么,和以往一样,查查MSDN,什么都出来了.
Linux那边倒是麻烦,虽然接触Linux有一段时间了,但是开发Linux应用倒是很少~ 没找到像MSDN一样集中的,全面的资料.我想应该是有类似的东西的,只不过没去仔细找.

没遇到什么非常值得写的东西,都是零碎,在这说说:

时间函数:
GetSystemTime
GetLocalTime
正常的Windows中,使用第一个函数取得的应该是中央时区的时间.使用GetLocalTime取得的应该是你右下角的时间.(这里的正常指的是时间时区设置正确,不是指交没交钱).
换句话说,如果你只是想显示时间,用第二个.如果你这个时间是用来通讯的,最好使用第一个.
我们的操作系统老师只是要在程序中显示时间,偏要我们使用第一个,我也没转换,就给他显示第一个了.

不过使用这两个函数得到的只是一个SYSTEMTIME结构,如果要进行计算(比如计算时间差), 就要转化为FILETIME(SystemTimeToFileTime FileTimeToSystemTime).然后转化为ULARGE_INTEGER,然后计算,然后再转化回来.当然你也可以使用GetSystemTimeAsFileTime直接取得FILETIME,然后转化为ULARGE_INTEGER.
总结,Windows时间函数很多很好用.

共享内存:
据说Windows没有直接提供共享内存机制.不过可以使用FileMapping.这东西虽然叫FileMapping,但是它并不需要真正创建一个文件.
可以使用CreateFileMapping创建一个FileMapping,如果要跨线程使用,可以在参数直接传递HANDLE, 或者使用全局HANDLE, 但是如果跨进程使用,就得给他起个名字.传HANDLE是不起作用的.(说实话我不知道为什么,我的同学焦阳说这是因为进程地址空间不一样,我没搞明白,我一直以为Windows里面的句柄都是全局的,有空得去找找资料),另外的进程(比如子进程)必须使用OpenFileMapping,用名字来取得其句柄.
如果要使用里面的类容,只要MapViewOfFile就可以了.用完了之后UnmapViewOfFile.

Mutex:
虽然信号量比较通用,但是大部分时候我认为Mutex比较简单.甚至有可能信号量也是通过Mutex实现的. (没有源码,谁有源码可以给我看看~)
用 CreateMutex 创建, 用 WaitForSingleObject 捕获 , 用ReleaseMutex释放.
和FileMapping相似的是, 跨进程要使用 OpenMutex 取得Mutex的HANDLE.

WaitForSingleObject(Ex), WaitForMultipleObjects(Ex):
这四个函数可真是伟大呀.它可以捕获以下东西.
Change notification
Console input
Event
Memory resource notification
Mutex
Process
Semaphore
Thread
Waitable timer
比如Mutex,他可以捕获Mutex可用.Process就是捕获Process结束.
从这里可以看出一点,就是这些东西有着相同的共性.我觉得他们是使用的是相同的内核对象,使用相同的方法标识状态.估计是一个计数器吧~ 我觉得这个东西对今后的编程是很有启发~.

最后给出一个使用共享内存, Mutex, WaitForSingleObject的综合小例子.


//main process
HANDLE hMapping;
HANDLE hMutexMapping;
hMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, /* SIZE */, /* NAME(CAN BE NULL) */);
hMutexMapping=CreateMutex(NULL, FALSE, /* NAME(CAN BE NULL)*/);
//
// Create Processes and do something
//

//sub process
HANDLE hMutexMapping;
HANDLE hMapping;
LPVOID pFile;
// get FileMapping and Mutex
hMapping = OpenFileMapping( FILE_MAP_ALL_ACCESS, FALSE, /* NAME */);
hMutexMapping = OpenMutex(MUTEX_ALL_ACCESS, FALSE, /* NAME */);
//wait for Mutex
WaitForSingleObject(hMutexMapping, INFINITE);
//get shared memory
pFile=MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
//
// pFile is the Point to the shared data ,do changes here
//
UnmapViewOfFile(pFile);
ReleaseMutex(hMutexMapping);