Однако при использовании этого приема необходимо учитывать следующие нюансы:
· при создании объекта класса лучше использовать один из базовых классов (CWnd или TWindow), так как все порожденные от них классы переопределяют значительно большее число методов, предполагая стандартную обработку сообщений, реализованную в DefWindowProc, а не в той процедуре, которую вы подменили. Это может привести к конфликтам между новой обработкой событий и прежней оконной процедурой. Особенно опасна ошибка в назначении класса — библиотека классов и компилятор никак не смогут проверить вас и предупредить, если вы, скажем, для кнопки, создадите объект класса “список” (LISTBOX). При такой ошибке конфликт практически неизбежен. В любом случае надо хорошо представлять себе, для какой стандартной оконной процедуры реализован какой класс библиотеки ООП и обработку каких сообщений он переопределяет, прежде чем решиться на подмену оконной процедуры.
· в случае Win32 для окон, созданных другим приложением, оконные процедуры (используемая окном и назначаемая вами) размещается в различных адресных пространствах разных процессов. Обращение из другого процесса по новому адресу функции приведет, скорее всего, к ошибке — так как этот адрес задан в адресном пространстве вашего приложения, а что находится в адресном пространстве другого процесса по этому адресу вам неизвестно. Решить эту проблему можно, выделяя описание объекта класса и его процедуры в отдельную DLL, а затем внедряя ее в адресное пространство процесса, создавшего окно. Однако этот прием существенно сложнее.
В этом примере используется несколько упрощенный метод реализации объектов. Главное ограничение — невозможность назначения обработчиков сообщений для окон, не созданных в качестве объектов класса. В остальном этот вариант сохраняет все необходимые функции, причем делает это более компактным и быстрым способом. Такой способ часто применяется в приложениях–примерах, сопровождающих компиляторы.
Коротко рассмотрим реализацию этого способа: вместо ведения таблиц соответствия хендлов объектам приложения можно хранить необходимые данные непосредственно в структуре описания окна в Windows (см. “Регистрация класса окон”). Так как доступ к этим данным осуществляется только с помощью функций, то размещать там все описание окна нецелесообразно, зато в этой структуре можно разместить указатель на связанный объект. Отсюда следует ограничение — этот метод будет работать только с теми окнами, в структуре описания которых в Windows зарезервировано специальное поле для указателя. Это могут быть только окна, созданные нами.
Рисунок 7. Поиск метода–обработчика сообщения в примере.
Помимо этого используется еще один прием — вместо таблиц функций–обработчиков сообщений для каждого класса окон формируется специальная виртуальная функция–диспетчер, которая осуществляет вызовы нужных методов. Если в случае MFC или OWL надо вести таблицы отклика, то в рассматриваемом примере надо разрабатывать соответствующую функцию.
Кроме того, для упрощения в примере остались некоторые следы обычного программирования — осталась, хотя и сильно измененная, функция WinMain, в которой создается объект “приложение”.
Рассматриваемый пример состоит из 3х файлов: 1c.h — общий заголовочный файл, содержащий описания базовых классов; 1c_cls.cpp — методы и статические данные базовых классов; 1c_main.cpp — собственно само приложение: описание собственных классов и их методов, а также функция WinMain.
#define STRICT
#include <windows.h>
#define UNUSED_ARG(arg) (arg)=(arg)
class Win0 {
protected:
HWND hwnd;
virtual LRESULT dispatch( UINT, WPARAM, LPARAM );
virtual BOOL OnCreate( LPCREATESTRUCT );
virtual void OnDestroy( void ) = 0;
virtual void OnPaint( HDC hdc ) = 0;
public:
Win0( void );
~Win0( void );
BOOL create( char* );
void destroy( void );
void update( void ) { UpdateWindow( hwnd ); }
void show( int nCmdShow ) { ShowWindow( hwnd, nCmdShow ); }
friend LONG WINAPI _export Win0proc( HWND, UINT, WPARAM, LPARAM );
};
class App0 {
public:
static HINSTANCE hInstance;
static HINSTANCE hPrevInstance;
static LPSTR lpszCmdLine;
static int nCmdShow;
App0( HINSTANCE, HINSTANCE, LPSTR, int );
~App0( void );
BOOL init( void );
int run( void );
void release( void );
};
#include "1c.h"
HINSTANCE App0::hInstance;
HINSTANCE App0::hPrevInstance;
LPSTR App0::lpszCmdLine;
int App0::nCmdShow;
static char szWndClass[]= "test window class";
static Win0* on_create_ptr;
Win0::Win0( void )
{
hwnd = NULL;
}
Win0::~Win0( void )
{
destroy();
}
LRESULT WINAPI _export Win0proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
Win0* pwin;
pwin = (Win0*)GetWindowLong( hWnd, 0 );
if ( !pwin ) {
SetWindowLong( hWnd, 0, (LONG)(Win0 FAR*)(pwin = on_create_ptr) );
pwin->hwnd = hWnd;
}
return pwin->dispatch( uMsg, wParam, lParam );
}
LRESULT Win0::dispatch( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
PAINTSTRUCT ps;
switch ( uMsg ) {
case WM_CREATE:return OnCreate( (LPCREATESTRUCT)lParam ) ? 0L : -1L;
case WM_PAINT: OnPaint( BeginPaint( hwnd, &ps ) ); EndPaint( hwnd, &ps ); return 0L;
case WM_DESTROY:OnDestroy(); return 0L;
default: break;
}
return DefWindowProc( hwnd, uMsg, wParam, lParam );
}
void Win0::destroy( void )
{
if ( IsWindow( hwnd ) ) DestroyWindow( hwnd );
hwnd = (HWND)NULL;
}
BOOL Win0::create( char* title )
{
on_create_ptr = this;
CreateWindow(
szWndClass, // class name
title, // window name
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT,CW_USEDEFAULT, // window position
CW_USEDEFAULT,CW_USEDEFAULT, // window size
NULL, // parent window
NULL, // menu
hInstance, // current instance
NULL // user-defined parameters
);
on_create_ptr = (Win0*)NULL;
return IsWindow( hwnd );
}
BOOL Win0::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
UNUSED_ARG( lpCreateStruct );
return TRUE;
}
App0::App0( HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpszCmd, int nShow )
{
hInstance = hInst;
hPrevInstance = hPrev;
lpszCmdLine = lpszCmd;
nCmdShow = nShow;
}
App0::~App0( void )
{
}
BOOL App0::init( void )
{
static BOOL done;
WNDCLASS wc;
if ( !done && !hPrevInstance ) {
wc.style = 0;
wc.lpfnWndProc = Win0proc;
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof(LONG);
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szWndClass;
done = RegisterClass( &wc ) ? TRUE : FALSE;
}
return done;
}
int App0::run( void )
{
MSG msg;
while ( GetMessage( &msg, NULL, NULL, NULL ) ) {
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return msg.wParam;
}
void App0::release( void )
{
}
#include "1c.h"
class MainWindow : public Win0 {
protected:
virtual void OnDestroy( void );
virtual void OnPaint( HDC hdc );
public:
MainWindow( void );
~MainWindow( void );
};
class MyApp : public App0 {
protected:
MainWindow wnd;
public:
MyApp( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow );
~MyApp( void );
BOOL init( void );
};
MainWindow::MainWindow( void ) : Win0()
{
}
MainWindow::~MainWindow( void )
{
}
void MainWindow::OnDestroy( void )
{
PostQuitMessage( 0 );
}
void MainWindow::OnPaint( HDC hdc )
{
TextOut( hdc, 0, 0, "Hello, world!", 13 );
}
MyApp::MyApp(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
: App0( hInst, hPrevInst, lpszCmdLine, nCmdShow )
{
}
MyApp::~MyApp( void )
{
}
BOOL MyApp::init( void )
{
if ( App0::init() ) {
if ( wnd.create( "window header" ) ) {
wnd.show( nCmdShow );
wnd.update();
return TRUE;
}
}
return FALSE;
}
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
int a;
MyApp app( hInst, hPrevInst, lpszCmdLine, nCmdShow );
if ( app.init() ) {
a = app.run();
} else a = -1;
app.release();
return a;
}
Пример содержит два базовых класса: App0 — описывает приложение и Win0 — описывает окно.
Класс App0 содержит 4 члена–данных: hInstance, hPrevInstance, lpszCmdLine и nCmdShow, которые являются аргументами функции WinMain. Интереснее разобраться с методами, описанными в этом классе. Конструктор просто инициализирует члены–данные для использования в последующем; деструктор вообще ничего не делает. Пара методов init и release предназначена для переопределения в дальнейшем — метод init должен выполнять специфичную инициализацию приложения, а метод release — операции при завершении. В классе App0 метод init осуществляет регистрацию оконной процедуры (в терминологии Windows — класса), которая будет применяться данным приложением. Метод run выполняет цикл обработки сообщений.
Класс Win0 содержит только один член–данные hwnd — хендл окна. Конструктор устанавливает значение хендла окна равным NULL (окно не создано), деструктор проверяет существование окна и, при необходимости, закрывает его. Методы create, destroy, update и show соответствуют функциям API: CreateWindow, DestroyWindow, UpdateWindow и ShowWindow. Методы OnCreate, OnDestroy и OnPaint соответствуют обработчикам сообщений WM_CREATE, WM_DESTROY и WM_PAINT. Метод dispatch является диспетчером, который распределяет пришедшие сообщения по соответствующим методам–обработчикам.
В том–же классе декларирована дружественная функция Win0proc, которая является собственно оконной процедурой.
Коротко рассмотрим, как создается окно в этом примере. Для создания окна необходимо вызвать метод create, который, в свою очередь, вызовет функцию CreateWindow из Windows. Во время создания окна его оконная процедура начнет получать сообщения (в том числе и WM_CREATE, хотя, на самом деле, это будет не первое полученное сообщение). Эта процедура для нормальной работы требует, что бы в структуре описания окна в Windows был сохранен указатель на объект, описывающий окно в приложении. Но в момент первого вызова обработчика сообщений этот указатель там не находиться — все происходит еще только во время работы функции CreateWindow. Соответственно мы используем некоторую статическую переменную (on_create_ptr), которая перед вызовом CreateWindow инициализируется указателем на объект. Тогда обработчик сообщений может быть построен по следующей схеме:
LONG WINAPI _export Win0proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
Win0* pwin;
pwin = (Win0*)GetWindowLong( hWnd, 0 ); // получаем указатель на объект
if ( !pwin ) { // указатель равен NULL — объект только создается
// инициализируем объект и указатель на него
SetWindowLong( hWnd, 0, (LONG)(Win0 FAR*)(pwin = on_create_ptr) );
pwin->hwnd = hWnd;
}
// вызываем виртуальную функцию-диспетчер
return pwin->dispatch( uMsg, wParam, lParam );
}
При нормальной работе первый вызов функции GetWindowLong вернет указатель на объект, так что следующий шаг — вызов функции–диспетчера. Таким образом дополнительные затраты ресурсов на реализацию ООП таким способом оказываются минимальными. В случае разработки классов–наследников от Win0 надо разработать собственную функцию–диспетчер, которая будет вместо процедуры DefWindowProc вызывать диспетчер класса–предка.