Архив за день: 06.05.2021



WinAPI: Меню и значки

В одной из заметок был описан пример создания окошка с помощью WinAPI C++. Возникающие в VSCode ошибки мы устранили. Затем я перешёл к примеру, в первой части которого к окошку добавили меню, а к исполняемому файлу был добавлен значок программы. Всё бы хорошо, но теперь мы стали использовать ресурсы (значок и меню), а для этого пришлось кое-что переделать в настройках VSCode. Но обо всём по порядку.

Файл resource.h

Чтобы не было путаницы, я создал новую папку проекта. Затем добавил в неё новый файл resource.h:

#define IDR_MYMENU 101
#define IDI_MYICON 201

#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002
Если работать в Visual Studio, то там не нужно ничего создавать руками. Там есть визуальный редактор, где можно набросать в окошко необходимые элементы и программа сама сгенерирует все нужные файлы. Но мы изучаем WinAPI с примерами сборки проекта с помощью утилиты командной строки cl.exerc.exe), которую мы используем в VSCode.

 

Файл menu_one.rc

Далее мы создаём файл ресурсов проекта. Мы создаём его вручную, поэтому он текстовый, а не двоичный. У файла должно быть расширение .rc. Содержимое файла menu_one.rc будет таким:

#include "resource.h"

IDR_MYMENU MENU
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit", ID_FILE_EXIT
    END

    POPUP "&Stuff"
    BEGIN
        MENUITEM "&Go", ID_STUFF_GO
        MENUITEM "G&o somewhere else", 0, GRAYED
    END
END

IDI_MYICON ICON "menu_one.ico"

Меню мы сделали совсем простым и тут должно быть всё понятно. На сайте оригинальной статьи я не нашёл файл со значком, поэтому использовал иконку своего сайта:
https://dentnt.trmw.ru/favicon.ico
Просто переименовал его на новое имя: menu_one.ico

При компиляции этого файла ресурсов мы можем получить такую ошибку:

resource.h(5) : fatal error RC1004: unexpected end of file found
The terminal process «C:\Windows\System32\cmd.exe /d /c «C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\rc.exe» menu_one.rc» terminated with exit code: 1.

 

Порывшись в инете я обнаружил, что это проблема компилятора ресурсов rc.exe.

Чтобы устранить данную ошибку, нужно добавить ещё одну пустую строку в конец файла resource.h.

 

Настройка VSCode для компиляции файла ресурсов

Чтобы было проще компилировать файлы ресурсов, я добавил ещё одну задачу в файл tasks.json:

{
  "label": "C/C++: convert to RES file",
  "detail": "Convert current file from .rc to .res",
  "type": "shell",
  "command": "C:\\Program Files (x86)\\Windows Kits\\10\\bin\\10.0.19041.0\\x64\\rc.exe",
  "args": ["${fileBasenameNoExtension}.rc"]
}
Обратите внимание, что компилятор ресурсов rc.exe находится не в папке Visual Studio Community, а в папке Windows Kits! При этом я использую компилятор x64.

 

Это был фрагмент кода для задачи. Чтобы её легко можно было вызвать, я добавил новую комбинацию клавиш Ctrl+Shift+c в файл keybindings.json:

{
    "key": "ctrl+shift+c",
    "command": "workbench.action.tasks.runTask",
    "args": "C/C++: convert to RES file"
},

Файл menu_one.cpp

Изменим наш файл window_click.cpp для работы с файлом ресурсов. Теперь в программе будет отображаться значок и меню. Файл menu_one.cpp:

#define UNICODE
#pragma comment(lib, "user32.lib")

#include <windows.h>
#include "resource.h"

const wchar_t g_szClassName[]= L"myWindowClass";

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
  case WM_LBUTTONDOWN:
  {
    wchar_t szFileName[MAX_PATH];
    HINSTANCE hInstance = GetModuleHandle(NULL);

    GetModuleFileName(hInstance, szFileName, MAX_PATH);
    MessageBox(hwnd, szFileName, L"This program is:", MB_OK | MB_ICONINFORMATION);
  }
  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;

  wc.cbSize = sizeof(WNDCLASSEX);
  wc.style = 0;
  wc.lpfnWndProc = WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName = NULL;
  wc.lpszClassName = g_szClassName;

/* Adding menu and icon */
  wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MYMENU);
  wc.hIcon  = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON));
  wc.hIconSm  = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON), IMAGE_ICON, 16, 16, 0);
/******/

  if (!RegisterClassEx(&wc))
  {
    MessageBox(NULL, L"Window Registration Failed!", L"Error!", MB_ICONEXCLAMATION | MB_OK);
    return 0;
  }

  hwnd = CreateWindowEx(
      WS_EX_CLIENTEDGE,
      g_szClassName,
      L"The title of my window",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
      NULL, NULL, hInstance, NULL);

  if (hwnd == NULL)
  {
    MessageBox(NULL, L"Window Creation Failed!", L"Error!", MB_ICONEXCLAMATION | MB_OK);
    return 0;
  }

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

  while (GetMessage(&Msg, NULL, 0, 0) > 0)
  {
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
  }
  return Msg.wParam;
}

Здесь мы добавили такую строчку:

#pragma comment(lib, "user32.lib")

Это позволило нам избавится от таких ошибок компилятора:

menu_one.obj : error LNK2019: unresolved external symbol __imp__LoadImageW@24 referenced in function _WinMain@16
C:\Users\Denis\source\repos\WinAPI\Sample04\build\menu_one.exe : fatal error LNK1120: 14 unresolved externals

 

Сборка проекта

Если собирать данный проект старой командой, то ошибок не будет, но и не будет меню и иконки приложения!

 

А всё потому, что мы собирали проект без учёта файлов ресурсов. Чтобы прилинковать файлы ресурсов, нам потребуется новая задача. Я не знаю, нужно ли мне это будет в будущем, поэтому создам задачу только для данного проекта. Для этого я в папке с проектом создам папку .vscode. В ней я создам новый файл tasks.json:

{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    {
      "label": "C/C++: cl.exe build active file and link resources",
      "detail": "Compile and build selected C++ project with Visual Studio cl.exe and link resources",
      "type": "shell",
      "command": "${config:buildCommand}",
      "args": [
        "/Zi",
        "/EHsc",
        "/Fe:",
        "${config:buildDir}\\${fileBasenameNoExtension}.exe",
        "/Fo${config:buildDir}\\",
        "/Fd${config:buildDir}\\",
        "${file}",
        "/link",
        "/SUBSYSTEM:WINDOWS",
        "${workspaceFolder}\\${fileBasenameNoExtension}.res"
      ],
      "options": {
        "cwd": "${workspaceFolder}"
      },
      "problemMatcher": ["$msCompile"],
      "group": {
        "kind": "build",
        "isDefault": true
      },
      "dependsOn": [
        "Set dependencies for build"
      ]
    }
    
  ]
}

Как видно, здесь мы добавили пару новых ключей для добавления к проекту файла ресурсов .res.

По умолчанию в настройках для дополнения/расширения C++ указана зависимость «preLaunchTask», поэтому, если нажать F5 или Ctrl+F5 будет запущена указанная задача. Т.е. будет заново перекомпилирован проект, но опять БЕЗ прилинкованных библиотек.

 

Всвязи с этим нужно подправить данную настройку в следующем файле:
«C:\Users\Denis\AppData\Roaming\Code\User\settings.json»
Здесь нам нужно закомментировать, либо удалить строчку:

"preLaunchTask": "C/C++: cl.exe build active file"

Таким образом мы не будем перекомпилировать файл перед выполнением «Start Debugging» либо «Start without Debugging.

Для запуска сборки проекта можно задать горячие клавиши в файле keybindings.json:

{
    "key": "ctrl+shift+b",
    "command": "workbench.action.tasks.runTask",
    "args": "C/C++: cl.exe build active file and link resources"
},