nodejs process.nextTick 同步代码异步执行

原文:nodejs 官方文档链接:http://nodejs.org/api/process.html#process_process_nexttick_callback

process.nextTick(callback)

On the next loop around the event loop call this callback. This is not a simple alias to setTimeout(fn, 0), it’s much more efficient. It typically runs before any other I/O events fire, but there are some exceptions. See process.maxTickDepth below.

在事件循环的下一次循环中调用 callback 回调函数。这不是 setTimeout(fn, 0) 函数的一个简单别名,因为它的效率高多了。该函数能在任何 I/O 事前之前调用我们的回调函数。但是这个函数在层次超过某个限制的时候,也会出现瑕疵,详细见 process.maxTickDepth。

process.nextTick(function() {
    // 回调函数内容
    console.log('nextTick callback');
});

This is important in developing APIs where you want to give the user the chance to assign event handlers after an object has been constructed, but before any I/O has occurred.

如果你想要在【对象创建】之后而【I/O 操作】发生之前执行某些操作,那么这个函数对你而言就十分重要了。

function MyThing(options) {
  this.setupOptions(options);

  process.nextTick(function() {
    this.startDoingStuff();
  }.bind(this));
}

var thing = new MyThing();
thing.getReadyForStuff();

// thing.startDoingStuff() gets called now, not before.

It is very important for APIs to be either 100% synchronous or 100% asynchronous. Consider this example:

【注意!!】保证你的函数一定是同步执行或者一定是异步执行,这非常重要!!参考如下的例子:

// WARNING!  DO NOT USE!  BAD UNSAFE HAZARD!
function maybeSync(arg, cb) {
  if (arg) {
    cb();
    return;
  }

  fs.stat('file', cb);
}

博主注:在上面的例子中,如果 arg 参数未定义或者为假,那么这个函数就会直接执行传入的回调函数 cb 就变成了同步执行,而 arg 参数未真 就会执行 fs.stat 这样一个 I/O 操作变成了异步执行。这样你的操作就不能确定到底是同步还是异步。

This API is hazardous. If you do this:

这样执行是很危险。如果你还不清楚上述行为的危害请看下面的例子:

maybeSync(true, function() {
  foo();
});
bar();

then it’s not clear whether foo() or bar() will be called first.

那么,使用刚才那个不知道是同步还是异步的操作,在编程的时候你就会发现,你不能确定到底是 foo() 先执行,还是 bar() 先执行。

This approach is much better:

用下面的方法就可以更好的解决:

function definitelyAsync(arg, cb) {
  if (arg) {
    process.nextTick(cb);
    return;
  }

  fs.stat('file', cb);
}

博主注:使用 process.nextTick 来调用 cb 会把这个调用变成和文件操作一样的异步操作,这样不论 arg 是真还是假,这个函数都是一个明确的异步操作了。

Advertisements

Windows SDK 教程(三) 一些细节以及动态创建控件

茵蒂克丝

资源编辑器一些小細節

1.如何指定窗口標題

找到窗口的属性面板,在【caption】处可以修改

2.如何設置窗口出現的位置

找到窗口的属性面板,在【位置】下可以设置【center】为【true】则能让窗口居中显示,x 和 y 则是窗口出现的横纵坐标。(横纵坐标起点为屏幕左上角)

3.如何添加圖片

在对话框的编辑界面,打开工具箱的面板,先在资源视图处添加一张图片资源(注意要是位图 *.bmp 格式),找到【pictrue control】拖到对话框上,接着打开【pictrue control】的属性面板,设置【Type】为【bitmap】接着到【Image】处,选择刚刚添加的图片ID即可。

4.如何設置菜單

在资源视图出添加【Menu】资源类型,双击可以编辑菜单项的文字,也可以右键【属性】在属性面板上编辑【caption】,顶级菜单项的ID不可编辑。触发事件的方式与前几件的按钮等类似。注意菜单编辑好了之后,要到对话框【属性面板】的【Menu】选项中添加。

5.如何设置窗口图标

如果用模板创建要用到 SendMessage, 将在后几讲中介绍

动态创建控件

动态创建窗口

#include <windows.h>
 
