【教程】基于C语言的Windows编程入门
acmilan 2015-8-5Windows
在本文中,我们将学习如何使用WinAPI创建你的第一个Windows应用程序
本文仅作入门之用,更深一步的了解,请参阅《Windows 程序设计(第五版)》(Charles  Petzold 著)等相关书籍

Win32编程的一大特点是类型系统繁杂。本文抛弃繁杂的Win32类型系统,大部分采用C原生类型,让Win32回归C语言的本质

(一)第一个Windows应用程序

一、头文件

C语言编写Windows程序的最简单和直接的方法是调用WinAPI,WinAPI的定义都包含在windows.h这个头文件里,所以我们要包含它。
windows.h通过宏来确定WinAPI的表达方式,其中比较重要的宏如下:
_UNICODEUNICODE宏表示使用Unicode版WinAPI,而不是老式的ANSI版WinAPI(由于历史原因,两个必须同时定义)
NO_STRICT宏表示将句柄均定义为void*类型,不区分各种句柄
// 始终使用Unicode函数
#define _UNICODE
#define UNICODE
                            
// 不严格区分句柄,均定义为void*
#define NO_STRICT
                            
// 包含WinAPI定义
#include <windows.h>

二、入口点

首先,一个程序要有入口点,学过C语言的同学都知道,C语言的入口点是main函数。Windows程序的入口点则是wWinMain函数(有些较老的书上可能写WinMain,其实只有一个参数的差别):
// 入口点 - wWinMain
int __stdcall wWinMain(void* hInstance, void* reserved, wchar_t* cmdline, int nshow)
{
    return 0;
}

三、链接器选项

还有一些问题,这些Visual Studio已经为我们考虑进去了,但是第一个程序我们采用命令行编译,所以需要自己注意:
Windows程序依赖kernel32.dll、user32.dll等各种DLL动态链接库,为了启动时能够自动加载这些DLL文件,需要添加对应的LIB导入库
Windows程序需要指定子系统类别为Windows而不是默认的Console
为了编译方便,这里将链接器参数直接写到了代码中,在Visual Studio中编写程序完全可以不写:
// 链接必要的库
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "user32.lib")
                          
// 编译为windows程序
#pragma comment(linker, "/subsystem:windows")

四、显示一个消息框

我们的第一个程序很简单 - 显示一个消息框
显示消息框使用MessageBox函数,该函数有4个参数:
int MessageBox(void* 父窗口句柄, wchar_t* 提示内容, wchar_t* 标题, unsigned int 样式)
其中父窗口句柄可以为空,这样便不会依附于任何一个窗口。
样式是以MB_开头的常量,这里我们新建一个只有确定按钮的对话框,使用MB_OK样式。
程序如下:
// 不严格区分句柄,均定义为void*
#define NO_STRICT
                            
// 包含WinAPI定义
#include <windows.h>
                         
// 入口点 - wWinMain
int __stdcall wWinMain(void* hInstance, void* reserved, wchar_t* cmdline, int nshow)
{
    MessageBox(NULL, L"欢迎来到Win32编程世界!", L"对话框标题", MB_OK);
    return 0;
}

这是最终的程序:

对话框00.png


五、编译和运行

由于程序只有一个文件,因此编译很简单,首先打开Visual Studio命令提示,cd到对应的目录,直接cl myfirst.c即可:

对话框01.png


对话框02.png


vs2015的命令行编译环境可能有bug,上图所示环境已经过修复,推荐使用visual studio集成开发环境编译程序
环境变量修补批处理脚本:
@echo off
set include=%include%;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10150.0\ucrt
set lib=%lib%;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10150.0\ucrt\x86
cmd /k cls

[修改于 4 年前 - 2015-08-07 13:23:30]

来自 Windows
 
2015-8-5 01:21:46
acmilan(作者)
1楼
(二)在 Visual Studio 中编译 Windows 程序

在命令行编译复杂程序会很麻烦,我们大部分时间都在Visual Studio中编写程序,在Visual Studio中编译Windows程序就简单多了

一、新建项目

