请问纯C语言如何不使用easyx实现窗口绘图?

最近我在学习Windows窗口编程。之前使用easyx图形库编写了一个贪吃蛇小程序,但是easyx图形库只能用在vs上,且不支持在纯C语言中使用,而且之前尝试在32位系统上编译失败报错了,所以想要尝试弄懂easyx图形库的原理,自己使用纯C语言编写一个简易的图形库。

img

如上图所示,如果在一个黑色背景的窗口中绘制一个白色的矩形,使用easyx可以很简单的做到:
#include "graphics.h"

int main() {
    initgraph(800, 600);
    setlinecolor(WHITE);
    setfillcolor(WHITE);
    fillrectangle(50, 50, 100, 100);
    system("pause");
}
但是如果不使用easyx图形库来还原这个效果,我想到的方法是创建一个子窗口,代码如下:
#include 

LRESULT CALLBACK ParentWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    WNDCLASS wc;
    HWND hwndParent, hwndChild;
    MSG msg;

    // 注册父窗口类
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = ParentWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = TEXT("ParentWndClass");
    RegisterClass(&wc);

    // 注册子窗口类
    wc.lpfnWndProc = ChildWndProc;
    wc.lpszClassName = TEXT("ChildWndClass");
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    RegisterClass(&wc);

    // 创建父窗口
    hwndParent = CreateWindow(TEXT("ParentWndClass"),
                              TEXT("Parent Window"),
                              WS_OVERLAPPEDWINDOW,
                              CW_USEDEFAULT,
                              CW_USEDEFAULT,
                              300,
                              300,
                              NULL,
                              NULL,
                              hInstance,
                              NULL);

    ShowWindow(hwndParent, iCmdShow);
    UpdateWindow(hwndParent);

    // 消息循环
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK ParentWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_CREATE:
            // 创建子窗口
            CreateWindow(TEXT("ChildWndClass"),
                         TEXT("Child Window"),
                         WS_CHILD | WS_VISIBLE,
                         10,
                         10,
                         50,
                         50,
                         hwnd,
                         NULL,
                         ((LPCREATESTRUCT)lParam)->hInstance,
                         NULL);
            break;

        case WM_SIZE:
            // 调整子窗口的大小和位置
            MoveWindow(GetDlgItem(hwnd, 1),
                       10,
                       10,
                       LOWORD(lParam) - 100,
                       HIWORD(lParam) - 100,
                       TRUE);
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    return 0;
}

LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_CREATE:
            break;

        case WM_PAINT:
        {
            PAINTSTRUCT ps;
        }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    return 0;
}

运行结果:

img

但是这样虽然可以还原出来一模一样的效果,但如果每创建一个矩形就要创建一个子窗口,在编游戏程序时无疑会占用很大的内存,而且通过Spy++查找窗口,easyx绘制的窗口也不是创建子窗口来绘图的。请问如何才能做到不依赖easyx实现简易的窗口绘图?求解答,感谢。
该回答引用于gpt与OKX安生共同编写:
  • 该回答引用于gpt与OKX安生共同编写:

实现窗口绘图可以使用 Windows API 中的 GDI (Graphics Device Interface)。GDI 是一组用于处理图形相关操作的函数和数据结构,包括画图、刷漆、字体等等。

