Saturday, June 20, 2009
Sunday, April 12, 2009
暂时停博...
主题修改到一半, 怎么没动静了呢? 连博也不发了.
<--more--!>
不发博因为, 最近没有搞开发, 这个是很有意思的事情. 因为是技术博客嘛, 没有搞新技术, 自然没有东西可以发.
为什么最近没有搞开发呢?
前段时间报了某公司的实习, 笔试之后, 觉得希望渺茫.
但是说实话, 除了那个公司, 别的地方都不想去 (应该就是传说中的高不成低不就) ... 但是去不成怎么办呢? 总不能毕业后在家带着吧... 于是我想到了考研.
考研是需要准备的, 于是我就在考研复习...~
bin32.com最近也遇到一些问题, 有计划在暑假之前进行一次商业性的转移, 这边的虚拟主机实在是让人觉得... 说什么都没用.
Monday, April 6, 2009
tchar 输入输出类 和 string 类 函数简单说明
wsprintf详细很多人用过, 但是没有wsscanf, 为什么呢? 感觉这样是完全不合理的~.
过去我不得不使用WideCharToMultiByte之类的函数对其进行转化. 网上很多人也是这么介绍的. 其实我们错了~ 知道我研究了一下tchar.h, 才明白为什么.
以下函数均在tchar.h中定义, 只是列举了常见的几个~
如果需要特殊功能的函数建议研究一下tchar.h~ 里面的函数实在是太多太多, 很多苛刻的需求都能满足.~
标准输入输出
printf类
_tprintf : 类同printf
_tprintf_l : 类同printf, 但是"_l"表示第二个参数是一个locale.
locale是操作系统对语言设定的参数, 这个会影响ANSI字符的语言识别, 在Unicode下应该是无差别的
_tprintf_s : 类图printf, 但是和_tprintf相比, _tprintf_s多会做一些检查工作.
如果你的"format string"是动态的, 这个能帮助你.
_tprintf_s_l : 前两者功能相加
_tprintf_p : 这个用法有点特别, 不过有个例子, 一看就明白
_tprintf_p(TEXT("%1$d times %1$d is %2$d"), 10, 100);
10 times 10 is 100
_tprintf_p_l : _p和_l相加
_tcprintf : 多个一个c, 表示输出到终端~ 要知道标准输入输出不一定是到终端的~
_tcprintf_l _tcprintf_s _tcprintf_p _tcprintf_p_l 就去类比~
_ftprintf : f表示输出到文件
_ftprintf_l ... 去类比
_stprintf : s表示输出到string
_stprintf_l ... 去类比
_sctprintf ... (这里用...表示一堆 _l _p 的函数, 下同)
哈哈, c表示终端, s表示string~ 那到底输出到哪里呢~ 哈哈~
其实_sc表示string count 这个函数不输出~ 只是算算输出的长度~
_tprintf(TEXT("String Length of %d is %d"), 100, _sctprintf(TEXT("%d"), 100));
String Length of 100 is 3
_sntprintf ... : 没啥区别, 有个参数限定输出长度
_v ... ...
_v的太多, 前面的每个函数都有一个_v版本, 它们有个共同特点, 就是最后一个参数是一个特殊的list的指针.
printf类有这么多, 来简单总结一下前缀和后缀~
前缀
_t 没的说
_ft 输出到文件
_st 输出到string
_sct 算下长度, 不输出
_snt 输出到string, 还限定长度
_v 用参数的list的指针来表示参数
后缀
_p 可以用数字表示参数
_l 可以值得locale
_s 多了一些检查
scanf类和printf类类似, 但是没有_v类
剩下来的函数基本上都类似, 大多数能够在c的标准库中找到类似的函数.
每一类中我只举几个典型的函数, _l 和_s后缀是经常可以用的~
get 和 put:
_gettc 我要get一个char
_getts 我要get一个string
_puttc, _putts类似
_gettch 我从终端来一个char
_cgetts 我从终端来一个string
_gettche 我从终端来一个char, 顺便输出
_fgettc 我从文件来个char~
string 向数值转换
_tstof string变浮点
_tstol string变长整
_tstoi string变整数
_itot 整数变string
string函数
_tcscat strcat
_tcsncat strncat
_tcscpy strcpy
_tcsncpy strncpy
_tcslen strlen
_tcscmp strcmp
_tcsncmp strncmp
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 使用需要注意点.
和以往一样, 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程序运行的原理应该是下面这个过程.
- 操作系统载入程序到内存.
- 操作系统找到程序入口: main函数.
- 操作系统运行main函数.
- 操作系统发现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, 也至少知道个大概了.如果哪里讲的不清楚, 可以给我发邮件, 也可以在这里回复.
- 概述 UNICODE 和数据类型
- HANDLE, INSTANCE, Window, 和创建窗口.
- MESSAGE, 鼠标, 键盘, 文件操作.
- 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 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个实验都写好了.
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);
Wednesday, February 25, 2009
C++ Windows API 实现避免屏幕保护和待机,休眠.
后来就有人问我这个是怎么实现的,想想也是,虽然很简单,但是找起来却是问题多多.
我先给出我推荐的方式,再说原因:
使用一个Timer,使用较短的时间定时运行下面这个函数.
void resetSystemIdleTime () {
SetThreadExecutionState(ES_DISPLAY_REQUIRED);
SetThreadExecutionState(ES_SYSTEM_REQUIRED);
}实际上这个函数作用就是重置两个系统参数,这个不是唯一的方法.在我看来,是比较好的方法.
在csdn上其他方法也很多,有模拟事件的,有激活窗口的.什么都有,一句话:不择手段.
唯独见到的正规一点的方法是:使用
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, NULL, 0);
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, NULL, 0);来临时关闭和开启屏幕保护.类似的方法可以实现避免待机.不过这有个问题,那就是如果程序中途中断出错关闭,会造成重新启动前,系统的屏幕保护不能运行. 这虽然问题不大,但是我还是不希望BinPlayer有这样的"隐患".
至于我在上面说的resetSystemIdleTime方法,只有一个问题:这个Timer的时间怎么定.
不过我要说明的是,这两个函数运行的代价非常低,因为任何鼠标事件,键盘事件,都会"运行"这两个函数.所以如果时间设的非常短,也不会影响性能,只是给人一种没有必要的感觉.
一般来说,1分钟内是合适的,因为屏幕保护和待机的最短激活时间也就是1分钟.我也见过有BT到1分钟休眠的用户...
Tuesday, February 24, 2009
BinPlayer 配置对话框的实现 Windows API C/C++, TreeView