点击文件-》新建-》项目,打开新建项目对话框

对话框03.png


选择模板-》Visual C++-》Win32-》Win32项目,输入项目名称,点击“确定”

对话框04.png


出现向导界面,点“下一步”:

对话框05.png


在第二页,选择“Windows 应用程序”和“空项目”,关闭“安全开发生命周期(SDL)检查”,点击“完成”确认并新建一个空项目

对话框06.png


二、添加源文件

在左侧导航栏,转到“解决方案资源管理器”,右键单击“源文件”,选择添加-》添加新项,打开添加新项对话框

对话框07.png


在对话框中,选择Visual C++-》代码-》C++文件(.cpp),输入文件名myfirst.c,点击“添加”添加源文件

对话框08.png


三、输入源代码

这里就没必要输入那么多了,只需要输入我们源代码的后半部分就可以了,前半部分Visual Studio已经为我们考虑过了

对话框09.png


四、调试、运行

单击工具栏上的绿色三角按钮,可以开始调试,也可以选择菜单项调试-》开始调试,或者按快捷键F5
只是想执行而不想调试的话,选择菜单项调试-》开始执行(不调试),或者按快捷键Ctrl+F5

对话框10.png

[修改于 4 年前 - 2015-08-05 06:01:16]

折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
2楼
(三) 窗口的创建与消息处理

原来那个程序是不是感觉有点简单?这里我们要制作一个复杂一点的Windows程序 - 显示一个空白窗口。

出于稳定性考量,Visual C++编译器中int、long等基本类型基本上没有变化(16->32仅int变化了一次,64位没有变化),但是指针则16位、32位、64位一直在变化,为了更好地适应指针的变化,以及方便地与指针相互转换,Windows中定义了平台相关整数,它们随指针长度而变化:
INT_PTRUINT_PTR - 16/32/64位平台相关整数,前者有符号,后者无符号
LONG_PTRULONG_PTR - 32/32/64位平台相关整数,前者有符号,后者无符号
这四个类型在以后的编程中要经常用到,务必明白它们的意思。

关于调用约定:调用约定表示调用函数时参数的传递方式。在Windows中绝大多数WinAPI都是__stdcall,只有wsprintf等参数数量可变的函数是__cdecl,自己定义的各种Win32函数也最好采用__stdcall。关于调用约定的内部信息请参阅相关资料,这里不详述。

一、准备一个窗口消息处理函数

Windows中窗口事件的处理是靠窗口消息来完成的,为了处理窗口消息,每一个窗口都至少对应一个窗口消息处理函数,窗口消息处理函数的定义如下:
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
    return DefWindowProc(hWnd, msg, wp, lp);
}

程序在处理完消息之后,return 0;直接返回,否则应该调用DefWindowProc来处理不想处理的消息。
常用的消息如下:
WM_CREATE - 窗口创建时被调用
WM_DESTROY - 窗口销毁时被调用
WM_PAINT - 窗口绘制时被调用
WM_COMMAND - 控件或窗口有消息时被调用
WM_SIZE - 窗口大小改变时被调用
为了在同一个函数中处理不同消息,我们一般使用switch (msg) { case WM_XXX: ... }分支来编写WndProc函数:
// 窗口消息处理函数 - WndProc
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
    switch (msg) // 判断窗口消息的种类
    {
    case WM_CREATE: // 窗口创建
        // ...
        return 0;
    case WM_DESTROY: // 窗口销毁
        // ...
        return 0;
    // case ...
    }
    return DefWindowProc(hWnd, msg, wp, lp);
}

在这里,我们只需处理WM_DESTROY即可:
// 窗口消息处理函数 - WndProc
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
    switch (msg) // 判断窗口消息的种类
    {
    case WM_DESTROY: // 窗口销毁
        // ...
        return 0;
    }
    return DefWindowProc(hWnd, msg, wp, lp);
}

二、全局变量

我们需要两个全局变量保存模块和主窗口的句柄:
void* hInst; // 模块句柄
void* hMainWnd; // 主窗口句柄