下面是一个使用 GDI 绘制矩形的例子:

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    WNDCLASSEX wc;
    HWND hwnd;
    MSG msg;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszClassName = "WindowClass";

    RegisterClassEx(&wc);

    hwnd = CreateWindowEx(0,
                          "WindowClass",
                          "Hello, World!",
                          WS_OVERLAPPEDWINDOW,
                          300,
                          300,
                          500,
                          400,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;

    switch (msg) {
        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);

            GetClientRect(hwnd, &rect);
            SetTextColor(hdc, RGB(0, 0, 255));
            DrawText(hdc, "Hello, World!", -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

            SelectObject(hdc, GetStockObject(NULL_BRUSH));
            SetROP2(hdc, R2_COPYPEN);
            SetBkMode(hdc, TRANSPARENT);
            Rectangle(hdc, 100, 100, 300, 200);

            EndPaint(hwnd, &ps);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    return 0;
}

在这个示例中,我首先创建了一个窗口类 WNDCLASSEX,并使用 RegisterClassEx 函数进行注册。接着,在窗口函数 WndProc 中处理 WM_PAINT 消息,使用 BeginPaint 来获取设备上下文句柄 HDC,然后通过 GDI 绘制矩形、文字等图形,最后使用 EndPaint 结束绘图。

需要注意的是,GDI 编程需要了解一些 Windows API 的基本知识,例如如何创建和处理消息循环,如何获取设备上下文句柄,如何绘制各种图形等等。

参考gpt,实现窗口绘图的方法是使用OpenGL,它是一个跨平台的图形库,可以在不同的操作系统上使用。OpenGL可以通过操作系统提供的原生窗口进行绘图,而不需要创建子窗口,这样可以减少内存占用和提高绘图效率。

OpenGL的绘图是基于3D空间的,但是可以通过设置投影矩阵来实现2D绘图。OpenGL的绘图方式是通过渲染管线来实现的,可以分为几个阶段:顶点着色器、图元装配、光栅化、片元着色器等。这里不再详细介绍OpenGL的绘图过程,有兴趣的读者可以自行查找相关资料学习。

下面是一个使用OpenGL实现绘制矩形的例子:


#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc = {0};
    wc.lpfnWndProc   = WndProc;
    wc.hInstance     = hInstance;
    wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
    wc.lpszClassName = TEXT("MyWindowClass");
    if(!RegisterClass(&wc)) {
        return 0;
    }
    hwnd = CreateWindow(TEXT("MyWindowClass"), TEXT("My Window"),
                        WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
                        NULL, NULL, hInstance, NULL);
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    while(GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    switch(msg)
    {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        // 设置视口大小
        glViewport(0, 0, ps.rcPaint.right - ps.rcPaint.left,
                   ps.rcPaint.bottom - ps.rcPaint.top);
        // 设置投影矩阵为正交投影
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, ps.rcPaint.right - ps.rcPaint.left,
                ps.rcPaint.bottom - ps.rcPaint.top, 0, 0, 1);
        // 设置模型视图矩阵
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        // 设置颜色
        glColor3f(1.0, 1.0, 1.0);
        // 绘制矩形
        glBegin(GL_QUADS);
        glVertex2f(50, 50);
        glVertex2f(100, 50);
        glVertex2f(100, 100);
        glVertex2f(50, 100);
        glEnd();
        // 刷新缓冲区
        glFlush();
SwapBuffers(hdc);

// 用黑色清空屏幕
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

// 绘制一个白色的矩形
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS);
glVertex2f(-0.5f, -0.5f);
glVertex2f(-0.5f, 0.5f);
glVertex2f(0.5f, 0.5f);
glVertex2f(0.5f, -0.5f);
glEnd();

// 绘制完成后交换缓冲区
glFlush();
SwapBuffers(hdc);



在这个代码中,我们使用 OpenGL 的方式绘制了一个白色矩形。首先,我们用 glClearColor 函数将屏幕清空为黑色,然后使用 glClear 函数清空颜色缓冲区。接下来,我们使用 glColor3f 函数指定颜色为白色,然后使用 glBegin(GL_QUADS) 函数开始绘制一个四边形。我们通过 glVertex2f 函数指定四个点的坐标,分别是左下角、左上角、右上角和右下角,形成了一个矩形。最后,我们使用 glEnd 函数结束绘制。绘制完成后,我们使用 glFlush 和 SwapBuffers 函数交换缓冲区,将矩形显示在屏幕上。

需要注意的是,在每次绘制之前,我们都需要清空颜色缓冲区,否则之前绘制的图形会留在屏幕上,影响后续的绘制。另外,由于 OpenGL 是使用矩阵进行绘制的,我们需要使用矩阵变换来控制绘制的位置和大小,这个可以在后续的学习中逐步了解。

综上所述,使用 OpenGL 来进行窗口绘制可以避免依赖第三方库,而且可以更灵活地控制绘制效果。但是需要注意的是,OpenGL 是一个底层的绘图 API,需要自己编写大量的代码来实现绘图功能,因此相对于使用现成的图形库,需要投入更多的时间和精力。

该回答引用ChatGPT
这是个例子,你可以看一下:

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX windowClass = {0};
    HWND hwnd = NULL;
    MSG messages = {0};
    HDC hdc = NULL;
    PAINTSTRUCT ps;
    RECT windowRect = {0};

    windowClass.lpfnWndProc = WindowProc;
    windowClass.hInstance = hInstance;
    windowClass.lpszClassName = "WindowClass";

    if (!RegisterClassEx(&windowClass))
    {
        return 0;
    }

    windowRect.right = 640;
    windowRect.bottom = 480;

    AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, FALSE);

    hwnd = CreateWindowEx(0,
                          "WindowClass",
                          "Hello, World!",
                          WS_OVERLAPPEDWINDOW,
                          300,
                          300,
                          windowRect.right - windowRect.left,
                          windowRect.bottom - windowRect.top,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hwnd, nCmdShow);

    while (GetMessage(&messages, NULL, 0, 0))
    {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    }

    return messages.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_PAINT:
        {
            HDC hdc = BeginPaint(hwnd, &ps);
            Rectangle(hdc, 100, 100, 200, 200);
            EndPaint(hwnd, &ps);
        }
        break;
    case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    return 0;
}

在该程序中,通过调用Windows API函数建立了一个窗口。在 WindowProc 函数中处理了窗口消息,例如当接收到 WM_PAINT 消息时,绘制了一个矩形。

注意一下,在 Windows 10 中,微软已经针对 C++/WinRT 提供了更加易用的 API,以简化 Windows 应用程序的编写。

easyx也有mingw的版本:

DevC++,添加 graphics.h和libbgi. a文件。
在链接器标签中粘贴以下内容:-lbgi -lgdi32 -lcomdlg32 -luuid -loleaut32 -lole32。
参考 :Simple Graphic Program Using Dev-C++
https://www.instms.com/cpp/graphics-using-dev-c++

#include<graphics.h>

![img](https://img-mid.csdnimg.cn/release/static/image/mid/ask/608090547976129.jpg "#left")

int main( ){
    initwindow( 400 , 400 , "Graphics using Dev-C++");
    setcolor(YELLOW);
    for(int i=5; i<=150; i+=5) {
    circle(200, 200, i);
    }
        
    getch();
    return 0;
}

img

可以用winapi,需要包含windows.h

不知道你这个问题是否已经解决, 如果还没有解决的话:

如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^