之前也尝试过多种版本,比如微软自己常用的tab标签型,KMPlayer的tree型.最终选择了这种样式.
我个人认为是在美观和简易上做的折衷.类似的样式在原版emule上也可以看到,不过那些有某些缺陷,样子是一样的.
左边的东西实质上是一个treeview,不过拥有样式TVS_SHOWSELALWAYS | TVS_FULLROWSELECT 这个TreeView只有根节点,没有子节点.
另外使用了TreeView_SetItemHeight来设置行的高度.一个Imagelist来设置图标.
做好之后要做的就是捕捉几个消息处理就是了.我在这里只对TVN_BEGINDRAG, TVN_BEGINRDRAG, TVN_SELCHANGED三个消息做了处理.同时还避免了emule上出现的一些小瑕疵.
右边被切换的对话框我使用CreateDialogIndirect来加速切换速度.
时间很晚了,毕竟开学了,要早睡早起~.下面给出部分关键代码,有空再把本文补充地完善些:
// self defined struct : for save infomation of items
struct ConfigerItemsInfo {
//dialog call back function
INT_PTR (CALLBACK * CallBackProc)(HWND, UINT, WPARAM, LPARAM);
//icon and string id in resources file
UINT iconID;
UINT StringID;
//dialog hwnd
HWND hwnd;
//dialog Template
DLGTEMPLATE * dlgTemplate;
};
//function to initial Dialog
//get TreeView Cotrol
HWND hwndTemp = GetDlgItem(hwndDlg,IDC_TREE_CONFIG);
//set new height
TreeView_SetItemHeight(hwndTemp, 22);
// get Configer class
if (NULL == BP_Configer) BP_Configer = new module_configurer();
// BP_Configer->getImageList() return the imagelist initied
TreeView_SetImageList(hwndTemp, BP_Configer->getImageList(), TVSIL_NORMAL);
// number of Items
int Temp = BP_Configer->getItemsCount();
TCHAR TempString[MAX_PATH];
for (int i=0; i
ConfigerItemsInfo * cfgitem;
// Get Item config info
cfgitem = BP_Configer->getItemByID(i);
// load string from resources
loadStringByID(cfgitem->StringID, TempString, MAX_PATH);
AddItemToTreeView(hwndTemp, TempString, cfgitem->iconID, (LPARAM)cfgitem);
}
//Add Item To TreeView
bool AddItemToTreeView (HWND hwndTV, LPWSTR lpszItem, int ImageId, LPARAM lparam) {
TVITEM tvi;
TVINSERTSTRUCT tvins;
HTREEITEM hti;
tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_PARAM | TVIF_SELECTEDIMAGE;
// Set the text of the item.
tvi.pszText = lpszItem;
tvi.cchTextMax = sizeof(tvi.pszText)/sizeof(tvi.pszText[0]);
// Assume the item is not a parent item, so give it a
// document image.
tvi.iImage = ImageId;
tvi.iSelectedImage = ImageId;
// Save the heading level in the item's application-defined
// data area.
tvi.lParam = lparam;
tvins.item = tvi;
tvins.hInsertAfter = TVI_LAST;
tvins.hParent = TVI_ROOT;
// Add the item to the tree-view control.
if (NULL == TreeView_InsertItem(hwndTV,&tvins)) return false;
else return true;
}
Saturday, February 21, 2009
C/C++ 递归 搜索 文件夹 Windows API (SDK)
核心的就是3个函数FindFirstFile, FindNextFile, FindClose;
建议不要使用C\C++的文件函数,那样会有很多很多问题.比如Unicode云云~
FindFirstFile如果想搜索一个文件夹里面的文件,需要在文件夹地址末尾加上"*"
FindNextFile成功的时候返回的是非0.
FindClose是和FindFirstFile一一对应的.FindNextFile不需要调用FindClose;
下面给出一个实例代码,这个代码是我为我的一个软件写的,在有些地方需要说明:
1.函数的第一个参数必须是一个文件夹路径,由于这个是一个核心函数,所以没有使用防御式编程的手动.
2.使用了lstrcpy这样的函数,没有使用Safe String函数.
3.函数的第二个参数是一个回调函数,当找到一个不是文件夹的文件时,会调用此函数.
void findRecurFiles(TCHAR * CurrentFilePath, void (__cdecl *CallBackFindFile)(TCHAR *)) {
WIN32_FIND_DATA FindFileData;
TCHAR TempFilePath[MAX_PATH];
TCHAR TempFilePathNext[MAX_PATH];
lstrcpy(TempFilePath, CurrentFilePath);
lstrcat(TempFilePath, TEXT(\\));
lstrcat(TempFilePath, TEXT("*"));
HANDLE hFind = INVALID_HANDLE_VALUE;
hFind = FindFirstFile(TempFilePath, &FindFileData);
if (hFind == INVALID_HANDLE_VALUE) return;
do {
if (!(FindFileData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)) {
lstrcpy(TempFilePathNext, CurrentFilePath);
lstrcat(TempFilePathNext, TEXT(\\));
lstrcat(TempFilePathNext, FindFileData.cFileName);
CallBackFindFile(TempFilePathNext);
} else if (FindFileData.cFileName[0] != TEXT('.')) {
lstrcpy(TempFilePathNext, CurrentFilePath);
lstrcat(TempFilePathNext, TEXT(\\));
lstrcat(TempFilePathNext, FindFileData.cFileName);
findRecurFiles(TempFilePathNext, CallBackFindFile);
}
} while (FindNextFile(hFind, &FindFileData)!= 0);
FindClose(hFind);
}
//sample usage
void msgMe(TCHAR * CurrentFilePath) {
MessageBox(NULL, CurrentFilePath, TEXT("OK"), MB_OK);
}
int _tmain(int argc, _TCHAR* argv[])
{
findRecurFiles(TEXT("C:\\"), msgMe);
return 0;
}
Monday, February 16, 2009
Windows API 使用控件ToolTip