在wWinMain中,第一个参数提供了模块句柄,现在保存到全局变量:
// 入口点 - wWinMain
int __stdcall wWinMain(void* hInstance, void* reserved, wchar_t* cmdline, int nshow)
{
    hInst = hInstance; // 保存模块句柄
                       
    // ... 注册窗口类 建立窗口 显示和更新窗口 消息循环 ...
}

三、注册窗口类

在Windows中,窗口的部分特性需要与窗口类(WNDCLASS)进行自定义。WNDCLASS是一个C语言结构体,我们需要填写它们的内容。
WNDCLASS {
    样式 函数(样式=窗口类样式,常用的有CS_HREDRAW和CS_VREDRAW;函数=窗口处理函数)
    类增 窗增(高级参数,表示窗口类和每个窗口附加数据的字节数,一般填0)
    模块 图标 光标(模块=模块句柄;图标=左上角图标;光标=常说的鼠标指针)
    背景 菜单 类名(背景=背景画刷;菜单=菜单资源标识,字符串或小于65536的整数;类名=窗口类的名称,随便写)
}
在这里如下填写,这些内容一般情况下是比较固定的:
// 窗口类设定
// 样式、函数
// 类增、窗增            (高级参数)
// 模块、图标、光标     (光标=鼠标指针)
// 背景、菜单、类名
WNDCLASS wc = {
    CS_HREDRAW | CS_VREDRAW, // 样式:横向、纵向改变大小时重绘
    WndProc, // 窗口消息处理函数
    0, 0,
    hInst, // 模块句柄
    LoadIcon(NULL, IDI_APPLICATION), // 图标:默认图标
    LoadCursor(NULL, IDC_ARROW), // 光标:箭头
    GetStockObject(WHITE_BRUSH), // 背景画刷:白色
    NULL, // 菜单:无
    L"MyWindow", // 窗口类名称
};

填写完这个类之后,我们可以调用RegisterClass函数注册这个窗口类:
if (!RegisterClass(&wc)) // 注册窗口类
    return 1;

四、新建窗口

新建窗口需要使用CreateWindowEx函数,这个函数有12个参数,看似比较多,其实也不难记:
CreateWindowEx(
    扩展样式, 窗口类名, 窗口标题, 窗口样式, // 窗口样式常用WS_OVERLAPPEDWINDOW
    左, 上, 宽, 高, // 不想填写可以使用CW_USEDEFAULT
    父窗句柄, 菜单句柄, 模块句柄, 可选参数
)
不是所有参数都是必要的,部分参数可以填0或NULL。
在这里,我们这样建立一个标准的窗口:
// 创建主窗口 - CreateWindowEx
// 扩展样式、窗口类名、窗口标题、窗口样式(样式=覆盖式窗口)
// 左、上、宽、高
// 父窗句柄、菜单句柄、模块句柄、可选参数
hMainWnd = CreateWindowEx(
    0, L"MyWindow", L"这是主窗口", WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    NULL, NULL, hInst, NULL);

建立窗口以后,我们一般需要显示并更新窗口,这一步比较简单:
ShowWindow(hMainWnd, nshow); // 显示窗口
UpdateWindow(hMainWnd); // 更新窗口

五、进入消息循环

窗口显示以后,我们的程序并没有就此停住并继续接收窗口消息,因此我们要在wWinMain中不停地等待消息,并让系统进行处理。
其中GetMessage等待并获取消息,TranslateMessage将虚拟键码转换为Unicode字符,DispatchMessage将消息分发到处理函数
代码如下:
MSG msg;
int rslt;
                       
// 消息循环 - 等待和处理各种消息
while (rslt = GetMessage(&msg, NULL, 0, 0)) // 获取消息
{
    if (rslt == -1) // 错误则退出
        return 1;
    TranslateMessage(&msg); // 将虚拟键码转换为Unicode字符
    DispatchMessage(&msg); // 分发消息到窗口消息处理函数
}

六、退出消息循环

