Структура оконного приложения

Working with Windows

The object provides methods for querying window properties. For instance, the desktop window fills the whole screen, so to find out the screen dimensions is straightforward:

Finding other windows is best done by iterating over all top-level windows and checking them for some desired property; (@{find_window} is provided for completeness, but you really have to provide the exact window caption for the second parameter.)

@{find_all_windows} returns all windows matching some function. For convenience, two useful matchers are provided, @{make_name_matcher} and @{make_class_matcher}. Once you have a group of related windows, you can do fun things like tile them:

This call needs the parent window (we just use the desktop), whether to tile horizontally, and a table of window objects. There is an optional fourth parameter, which is the bounds to use for the tiling, specified like so .

With tiling and the ability to hide windows with it is entirely possible to write a little ‘virtual desktop’ application.

@{find_window_ex} also uses a matcher function; @{find_window_match} is a shortcut for the operation of finding a window by its caption.

Every window has an associated text value. For top-level windows, this is the window caption:

So the equivalent of the old DOS command would here be:

Any top-level window will contain child windows. For example, Notepad has a simple structure revealed by @{Window:enum_children}:

Windows controls like the ‘Edit’ control interact with the unverse via messages.
will tell the control to return the number of lines. Looking up the numerical value of this message, it’s easy to query Notepad’s edit control:

An entertaining way to automate some programs is to send virtual keystrokes to them. The function @{send_to_window} sends characters to the current foreground window:

After launching a window, you can make it the foreground window and send it text:

Waiting on the process is important: it gives the other process a chance to get going, and to create a new window which we can promote.

Window Procedure

Window Procedure is a special function in which we handle messages. We need to handle only important messages and for all others perform standard action. Let’s look at our window procedure:

LRESULT __stdcall WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}

Pay attention that we don’t call WindowProc anywhere in the code. The window procedure is bound to window class. And when we call DispatchMessage, system internally calls window procedure. Such functions are called callback functions — you don’t call it yourself, but it will be called by some other function. Also note, that this function receives only some of MSG properties.

In WindowProc we check field message of MSG structure variable. It contains message id. Windows has many constants for different messages. In this program, we check only WM_DESTROY. This message is sent when the window is being destroyed at the moment when it’s already removed from the screen. In response to this message we call PostQuitMessage — it tells the system that application will be terminated. And also it sends WM_QUIT message to its message queue (not to system message queue).

If there is a message that we don’t want to handle ourselves, we pass it to DefWindowProc — default action.

Registering a Window Class

A window class defines the attributes of a window, such as its style, icon, cursor, menu, and window procedure. The first step in registering a window class is to fill in a WNDCLASSEX structure with the window class information. For more information, see . Next, pass the structure to the RegisterClassEx function. For more information, see Using Window Classes.

To register an application global class, specify the CS_GLOBALCLASS style in the style member of the WNDCLASSEX structure. When registering an application local class, do not specify the CS_GLOBALCLASS style.

If you register the window class using the ANSI version of RegisterClassEx, RegisterClassExA, the application requests that the system pass text parameters of messages to the windows of the created class using the ANSI character set; if you register the class using the Unicode version of RegisterClassEx, RegisterClassExW, the application requests that the system pass text parameters of messages to the windows of the created class using the Unicode character set. The IsWindowUnicode function enables applications to query the nature of each window. For more information on ANSI and Unicode functions, see Conventions for Function Prototypes.

The executable or DLL that registered the class is the owner of the class. The system determines class ownership from the hInstance member of the WNDCLASSEX structure passed to the RegisterClassEx function when the class is registered. For DLLs, the hInstance member must be the handle to the .dll instance.

The class is not destroyed when the .dll that owns it is unloaded. Therefore, if the system calls the window procedure for a window of that class, it will cause an access violation, because the .dll containing the window procedure is no longer in memory. The process must destroy all windows using the class before the .dll is unloaded and call the UnregisterClass function.

Компоненты

При создании WinAPI C следует учитывать базовые возможности, предоставляемые Windows API, которые можно упорядочить в семи категориях. Рассмотрим каждую из них подробнее.

Основные услуги предоставляют доступ к базовым системным ресурсам, доступным в Windows. Примеры: файловая система, периферийные устройства, процессы, доступ к системному реестру и система управления исключениями. Эти функции хранятся в файлах kernel.exe, krnl286.exe или krnl386.exe для 16-разрядных систем и kernel32.dll и advapi32.dll для 32-разрядных систем.