既然ToolTip使用在各种各样的场合,那它必然很灵活... 所以他使用起来一定有点麻烦.这个逻辑在这里貌似没错.
ToolTip不是始终显示的控件,所以如果写错代码... 往往找不到北:"这东西不见了... 但是.. 它在哪里?",虽然MSDN给出了几个常见用法,但显然是有点问题的它没有说清楚.
主要是TOOLINFO.uFlags的细节.
如果只是想为一个控件给出提示,最好使用TTF_SUBCLASS.当有鼠标悬停事件发生时,他能够延时自动显示.
如果是为一个控件给出可变的信息来表示状态,TTF_TRACK | TTF_ABSOLUTE是最佳的解决方案.不过此时要通过TTM_TRACKPOSITION消息和TTM_TRACKACTIVATE消息来控制显示与否.并且通过TTM_SETTOOLINFO消息来控制.
common comtrol 6里面有很多控件都有ToolTip的属性.有些窗体熟悉也可以配置ToolTip,这时候可以避免繁琐的ToolTip编写.
下面给出了BinPlayer的现在版本中声音控件的ToolTip实现代码:(上图第2个)这是一个测试代码,写得不太规范,也不太通用,但毕竟是可以运行的.有空再给出详细的ToolTip使用实例.
//定义这几个量,因为TOOLINFO这个结构会被一直使用,所以把它作为静态量.
static TOOLINFO ToolInfo;
//控制是否显示/隐藏
static bool IsToolInfoTrack = false;
//显示的点和字
static POINT pt;
static TCHAR ToolWords[4];
//创建ToolTip的函数
HWND CreateTrackingToolTip(TOOLINFO * s_toolItem, HWND hDlg, WCHAR* pText) {
// Create a ToolTip.
pText[0] = 0;
HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST,
TOOLTIPS_CLASS, NULL,
WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
hDlg, NULL, hInst,NULL);
s_toolItem->cbSize = sizeof(TOOLINFO);
s_toolItem->uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE;
s_toolItem->hwnd = hDlg;
s_toolItem->hinst = hInst;
s_toolItem->lpszText = pText;
s_toolItem->uId = (UINT_PTR)hDlg;
GetClientRect (hDlg, &s_toolItem->rect);
// Associate the ToolTip with the tool window.
SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) s_toolItem);
return hwndTT;
}
//使用这个函数来创建ToolTip
CreateTrackingToolTip(& ToolInfo, hWnd, ToolWords);
//鼠标松开事件
SendMessage(BP_trie.ToolTip, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&ToolInfo);
IsToolInfoTrack = false;
//鼠标按下且移动事件
//ThumbRect是被拖动滑块的RECT
pt.x = ThumbRect.left;
pt.y = ThumbRect.top - 18;
//Position是一个double,从0~1,表示拖动位置,在这里很安全
wsprintf(ToolInfo.lpszText, TEXT("%d\0"), (int)(Position * 100));
SendMessage(BP_trie.ToolTip, TTM_SETTOOLINFO, 0, (LPARAM)&ToolInfo);
//TTF_TRACK属性就意味着TTF_ABSOLUTE属性,比如映射到窗口的位置上.
ClientToScreen(hWnd, &pt);
SendMessage(BP_trie.ToolTip, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x, pt.y));
if (!IsToolInfoTrack) {
SendMessage(BP_trie.ToolTip, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&ToolInfo);
IsToolInfoTrack = true;
}
Saturday, February 14, 2009
编程常用工具之 Depends
这个工具上表明是Microsoft Corporation,官网是http://www.dependencywalker.com/
它是免费使用,并且被很多工具收录其中,据说Windows安装CD里面就有,不过我还没找到.(话说Windows安装包里面有很多好玩的东东... 当然,那些修改版们基本上把它们都删了...) .. 跟往常一样看截图