接收到一般消息时,GetMessage会返回TRUE,这时消息循环继续运行。当接收到WM_QUIT消息时,GetMessage会返回FALSE,程序将退出。退出代码保存在msg.wParam中:
return (int)msg.wParam;

在窗口被销毁时,我们期望消息循环退出,因此要向消息循环发送WM_QUIT消息,方法是调用PostQuitMessage(n)函数,n为退出码。在WndProc中添加以下代码:
// 窗口消息处理函数 - WndProc
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
    switch (msg) // 判断窗口消息的种类
    {
    case WM_DESTROY: // 窗口销毁
        PostQuitMessage(0); // 发送WM_QUIT,退出消息循环
        return 0;
    }
    return DefWindowProc(hWnd, msg, wp, lp);
}

七、运行效果


主窗口0.png


八、完整的程序
// 不严格区分句柄,均定义为void*
#define NO_STRICT
                       
// 包含WinAPI定义
#include <windows.h>
                       
// 平台相关的整数类型:
// INT_PTR 16 -> 32 -> 64
// UINT_PTR 16 -> 32 -> 64
// LONG_PTR 32 -> 32 -> 64
// ULONG_PTR 32 -> 32 -> 64
                       
void* hInst; // 模块句柄
void* hMainWnd; // 主窗口句柄
                       
// 窗口消息处理函数 - WndProc
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
    switch (msg) // 判断窗口消息的种类
    {
    case WM_DESTROY: // 窗口被销毁时
        PostQuitMessage(0); // 发送WM_QUIT,退出消息循环
        return 0;
    }
    // 如果消息未经处理,则由DefWindowProc处理
    return DefWindowProc(hWnd, msg, wp, lp);
}
                       
// 入口点 - wWinMain
int __stdcall wWinMain(void* hInstance, void* reserved, wchar_t* cmdline, int nshow)
{
    hInst = hInstance; // 保存模块句柄
                       
    // 窗口类设定
    // 样式、函数
    // 类增、窗增            (高级参数)
    // 模块、图标、光标     (光标=鼠标指针)
    // 背景、菜单、类名
    WNDCLASS wc = {
        CS_HREDRAW | CS_VREDRAW, // 样式:横向、纵向改变大小时重绘
        WndProc, // 窗口消息处理函数
        0, 0,
        hInst, // 模块句柄
        LoadIcon(NULL, IDI_APPLICATION), // 图标:默认图标
        LoadCursor(NULL, IDC_ARROW), // 光标:箭头
        GetStockObject(WHITE_BRUSH), // 背景画刷:白色
        NULL, // 菜单:无
        L"MyWindow", // 窗口类名称
    };
                       
    if (!RegisterClass(&wc)) // 注册窗口类
        return 1;
                       
    // 创建主窗口 - CreateWindowEx
    // 扩展样式、窗口类名、窗口标题、窗口样式(样式=覆盖式窗口)
    // 左、上、宽、高
    // 父窗句柄、菜单句柄、模块句柄、可选参数
    hMainWnd = CreateWindowEx(
        0, L"MyWindow", L"这是主窗口", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInst, NULL);
                       
    ShowWindow(hMainWnd, nshow); // 显示窗口
    UpdateWindow(hMainWnd); // 更新窗口
                       
    MSG msg;
    int rslt;
                       
    // 消息循环 - 等待和处理各种消息
    while (rslt = GetMessage(&msg, NULL, 0, 0)) // 获取消息
    {
        if (rslt == -1) // 错误则退出
            return 1;
        TranslateMessage(&msg); // 将虚拟键码转换为Unicode字符
        DispatchMessage(&msg); // 分发消息到窗口消息处理函数
    }
                       
    return (int)msg.wParam;
}

[修改于 4 年前 - 2015-08-05 05:32:32]

折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
3楼
(四)给窗口添加控件

在Windows中,控件的本质是一个WS_CHILD样式的子窗口,所以我们想为窗口添加控件,就是要在窗口上添加子窗口。创建控件和创建窗口的方法几乎完全一样,也是调用CreateWindowEx,不同的是,创建控件可以使用预定义的窗口类,不必再自己创建窗口类,也不必写消息处理函数WndProc。

