| Language: | C |
| Category: | Free books |
| Date: | 2008-10-08 |
| Author: | WEB Consulting |
| Attachment: | Download |
Lests take a deeper look at Win32 Application. A more comprehensive description can be found in specialized books on Visual C++, but now I'll give you only the necessary information about Win 32 programming. In this article we consider how to create a Win32 application with the small executable file size.
Create a new Win32 Application using Visual Studio and name it Ctest. Open the CTest project and look at the Solution Explorer pane. It displays a hierarchy that contains all components of your project. You'll see something like this:
The project consists of the following folders:
Let's look at our resources. Double-click the ctest.rc file name, and the Resource View tab will appear in the same pane. It displays a hierarchy of the resources.
The Accelerator folder contains a list of hotkeys for the program. There can be several resources there, but by the moment the wizard has created only one resource named IDC_CTEST. We won't use hotkeys, and in most cases you can delete everything from this folder. To do this, right-click the resource and select the Delete item in the pop-up menu. On the one hand, the program shouldn't contain extra components, but on the other hand we won't gain much space.
The Dialog folder contains dialog boxes. If you intend to create an invisible program, it won't need any dialog boxes. By default, our sample contains the IDD_ABOUTBOX dialog box with information about the program. To delete the dialog box, right-click it and select the Delete item in the pop-up menu.
The Icon folder contains the program's icons. There can be several icons of various sizes and colors. They are often needed for joke programs, but they shouldn't be suspicious so that a user runs the program and sees what you "cooked". If you suggest him or her to run the program, it won't be spectacular. However, if the user does it on his or her own, the surprise will make your joke sharp.
For the user to run the program, the icon should be familiar to him or her and remain above suspicion. For example, you can assign the icon of MS Word to your joke program. If the standard file extensions aren't displayed in the user's system (which is the default), he or she will take the file for a Word document. If, in addition, the file name is alluring, he or she will certainly run the file.
To change the icon and draw something effective and not suspicious, double-click the icon name, and a simple graphics editor will open in the main window (see figure bellow). There you can change the image. It is impossible to draw anything serious with this editor, so its is best to paste a ready picture (for example, via the clipboard). You always can find suitable pictures on the Internet.
In the Menu folder, you can store a menu for your program. We'll discuss it later, and I won't use menus frequently in subsequent examples.
The String Table folder allows you to store strings as constants. By default, it contains window captions. They don't take up much space, so leave them.
Let's look at the program code generated by the wizard. The code is contained in the ctest.cpp file, and it is shown in Listing bellow. Of course, we won't discuss the code entirely. This book isn't a C++ tutorial though necessary things are described here in detail. If you are already familiar with this programming language, you'll understand the code in the file. Otherwise, it will be enough to read the explanations below.
#include "stdafx.h"
#include "ctest.h"
#define MAX_LOADSTRING 100
// Global Variables:
HINSTANCE hInst; // current instance
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
MSG msg;
HACCEL hAccelTable;
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_CTEST, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_CTEST);
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
// FUNCTION: MyRegisterClass()
// PURPOSE: Registers the window class.
// COMMENTS:
// This function and its usage are only necessary if you want this code
// to be compatible with Win32 systems prior to the 'RegisterClassEx'
// function that was added to Windows 95. It is important to call this function
// so that the application will get 'well formed' small icons associated
// with it.
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_CTEST);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = (LPCTSTR)IDC_CTEST;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
return RegisterClassEx(&wcex);
}
// FUNCTION: InitInstance(HANDLE, int)
// PURPOSE: Saves instance handle and creates main window
// COMMENTS:
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
// FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
// PURPOSE: Processes messages for the main window.
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Message handler for about box.
// As we deleted the "About" window, we can delete the following code.
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
You already know that this program shouldn't have the "About" window, and we deleted it. However, the code still contains mentions of this window, and you cannot run the program. To run the project, delete everything that is below the line:
// As we deleted the "About" window, we can delete the following code.
This code displays the "About" window, and you can delete it completely.
Now open the WndProc procedure and delete the call to the About procedure from it. To do this, locate and delete the following three lines:
case IDM_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break;
This is an event handler for the Help/About menu item of our program. All event handlers are located in the WndProc procedure and look like this:
case Identifier actions break;
Here, Identifier is a constant assigned to a control (such as a menu item). The case statement checks whether an event with this identifier has come from the control. If it has, the code up to the break statement is executed. We'll meet events in our practical work a bit later.
We deleted everything we don't need. Therefore, let's decide in favor of this sample program. It size is small enough, and there is little extra code.
When we create invisible programs, it will suffice to find and delete the following two lines:
ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
These lines are located in the InitInstance procedure that is designed for creating and displaying a window on the desktop. You may leave the code that creates the window, but you shouldn't display it if you want your program to be invisible.
The first of these two lines shows the window created in the program, and the second one updates the contents of the window. Comment out these lines by putting two slashes (//) at the beginning of each. Now compile and run the program. You will see nothing on the screen nor in the window that appears when you press . If you have Windows 2000/XP, you'll be able to find your program only on the Processes tab:
If you are an inexperienced Visual C++ programmer, and you don't understand something, don't worry. Everything will become clear eventually. Later in this book, I'll explain much of the source code you're looking at.
Let's look at a few fragments of the code. The program starts execution at the _tWinMain function that looks like shown in the next banch of code
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
// Declaration of two variables
MSG msg;
HACCEL hAccelTable;
// Initialization of string variables
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_CTEST, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Initialization of the application
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_CTEST);
// The main loop for processing Windows messages
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
I've used the word "function" several times, but didn't give a definition for it. If you have experience in C++ programming, you are familiar with this notion. There are things that are beyond the scope of this book, and I'll leave them out. You should refer to specialized literature for more comprehensive information on C++. Nevertheless, I'll give you as much information as necessary to understand the examples.
In C language, all functions are declared as follows:
Type Name (Parameters)
{
}
Our main function has the APIENTRY keyword after the return type. This points to the entry point of the program.
Now look at our first listing. I put a few comments there so that you understand the code better. As you know, comments begin with two slashes (//). The text that follows the slashes doesn't affect the program execution and just explains the code.
Two variables are declared at the beginning of the procedure:
MSG msg; HACCEL hAccelTable;
A declaration looks like this: Type Name. The type tells the program how much memory should be allocated to store the variable's value. You can access this memory with the name specified after the type. There are quite a lot of types in C++, and I'll explain them when discussing examples.
If a variable is of a simple type (such as a string, number, or structure), no additional actions are required. However, if it is an object or pointer, you should allocate memory for it. Objects are used when programming with MFC, and pointers are variables that point to certain areas in the memory. For a pointer to point to a reasonable address, you should allocate memory for data.
What for are pointers? Many programmers don't understand their power or are simply afraid to use them because of the absence of protection against going outside the allocated memory. Suppose you have a 1 Mb picture loaded into the memory, and you want to give a function an opportunity to read the data of this picture. You can pass a megabyte of data to the function, but it will take much time and extra memory. Instead, you can show the function the place where the picture is stored. In other words, you can pass the function a pointer to the memory with the picture data.
Another way involves use of global variables, but this isn't a good practice. Global variables are seen in every function. As a rule, they are declared in a header file (with the H extension) or at the beginning of the program file, before function declarations.
Local variables are declared inside a function, and you can access them only within this function. Such variables are created automatically when the function starts. They are stored in a special memory area called a stack and are destroyed automatically when the function terminates. Automatically creating/destroying variables refers only to simple ones, not to pointers which should be freed manually.
Variable declaration is followed by these two lines:
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_CTEST, szWindowClass, MAX_LOADSTRING);
These are two functions named LoadString. They load text from string resources. A function is a piece of code that has a name and can be called from other places in the program. In this case the resource loading code will be executed. To tell the function which resource you need, and where it should be loaded, specify its parameters in parentheses. The parameters are comma-delimited, and in this case there are four parameters with the following values:
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
As you already know, a variable declaration begins with a type. In this case, TCHAR is specified which means a string. It is followed by the variable name and the string length (i. e., the maximum number of its characters) in square brackets which is required in string declarations. In this code, MAX_LOADSTRING is specified. It is a constant equal to the maximum number of loaded characters. You could specify an actual number in square brackets, but it is best to use predefined constants where possible.
Then the MyRegisterClass(hInstance) function is called. It fills the WNDCLASSEX structure. What is a structure? It is a variable that stores a set of variables of any types. For example, a structure can store one numeric variable named Name and one string named Age. To read or update the values of these variables, you should write Structure.Variable. Here Structure is the name of the structure, and Variable is the name of one of its variables.
The WNDCLASSEX structure is used when creating a new window class. To obtain a small application, you'll have to fill the following fields:
The structure is ready, and you can register the new class for a window. To do this, call a WinAPI function RegisterClassEx(&wcex). Now the system has a description of your future window. Why "future"? In fact, you haven't created the window yet. To do this, it is necessary to call the CreateWindowEx function. This is done in the InitInstance procedure which is called in _tWinMain after the call to MyRegisterClass. This procedure has quite a lot of parameters, and I'll describe them in more detail:
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
I'll skip the other parameters for a while. After a window is created, it should be displayed. This is done by calling the ShowWindow procedure we spoke about earlier. It has two parameters:
The last of preliminary functions is UpdateWindow. It simply draws the created window.
When you don't need a window and want your program to be invisible, delete or comment out the last two functions.
Now let's look at the message processing cycle:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
The GetMessage function waits for a user or system message and returns true as soon as a message comes. The message is appropriately converted with the TranslateMessage function and sent to a message handler with the DispatchMessage function.
Every program should have a message handler. Which one? We specified it when we were creating the window class, in the WindowClass.Lpfnwndproc property. In Visual C++, it is common to name it WndProc which is the standard name used by default. As for the procedure, it should look like shown in the first listing.
The message handler should always contain a call to the defwindowproc function. This function looks for a default message handler for the received event. This is very important because it saves you from writing code already available in the operating system.
However, you should check the message first. If it is really necessary, handle it. There is no need in handling all messages; handle only those you need. The others will be handled in the default message handler. This is done by comparing the message parameter with standard messages. For example, if it is equal to wm_destroy, this means the program would like to be destroyed. Therefore, the handler can free the memory allocated for the program.
Well, now you understand the sample. If you start the created program right now, an empty window will appear. To close it, hit
If you want to make this window invisible, just delete the ShowWindow function from the code. This function displays the window, and your program will become invisible without it. Alternatively, you can change the second parameter of this procedure to SW_HIDE. This also results in invisibility. In appearance, specifying SW_HIDE is equivalent to the absence of a call to the procedure. As a rule, ShowWindow is used with the SW_HIDE parameter when it is necessary to hide the window without removing it from the computer memory.
To compile the project, select Build/Build Solution. This builds the project and creates an executable file. To start the program, select Debug/Start.