它最基本的功能,或者说最常用的功能就是两个:
1.查看这个dll有那些接口(这里指的是函数).
2.看这个dll依赖哪些dll.
这个工具的一些细节功能我就不说了,把上面工具栏依次点一下就都明白了.我说说这个工具使用的时候有几点要注意:
dll依赖功能上,我做了实验,貌似他不能检测出手动LoadLibrary的情况... 至少他没有默认显示... 换句话说,他可能仍然要依赖别的dll,而这里没有显示出来.
大家都知道dll的载入搜索是有顺序的,具体顺序MSDN写的很清楚.depends的搜索顺序和你自己的程序的搜索顺序不一定一样... 换句话说:有可能depends检验能够顺利载入的dll你的程序却找不到. 反之也是. 在说的明朗点: 编程的时候时刻小心dll的位置.
Monday, February 9, 2009
C/C++ , GDI , GDI+ 双缓冲解决窗口闪烁 (Windows API)
后来才明白,原来大多数程序都使用了"双缓冲"技术. 然后我又有一个问题:为什么Windows不在内部实现双缓冲呢? 都来知道,在Vista有个WDM, 差不多就有一个双缓冲功能...
闲话少说:GDI实现双缓冲过程:
取得DC
- 创建一个位图:HBITMAP CreateCompatibleBitmap
- 创建两个个兼容内存DC:CreateCompatibleDC
- 两个DC分别绑定"要画的图"和"刚才创建的位图":SelectObject
- 把想画的样子画在"刚才创建的位图"上:BitBlt, StretchBlt等函数
- 把"刚才创建的位图"画在真正的DC上:BitBlt, StretchBlt等函数
- 释放该释放的东西,位图和内存DC:DeleteObject, DeleteDC
再来个GDI+的过程:
- 根据窗口大小建立合适的位图Gdiplus::Bitmap,并且给他创建Gdiplus::Graphics
- 取得DC,创建Gdiplus::Graphics
- 取得想画的图Gdiplus::Image
- DrawImage,把东西都画在Gdiplus::Bitmap上.
- DrawImage,把Gdiplus::Bitmap画在DC的Gdiplus::Graphics上
- 释放该释放的东西.
C/C++ i18n 国际化 多语言 解决方案 (Windows API)
i18n 程序员都是大懒蛋,我也不例外. i18n就是internationalization,国际化.
说白了就是能让一个程序显示不同的语言. 类是的还有l10n,本地化,一般是两种语言,其中一种是英语.
理论上讲,如果想要实现i18n,使用Unicode方案是必须,好在微软的NT平台是基于UTF-16的.
所有的ANSI程序都是通过内部转化为UTF-16实现,所以在NT下编写ANSI程序是不明智的.
扯远了,那C/C++做的Windows API程序怎么实现i81n呢.
有没有像.NET,STRUTS提供的那样的专用函数呢? 如果你只是想知道答案,那就是没有.真的没有.
我们只能使用程序控制的手段实现i18n.
那有哪些实现方法呢?
纯资源dll
ini语言文件
xml语言文件
自定义的语言数据文件
大多数的程序选择前3者.其中纯资源dll还有两种实现方案.
资源文件里面包含菜单,对话框,String表.
只包含String表
其中后者的本质是和方案2,3是一样的.这个是个程序员后能明白.
如果想追求效率,使用方案1的1方案,但是要修改就麻烦了.
最求绿色简单,String表,或者ini语言文件
最求全面,必然是选择xml
其他:
使用微软为使用XML提供了多套方案.如MSXML,XmlLite.但是小程序建议不要使用.因为他的依赖性比较高.
ini的话Windows API提供的几个函数,都是GetPrivateProfile和SetPrivateProfile开头的.不过微软表示这几个函数都是为了兼容16位Windows而存在.只支持ANSI字符集.相对不过通用.微软建议使用注册表存东西.(难道要我用注册表存多语言...)
使用DLL作为资源只要合理使用LoadLibrary和LoadString函数.
PS:今天不知道谁把bin32贴在cb上... 这下可好... 一个连内测水平都没到的软件半成品被几百个人下载去... 唉...
Friday, February 6, 2009
ListView 控件的使用之一:如何绑定数据 (简洁的方案). (C++, Windows API)
当我开始深入Windows API编程的时候,我一直觉得Windows的窗体概念的设计是非常有意思的.
在编程的时候有一些和尝试格格不入的概率.尽管刚开始会理解困难,但是编程的时候却发现有着重大的优越性.
不过我对Windows的大多数common Control的设计却不怎么喜欢.
他让会让我这样的新手有时候觉得... 啊...怎么会这样.. 比如说我要捕获一个按钮的XXX事件. 按照我现在的知识面要么翻译整个消息.要么做钩子.. 我觉得两者都不是"正常"的手段. 应该发个消息,替换其处理函数,这样的功能才会显得高效.不知道他为什么不这么设计...也许有类似的功能,我没发现?...
不过对Listview控件的设计我却觉得十分的优秀.我们最常用的Explorer的主要部分就是一个TreeView.
很多系统软件的主要部分也是Listview... 可以说相当的常用.