常见的控件窗口类如下,这些类在commctrl.h中有定义(例如L"button"定义为WC_BUTTON):
L"button" 按钮(包含下压按钮、复选框、单选框等)
L"edit" 文本框
L"static" 静态文本
L"listbox" 列表框
L"combobox" 组合框

在这里我们为上一节的窗口添加一个按钮,并处理按下它的消息

一、创建按钮

首先定义按钮的编号,这里定义ID_BUTTON为1
#define ID_BUTTON 1 // 按钮编号(必须小于65536)

窗口在创建时会调用WM_CREATE消息,此时可以在窗口上创建各种控件,在这里我们创建一个普通按钮,代码如下
其中创建主窗口时的“菜单句柄”这个参数,一般创建控件时这个参数改做“控件编号"使用。
CreateWindowEx(
    扩展样式, 窗口类名, 窗口标题, 窗口样式, // 窗口类名有固定名称,窗口样式常用WS_CHILD|WS_VISIBLE
    左, 上, 宽, 高,
    父窗句柄, 控件编号, 模块句柄, 可选参数 // 菜单句柄作控件编号使用
)
代码如下,注意窗口类名=L"button",窗口样式=WS_CHILD|WS_VISIBLE,父窗句柄=hWnd,控件编号=(void*)ID_BUTTON
case WM_CREATE: // 窗口创建
    // 创建ID_BUTTON按钮 - CreateWindowEx
    // 扩展样式、窗口类名、窗口标题、窗口样式(类名=“button”,样式=子窗口+可见)
    // 左、上、宽、高
    // 父窗句柄、控件编号、模块句柄、可选参数(菜单句柄=控件编号)
    CreateWindowEx(
        0, L"button", L"按钮", WS_CHILD | WS_VISIBLE,
        100, 20, 100, 30,
        hWnd, (void*)ID_BUTTON, hInst, NULL);
    return 0;

二、响应消息

按钮被按下的消息为WM_COMMAND,其中WndProc的第三个参数wp低16位表示对应的控件编号,可使用LOWORD(wp)提取之。
在这里我们在按钮按下时弹出一个消息框,代码如下:
case WM_COMMAND: // 接收到命令消息
    switch (LOWORD(wp)) // 判断来源编号(包含在参数wp的低16位中)
    {
    case ID_BUTTON: // 来自ID_BUTTON按钮
        MessageBox(hWnd, L"欢迎来到Win32编程世界", L"消息框", 0); // 弹出消息框
        return 0;
    }
    break; // 消息未处理

三、运行效果

窗口中出现一个按钮,按下这个按钮,弹出一个消息框。

主窗口1.png


四、美化控件

我们新建的按钮有点丑,因为系统默认使用Common Controls V5(通用控件版本5),是Windows 98样式的,如果想让控件具有Windows XP以及以后系统的样式,需要指定应用程序使用Common Controls V6(通用控件版本6),最简单的方法是在代码中添加以下几行:
#if defined _M_IX86
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_X64
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#else
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif

使用效果如下:
主窗口2.png

[修改于 4 年前 - 2015-08-05 05:27:24]

折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
4楼
(五)给窗口添加菜单

给窗口添加菜单要用到资源文件,资源文件的扩展名为.rc,保存有各种可集成在程序内部的资源(如图标、光标、位图、菜单模板、对话框模板、快捷键、字符串表等),可以在程序中方便地加载。

一、新建资源文件

右击“解决方案资源管理器”中的“资源文件”,选择添加-》添加新项
在对话框中选择Visual C++-》资源-》资源文件(.rc),点击“添加”

主窗口3.png


再次转到“解决方案资源管理器”,可以发现多了两个文件:resource.h和resource.rc,其中前者为符号定义文件,后者为资源文件的主体

主窗口5.png


二,添加菜单资源

双击resource.rc,将转到“资源视图”,右击顶层项目“Win32Project1”,选择添加-》添加资源

主窗口4.png


在对话框中,选择Menu,双击或点击“新建”