Графический интерфейс обеспечивает доступ к ресурсам для отображения на мониторах, принтерах и другом периферийном оборудовании. Хранится в файле gdi.exe на 16-разрядных системах и gdi32.dll в 32-разрядных системах.

Пользовательский интерфейс отвечает за просмотр и управление основными элементами, такими как кнопки и полосы прокрутки, получение информации о клавиатуре и мыши, а также связанные с ними функции. Эти функции хранятся в файле user.exe в 16-разрядных системах и user32.dll comctl32.dll в 32-разрядных системах. Начиная с версии XP элементы управления были сгруппированы в comctl32.dll.

Общие диалоги — отображают данные для открытия и сохранения файлов, выбора цвета и шрифта. Находятся в файле comdlg.dll на 16-разрядных системах и comdlg32.dll в 32-разрядных системах.

Windows Shell — компонент WinAPI, который позволяет приложениям получать доступ к функциям, предоставляемым оболочкой операционной системы.

Сетевые службы обеспечивает доступ к различным сетевым возможностям операционной системы. Его подкомпоненты включают NetBIOS, Winsock, RPC. В старых версиях — NetDDE.

Pelles C

Pelles C — бесплатная программа и лучший компилятор C и интегрированная среда разработки (IDE) для языка программирования C. Поддерживает 32-разрядную Windows (x86) и 64-разрядную Windows (x64). Реализует как стандарты C99, так и C11. Pelles C имеет встроенный редактор ресурсов, растровое изображение, значок и редактор курсоров и редактор шестнадцатеричных дампов. Он разработан шведским разработчиком Пелле Ориниусом. Название компилятора носит имя своего автора. Поставляется с SDK, поэтому программист сразу может приступить к созданию приложений без дальнейшей установки.

Drive and Directory Operations

There are functions for querying the filesystem: @{get_logical_drives} returns all available drives (in ‘D:\’ format) and @{get_drive_type} will tell you whether these drives are fixed, remote, removable, etc. @{get_disk_free_space} will return the space used and the space available in kB as two results.

This script gives the following output on my home machine:

Or at work:

A useful operation is watching directories for changes. You specify the directory, the kind of change to monitor and whether subdirectories should be checked. You also provide a function that will be called when something changes.

Using a callback means that you can watch multiple directories and still respond to timers, etc.

Finally, @{copy_file} and @{move_file} are indispensible operations which are surprisingly tricky to write correctly in pure Lua. For general filesystem operations like finding the contents of folders, I suggest a more portable library like LuaFileSystem. However, you can get pretty far with a well-behaved way to call system commands:

Версии

Win16, Win32 и Win32s являются стандартными наборами компонентов, которые позволяют прикладному программному обеспечению использовать функции различных операционных систем семейства Windows.

Win32, преемник Win16, был представлен в 1993 году в 32-разрядных продуктах семейства Windows, таких как Windows NT, 2000, 95. Этот программный интерфейс реализован тремя программными библиотеками: Kernel32.dll, User32.dll и GDI32.dll2. Те же функции Win32 доступны во всех продуктах Windows, и, в зависимости от продукта, использование определенных функций может привести к ошибке обслуживания.

Возможности Win32 включают в себя взаимодействие между программами, управление процессами, компьютерными сетями, файлами, принтерами, серверами и коммуникационными портами.

WinAPI main loop

After we’ve created the window we need to run an infinite loop. In this loop, we’ll react to different events that will happen during the interaction of the user with our app.

MSG msg = {};
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

First we declare structure variable MSG — in this structure Windows encodes events like when a user clicked a mouse button or pressed a button on a keyboard. If the message is WM_QUIT, then we finish app loop.

There is a different kind of messages generated by the system. They generated when something happens: a window is resized, mouse clicked, a button on keyboard pushed.

Message queue in WinAPI

All Windows applications are event-driven. There is a system message queue. When something happens in any program, the message is sent to this queue. Also, each program has its own message queue. So Windows checks each message in the system message queue and sends it to the program’s message queue. Actually, it’s a bit more complicated as message queues are bound to threads, but we’ll discuss it later. For now, think that each application has its own message queue.

In system message is just a structure variable of MSG. Let’s look at the definition of this structure:

typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
DWORD lPrivate;
} MSG, *PMSG, *NPMSG, *LPMSG;

hwnd — window’s handler to which message belongs.

message — message id (UINT — unsigned integer).