这个控件虽然看起来简单,但是却是我发现的最复杂的控件之一:
有多复杂呢?
它有135个专用宏,126个专用消息,36个专用通知,27个专用结构.它还有一堆别的控件很少见的专用扩展STYLE.
别的不说,一般控件有两三个专用的结构...它有27个...
我最初使用Listview的时候就给这复杂给吓坏了... 当时在做小学期的软件,拿着几个最基本的添加删除宏.
自己做了一个Liked List,然后动态刷新显示...不停地ListView_SetItemText()...现在想想真是吃力不讨好的事情.
好了,直接说如何绑定数据.
这个简洁的手段的核心就是使用LVITEM的lParam参数.(LVITEM是记录ListView里面Item数据的核心结构.)
1.首先定义一个结构,这个结构用来存储需要保存的单个item的数据.
2.最是当添加数据的时候,使用C++的特性为他new 一个这样的结构,然后把它传给LVITEM的lParam
3.当任何时候需要取得这个数据时使用ListView_GetItem宏将其取出.此时该干嘛干嘛...
4.当需要删除时,先使用free将对应的内存释放..
参考代码:PlayListItem为自定义的结构.
struct PlayListItem {
TCHAR szInfo[MAX_PATH];
TCHAR szTime[10];
TCHAR szFileFullPath[MAX_PATH];
};
//插入数据示例
PlayListItem * rgInfo = new PlayListItem();
HWND hWndListView;
int index;
LVITEM lvI;
...//init
lvI.iItem = index;
lvI.iImage = index;
lvI.iSubItem = 0;
lvI.lParam = (LPARAM) rgInfo;
lvI.pszText = LPSTR_TEXTCALLBACK; // sends an LVN_GETDISP message.
ListView_InsertItem(hWndListView, &lvI);
//取数据示例:
LPNMITEMACTIVATE lpnmitem;
LVITEM lvItem;
....
case NM_DBLCLK:
plvdi = (NMLVDISPINFO*)lParam
lvItem.iItem = lpnmitem->iItem;
lvItem.mask = LVIF_PARAM;
ListView_GetItem(ListView,&lvItem);
//LVN_GETDISPINFO消息实例(由于使用了LPSTR_TEXTCALLBACK)
case LVN_GETDISPINFO:
plvdi = (NMLVDISPINFO*)lParam;
switch (plvdi->item.iSubItem) {
case 0:
plvdi->item.pszText = ((PlayListItem *)plvdi->item.lParam)->szInfo;
break;
case 1:
plvdi->item.pszText = ((PlayListItem *)plvdi->item.lParam)->szTime;
break;
default:
break;
}
Thursday, February 5, 2009
可拖拽文件窗口的使用.WS_EX_ACCEPTFILES和DragQueryFile
其就可以使其可以接受拖拽效果

