Однако при использовании этого приема необходимо учитывать следующие нюансы:
· при создании объекта класса лучше использовать один из базовых классов (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 вызывать диспетчер класса–предка.