主窗口6.png


在打开的菜单编辑器中编辑刚刚插入的菜单资源,这里菜单结构如下,其中字符&可以指定助记键:
通用(&G) - Popup菜单项
关于(&A) - 编号ID_ABOUT
退出(&X) - 编号ID_EXIT

主窗口7.png


三、修改代码加载菜单

转到“解决方案资源管理器”,双击打开myfirst.c源文件,添加一行导入资源符号
#include "resource.h"

加载菜单有若干种方法,其中最方便的是直接在窗口类中指定菜单的编号。
转到wWinMain函数,找到我们的窗口类,修改菜单字段为我们的编号,要注意需要转换为(wchar_t*):
// 窗口类设定
// 样式、函数
// 类增、窗增            (高级参数)
// 模块、图标、光标     (光标=鼠标指针)
// 背景、菜单、类名
WNDCLASS wc = {
    CS_HREDRAW | CS_VREDRAW, // 样式:横向、纵向改变大小时重绘
    WndProc, // 窗口消息处理函数
    0, 0,
    hInst, // 模块句柄
    LoadIcon(NULL, IDI_APPLICATION), // 图标:默认图标
    LoadCursor(NULL, IDC_ARROW), // 光标:箭头
    GetStockObject(WHITE_BRUSH), // 背景画刷:白色
    (wchar_t*)IDR_MENU1, // 菜单:IDR_MENU1
    L"MyWindow", // 窗口类名称
};

四、处理菜单消息

和按钮一样,菜单的项的消息也是WM_COMMAND,因此处理方法也是一样的,这里就不多讲了:
case WM_COMMAND: // 接收到命令消息
    switch (LOWORD(wp)) // 判断来源编号(包含在参数wp的低16位中)
    {
    case ID_BUTTON: // 来自ID_BUTTON按钮
        MessageBox(hWnd, L"欢迎来到Win32编程世界", L"消息框", 0); // 弹出消息框
        return 0;
    case ID_ABOUT: // 来自ID_ABOUT菜单项
        MessageBox(hWnd, L"我的第一个Win32程序", L"关于", 0); // 弹出消息框
        return 0;
    case ID_EXIT: // 来自ID_EXIT菜单项
        SendMessage(hWnd, WM_CLOSE, 0, 0); // 关闭窗口
        return 0;
    }
    break; // 消息未处理

五、运行

运行效果:

主窗口8.png

[修改于 4 年前 - 2015-08-05 05:04:01]

折叠评论
加载评论中,请稍候...
折叠评论
5楼
这算是C的图形化吗,学习了
折叠评论
加载评论中,请稍候...
折叠评论
6楼
我以前用汇编写过类似的程序,真能累死。

[修改于 4 年前 - 2015-08-05 18:57:06]

折叠评论
加载评论中,请稍候...
折叠评论
7楼
[s:38] 完全看不懂啊。
是不是我的技术太差了呢。
折叠评论
加载评论中,请稍候...
折叠评论
2015-08-06 00:53:27
8楼
个人觉得应该补充一些篇幅介绍一下消息机制的原理和作用,这是Windows GUI编程的关键点
折叠评论
加载评论中,请稍候...
折叠评论
9楼
现在玩这个太落伍了,,实在是跟不上时代了,,,,还是换学C#开发
折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
10楼
引用 咩咩糖:
现在玩这个太落伍了,,实在是跟不上时代了,,,,还是换学C#开发
C#中的窗体编程本质上也是封装的WinAPI,只不过方便一些罢了
折叠评论
加载评论中,请稍候...
折叠评论
2015-08-07 20:59:03
11楼
引用 acmilan:
C#中的窗体编程本质上也是封装的WinAPI,只不过方便一些罢了
但也得讲究效率呀,,,,,研究了半天没有实际应用的价值,,,,
折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
12楼
引用 咩咩糖:
但也得讲究效率呀,,,,,研究了半天没有实际应用的价值,,,,
C#确实比这个应用方便,然而也不能应付所有场合。效率并不能代表一切。
C语言有低级性和灵活性,C#有高效性和便捷性,特点各不相同,不存在替代和被替代的问题。
而且你可能见得太少了,使用WinAPI的实际应用非常的普遍,并不像你说的那样没有实际应用的价值。
众多国内知名软件,有很多都是采用WTL,也就是轻度封装的WinAPI编写。
即使是重度包装的GUI框架,学习WinAPI对于加深对GUI编程的理解和运用也是很有帮助的。