当拖拽成功时,被拖拽的窗口接受到WM_DROPFILES消息.
这时候,应该手动调用DragQueryFile函数看看被拖拽的是谁~ 然后处理.
UINT DragQueryFile(
HDROP hDrop,
UINT iFile,
LPTSTR lpszFile,
UINT cch
);这个很简单,固然很简单,每个看MSDN的人都能找得到.
不过这个函数却有着Windows函数的某类函数的特点:不同参数值,不同行为表示.
这个函数的第二个参数iFile,如果iFile=0xFFFFFFFF,它则表示要查询拖拽了几个文件.(返回值)
如果iFile是其他值,则表示我要查询被拖拽第iFile个文件,其返回值是被复制的字节长度.
类似的还有GetEnvironmentVariable这样的函数,使用的时候很灵活.
另外cch是被复制的最大长度.
前段时间刚发现一个东西MAX_PATH, 它为260;
过去做程序老是有一个问题...这个文件名会不会很长....这个URL会不会很长....,曾经有一段时间我做程序都自定义一个MAX_FILE_LENGTH,然后在程序说明上说:支持文件地址长度小于XXX...
原来在Windows的最大文件长度是255!!!URL也是一样.... 做过实验,在Windows文件名太长系统会自动使用缩略文件名的功能.
不多说,给出使用范例.
case WM_DROPFILES:
//...
TCHAR Buffer[MAX_PATH];
unsigned int i;
unsigned int NumberOfDrags;
NumberOfDrags = DragQueryFile((HDROP)wParam, 0xFFFFFFFF, Buffer , MAX_PATH);
for (i = 0, NumberOfDrags , i++) {
DragQueryFile((HDROP)wParam, i, Buffer , MAX_PATH);
//actions here
}
Wednesday, February 4, 2009
自绘Tracebar控件 (Windows API)
先罗嗦一下:
我有个习惯,就是使用common control的时候尽量避免自己重绘.这样做有许多好处:
1.编程简单,不容易出问题.
2.不会因为界面增加程序体积.
3.能够自动拥有系统提供的主题和特效.
当然有的时候会出现点问题.
比如Tracebar控件.我一直认为这样的控件本应该设计得更好.下面是两个版本的截图.
两者绝对都符合我的风格:至简.

但是前者的两个Tracebar怎么看都不像是播放器的控件.
就连以界面简洁为特征之一的Foobar2000都进行了一定的重绘.

好了,兜了一圈直入正题.- -~
捕获NM_CUSTOMDRAW这个Notify(我不知道这个应该怎么翻译,消息?肯定不是.通知?通知好像有别的地方用到了....)
通常被认为正确的使用方法是这样:
case NM_CUSTOMDRAW:
lpDraw = (LPNMCUSTOMDRAW)lParam;
switch(lpDraw->dwDrawStage) {
case CDDS_PREPAINT:
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
switch(lpDraw->dwItemSpec) {
case TBCD_CHANNEL:
....
return CDRF_SKIPDEFAULT;
case TBCD_THUMB:
....
return CDRF_SKIPDEFAULT;
}
break;
}
break;
要注意的主要是返回值,尤其是CDDS_PREPAINT消息的返回值.
具体的细节像TBCD_CHANNEL和TBCD_THUMB我就不说了,我不是MSDN. ^_^
可以说这个和通常的重绘没有两样.但是Tracebar自身设计的问题,却有点让程序员难以驾驭.