// 5. 窗口过程处理
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{   
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;  // 更多详细都可以去百度的 http://baike.baidu.com/view/1750396.htm
    HWND hwnd;
    MSG Msg;
    char text[30];
 
    const char szClassName[] = "myWindowClass";
 
    // 1. 设置注册窗口结构体
    wc.cbSize        = sizeof(WNDCLASSEX);              // 注册窗口结构体的大小
    wc.style         = 0;                               // 窗口的样式
    wc.lpfnWndProc   = WndProc;                         // 指向窗口处理过程的函数指针
    wc.cbClsExtra    = 0;                               // 指定紧跟在窗口类结构后的附加字节数
    wc.cbWndExtra    = 0;                               // 指定紧跟在窗口事例后的附加字节数
    wc.hInstance     = hInstance;                       // 本模块的实例句柄
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION); // 图标的句柄
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);     // 光标的句柄
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);        // 背景画刷的句柄
    wc.lpszMenuName  = NULL;                            // 指向菜单的指针
    wc.lpszClassName = szClassName;                     // 注册窗口的类名
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION); // 和窗口类关联的小图标
 
    // 2. 使用【窗口结构体】注册窗口
    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }
 
    // 3. 创建窗口
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,       // 窗口的扩展风格
        szClassName,            // 控件的类名
        TEXT("窗口标题"),       // 指向窗口名称的指针
        WS_OVERLAPPEDWINDOW,    // 窗口风格
        CW_USEDEFAULT, CW_USEDEFAULT, 350, 200, // 窗口的 x,y 坐标以及宽高
        NULL,                   // 父窗口的句柄
        NULL,                   // 菜单的句柄
        hInstance,              // 应用程序实例的句柄
        NULL                    // 指向窗口的创建数据
        );
 
    if(hwnd == NULL)
    {
        MessageBox(NULL, TEXT("窗口创建失败"), TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }
 
    // 4. 显示窗口
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
 
    // 6. 消息循环
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

wc.lpszClassName 本质上确实是指向类名称的指针,不过博主没睡好,大清早又重录了一遍,录视频的时候再看到这种容易让人误会的说法就爆了粗口,这个意外 XD

动态创建BUTTON

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{   
    switch(msg)
    {
    case WM_CREATE:
        // 动态创建button
        CreateWindowEx(
                0,              // 拓展样式
                "BUTTON",       // 创建的控件的类名
                "我的按钮",     // 控件显示的文字
                WS_CHILD | WS_VISIBLE,  // 控件的样式
                0, 0,           // 空间出现的 x y 坐标
                100, 30,        // 控件的宽高
                hwnd,           // 父窗口的句柄
                NULL,           // ID
                hgInstance,     // 当前实例的句柄
                NULL            // 传递给控件的参数 默认是NULL
            );
        break;

    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

处理动态 BUTTON 的点击

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{   
    switch(msg)
    {
    case WM_CREATE:
        // 动态创建button
        CreateWindowEx(
                0,              // 拓展样式
                "BUTTON",       // 创建的控件的类名
                "我的按钮",     // 控件显示的文字
                WS_CHILD | WS_VISIBLE,  // 控件的样式
                0, 0,           // 空间出现的 x y 坐标
                100, 30,        // 控件的宽高
                hwnd,           // 父窗口的句柄
                (HMENU)1234,    // ID
                hgInstance,     // 当前实例的句柄
                NULL            // 传递给控件的参数 默认是NULL
            );
        break;

    case WM_COMMAND:
        switch(wParam)
        {
        case 1234:
            echo("按钮被按下");
            break;
        }
        break;

    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

动态创建 EDIT

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{   
    switch(msg)
    {
    case WM_CREATE:
        CreateWindowEx(
                0,
                "EDIT",
                "",
                WS_CHILD | WS_VISIBLE,
                0, 50,
                100, 30,
                hwnd,
                NULL,
                hgInstance,
                NULL
            );
        break;

    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

获取动态 EDIT 中的文字并显示

#include <windows.h>

#define IDC_BUTTON_1 1234

HINSTANCE hgInstance;

void echo(LPSTR str)
{
    MessageBox(NULL, str, TEXT("提示信息"), MB_OK);
}

// 5. 窗口过程处理
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{   
    char text[50];
    switch(msg)
    {
    case WM_CREATE:
        // 动态创建 BUTTON
        CreateWindowEx(
                0,              // 拓展样式
                "BUTTON",       // 创建的控件的类名
                "我的按钮",     // 控件显示的文字
                WS_CHILD | WS_VISIBLE,  // 控件的样式
                0, 0,           // 空间出现的 x y 坐标
                100, 30,        // 控件的宽高
                hwnd,           // 父窗口的句柄
                (HMENU)IDC_BUTTON_1,    // ID
                hgInstance,     // 当前实例的句柄
                NULL            // 传递给控件的参数 默认是NULL
            );

        // 动态创建 EDIT 控件
        CreateWindowEx(
                0,
                "EDIT",
                "",
                WS_CHILD | WS_VISIBLE,
                0, 50,
                100, 30,
                hwnd,
                (HMENU)1000,
                hgInstance,
                NULL
            );
        break;

    case WM_COMMAND:
        switch(wParam)
        {
        // 当按钮被点击
        case IDC_BUTTON_1:
            // 获取Edit 控件中的字符串
            GetDlgItemText(hwnd, 1000, text, sizeof(text));
            // 输出字符串
            echo(text);
            break;
        }
        break;

    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;  // 更多详细都可以去百度的 http://baike.baidu.com/view/1750396.htm
    HWND hwnd;
    MSG Msg;
    char text[30];

    const char szClassName[] = "myWindowClass";

    // 1. 设置注册窗口结构体
    wc.cbSize        = sizeof(WNDCLASSEX);              // 注册窗口结构体的大小
    wc.style         = 0;                               // 窗口的样式
    wc.lpfnWndProc   = WndProc;                         // 窗口处理过程的函数指针
    wc.cbClsExtra    = 0;                               // 指定紧跟在窗口类结构后的附加字节数
    wc.cbWndExtra    = 0;                               // 指定紧跟在窗口事例后的附加字节数
    wc.hInstance     = hInstance;                       // 本模块的实例句柄
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION); // 图标的句柄
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);     // 光标的句柄
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+2);        // 背景画刷的句柄
    wc.lpszMenuName  = NULL;                            // 指向菜单的指针
    wc.lpszClassName = szClassName;                     // 注册窗口的类名
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION); // 和窗口类关联的小图标

    // 2. 使用【窗口结构体】注册窗口
    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // 3. 创建窗口
    hwnd = CreateWindowEx(
        0,                      // 窗口的扩展风格
        szClassName,            // 注册的类名
        TEXT("窗口标题"),       // 指向窗口名称的指针
        WS_OVERLAPPEDWINDOW,    // 窗口风格
        CW_USEDEFAULT, CW_USEDEFAULT, 350, 200, // 窗口的 x,y 坐标以及宽高
        NULL,                   // 父窗口的句柄
        NULL,                   // 菜单的句柄
        hInstance,              // 应用程序实例的句柄
        NULL                    // 指向窗口的创建数据
        );

    hgInstance = hInstance;

    if(hwnd == NULL)
    {
        MessageBox(NULL, TEXT("窗口创建失败"), TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // 4. 显示窗口
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // 6. 消息循环
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

文章索引百度网盘
上一讲:Windows SDK 教程(二)简单的计算器
下一讲:Windows SDK 教程(四)记事本与 SendMessage

c 屏幕铺满星星

snow_small

#include <Windows.h>
#include<time.h>

HDC hScreenDC;		// 屏幕画图设备句柄
POINT pData[500];	// 存储星星坐标
long pColor[500];	// 存取原来坐标的颜色值
int Vx;				// vx星星整体水平漂移速度
int Vy;				// vy星星总体垂直下落速度
int PVx;			// pvx单个星星实际水平速度
int PVy;			// pvy单个星星实际垂直速度
int timeCount;		// 计时循环次数

const int StarNum = 500;// 星星数量为500
const int ID_TIMER = 1; // 定时器标识符

const long   StarColor = 0xFEFFFE;  // 星星颜色
const long StarColDown = 0xFFFFFF;  // 积雪星星颜色
const long StarColDuck = 0xFFDDDD;  // 深色积雪颜色

const int ScrnWidth = GetSystemMetrics ( SM_CXSCREEN ); // 全屏宽度
const int ScrnHight = GetSystemMetrics ( SM_CYSCREEN ); // 全屏高度

long Abs(long num)
{
    if (num >= 0)
		return (num);
    else
		return (-num);
}

int Random(int max)
{
    return (rand() % max);
}

void InitPoint(int i)
{
    pData[i].x = Random(ScrnWidth);
    pData[i].y = Random(5);
    pColor[i]  = GetPixel(hScreenDC, pData[i].x, pData[i].y);
}

long GetContrast(int i)
{
    long ColorCmp;	//存取作对比点的颜色值
	long tempR;		//存取ColorCmp的红色值
	long tempG;		//同理
	long tempB;		//同理
	int  Slope;		//存取星星飘落的方向

    if (PVy != 0) 
		Slope = PVx / PVy; // 若pvx/pvy在-1和1之间则slope=0 就取正下方的的像素点
    else // 若pvx/pvy>1,取右下方的点 pvx/pvy<-1 则取左下方
		Slope = 2; // 根据星星飘落方向决定取哪一点做对比点

    if (Slope == 0)
		ColorCmp = GetPixel(hScreenDC, pData[i].x, pData[i].y + 1);
    else if (Slope > 1)
		ColorCmp = GetPixel(hScreenDC, pData[i].x + 1, pData[i].y + 1);
    else
		ColorCmp = GetPixel(hScreenDC, pData[i].x - 1, pData[i].y + 1);

    //确定当前位置没有与另一个星星重叠,重叠就返回0;用于防止由于不同星星重叠造成星星乱堆
    if (ColorCmp == StarColor)
		return 0;

    //分别获取ColorCmp与对比点的蓝,绿,红的差值
    tempB = Abs((ColorCmp >> 16) & 0xff - (pColor[i] >> 16) & 0xff);
    tempG = Abs((ColorCmp >> 8) & 0xff - (pColor[i] >> 8) & 0xff);
    tempR = Abs((ColorCmp) & 0xff - (pColor[i]) & 0xff);

    return ( (tempR + tempG + tempB) / 3 ); //返回对比度
}

void DrawPoint(void)
{
    int i;
    for (i = 0; i < StarNum; i++) //防止星星重叠造成干扰
    {
        if (pColor[i] != StarColor && pColor[i] != -1)
		{
            SetPixel(hScreenDC, pData[i].x, pData[i].y, pColor[i]); //还原上一个位置的颜色
		}

        //设置新的位置,i%3用于将星星分为3类采用不同的速度,以便形成参次感
        PVx = Random(2) - 1 + Vx * (i % 3);
        PVy = Vy * (i % 3 + 1);
        pData[i].x = pData[i].x + PVx; //+pvx与前一点形成的距离差,形成速度的差别
        pData[i].y = pData[i].y + PVy; //同理

        //取的新位置的原始点的颜色值,用于下一步星星飘过时恢复此处的颜色
        pColor[i] = GetPixel(hScreenDC, pData[i].x, pData[i].y);

        //如果获取颜色值失败,表明星星已经飘出屏幕,重新初始化,GetPixel如果获取失败就返回-1.
        if (pColor[i] == -1)
		{
            InitPoint(i);
		}
        else
        {
            //如果星星没有重叠,若对比度较小(即不能堆积)就画出星星
            //Random(16)>5用于防止某些连续而明显的边界截获所有的星星
            if (pColor[i] != StarColor)
            {
				//GetContrast(i)<50判断是否超出对比度50这个值就认为是边缘就会堆积星星
                if (Random(16) > 5 || GetContrast(i) < 50)
                {
                    SetPixel(hScreenDC, pData[i].x, pData[i].y, StarColor);
                } else
                {
				//否者表明找到明显的边界,画出堆积的学,并初始化以便画新的的星星
                    SetPixel(hScreenDC, pData[i].x, pData[i].y - 1, StarColDuck);
                    SetPixel(hScreenDC, pData[i].x - 1, pData[i].y, StarColDuck);
                    SetPixel(hScreenDC, pData[i].x + 1, pData[i].y, StarColDown);
                    InitPoint(i);
                }
            }
        }
    }
}

int main()
{
	MSG msg;
	HWND hwnd = GetActiveWindow(); // 获取当前窗口的句柄

	int i;
	srand(time(0));
	Vx = Random(4) - 2;
	Vy = Random(2) + 2;

	// 随机获取屏幕上一点
	for (i = 0; i < StarNum; i++)
	{
		// 保存坐标
		pData[i].x = Random(ScrnWidth);
		pData[i].y = Random(ScrnHight);
		// 保存颜色
		pColor[i] = GetPixel(hScreenDC, pData[i].x, pData[i].y);
	}

	// 设置计时器
	SetTimer(hwnd, ID_TIMER, 5, NULL);

	// 获得整个屏幕画图设备
	hScreenDC = GetDC(0);

	// 计时
	timeCount = 0;

	// 循环获取操作系统发来的消息
	while (GetMessage(&msg, NULL, 0, 0) != 0)
	{
		switch(msg.message)
		{
		case WM_TIMER: // 时钟信号
			{
				if (timeCount > 200)
				{
					timeCount = 0;
					Vx = Random(4) - 2;
					Vy = Random(2) + 2;
				}
				else
				{
					timeCount += 1;
				}
				DrawPoint();
			}
			break;
		}
	}
}

先随机取的一个屏幕的坐标值(放在pData[i]),对应点的颜色存储在 pcolor[i] 中。
然后将其设置成星星的颜色值 #HFEFFFE(就是画出星星),

到下一个计时器触发时先根据原来存储的 pDate[i],恢复原来点的颜色,再随机取新的坐标值,画新的颜色。反复重复······从而形成星星飘动的效果

在此基础上加上判断屏幕边缘(用自定义的对比度函数 GetContrast,它返回与点 pDate[i]下面相邻的点的颜色值各 RGB 分量之差的和。当这个差大于某一个值,比喻程序用50是认为此处达到边缘)随机,风向等。星星颜色值是 Starcol = #HFEFFFE ,之所以取这一个值,是因为发现有时候2个星星重叠会混乱(如上下2个星星相邻,这样下一个星星会干扰上一个星星取得屏幕原来的颜色),
所以将星星设置成这种接近白色当不如白色FFFFFF常见颜色,当遇到取得的屏幕原始值是 StarColor 就认为出现重叠,就跳过这一个计时器事件不进行屏幕操作

Windows API 教程(八) 注册快捷键

茵蒂克丝

注册快捷键与hook监听键盘的区别

快捷键注册是针对某一个窗口的,注册之后当这个窗口收到快捷键,操作系统就会发送 WM_HOTKEY 消息来通知该窗口。所以快捷键注册是没办法监听全局的,比如你要定义一个快捷键 Ctrl + ` 在桌面上也能调出你的窗口,可是这个消息是发给桌面这个程序去了没办法触发你的程序。 快捷键收到消息之后消息会被转发给注册的hwnd 相当于截断了消息,如果要做全局监听的话还是推荐用 hook。

控制台注册快捷键


#include <stdio.h>
#include <Windows.h>

int main()
{
	MSG msg = {0};
	HWND hConsole = GetActiveWindow(); // 获取当前显示窗口的句柄(即控制台这个窗口的句柄)

	RegisterHotKey(
		hConsole,	// 注册快捷键的窗口句柄
		1,			// 热键标识符
		MOD_CONTROL | MOD_NOREPEAT, // Ctrl 键  No Repeat 不重复发送
		'A'			// A
	);	// Ctrl + A

	RegisterHotKey(hConsole, 2, MOD_CONTROL | MOD_NOREPEAT,'B');// Ctrl + B
	RegisterHotKey(hConsole, 3, MOD_ALT | MOD_NOREPEAT,'A');	// Alt + A
	RegisterHotKey(hConsole, 4, MOD_ALT | MOD_NOREPEAT,'B');	// Alt + B
	RegisterHotKey(hConsole, 5, MOD_NOREPEAT,'S');				// 直接按 S

	
	// 循环获取操作系统发来的消息
	while (GetMessage(&msg, NULL, 0, 0) != 0)
	{
		// 当收到快捷键消息时
		if (msg.message == WM_HOTKEY)
		{
			switch(msg.wParam)
			{
				case 1:
					printf("Ctrl + A 被按下! nn");
				break;

				case 2:
					printf("Ctrl + B 被按下! nn");
				break;

				case 3:
					printf("Alt + A 被按下! nn");
				break;
				
				case 4:
					printf("Alt + B 被按下! nn");
				break;

				case 5:
					printf("S 被按下! nn");
				break;
			}
		}
	}
}

hotkey_console

窗口注册快捷键

在window窗体开发中,窗口过程函数中实际上还可以通过 WM_KEYUP WM_KEYDOWN 等消息获取到用户的按键,大家可以通过如下类似的代码来查看窗口收到的消息(其实控制台下也是可以这样拿的)

case WM_KEYDOWN:
	wsprintf(text, "wParam : 0x%x  lParam : 0x%x", wParam, lParam);
	echo(text);
break;

不过这种的缺点也是很明显的,就是复合键等快捷键定义没有使用 WM_HOTKEY 方便

#include <Windows.h>

void echo(LPSTR str)
{
	MessageBox(NULL, str, TEXT("提示"), MB_OK);
}

// 5. 窗口过程处理
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{	
	CHAR text[50];
	switch(msg)
	{
	// 窗口创建消息
	case WM_CREATE:
		RegisterHotKey(
			hwnd,	// 注册快捷键的窗口句柄
			1,		// 热键标识符避免热键冲突
			MOD_CONTROL | MOD_NOREPEAT, // Ctrl 键  No Repeat 不重复发送
			'A'		// A
			); // Ctrl + A
		RegisterHotKey(hwnd, 2, MOD_CONTROL | MOD_NOREPEAT,'B');// Ctrl + B
		RegisterHotKey(hwnd, 3, MOD_ALT | MOD_NOREPEAT,'A');	// Alt + A
		RegisterHotKey(hwnd, 4, MOD_ALT | MOD_NOREPEAT,'B');	// Alt + B
		RegisterHotKey(hwnd, 5, MOD_NOREPEAT,'S');				// 直接按 S
	break;

	// 快捷键消息
	case WM_HOTKEY:

		switch(wParam)
		{
			case 1:
				printf("Ctrl + A 被按下! nn");
			break;

			case 2:
				printf("Ctrl + B 被按下! nn");
			break;

			case 3:
				printf("Alt + A 被按下! nn");
			break;
			
			case 4:
				printf("Alt + B 被按下! nn");
			break;

			case 5:
				printf("S 被按下! nn");
			break;
		}		
	break;

	case WM_CLOSE:
		DestroyWindow(hwnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
	return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASSEX wc;
	HWND hwnd;
	MSG Msg;
	char text[30];

	const char szClassName[] = "myWindowClass";

	// 1. 设置注册窗口结构体
	wc.cbSize        = sizeof(WNDCLASSEX);				// 注册窗口结构体的大小
	wc.style         = 0;								// 窗口的样式
	wc.lpfnWndProc   = WndProc;							// 指向窗口处理过程的函数指针
	wc.cbClsExtra    = 0;								// 指定紧跟在窗口类结构后的附加字节数
	wc.cbWndExtra    = 0;								// 指定紧跟在窗口事例后的附加字节数
	wc.hInstance     = hInstance;						// 本模块的实例句柄
	wc.hIcon         = LoadIcon(hInstance, IDI_APPLICATION);	// 图标的句柄
	wc.hCursor       = LoadCursor(NULL, IDC_ARROW);		// 光标的句柄
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);		// 背景画刷的句柄
	wc.lpszMenuName  = NULL;							// 指向菜单的指针
	wc.lpszClassName = szClassName;						// 指向类名称的指针
	wc.hIconSm       = LoadIcon(hInstance, IDI_APPLICATION);	// 和窗口类关联的小图标

	// 2. 使用【窗口结构体】注册窗口
	if(!RegisterClassEx(&wc))
	{
		MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
		return 0;
	}

	// 3. 创建窗口
	hwnd = CreateWindowEx(
		WS_EX_CLIENTEDGE,		// 窗口的扩展风格
		szClassName,			// 指向注册类名的指针
		TEXT("窗口标题"),		// 指向窗口名称的指针
		WS_OVERLAPPEDWINDOW,	// 窗口风格
		CW_USEDEFAULT, CW_USEDEFAULT, 350, 200, // 窗口的 x,y 坐标以及宽高
		NULL,					// 父窗口的句柄
		NULL,					// 菜单的句柄
		hInstance,				// 应用程序实例的句柄
		NULL					// 指向窗口的创建数据
		);

	if(hwnd == NULL)
	{
		MessageBox(NULL, TEXT("窗口创建失败"), TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);
		return 0;
	}

	// 4. 显示窗口
	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);

	// 6. 消息循环
	while(GetMessage(&Msg, NULL, 0, 0) > 0)
	{
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);
	}
	return Msg.wParam;
}

hotkey_window

Ctrl + Alt 等复杂组合键

还是在控制台下编写例子,其实没有差多少


#include <stdio.h>
#include <Windows.h>

int main()
{
	MSG msg = {0};
	HWND hConsole = GetActiveWindow();

	// Ctrl + D
	RegisterHotKey(hConsole,1,MOD_CONTROL | MOD_NOREPEAT,'D');
	// Win键 + Z
	RegisterHotKey(hConsole,2,MOD_WIN | MOD_NOREPEAT,'Z');
	// Ctrl + Alt + S
	RegisterHotKey(hConsole,3,MOD_CONTROL | MOD_ALT | MOD_NOREPEAT,'S');
	// Ctrl + Alt + Shift + A
	RegisterHotKey(hConsole,4,MOD_CONTROL | MOD_ALT | MOD_SHIFT | MOD_NOREPEAT,'A');
	
	// 循环获取操作系统发来的消息
	while (GetMessage(&msg, NULL, 0, 0) != 0)
	{
		// 当收到快捷键消息时
		if (msg.message == WM_HOTKEY)
		{
			printf("收到 WM_HOTKEY 快捷键消息n");
			printf("wParam : 0x%x  lParam : 0x%x n", msg.wParam, msg.lParam);

			switch(msg.wParam)
			{
				case 1:
					printf("Ctrl + D 被按下! nn");
				break

				case 2:
					printf("Win + Z 被按下! nn");
				break

				case 3:
					printf("Ctrl + Alt + S 被按下! nn");
				break

				case 4:
					printf("Ctrl + Alt + Shift + A 被按下! nn");
				break
			}
		}
	}
}

hotkey_complex

文章索引
上一讲: Windows API 教程(七) hook 监听
下一讲: Windows API 教程(九) 网络编程

Node.js 入门教程 第二讲 常见的全局对象

全局对象列表

使用util工具模块提供的inspect方法可以遍历出 global 全局变量中所存在的对象与方法

var util = require("util");
console.log(util.inspect(global, true, 0));

输出结果(已排序)

{ 
    // global自身
    root                 : [Circular],
    global               : [Circular],
    GLOBAL               : [Circular],

    // node 內置对象
    console              : [Getter],  // 控制台对象
    process              : [Object],  // 进程对象
    Buffer               : [Object],  // 缓冲对象 (不满纯js的ArrayBuffer?)

    // 基础对象
    [Object]             : [Object],  // Object 对象
    [Function]           : [Object],  // Function 对象
    [Date]               : [Object],  // 日期对象
    [Number]             : [Object],  // 数字对象对象
    [String]             : [Object],  // 字符串对象
    [Array]              : [Object],  // 数组对象
    [Boolean]            : [Object],  // 布尔对象对象
    [Math]               : [Object],  // Math 对象
    [RegExp]             : [Object],  // RegExp 正则对象
    [JSON]               : [Object],  // json 对象
    [eval]               : [Object],  // eval 对象

    // 类型化数组                     //  字节      描述                C语言描述
    Int8Array            : [Object],  //   1   8位有符号整数           signed char
    Uint8Array           : [Object],  //   1   8位无符号整数           unsigned char
    Uint8ClampedArray    : [Object],  //   1   8位无符号整数 (clamped) unsigned char
    Int16Array           : [Object],  //   2   16位有符号整数          short
    Uint16Array          : [Object],  //   2   16位无符号整数          unsigned short
    Int32Array           : [Object],  //   4   32位有符号整数          int
    Uint32Array          : [Object],  //   4   32位无符号整数          unsigned int
    Float32Array         : [Object],  //   4   32位IEEE浮点数          float
    Float64Array         : [Object],  //   8   64位IEEE浮点数          double
    ArrayBuffer          : [Object],  // 二进制数据的原始缓冲区
    DataView             : [Object],  // 可用在 ArrayBuffer 中的任何位置读写入不同类型的数据

    // 錯誤對象
    [Error]              : [Object],  // 错误错误
    [EvalError]          : [Object],  // Eval 错误
    [TypeError]          : [Object],  // Type 错误
    [URIError]           : [Object],  // URI 错误
    [RangeError]         : [Object],  // Range 错误
    [ReferenceError]     : [Object],  // Reference 错误
    [SyntaxError]        : [Object],  // Syntax 错误

    // 类型判断
    [NaN]                : NaN,       // not a number 不是一個数字
    [Infinity]           : Infinity,  // 正无穷大
    [isFinite]           : [Object],  // 判断是否为正无穷大
    [undefined]          : undefined  // 未定义
    [isNaN]              : [Object],  // 判断是否为未定义 

    // 定时延时对象
    setInterval          : [Object],  // 设置定时器
    clearInterval        : [Object],  // 清除定时器
    setTimeout           : [Object],  // 设置延迟执行
    clearTimeout         : [Object],  // 清除延迟执行
    setImmediate         : [Object],  // 设置延迟执行(更快更兼容)
    clearImmediate       : [Object],  // 清除延迟执行

    // 编码
    [decodeURI]          : [Object],  // 解码某个编码的 URI
    [decodeURIComponent] : [Object],  // 解码一个编码的 URI 组件
    [encodeURI]          : [Object],  // 把字符串编码为 URI
    [encodeURIComponent] : [Object],  // 把字符串编码为 URI 组件
    [escape]             : [Object],  // 对字符串进行编码
    [unescape]           : [Object],  // 对由 escape() 编码的字符串进行解码
    [parseInt]           : [Object],  // 解析一个字符串并返回一个整数
    [parseFloat]         : [Object],  // 解析一个字符串并返回一个浮点数

    // node 内置对象(请先无视)
    COUNTER_HTTP_SERVER_REQUEST        : [Object],
    COUNTER_HTTP_SERVER_RESPONSE       : [Object],
    COUNTER_HTTP_CLIENT_REQUEST        : [Object],
    COUNTER_HTTP_CLIENT_RESPONSE       : [Object],
    COUNTER_NET_SERVER_CONNECTION      : [Object],
    COUNTER_NET_SERVER_CONNECTION_CLOSE: [Object],
    DTRACE_HTTP_CLIENT_REQUEST         : [Object],
    DTRACE_HTTP_SERVER_REQUEST         : [Object],
    DTRACE_HTTP_CLIENT_RESPONSE        : [Object],
    DTRACE_HTTP_SERVER_RESPONSE        : [Object],
    DTRACE_NET_SERVER_CONNECTION       : [Object],
    DTRACE_NET_STREAM_END              : [Object],
    DTRACE_NET_SOCKET_WRITE            : [Object],
    DTRACE_NET_SOCKET_READ             : [Object],
}

Circular 可以理解为递归到自己

大家也可以去看w3school 上的 JavaScript 全局对象参考手册 来对比 浏览器上的 js 与 node 上 js 全局对象的一些区别

global 对象

global 为全局命名空间对象,不过需要注意的是,这个全局只是针对当前文件本身。

console 对象

console.log(“hello node.js”)
for(var i in console){
console.log(i+” “+console[i])
}

var log = function () {
process.stdout.write(format.apply(this, arguments) + ‘n’);
}
var info = function () {
process.stdout.write(format.apply(this, arguments) + ‘n’);
}
var warn = function () {
writeError(format.apply(this, arguments) + ‘n’);
}
var error = function () {
writeError(format.apply(this, arguments) + ‘n’);
}
var dir = function (object) {
var util = require(‘util’);
process.stdout.write(util.inspect(object) + ‘n’);
}
var time = function (label) {
times[label] = Date.now();
}
var timeEnd = function (label) {
var duration = Date.now() – times[label];
exports.log(‘undefined: NaNms’, label, duration);
}
var trace = function (label) {
// TODO probably can to do this better with V8’s debug object once that is
// exposed.
var err = new Error;
err.name = ‘Trace’;
err.message = label || ”;
Error.captureStackTrace(err, arguments.callee);
console.error(err.stack);
}
var assert = function (expression) {
if (!expression) {
var arr = Array.prototype.slice.call(arguments, 1);
require(‘assert’).ok(false, format.apply(this, arr));
}
}

process 对象

buffer 对象

module 对象

for(var i in module){
console.log(“var ” + i + ” = “+module[i])
}

var id = .
var exports = [object Object]
var parent = null
var filename = /home/cheng19840218/node/example.js
var loaded = false
var exited = false
var children =
var paths = /home/cheng19840218/node/node_modules,/home/cheng19840218/node_modules,/home/node_modules,/node_modules
var load = function (filename) {
//太长了,省略
}
var _compile = function (content, filename) {
//太长了,省略
}

原来那个著名的exports是在此提供的,__filename大概也是filename的引用。只要遍历一下,你就发现许多有趣的东西。但别以为一下秘密就暴光在你眼皮下,还有许多不可遍历属性。比如上面我遍历global对象,只有尞尞可数几个成员,我们可以使用ecma262v5新增的方法去考察一下:

console.log(Object.getOwnPropertyNames(global))

许多人学node.js就立即看其文档,殊不知node.js本身所依赖的V8引擎就拥有许多要学的东西,这其中包括ecma262v5带来的新方法新对象,还有效仿firefox的一些语法:

__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
set
get
__proto__

好了,我们来点实质的内容吧。node.js本来就是一个http服务器,它是要与前端交互的,因此少不了两个对象:请求(request)与响应(response)。请求与响应显然一种异步的东西,因为我们 不知道前端什么时候发请求过来,响应也不能立即给前端,还要做日志,读写数据库等操作呢。因此对于javascript来说,这用回调函数来实现最好。那么由誰来接受这个回调呢?一个服务器对象!

var http = require(“http”);
http.createServer(function(request, response) {
response.writeHead(200, {“Content-Type”: “text/plain”});
response.write(“Hello node.js”);
response.end();
}).listen(8888);
node.js有个特殊的require,用于同步加载其他模块的对象,这与其他语言的require, import差不多。能同步就是好,不像前端那样一层套一层。然后利用一个函数去实例化一个服务器对象,然后监听8888端口。这是node.js官网最初的例子,大家都写烂了。但这样的程序在现实中一无是处,我们在地址栏上输入URL,你起码要返回一个完整页面给我吧!

对此,我们首先要进行模块化。模块化是以文件为单位的,把example.js更名为server.js,然后再把里面的内容改为一个模块。对于一个node.js的文件,其实它里面的内容是在一个封闭的环境中执行。要想共享给其他模块使用,就必须绑定在exports对象上。

var http = require(“http”);

exports.start = function(){
http.createServer(function(request, response) {
console.log(“Request received…”);
response.writeHead(200, {“Content-Type”: “text/plain”});
response.write(“Hello node.js”);
response.end();
}).listen(8888);
console.log(“server start…”);
}
然后我们再建一个index.js作为入口(index.js与server.js放在同一目录下)。

var server = require(“./server”);

server.start();
然后建一个index.html页面。

index

这是首页

现在我们就在要请求过来时,把此页的内容读出来,返给用户。这时我们就要用到fs模块的方法了。

var http = require(“http”);
var fs = require(‘fs’);
exports.start = function(){
http.createServer(function(request, response) {
fs.readFile(‘./index.html’, ‘utf-8’,function (err, data) {//读取内容
if (err) throw err;
response.writeHead(200, {“Content-Type”: “text/html”});//注意这里
response.write(data);
response.end();
});
}).listen(8888);
console.log(“server start…”);
}
好了,这时我们重启再次输入地址,就看到一个完整的页面了。

但一个页面除了HTML结构层外,还有javascript与css。那么,我们在当前目录建一个文件夹javascripts, 里面建index.js,内容如下:

window.onload = function(){
var p = document.createElement(“p”);
p.innerHTML = “这是动态添加的”
document.body.appendChild(p);
}
再建一个styles目录,里面建index.css,内容如下:

html,body{
background: #3671A5;
height: 100%
}
然后在index.html引入这两个文件:

index

/javascripts/index.js

这是首页

重新打开,发现没有改变,google,说要处理js与css文件的请求。没有办法,取得request.url属性,再判定后缀名,为它进行文件读取与设置首部。

var http = require(“http”);
var fs = require(‘fs’);
var url = require(‘url’);
exports.start = function(){
http.createServer(function(request, response) {
var pathname = url.parse(request.url).pathname;
var ext = pathname.match(/(.[^.]+|)$/)[0];//取得后缀名
switch(ext){
case “.css”:
case “.js”:
fs.readFile(“.”+request.url, ‘utf-8’,function (err, data) {//读取内容
if (err) throw err;
response.writeHead(200, {
“Content-Type”: {
“.css”:”text/css”,
“.js”:”application/javascript”,
}[ext]
});
response.write(data);
response.end();
});
break;
default:
fs.readFile(‘./index.html’, ‘utf-8’,function (err, data) {//读取内容
if (err) throw err;
response.writeHead(200, {
“Content-Type”: “text/html”
});
response.write(data);
response.end();
});

}

}).listen(8888);
console.log(“server start…”);
}

Windows API 教程(七) hook 钩子监听

茵蒂克丝

如何创建一个窗口

另外一个再录的 Windows SDK教程 里面有讲到快捷创建窗口的方式,不过这样的话要分好几个文件,感觉有点混所以这里就用原始的方式创建一个窗口。

那么,为什么讲到 hook(钩子)的时候要去创建窗口呢?其实这个问题说起来也不复杂,简单点说,按博主这样写不用写DLL也不用资源文件,实际上是把问题简化了一些。通常 hook 是用来监听自己窗口上的键盘和鼠标输入的,监听全局的通常是设置一些全局的热键(如QQ的 Ctrl+Alt+Z 调出QQ窗口),这些常见的功能也都是要依托窗口才能存在。所以我们先来简单说下手动建立一个窗口的流程。

手动创建窗口的流程

  1. 设置注册窗口结构体
  2. 使用【窗口结构体】注册窗口
  3. 创建窗口
  4. 显示窗口
  5. 窗口过程处理
  6. 消息循环

实际代码

这里不会详细讲这个,有感兴趣的可以去追博主的 SDK教程 或者去搜 杨中科的《C语言也能干大事》


#include <windows.h>

// 5. 窗口过程处理
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{	
	switch(msg)
	{
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;	// 更多详细都可以去百度的 http://baike.baidu.com/view/1750396.htm
    HWND hwnd;
    MSG Msg;
	char text[30];

	const char szClassName[] = "myWindowClass";

    // 1. 设置注册窗口结构体
    wc.cbSize        = sizeof(WNDCLASSEX);				// 注册窗口结构体的大小
    wc.style         = 0;								// 窗口的样式
    wc.lpfnWndProc   = WndProc;							// 指向窗口处理过程的函数指针
    wc.cbClsExtra    = 0;								// 指定紧跟在窗口类结构后的附加字节数
    wc.cbWndExtra    = 0;								// 指定紧跟在窗口事例后的附加字节数
    wc.hInstance     = hInstance;						// 本模块的实例句柄
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);	// 图标的句柄
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);		// 光标的句柄
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);		// 背景画刷的句柄
    wc.lpszMenuName  = NULL;							// 指向菜单的指针
    wc.lpszClassName = szClassName;						// 指向类名称的指针
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);	// 和窗口类关联的小图标

	// 2. 使用【窗口结构体】注册窗口
    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // 3. 创建窗口
    hwnd = CreateWindowEx(
		WS_EX_CLIENTEDGE,		// 窗口的扩展风格
		szClassName,			// 指向注册类名的指针
		TEXT("窗口标题"),		// 指向窗口名称的指针
		WS_OVERLAPPEDWINDOW,	// 窗口风格
        CW_USEDEFAULT, CW_USEDEFAULT, 350, 200, // 窗口的 x,y 坐标以及宽高
        NULL,					// 父窗口的句柄
		NULL,					// 菜单的句柄
		hInstance,				// 应用程序实例的句柄
		NULL					// 指向窗口的创建数据
		);

    if(hwnd == NULL)
    {
        MessageBox(NULL, TEXT("窗口创建失败"), TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

	// 4. 显示窗口
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // 6. 消息循环
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

因为是比较死的形式,以上代码大家混个脸熟,大概知道各个部分的作用就行了,博主也从来没有专门记过。

安装钩子 (Install hook)

简介

窗口建好了之后就要开始转到我们的正题了,首先我们需要明确的是,这个钩子(hook)到底是什么,那么博主这里也不做太书面的解释留下几个链接:

百度百科:hook
MSDN: Hooks
博客园: Beginning HOOK

各位可以多多参考,那么博主说下自己的理解:

windows 系统中的【hook 机制】,就类似于一个【消息过滤网】,如果我们向操作系统申请并成功对某个窗口安装了一个【hook】指定了【回调函数】,那么这个【回调函数】也就相当于我们人为对这个窗口添加了一个【消息过滤网】。此时当 windows 操作系统要对这个窗口发送任何消息的时候(例如按键、鼠标点击等消息)操作系统会先调用我们在【消息过滤网】中设置的【回调函数】去接受、处理、过滤等等,当然如果你在【回调函数】中拿到了数据却没有继续传递给窗口的话,就相当于拦截了这些消息。

打个简单的比方,如果你在系统全局安装了一个【键盘消息】的钩子,并且在其指定的【回调函数】中没有把这个键盘消息继续传递给系统上的窗口,那么你的所有【键盘消息】都被这个【hook】也就我们挂在这个【消息过滤网】上的【回调函数】给拦截了,这也意味着你的键盘会失灵。

SetWindowsHookEx 函数

那么 SetWindowsHookEx 函数就是我们用来在 windows 操作系统上安装钩子的函数,我们简单来看一下这个函数的原型:

HHOOK WINAPI SetWindowsHookEx(
  _In_  int idHook,			// 安装的钩子类型
  _In_  HOOKPROC lpfn,		// 处理消息的回调函数
  _In_  HINSTANCE hMod,		// 当前实例句柄
  _In_  DWORD dwThreadId	// 线程ID
);

钩子类型有很多种,本页中留的大部分链接上都有讲到这里就不废话了,关于 hMod(当前实例句柄)和 dwThreadId(线程ID)之间的一些小九九博主这里也不多说,各位可以到下方的链接中去看看,博主这里就举一个容易实现的实例。

百度百科: SetWindowsHookEx
MSDN: SetWindowsHookEx

设置监听【键盘】消息

PKBDLLHOOKSTRUCT 是 WH_KEYBOARD_LL 方式中用来接收消息的结构体,大家可以到 WindUser.h 中多逛逛。

我们监听键盘的时候主要用的是该结构体的 vkCode(value code)和 scanCode 这两个字段。即键盘的【值码】和【扫描码】那么为什么判断一个按键要分成两个部分呢,原因是因为世界上的键盘有很多种,不同国家、不同厂商生产的键盘甚,至同一个键盘上【同样的键】不同的地方按下都可能会有差异。vkCode 是常见的一般都是公用的键盘值,而 scanCode 扫描码则是用来辅助区分的一个一个参数,例如同样是按下 ctrl 键,他们的 vkCode 是相同的但是 scanCode 却不同。


#include <windows.h>

HHOOK myhook;	// 保存当前钩子句柄

/**************************************************************** 
  WH_KEYBOARD hook procedure 
  鍵盤钩子处理过程
 ****************************************************************/ 
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) 
{ 	
	char text[50], data[20];	// 输出字符串
	const char *info = NULL;	// 类型字符指针
	PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam; // 获取按键消息
	HDC hdc;	// 画图设备句柄

	// 判断是否收到键盘消息
    if (nCode >= 0)
	{
		// 判断消息类型
		if 		(wParam == WM_KEYDOWN) 		info = "普通按鍵抬起";
		else if (wParam == WM_KEYUP) 		info = "普通按鍵按下";
		else if (wParam == WM_SYSKEYDOWN) 	info = "系統按鍵抬起";
		else if (wParam == WM_SYSKEYUP) 	info = "系統按鍵按下";

		// 初始化数组
		ZeroMemory(text, sizeof(text));
		ZeroMemory(data, sizeof(data));
		// 拼装字符串
		wsprintf(text, "%s - 键盘码 [%04d], 扫描码 [%04d]  ", info, p->vkCode, p->scanCode);
		wsprintf(data, "按鍵目测为: %c  ", p->vkCode);

		// 此处调用 GDI 画图函数来将截取到的内容画在窗口上
		hdc = GetDC(画图的窗口句柄);		// 获取要画图的设备句柄
		TextOut(hdc, 10, 10, text, strlen(text));	// 在窗口上画文字 
		TextOut(hdc, 10, 30, data, strlen(data));	// 参数分别是 目标设备, x坐标, y坐标, 字符串内容, 字符串长度
		ReleaseDC(画图的窗口句柄, hdc);		// 释放设备句柄
	}

    // 将消息继续往下传递
    return CallNextHookEx(myhook, nCode, wParam, lParam);
} 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
	/* 其他代码 */

	// 设置键盘全局监听
	myhook = SetWindowsHookEx( 
		WH_KEYBOARD_LL, // 监听类型【键盘消息】
		KeyboardProc,	// 处理函数
		hInstance,		// 当前实例句柄
		0				// 监听线程ID(NULL为全局监听)
	); 

	// 判断是否成功
	if(myhook == NULL)
	{		
		wsprintf(text, "键盘监听失败!error : %d n", GetLastError());
		MessageBox(hwnd, text, TEXT("错误"), MB_OK);
	}

	/* 其他代码 */
}

注:其中在输出按键的时候,直接用 %c 输出了 p->vkCode 部分,这个实际上不是很准确。

顺便提一句,各位也不要用这个功能去做什么坏事,比如去监听QQ窗口的键盘消息然后偷到密码盗别人号之类的, 博主已经试过了 这个本身有带防护的,用户在输密码的时候,会有干扰的键盘消息也一起冒出来所以没那么简单能到到别人输的密码。至于写个程序去拦截别人的键盘还有鼠标消息让别人的电脑不能用的情况,这个确实很容易做到,而且貌似杀毒软件都没办法防,只能自己在自己的电脑上留个后门,怎么写后门?后面的网络编程会说这个。

键盘监听完整代码:


#include <windows.h>

HWND hgWnd;
HHOOK myhook;

/**************************************************************** 
  WH_KEYBOARD hook procedure 
  鍵盤钩子处理过程
 ****************************************************************/ 
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) 
{ 	
	PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
	const char *info = NULL;
	char text[50], data[20];

	PAINTSTRUCT ps;
	HDC hdc;

    if (nCode >= 0)
	{
		if      (wParam == WM_KEYDOWN)		info = "普通按鍵抬起";
		else if (wParam == WM_KEYUP)		info = "普通按鍵按下";
		else if (wParam == WM_SYSKEYDOWN)	info = "系統按鍵抬起";
		else if (wParam == WM_SYSKEYUP)		info = "系統按鍵按下";

		ZeroMemory(text, sizeof(text));
		ZeroMemory(data, sizeof(data));
		wsprintf(text, "%s - 键盘码 [%04d], 扫描码 [%04d]  ", info, p->vkCode, p->scanCode);
		wsprintf(data, "按鍵目測為: %c  ", p->vkCode);

		hdc = GetDC(hgWnd);			
		TextOut(hdc, 10, 10, text, strlen(text));
		TextOut(hdc, 10, 30, data, strlen(data));
		ReleaseDC(hgWnd,hdc);
	}
    
    return CallNextHookEx(myhook, nCode, wParam, lParam);
} 

// 5. 窗口过程处理
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{	
	hgWnd = hwnd;

	switch(msg)
	{
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;	// http://baike.baidu.com/view/1750396.htm
    HWND hwnd;
    MSG Msg;
	char text[30];

	const char szClassName[] = "myWindowClass";

    // 1. 设置注册窗口结构体
    wc.cbSize        = sizeof(WNDCLASSEX);				// 注册窗口结构体的大小
    wc.style         = 0;								// 窗口的样式
    wc.lpfnWndProc   = WndProc;							// 指向窗口处理过程的函数指针
    wc.cbClsExtra    = 0;								// 指定紧跟在窗口类结构后的附加字节数
    wc.cbWndExtra    = 0;								// 指定紧跟在窗口事例后的附加字节数
    wc.hInstance     = hInstance;						// 本模块的实例句柄
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);	// 图标的句柄
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);		// 光标的句柄
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);		// 背景画刷的句柄
    wc.lpszMenuName  = NULL;							// 指向菜单的指针
    wc.lpszClassName = szClassName;						// 指向类名称的指针
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);	// 和窗口类关联的小图标

	// 2. 使用【窗口结构体】注册窗口
    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // 3. 创建窗口
    hwnd = CreateWindowEx(
		WS_EX_CLIENTEDGE,		// 窗口的扩展风格
		szClassName,			// 指向注册类名的指针
		TEXT("窗口标题"),		// 指向窗口名称的指针
		WS_OVERLAPPEDWINDOW,	// 窗口风格
        CW_USEDEFAULT, CW_USEDEFAULT, 350, 200, // 窗口的 x,y 坐标以及宽高
        NULL,					// 父窗口的句柄
		NULL,					// 菜单的句柄
		hInstance,				// 应用程序实例的句柄
		NULL					// 指向窗口的创建数据
		);

    if(hwnd == NULL)
    {
        MessageBox(NULL, TEXT("窗口创建失败"), TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

	// 4. 显示窗口
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

	// 设置键盘全局监听
	myhook = SetWindowsHookEx( 
		WH_KEYBOARD_LL, // 监听类型【键盘】
		KeyboardProc,	// 处理函数
		hInstance,		// 当前实例句柄
		0				// 监听窗口句柄(NULL为全局监听)
	); 

	if(myhook == NULL)
	{		
		wsprintf(text, "键盘监听失败!error : %d n", GetLastError());
		MessageBox(hwnd, text, TEXT("错误"), MB_OK);
	}


    // 5. 消息循环
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

运行截图

键盘监听

设置监听【鼠标】消息

与键盘监听类似,各位直接看代码:

#include <windows.h>

HWND hgWnd;
HHOOK myhook;

/**************************************************************** 
  WH_KEYBOARD hook procedure 
  鍵盤钩子处理过程
 ****************************************************************/ 
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) 
{ 	
	LPMSLLHOOKSTRUCT p = (LPMSLLHOOKSTRUCT)lParam;
	POINT   pt = p->pt;
	DWORD   mouseData = p->mouseData;
	const char *info = NULL;
	char text[60], pData[50], mData[50];

	PAINTSTRUCT ps;
	HDC hdc;

    if (nCode >= 0)
	{
		if   (wParam == WM_MOUSEMOVE)		info = "鼠标移动    ";
		else if(wParam == WM_LBUTTONDOWN)	info = "鼠标【左键】按下";
		else if(wParam == WM_LBUTTONUP)		info = "鼠标【左键】抬起";
		else if(wParam == WM_LBUTTONDBLCLK)	info = "鼠标【左键】双击";
		else if(wParam == WM_RBUTTONDOWN)	info = "鼠标【右键】按下";
		else if(wParam == WM_RBUTTONUP)		info = "鼠标【右键】抬起";
		else if(wParam == WM_RBUTTONDBLCLK)	info = "鼠标【右键】双击";
		else if(wParam == WM_MBUTTONDOWN)	info = "鼠标【滚轮】按下";
		else if(wParam == WM_MBUTTONUP)		info = "鼠标【滚轮】抬起";
		else if(wParam == WM_MBUTTONDBLCLK)	info = "鼠标【滚轮】双击";
		else if(wParam == WM_MOUSEWHEEL)	info = "鼠标【滚轮】滚动";

		ZeroMemory(text, sizeof(text));
		ZeroMemory(pData, sizeof(pData));
		ZeroMemory(mData, sizeof(mData));

		wsprintf( text, "当前状态: %10s   ", info);
		wsprintf(pData, "0x%x - X: [%04d], Y: [%04d]  ", wParam, pt.x, pt.y);
		wsprintf(mData, "附带数据: %16u   ", mouseData);

		hdc = GetDC(hgWnd);			
		TextOut(hdc, 10, 10,  text, strlen(text));
		TextOut(hdc, 10, 30, pData, strlen(pData));
		TextOut(hdc, 10, 50, mData, strlen(mData));
		ReleaseDC(hgWnd,hdc);
	}
    
    return CallNextHookEx(myhook, nCode, wParam, lParam);
} 

// 5. 窗口过程处理
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{	
	hgWnd = hwnd;

	switch(msg)
	{
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;	// http://baike.baidu.com/view/1750396.htm
    HWND hwnd;
    MSG Msg;
	char text[30];

	const char szClassName[] = "myWindowClass";

    // 1. 设置注册窗口结构体
    wc.cbSize        = sizeof(WNDCLASSEX);				// 注册窗口结构体的大小
    wc.style         = 0;								// 窗口的样式
    wc.lpfnWndProc   = WndProc;							// 指向窗口处理过程的函数指针
    wc.cbClsExtra    = 0;								// 指定紧跟在窗口类结构后的附加字节数
    wc.cbWndExtra    = 0;								// 指定紧跟在窗口事例后的附加字节数
    wc.hInstance     = hInstance;						// 本模块的实例句柄
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);	// 图标的句柄
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);		// 光标的句柄
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);		// 背景画刷的句柄
    wc.lpszMenuName  = NULL;							// 指向菜单的指针
    wc.lpszClassName = szClassName;						// 指向类名称的指针
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);	// 和窗口类关联的小图标

	// 2. 使用【窗口结构体】注册窗口
    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // 3. 创建窗口
    hwnd = CreateWindowEx(
		WS_EX_CLIENTEDGE,		// 窗口的扩展风格
		szClassName,			// 指向注册类名的指针
		TEXT("窗口标题"),		// 指向窗口名称的指针
		WS_OVERLAPPEDWINDOW,	// 窗口风格
        CW_USEDEFAULT, CW_USEDEFAULT, 350, 200, // 窗口的 x,y 坐标以及宽高
        NULL,					// 父窗口的句柄
		NULL,					// 菜单的句柄
		hInstance,				// 应用程序实例的句柄
		NULL					// 指向窗口的创建数据
		);

    if(hwnd == NULL)
    {
        MessageBox(NULL, TEXT("窗口创建失败"), TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

	// 4. 显示窗口
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

	// 设置鼠标全局监听
	myhook = SetWindowsHookEx( 
		WH_MOUSE_LL,	// 监听类型【鼠标】
		MouseProc,	// 处理函数
		hInstance,		// 当前实例句柄
		0				// 监听窗口句柄(NULL为全局监听)
	); 

	if(myhook == NULL)
	{		
		wsprintf(text, "键盘监听失败!error : %d n", GetLastError());
		MessageBox(hwnd, text, TEXT("错误"), MB_OK);
	}


    // 5. 消息循环
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

MouseHook

上一讲: Windows API 教程(六) 动态链接库
下一讲: Windows API 教程(八) 注册快捷键

c 获取 windows 显示的窗口列表


#include <Windows.h>

HWND list[200] = {0};
INT length = 0;

BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam)
{
	if(IsWindowVisible(hwnd))
	{
		list[length] = hwnd;
		length++;
	}
	return TRUE;
}

int main()
{
	int i;
	CHAR buffer[256] = {0};
	CHAR text[2046] = {0};

	// 获取桌面的句柄
	HWND hExplorer = FindWindow("explorer.exe", NULL);
	// 获取子窗口
	EnumChildWindows(
		hExplorer ,		// 桌面的句柄
		EnumChildProc,	// 获取到某个窗口句柄时的回调函数
		NULL			// 传给回调函数的值
	);

	for (i = 0; i < length; i++)
	{
		ZeroMemory(buffer, 256);
		// 获取标题
		GetWindowText(list[i], buffer, 256);
		wsprintf(text, "%s%d.%sn", text, i, buffer);				
	}	
	// 显示
	MessageBox(NULL, text, NULL, MB_OK);
}

window_list