[修改于 4 年前 - 2015-08-08 03:02:51]

折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
13楼
对于编程爱好者来说,什么都可以学,学习编程技术不要过于功利
折叠评论
加载评论中,请稍候...
折叠评论
2015-08-08 01:05:51
14楼
关于c语言库函数调用确实可以讲很多,学习c语言让我知道c对于学习编程的重要性。
折叠评论
加载评论中,请稍候...
折叠评论
15楼
引用 金星凌日:
我以前用汇编写过类似的程序,真能累死。
同感,c语言确实是语法严谨。
折叠评论
加载评论中,请稍候...
折叠评论
2015-08-25 09:19:01
2015-8-25 09:19:01
acmilan(作者)
16楼
引用 金星凌日:
我以前用汇编写过类似的程序,真能累死。
微软不推荐使用汇编写Win32程序,个人感觉汇编在Win32中除了一些奇技淫巧以外跟C语言差不多,并且束手束脚的。
折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
17楼
引用 金星凌日:
我以前用汇编写过类似的程序,真能累死。
实际上大部分小程序(工具程序)都可以用一个模态对话框解决,直接DialogBoxW就行,很方便。

用通用的方法建窗口的话,WNDCLASSW也就10个字段,CreateWindowExW也就12个参数,也没有难到哪里去。

Win32编程中比较大的难题其实是窗口数据的放置问题(因为WndProc是静态函数,没有this指针)。对于只建立一次的窗口,可以使用静态变量。对于动态创建的窗口,cbWndExtra/SetWindowLongPtrW方法、C++的unordered_map方法、ATL的CWindowImpl/CDialogImpl方法都是可行的解决方法。
折叠评论
加载评论中,请稍候...
折叠评论
18楼
引用 acmilan:
微软不推荐使用汇编写Win32程序,个人感觉汇编在Win32中除了一些奇技淫巧以外跟C语言差不多,并且束手束脚的。
因为我不知道远线程注入、API Hook怎么用C语言写,所以用了汇编。而且用汇编的话,调用约定之类的东西都可以自己控制,C语言就没有这么自由。
折叠评论
加载评论中,请稍候...
折叠评论
acmilan(作者)
19楼
引用 金星凌日:
因为我不知道远线程注入、API Hook怎么用C语言写,所以用了汇编。而且用汇编的话,调用约定之类的东西都可以自己控制,C语言就没有这么自由。
汇编的好处是调用栈可随意修改,可以调用一些特殊指令。缺点也很明显,全用汇编的话太麻烦了。
C语言可以很方便地升级为C++程序,然而汇编调用C++标准库基本不可能。
不想要CRT初始化代码的话,C语言也可以不链接标准库,然后自己写一个入口点。
对于关键代码,32位程序一般可以用内联汇编,或者32位/64位都可以调用asm中的汇编函数。

[修改于 4 年前 - 2015-08-25 12:23:53]

折叠评论
加载评论中,请稍候...
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

插入资源
全部
图片
视频
音频
附件
全部
未使用
已使用
正在上传
空空如也~
上传中..{{f.progress}}%
处理中..
上传失败,点击重试
{{f.name}}
空空如也~
(视频){{r.oname}}
{{selectedResourcesId.indexOf(r.rid) + 1}}
ID:{{user.uid}}
{{user.username}}
{{user.info.certsName}}
{{user.description}}
{{format("YYYY/MM/DD", user.toc)}}注册,{{fromNow(user.tlv)}}活动
{{submitted?"":"投诉"}}
请选择违规类型:
{{reason.description}}
支持的图片格式:jpg, jpeg, png