这个问题是通常大家使用Tracebar控件常见的问题.面对这种问题.我至今没有找到较好的解决方案.
强制进行背景重绘,强制窗口覆盖等方法通常都有效,却都不是通用的解决方案.最终我在BinPlayer的解决方案是自己绘制了这些"类"Tracebar控件...毕竟以后要支持PNG皮肤,很多东西都要做到很通用.
Monday, February 2, 2009
win32 自绘按钮的简单使用和小技巧. (Windows API)
要想使用很简单,在创建按钮的时候,加入BS_OWNERDRAW的STYLE.这样在需要绘制的时候,其父窗体就会收到一个"WM_DRAWITEM"消息. 同时使用一个DRAWITEMSTRUCT结构来控制内容.其代码大致如下:
DRAWITEMSTRUCT* pdis;
...
case WM_DRAWITEM:
pdis = (DRAWITEMSTRUCT*) lParam;
switch(pdis->CtlID) {
case ID_PLAY_RESUMEPAUSE:
if (pdis->itemState & ODS_SELECTED) ...
}
...
...首先根据CtlID熟悉看是谁在画, 然后使用itemState熟悉决定画什么, 关于itemState还是MSDN比较全.
不过这样的代码却有点小问题, 当双击按钮的时候, 并没有像普通的按钮被解析为两次点击.
为什么出现这个情况其实很容易理解, 在Windows中按钮和菜单被认为是一个东西的不同形式(当然还包括check box云云).. 菜单怎么可能被双击呢?
但是按钮毕竟不是菜单,那如果我非要他把双击作为两次单击怎么办呢? 要做的其实很简单, 并不需要手动勾住双击事件. 只要设置一下按钮的属性.
LONG dwValue = GetClassLong(hButton, GCL_STYLE);
SetClassLong(hButton, GCL_STYLE, dwValue & ~CS_DBLCLKS);CS_DBLCLKS是窗体类的通用熟悉,它表明窗体愿意接受双击. 去掉这个属性, 两次点击, 就是两次点击, 一次也少不了.
这个东西其实很Normal, 但是为什么要拿出来说捏, 不是我半夜闲得蛋疼. 是我发现常见的播放器中只有Media Player的播放按钮能够有效屏蔽双击. 网上甚至有人说这个必须要自己做消息解析才行... 我估计是他只用过MFC,没用过API...
PS:为BinPlayer做的自绘按钮.算是效果图吧
Tuesday, January 27, 2009
Windows 窗口创建时的几个关键消息调用顺序,及其使用注意点.
CreateWindow Called
WM_CREATE
CreateWindow Return
Message Loop
在CreateWindow和Message Loop之间,可以调用相应的函数如ShowWindow, UpdateWindow.
其事件顺序是:
ShowWindow Called
WM_SIZE
ShowWindow Return
UpdateWindow Called
WM_PAINT
UpdateWindow Return
如果带有WS_VISIBLE的窗体,并不主动调用ShowWindow和UpdateWindow函数.则在Message Loop开始后,先调用 WM_SIZE,后调用WM_PAINT.
其实这些都很容易理解,不过使用的时候就有很多地方需要注意了.
比如我们经常将CreateWindow的返回值作为全局的变量.我们也会在WM_CREATE时调用一些初始化函数.
这些初始化函数需要的主窗口句柄就必须是WM_CREATE消息的,而不能是全局的那个.因为全局的那个还没有被正确地返回.
同样的道理.如果WM_SIZE和WM_PAINT都使用一些公共的变量,就要在WM_SIZE里面先准备好.也尽量不要主动调用WM_PAINT.否则难免出现变量混乱的局面.
Saturday, January 24, 2009
工具介绍 Control Spy 2.0

如果你做过WIN32开发.可能你已经看出来了.这是一个用来测试Windows公共控件的工具.
微软的原话是Control Spy is a tool to help developers understand common controls.
公共控件(common controls)给人的感觉是虽然很普通,但是却很复杂.(这点用.NET编程的人我猜一般是不会体会到的,前几天一个学弟对我说"C#的按钮划过的事件怎么这么复杂啊...". –_-~~)
使用Control Spy可以几乎做到对控件的完全控制.包括Message, Notify.可以直接更改STYLE.
对应不熟悉控件消息的人来说这绝对是福音.
不过我使用的时候发现很多BUG,比如某些Style识别出错.有些STYLE不起效果.
我想这就是为什么他没有被加入到VS中的原因.
还有就是由于其自身设计的原因,有些东西是一辈子捕捉不到的.比如更改某些STYLE后重绘的XXX.还有窗体移动的消息....
一个简单的小例子,更改按钮文字.通过他至少知道参数是从LPARAM传进去的.