wParam and lParam contains additional information and they depend of message id.

time — self explanatory — time when messages was created.

pt — cursor position on the screen at the time when message was generated. POINT — is the WinAPI type for describing points with (x,y) coordinates.

Application in infinite loop checks its message queue, looks at its message property and decides what to do with it.

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

PeekMessage looks at the queue and takes one message. Then it grabs information about the message and puts it in the msg variable. The last parameter tells to remove the message from the queue. So in the condition, we check if the application queue contains any message. If it does — we fill msg variable and remove the message from the queue, then we call two functions.

TranslateMessage — generates an additional message if there was input from the keyboard (character key was pressed or relieved). By default, keyboard generates a so-called virtual-key message. And TranslateMessage generates another message which tells information about the actual character. We’ll talk about this later. For now: TranslationMessage is needed to handle character input from keyboard.

DispatchMessage sends message to WindowProc function.

Creating and working with Processes

An irritating fact is that Lua GUI applications (such as IUP or wxLua) cannot use @{os.execute} without the infamous ‘flashing black box’ of console creation. And @{io.popen} may in fact not work at all.

@{execute} provides a quiet method to call a shell command. It returns the result code (like @{os.execute}) but also any text generated from the command. So for many common applications it will do as a @{io.popen} replacement as well.

This function is blocking, but provides more general ways of launching processes in the background and even capturing their output asynchronously. This will be discussed later with @{spawn_process}.

Apart from @{execute}, @{shell_exec} is the Swiss-Army-Knife of Windows process creation. The first parameter is the ‘action’ or ‘verb’ to apply to the path; common actions are ‘open’, ‘edit’ and ‘print’. Notice that these are the actions defined in Explorer (hence the word ‘shell’). So to open a document in Word (or whatever application is registered for this extension):

Or an explorer window for a directory:

Note that this function launches the process and does not block. The path may be an explicit program to use, and then we can also specify the command-line parameters:

The fourth parameter is the working directory for the process, and the fifth indicates how the program’s window is to be opened. For instance, you can open a file in Notepad already minimized:

For fine control over console programs, use @{spawn_process} — you pass it the command-line, and receive two values; a process object and a file object. You monitor the process with the first, and can read from or write to the second.

If the command is invalid, then you will get an error message instead:

This is what @{execute} does under the hood, but doing it explicitly gives you more control. For instance, the @{Process:wait} method of the process object can take an optional time-out parameter; if you wait too long for the process, it will return the process object and the string ‘TIMEOUT’.

The file object is unfortunately not a Lua file object, since it is not possible to portably re-use the existing Lua implementation without copying large chunks of into this library. So @{File:read} grabs what’s available, unbuffered. But I feel that it’s easy enough for Lua code to parse the result into separate lines, if needed.

Having a @{File:write} method means that, yes, you can capture an interactive process, send it commands and read the result. The caveat is that this process must not buffer standard output. For instance, launch interactive Lua with a command-line like this:

Note that reading the result also returns the prompt ‘>’, which isn’t so obvious if we’re running Lua from within Lua itself. It’s clearer when using Python:

This kind of interactive process capture is fine for a console application, but @{File:read} is blocking and will freeze any GUI program. For this, you use @{File:read_async} which returns the result through a callback. Continuing the Python example:

This can work nicely with Lua coroutines, allowing us to write pseudo-blocking code for interacting with processes.

The process object can provide more useful information:

@{Process:get_working_size} gives you a lower and an upper bound on the process memory in kB; @{Process:get_run_times} gives you the time (in milliseconds) spent in the user process and in the kernel. So the time to calculate twice is too fast to even register, and it has only spent 31 msec in the system.

It is possible to wait on more than one process at a time. Consider this simple time-wasting script:

It takes me 0.743 seconds to do this, with stock Lua 5.1. But running two such scripts in parallel is about the same speed (0.776):

So my i3 is effectively a two-processor machine; four such processes take 1.325 seconds, just under twice as long. The second parameter means ‘wait for all’; like the @{Process:wait} method, it has an optional timeout parameter.

The parameter forces it to wait until all the proceses are finished. Jf successful, will return the index of the exiting process in the array of processes, so by using we can wait for any process to finish, deal with the results, and continue waiting for the others. This is how Lake does on Windows.

Creating the Window

To create a new instance of a window, call the CreateWindowEx function:

You can read detailed parameter descriptions on MSDN, but here is a quick summary:

  • The first parameter lets you specify some optional behaviors for the window (for example, transparent windows). Set this parameter to zero for the default behaviors.
  • is the name of the window class. This defines the type of window you are creating.
  • The window text is used in different ways by different types of windows. If the window has a title bar, the text is displayed in the title bar.
  • The window style is a set of flags that define some of the look and feel of a window. The constant WS_OVERLAPPEDWINDOW is actually several flags combined with a bitwise OR. Together these flags give the window a title bar, a border, a system menu, and Minimize and Maximize buttons. This set of flags is the most common style for a top-level application window.
  • For position and size, the constant CW_USEDEFAULT means to use default values.
  • The next parameter sets a parent window or owner window for the new window. Set the parent if you are creating a child window. For a top-level window, set this to NULL.
  • For an application window, the next parameter defines the menu for the window. This example does not use a menu, so the value is NULL.
  • hInstance is the instance handle, described previously. (See WinMain: The Application Entry Point.)
  • The last parameter is a pointer to arbitrary data of type void*. You can use this value to pass a data structure to your window procedure. We’ll show one possible way to use this parameter in the section Managing Application State.

CreateWindowEx returns a handle to the new window, or zero if the function fails. To show the window—that is, make the window visible —pass the window handle to the ShowWindow function:

The hwnd parameter is the window handle returned by CreateWindowEx. The nCmdShow parameter can be used to minimize or maximize a window. The operating system passes this value to the program through the wWinMain function.

Here is the complete code to create the window. Remember that is still just a forward declaration of a function.

Congratulations, you’ve created a window! Right now, the window does not contain any content or interact with the user. In a real GUI application, the window would respond to events from the user and the operating system. The next section describes how window messages provide this sort of interactivity.

WinAPI C для начинающих

WinAPI как интерфейс прикладного программирования для Windows предлагает множество мощных функций, которые позволяют создавать свои программы, начиная с простой обработки файлов и заканчивая построением графического интерфейса для программирования низкоуровневых драйверов устройств.

Прежде чем начать программирование в WinAPI, необходимо настроить среду для кода в ОС Windows. Поскольку это не дистрибутив Linux, у него нет встроенного компилятора для создания приложений. Рассмотрим следующие варианты для компиляции кода:

  • MinGW/Cygwin — стороннее приложение C WinAPI, которые обеспечивает подходящую среду программирования на платформе Windows. Данный компилятор идеален для начинающего пользователя благодаря простоте и удобству организации среды. При работе с этим решением потребуется текстовый редактор, так как он не предусмотрен графическим интерфейсом приложения.

  • Сторонние IDE, или интегрированные среды разработки, — это приложения, которые создают среду GUI для разработчиков. Они поставляются в комплекте с соответствующими инструментами, такими как MinGW, для создания программ Windows.

  • Microsoft Visual Studio (MSVS) представляет собой разработанную Microsoft IDE в комплекте со своим собственным компилятором, предоставляющим множество функций и возможностей при создании собственных приложений Windows, включая графический интерфейс, информацию о версии приложения, значки и другие ресурсы. Оптимален при исследованиях разработки вредоносных программ.

Для Windows доступен комплект разработчика, который предоставляет документацию и инструменты, позволяющие разработчикам создавать программное обеспечение с использованием API и связанных с ним технологий.

WinAPI Data Types

WinAPI redefines many standard data types. Some of redefinitions depend on target platform. For example, for type LRESULT, if you compile your code for x86 platform, LRESULT will be long, but if you compile for x64, LRESULT will be __int64. Here is how LRESULT defined internally (it depends on LONG_PTR, and LONG_PTR itself is either __int64 or long):

typedef LONG_PTR LRESULT;

#if defined(_WIN64)
typedef __int64 LONG_PTR;
#else
typedef long LONG_PTR;
#endif

Calling Conventions

In the code I used __stdcall before names of all functions. It’s one of calling conventions. Calling conventions define in what order arguments are pushed on the stack. For __stdcall arguments are pushed in reverse order — from right to left. Also, __stdcall tells that after the function is finished, it will pop its arguments itself (not the caller function). All WinAPI functions use __stdcall convention.

WinAPI redefines __stdcall to WINAPI or CALLBACK or APIENTRY. So in MSDN examples, you will not see __stdcall, but it used behind the curtains anyway.

WinAPI types are written in uppercase.

Handles in WinAPI