另外,通过它我终于明白了为什么Vista的控件有时候渐变得很干涩...
原来它是通过把原来一次解决的WM_PAINT事件一连调用16次来达到渐变...
以图为证.

PS:内部代号为BinPlayer的播放器进展缓慢.在家7天重构了3次.还是觉得不满意.核心功能没增加反而减少了...
Windows API 手动载入 动态连接库 (.dll, .ax等) 运行其函数
大多数可以编程的库都提供文档,头文件,lib等文件.
不过有些是不提供的.活生生只给你一个dll.
不过这时候如果知道里面函数的名字和参数啥的,就可以调用.
以ax为例,ax后缀的一般是解码器的Filter.编写过filter的都知道,其实他就是一个DLL,有些解码器就是以dll作为后缀的.
现在:我要载入RealMediaSplitter.ax这个连接库,并且运行DllRegisterServer这个无参函数.
这里仅给出一个样例.核心就是使用LoadLibrary和GetProcAddress函数,用了一个typedef来使其看起来像模像样.
if(HMODULE h = LoadLibrary(TEXT("RealMediaSplitter.ax"))) {
typedef HRESULT (__stdcall * PDllRegisterServer)();
if (PDllRegisterServer p = (PDllRegisterServer)GetProcAddress(h, "DllRegisterServer")) {
p();
}
FreeLibrary(h);
}测试结果OK.其效果等价于在终端运行regsvr32 RealMediaSplitter.ax, 但是不是绿色载入, 以后会介绍一种绿色载入的方法, 那种方法在BinPlayer中被采用, 非常安全.
Wednesday, January 21, 2009
DirectShow 之 自定义视频窗口
貌似我们不能让他在指定的窗体中播放,那我们可以把他放在其他窗体中,作为子窗体.
IVideoWindow::put_Owner似乎是可以用的方法,但是它却又一些注意点和技巧.有些是MSDN没有提及的:
1. put_Owner方法必须在Stream流已经生存的情况下才能使用.一般在IGraphBuilder::RenderFile和IGraphBuilder::Run之间比较合适.
2. 利用IVideoWindow::put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);来让窗体看上去像是一个播放器的视频部分.当然还有IVideoWindow::SetWindowPosition方法
3. 在Filter Graph Manager的Release之前put_Owner为NULL.不然会很惨...(会给代替带来N多垃圾消息,详见MSDN)
高清电影中出现人声过小的问题的原因和解决
这种问题在高清格式的电影上出现频率较高.
原因:正常的音频是双声道的.就是普通的立体声.比如大家常听的普通MP3.
事实上音频可以是多声道的.比如3~6个声道都是有可能的.高清格式为了保证音频的高质量,也通常采用多声道压缩.
比如AC3一般有6声道,DTS一般为5声道.
这些声道有中间声道和两边声道之分.一般中间声道担负的主要的人声(其他声道也有,不过主要做辅助用)
通常来说,电脑中的播放器播放多声道音频时,会把多声道转化为双声道.
但是有些播放器没有这么做.比如KMPlayer.结果只播放了两边声道.
解决:手动配置,使其将中间声道也混入两边声道.
Saturday, January 3, 2009
工具介绍 Microsoft Spy++
1.编写要操作其他程序的程序.
2.是想观察其他程序的窗口属性,分析它是怎么实现的.

以前在不熟悉windows操作系统结构的时候,想编写一些操作系统辅助程序,老是遇到一些奇奇怪怪的问题.
时间长了之后才明白Windows的窗体结构...是一棵树..
然而从这里就可以一目了然地看出来.
还可以查看窗体的STYLE和CLASS,不过这个程序还是有些问题的.STYLE可能显示不标准.尤其是组件的STYLE.

最上面的图中, 灰色的图标表示不可见的Window,蓝色的是可见Window(包括最小化的啥的..)
窗体太多,想判断哪个对应哪个不太容易, 最基础的方式是看名称.
对于有些窗体,可以使用窗体高亮的功能:

最绝的是打开窗体属性,看窗体位置...,哈哈,这个基本上是无误的.

同时它还可以查看各个进程和线程的属性的功能, 使用一下, 还是很实用的.