Handle is a reference to a resource in memory. For example, you create a window. This window is stored in memory. And this window has an entry in a table that stores pointers to all system resources that you created (windows, fonts, files, bitmaps). The pointer to your window in this table is called the handle of the window.

Internally any handle is just a redefinition of type void*. Examples of handle types in WinAPI: HWND, HINSTANCE, HBITMAP, HCURSOR, HFILE, HMENU.

So, we use handles to get access to some system resources.

First WinAPI Program — Empty Window

Let’s first check the whole code for simplest WinAPI program.

#pragma comment( lib, «user32.lib» )
#include <windows.h>

LRESULT __stdcall WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
WNDCLASS windowClass = { 0 };
windowClass.lpfnWndProc = WindowProc;
windowClass.hInstance = hInstance;
windowClass.lpszClassName = «HELLO_WORLD»;
RegisterClass(&windowClass);

HWND hwnd = CreateWindow(
windowClass.lpszClassName,
«WinAPI Empty Window — Hello World»,
WS_OVERLAPPEDWINDOW,
100, 50, 1280, 720,
nullptr, nullptr,
hInstance,
nullptr);

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

MSG msg = {};
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}

__int64 __stdcall WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}

#pragma comment( lib, «user32.lib» )
#include <windows.h>

First, we need to add WinAPI: link library that contains the implementation of different functions, and include header files with declarations of these functions, structures, and constants. user32.lib contains main Windows capabilities: everything related to windows and event handling.

On next line we declare callback functions that will be called when our app receives some message from the operating system. We’ll return to it a bit later.

Reading from the Registry

The registry is an unavoidable part of living with Windows, since much useful information can be found in it, if you know the key.

For instance, the environment for the current user can be queried:

And will then allow you to update this path, which is useful for install programs. In that case, set the optional second argument to to get write-access.

This has an optional third parameter, which is the data type of the key: has the constants ,, , and . can be passed a number value, and is passed a plain un-encoded binary Lua string; all the other types use the current encoding. The type is special, and requires strings that look like «alice\0bob\0».

will return a list of all subkeys of the current key.

When finished, it’s best to explicitly use the method.

Pipe Server

Interprocess communication (IPC) is one of those tangled, operating-system-dependent things that can be terribly useful. On Unix, named pipes are special files which can be used for two processes to easily exchange information. One process opens the pipe for reading, and the other process opens it for writing; the first process will start reading, and this will block until the other process writes to the pipe. Since pipes are a regular part of the filesystem, two Lua scripts can use regular I/O to complete this transaction.

Life is more complicated on Windows (as usual) but with a little bit of help from the API you can get the equivalent mechanism from Windows named pipes. They do work differently; they are more like Unix domain sockets; a server waits for a client to connect (‘accept’) and then produces a handle for the new client to use; it then goes back to waiting for connections.

Like timers and file notifications, this server runs in its own thread so we have to put the main thread to sleep. This function is passed a callback and a pipe name; pipe names must look like ‘\\.\pipe\NAME’ and the default name is ‘\\.\pipe\luawinapi’. The callback receives a file object — in this case we use @{File:read_async} to play nice with other Lua threads. Multiple clients can have open connections in this way, up to the number of available pipes.

The client can connect in a very straightforward way, but note that as with Unix pipes you have to flush the output to actually physically write to the pipe:

and our server will say:

(Note that @{File:read} receives an empty string when the handle is closed.)

However, we can’t push ‘standard’ I/O very far here. So there is also a corresponding @{open_pipe} which returns a file object, both readable and writeable. It’s probably best to think of it as a kind of socket; each call to @{File:read} and @{File:write} are regarded as receive/send events.

The server can do something to the received string and pass it back:

On the client side:

Применение DLL в WinAPI C

Библиотека общих элементов управления обеспечивает доступ к расширенным функциям операционной системы, таким как строки состояния, индикаторы выполнения, панели инструментов и вкладки. Эти команды находятся в библиотеке commctrl.dll в 16-разрядных системах и comctl32.dll и сгруппированы с пользовательским интерфейсом.

DLL — это формат файла динамической библиотеки ссылок, используемый для хранения нескольких кодов и процедур для программ Windows. Файлы DLL были созданы таким образом, что несколько программ могли использовать их информацию одновременно, помогая сохранить память. Позволяет пользователю редактировать кодирование сразу нескольких приложений без их изменения. Библиотеки DLL можно преобразовать в статические, используя MSIL Disassembler или DLL для Lib 3.00.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector