Файл cliprndr/cliprndr.rc
#include "cliprndr.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END POPUP "&Edit" BEGIN MENUITEM "&Copy", CM_EDITCOPY MENUITEM "&Paste", CM_EDITPASTE END POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END APP_ICON ICON "cliprndr.ico"
Файл определения модуля вы найдете в листинге 2.14.
Файл cliprndr/cliprndr.def
NAME CLIPRNDR DESCRIPTION 'Приложение CLIPRNDR, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл ddeml/ddemlsr.cpp
// ---------------------------------------- // Приложение DDEMLSR // Сервер DDEML // ---------------------------------------- #define STRICT #include <windows.h>
#include <windowsx.h>
#include <mem.h>
#pragma hdrstop
#include "ddemlsr.hpp"
// Прототипы функций BOOL DDEServerOpen(HINSTANCE hInst, LPSTR szService, LPSTR szTopic, LPSTR szItem);
void DDEServerClose(void);
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
// Имя класса окна char const szClassName[] = "DDEMLSERVER";
// Заголовок окна char const szWindowTitle[] = "DDEML Server";
HWND hwnd; HINSTANCE hInst;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg;
// Можно запускать только одну копию приложения if(hPrevInstance) return FALSE;
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// Сохраняем идентификатор приложения hInst = hInstance;
hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_CAPTION | WS_BORDER | WS_MINIMIZEBOX | WS_OVERLAPPED | WS_SYSMENU, 0, 0, 200, 100, 0, 0, hInstance, NULL);
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Если главное окно сервера должно быть // невидимым, поставьте перед следующей // строкой символ комментария ShowWindow(hwnd, SW_SHOWNORMAL);
while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
switch (msg) { case WM_CREATE: { // Инициализируем DDEML DDEServerOpen(hInst, (LPSTR)"BMPServer", (LPSTR)"BMPFile", (LPSTR)"DDEData");
return 0; }
// Обработка сообщений от меню case WM_COMMAND: { switch (wParam) { case CM_HELPABOUT: { MessageBox(hwnd, "DDEML Server\nVersion 1.0\n" "(C) Frolov A.V., 1995", "About DDEML Server", MB_OK | MB_ICONINFORMATION);
return 0; }
// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd);
return 0; } default: return 0; } }
case WM_DESTROY: { PostQuitMessage(0);
// Завершаем работу с DDEML DDEServerClose();
return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}
Приложение DDEMLSR создает обычное главное окно, имеющее меню. Однако в таком окне не всегда есть необходимость - сервер может вообще не рисовать на экране свое окно, так как его функции могут быть не связаны с отображением информации.
Для того чтобы скрыть главное окно приложения (а точнее, не показывать его вовсе), достаточно убрать вызов функции ShowWindow перед запуском цикла обработки сообщений. Разумеется, для стиля окна в этом случае не следует использовать значение WS_VISIBLE.
При создании главного окна приложения обработчик сообщения WM_CREATE инициализирует сервер, вызывая функцию DDEServerOpen, определенную в файле ddemlfn.cpp (листинг 3.2). Функции передается имена сервиса, раздела данных и элемента данных.
Обработчики сообщений от меню предназначены для просмотра версии сервера и завершения работы приложения. Эти действия выполняются обычным образом.
При завершении работы приложения обработчик сообщения WM_DESTROY закрывает канал DDEML и освобождает все занятые ресурсы, вызывая функцию DDEServerClose, определенную в файле ddemlfn.cpp.
Для удобства мы собрали все функции, имеющие отношение к DDEML, в файле ddemlfn.cpp.Там же определена и функция обратного вызова для сервера DDEML.
Файл ddeml/ddemlfn.cpp
// ----------------------------------------------------- // Функции для работы с библиотекой DDEML // Сервер DDEML // ----------------------------------------------------- #define STRICT #include <windows.h>
#include <windowsx.h>
#include <ddeml.h>
#include <dde.h>
#include <mem.h>
#include <string.h>
#pragma hdrstop
#include "ddemlsr.hpp"
HDDEDATA EXPENTRY _export DDEServerCallback(WORD wType, WORD wFmt, HCONV hConv, HSZ hsz1, HSZ hsz2, HDDEDATA hData, DWORD dwData1, DWORD dwData2 );
// Идентификатор приложения, полученный после регистрации в // библиотеке DDEML DWORD idInst = 0L;
FARPROC lpDdeProc; HSZ hszService = NULL; HSZ hszTopic = NULL; HSZ hszItem = NULL;
// Идентификатор канала HCONV hConvApp = NULL;
// Буфер для приема данных char szDDEData[200];
// Версия сервера. Эта текстовая строка // передается клиенту по его запросу char szDDEServerVersion[] = "DDEML Server v.1.0, (C) Frolov A.V.";
//----------------------------------------------------- // Функция DDEServerOpen // Инициализация библиотеки DDEML //----------------------------------------------------- BOOL DDEServerOpen(HINSTANCE hInst, LPSTR szService, LPSTR szTopic, LPSTR szItem) { int rc;
// Создаем переходник для функции обратного вызова lpDdeProc = MakeProcInstance((FARPROC)DDEServerCallback, hInst);
// Выполняем инициализацию if(DdeInitialize((LPDWORD)&idInst, (PFNCALLBACK)lpDdeProc, APPCLASS_STANDARD, 0L)) { return FALSE; }
// После успешной инициализации получаем идентификаторы // строк для сервиса, раздела и элемента данных else { hszService = DdeCreateStringHandle(idInst, szService, CP_WINANSI);
hszTopic = DdeCreateStringHandle(idInst, szTopic, CP_WINANSI);
hszItem = DdeCreateStringHandle(idInst, szItem, CP_WINANSI);
// Регистрируем сервис DdeNameService(idInst, hszService, (HSZ)NULL, DNS_REGISTER);
return TRUE; } }
//----------------------------------------------------- // Функция DDEServerClose // Завершение работы с DDEML //----------------------------------------------------- void DDEServerClose(void) { // Завершение работы канала связи if(hConvApp != NULL) { DdeDisconnect(hConvApp);
hConvApp = NULL; }
// Сервис больше не доступен DdeNameService(idInst, hszService, (HSZ)NULL, DNS_UNREGISTER);
// Освобождение идентификаторов строк DdeFreeStringHandle(idInst, hszService);
DdeFreeStringHandle(idInst, hszTopic);
DdeFreeStringHandle(idInst, hszItem);
// Удаление переходника функции обратного вызова FreeProcInstance(lpDdeProc);
}
//----------------------------------------------------- // Функция DDEServerCallback // Функция обратного вызова для сервера DDEML //----------------------------------------------------- #pragma argsused HDDEDATA EXPENTRY _export DDEServerCallback(WORD wType, WORD wFmt, HCONV hConv, HSZ hsz1, HSZ hsz2, HDDEDATA hData, DWORD dwData1, DWORD dwData2) { switch(wType) { // Создание канала передачи данных case XTYP_CONNECT: { // Если сервис поддерживается, возвращаем // признак успешного создания канала if((HSZ)hsz2==(HSZ)hszService) return((HDDEDATA)TRUE);
else return((HDDEDATA)FALSE);
}
// Запрос данных от сервера case XTYP_REQUEST: { // Создаем идентификатор данных hData = DdeCreateDataHandle(idInst, szDDEServerVersion, lstrlen(szDDEServerVersion) + 1, 0L, hszItem, CF_TEXT, 0);
// В случае успеха возвращаем созданный идентификатор if(hData != NULL) return(hData);
else return(NULL);
}
// Запрос на выполнение команды, отрабатывается вхолостую case XTYP_EXECUTE: break;
// Передача данных серверу case XTYP_POKE: { // Проверяем элемент данных if(hsz1 == hszTopic) { // Получаем данные DdeGetData(hData, (LPBYTE) szDDEData, 200L, 0L);
// Отображаем принятые данные на экране if(szDDEData != NULL) { MessageBox(NULL, szDDEData, "DDEML Server", MB_OK | MB_SYSTEMMODAL | MB_ICONINFORMATION);
// Признак успешного завершения транзакции return((HDDEDATA)DDE_FACK);
} } else return((HDDEDATA)NULL);
break; }
// Подтверждение создания канала case XTYP_CONNECT_CONFIRM: { // Сохраняем идентификатор канала hConvApp = hConv; break; }
// Завершение работы канала case XTYP_DISCONNECT: { hConvApp = NULL; break; }
// Ошибка case XTYP_ERROR: { break; } } return((HDDEDATA)NULL);
}
Транзакция XTYP_ERROR передается в функцию обратного вызова в случае возникновения ошибки. Младшее слово параметра dwData1 содержит код ошибки.
Библиотека DDEML в Windows версии 3.1 поддерживает только один код ошибки - DML_ERR_LOW_MEMORY. Эта ошибка возникает при нехватке оперативной памяти. В случае возникновения такой ошибки выполнение обработки транзакции может быть не завершено.
Файл ddemlsr.hpp (листинг 3.3) содержит определения констант для приложения DDEMLSR.
Файл ddeml/ddemlsr.hpp
#define CM_HELPABOUT 301 #define CM_FILEINFO 302 #define CM_FILECLOSE 303 #define CM_FILEEXIT 304 #define CM_EDITCOPY 305
В файле определения ресурсов (листинг 3.4) описано главное меню приложения и пиктограмма.
Файл ddeml/ddemlsr.rc
#include "ddemlsr.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END APP_ICON ICON "ddemlsr.ico"
Файл определения модуля приложения DDEMLSR приведен в листинге3.5.
Файл ddeml/ddemlsr.def
NAME DDEMLSR DESCRIPTION 'Приложение DDEMLSR, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл ddeml/ddemlcl.cpp
// ---------------------------------------- // Приложение DDEMLCL // Клиент DDEML // ---------------------------------------- #define STRICT #include <windows.h>
#include <windowsx.h>
#include <ddeml.h>
#include <dde.h>
#include <mem.h>
#pragma hdrstop
#include "ddemlcl.hpp"
// Прототипы функций HCONV DDEClientOpen(HINSTANCE hInst, LPSTR szService, LPSTR szTopic, LPSTR szItem);
void DDEClientClose(HCONV);
BOOL DDESend(HCONV, LPSTR);
BOOL DDEReceive(HCONV, LPSTR szBuf, int nBufSize);
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
// Имя класса окна char const szClassName[] = "DDEMLCLIENT";
// Заголовок окна char const szWindowTitle[] = "DDEML Client";
HWND hwnd; HINSTANCE hInst; HCONV hConv = NULL;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg;
// Можно запускать только одну копию приложения if(hPrevInstance) return FALSE;
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// Сохраняем идентификатор приложения hInst = hInstance;
hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, 20, 20, 250, 100, 0, 0, hInstance, NULL);
if(!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; WNDCLASS wc;
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
switch (msg) { case WM_CREATE: { // Инициализируем DDEML и создаем канал связи if(hConv == NULL) { hConv = DDEClientOpen(hInst, (LPSTR)"BMPServer", (LPSTR)"BMPFile", (LPSTR)"DDEData");
// Если сервер не запущен, предоставляем // пользователю возможность запустить его if(hConv == NULL) { if(IDYES == MessageBox(hwnd, "Сервер не запущен.\nЗапустить?", "DDEML Client", MB_YESNO | MB_ICONHAND)) { WORD rc; // Выполняем попытку запуска сервера rc = WinExec("DDEMLSR", SW_SHOW);
if(rc < 32) { MessageBox(hwnd, "Невозможно запустить сервер", "DDEML Client", MB_ICONHAND);
return -1; } else { // После удачного запуска повторяем // попытку инициализации DDEML // и создания канала связи hConv = DDEClientOpen(hInst, (LPSTR)"BMPServer", (LPSTR)"BMPFile", (LPSTR)"DDEData");
if(hConv == NULL) return -1; else return 0; } } return -1; } return 0; } return 0; }
// Обработка сообщений от меню case WM_COMMAND: { switch (wParam) { case CM_HELPABOUT: { MessageBox(hwnd, "DDEML Client\nVersion 1.0\n" "(C) Frolov A.V., 1995", "About DDEML Client", MB_OK | MB_ICONINFORMATION);
return 0; }
// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd);
return 0; }
// Посылаем текстовую строку серверу case CM_MSG_TO_SERVER: { if(!DDESend(hConv, (LPSTR)"c:\\nicebmp\\sky.bmp")) MessageBox(hwnd, "Сервер не отвечает", "DDEML Client", MB_OK);
return 0; }
// Принимаем текстовую строку от сервера case CM_MSG_FROM_SERVER: { BYTE szBuf[256]; if(DDEReceive(hConv, szBuf, 80L)) MessageBox(hwnd, szBuf, "DDEML Client", MB_OK);
else MessageBox(hwnd, "Сервер не отвечает", "DDEML Client", MB_OK);
return 0; } default: return 0; } }
case WM_DESTROY: { PostQuitMessage(0);
// Завершаем работу с DDEML DDEClientClose(hConv);
return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}
Обработчик сообщения WM_CREATE при инициализации главного окна приложения вызывает функцию DDEClientOpen, определенную в файле ddemlcf.cpp (листинг 3.7). Эта функция регистрирует приложение в библиотеке DDEML и пытается создать канал связи с сервером.
Если канал связи создать не удалось, приложение DDEMLCL делает вывод о том, что сервер не запущен, и предлагает пользователю запустить его. Запуск выполняется при помощи функции WinExec. После удачного запуска сервера делается еще одна попытка создать канал связи.
Когда пользователь выбирает из меню "Action" строку "Send Filename", приложение вызывает функцию DDESend, определенную в файле ddemlcf.cpp. Этой функции передается идентификатор созданного канала связи и текстовая строка "c:\\nicebmp\\sky.bmp". Функция DDESend передаст строку серверу, который отобразит ее на экране.
Если пользователь выбирает из меню "Action" строку "Get Server Version", вызывается функция DDEReceive, также определенная в файле ddemlcf.cpp. Этой функции помимо идентификатора канала связи передается адрес и размер буфера, в который нужно записать принятую информацию. После приема данные отображаются на экране в виде текстовой строки с помощью функции MessageBox.
Когда приложение DDEMLCL завершает свою работу, вызывается функция DDEClientClose, закрывающая канал и освобождающая связанные с ним ресурсы.
В файле ddemlcf.cpp (листинг 3.7) собраны все функции, имеющие отношение к DDEML.
Файл ddeml/ddemlcf.cpp
// ----------------------------------------------------- // Функции для работы с библиотекой DDEML // Клиент DDEML // ----------------------------------------------------- #define STRICT #include <windows.h>
#include <windowsx.h>
#include <ddeml.h>
#include <dde.h>
#include <mem.h>
#include <string.h>
#pragma hdrstop
#include "ddemlcl.hpp"
HDDEDATA EXPENTRY _export DDEClientCallback(WORD wType, WORD wFmt, HCONV hConv, HSZ hsz1, HSZ hsz2, HDDEDATA hData, DWORD dwData1, DWORD dwData2);
// Идентификатор приложения для DDEML DWORD idInst;
// Функция обратного вызова для DDE FARPROC lpDdeProc;
HSZ hszService; HSZ hszTopic; HSZ hszItem;
HDDEDATA hData; DWORD dwResult; WORD wFmt = CF_TEXT;
//----------------------------------------------------- // Функция DDEClientOpen // Инициализация DDEML, создание идентификаторов строк //----------------------------------------------------- HCONV DDEClientOpen(HINSTANCE hInst, LPSTR szService, LPSTR szTopic, LPSTR szItem) { HCONV hConv = NULL;
// Создаем переходник для функции обратного вызова lpDdeProc = MakeProcInstance((FARPROC)DDEClientCallback, hInst);
// Инициализируем DDEML if(DdeInitialize((LPDWORD)&idInst, (PFNCALLBACK)lpDdeProc, APPCMD_CLIENTONLY, 0L)) { return NULL; } else { // При успешной инициализации создаем // идентификаторы строк для сервиса, раздела // и элемента данных hszService = DdeCreateStringHandle(idInst, szService, CP_WINANSI);
hszTopic = DdeCreateStringHandle(idInst, szTopic, CP_WINANSI);
hszItem = DdeCreateStringHandle(idInst, szItem, CP_WINANSI);
// Устанавливаем канал связи hConv = DdeConnect(idInst, hszService, hszTopic, (PCONVCONTEXT)NULL);
// Возвращаем идентификатор созданного канала связи return hConv; } }
//----------------------------------------------------- // Функция DDEClientClose // Завершение работы с библиотекой DDEML //----------------------------------------------------- void DDEClientClose(HCONV hConv) { // Закрываем канал связи if(hConv != NULL) { DdeDisconnect(hConv);
hConv = NULL; }
// Освобождаем идентификаторы строк DdeFreeStringHandle(idInst, hszService);
DdeFreeStringHandle(idInst, hszTopic);
DdeFreeStringHandle(idInst, hszItem );
// Удаляем переходник для функции обратного вызова FreeProcInstance(lpDdeProc);
}
//----------------------------------------------------- // Функция DDESend // Передача серверу текстовой строки //----------------------------------------------------- BOOL DDESend(HCONV hConv, LPSTR szString) { if(hConv != NULL) { // Создаем идентификатор данных hData = DdeCreateDataHandle (idInst, szString, lstrlen(szString) + 1, 0L, hszItem, CF_TEXT, 0);
// Запускаем транзакцию записи данных if(hData != NULL) hData = DdeClientTransaction((LPBYTE)hData, -1, hConv, hszItem, CF_TEXT, XTYP_POKE, 1000, &dwResult);
if(hData != NULL) return TRUE; else return FALSE; } else return FALSE; }
//----------------------------------------------------- // Функция DDEReceive // Получение от сервера текстовой строки //----------------------------------------------------- BOOL DDEReceive(HCONV hConv, LPSTR szBuf, int nBufSize) { // Запускаем транзакцию чтения данных if(hConv != NULL) { hData = DdeClientTransaction(NULL, 0, hConv, hszItem, CF_TEXT, XTYP_REQUEST, 1000, &dwResult);
// Получаем строку от сервера if(hData != NULL) { DdeGetData(hData, szBuf, nBufSize, 0L);
return TRUE; } else return FALSE; } else return FALSE; }
//----------------------------------------------------- // Функция DDEClientCallback // Функция обратного вызова для клиента DDEML //----------------------------------------------------- #pragma argsused HDDEDATA EXPENTRY DDEClientCallback(WORD wType, WORD wFmt, HCONV hConvX, HSZ hsz1, HSZ hsz2, HDDEDATA hData, DWORD dwData1, DWORD dwData2) { switch(wType) { case XTYP_DISCONNECT: return((HDDEDATA) NULL);
case XTYP_ERROR: break;
case XTYP_XACT_COMPLETE: break; } return((HDDEDATA)NULL);
}
Все символические константы определены в файле ddemlcf.hpp (листинг3.8).
Файл ddeml/ddemlcf.hpp
#define CM_HELPABOUT 301 #define CM_FILEEXIT 302 #define CM_EDITCOPY 303 #define CM_MSG_TO_SERVER 304 #define CM_MSG_FROM_SERVER 305
Файл описания ресурсов представлен в листинге 3.9.
Файл ddeml/ddemlcf.rc
#include "ddemlcl.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END POPUP "&Action" BEGIN MENUITEM "&Send Filename", CM_MSG_TO_SERVER MENUITEM "&Get Server Version", CM_MSG_FROM_SERVER END POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END APP_ICON ICON "ddemlcl.ico"
Файл определения модуля для приложения DDEMLCL вы найдете в листинге 3.10.
Файл ddeml/ddemlcf.def
NAME DDEMLCL DESCRIPTION 'Приложение DDEMLCL, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл hlpfile/hlpfile.hpj
[OPTIONS] errorlog = hlpfile.err title = Text Editor Help contents = cont compress = 1 warning = 3 report = 1
[FILES] hlpfile.rtf
[WINDOWS] main = "Text Editor Help",,,, (255,255,192 )
[CONFIG] BrowseButtons()
Посмотрев на листинг 4.1, вы можете заметить, что файл проекта состоит из отдельных секций, названия которых записаны в квадратных скобках. Внутри каждой секции определяются различные параметры.
Рассмотрим назначение секций файла проекта нашей справочной системы.
Файл hlpmore/hlpmore.hpj
[OPTIONS] errorlog = hlpmore.err title = Help Sample contents = cont compress = 1 warning = 3 report = 1 copyright = (C) Frolov A.V., 1995 icon=hlpmore.ico
[FILES] hlpmore.rtf
[WINDOWS] main = "Help Sample",,,, (255,255,192 ) grwnd = "Graphics",(220,200,700,300),,,(255,255,192)
[MAP] #define clock 200 #define cmd 201 #define key 202 #define file 203 #define edit 204 #define view 205
[CONFIG] BrowseButtons() CB("calc_btn","Calc","EP(`calc.exe', 0)") RR("hlpmore.dll", "MsgBox", "US")
В секции [OPTION] добавились строки copyright и icon. Первая из них предназначена для добавления сведений о разработчиках справочной системы в диалоговую панель, отображаемую при выборе строки "About Help..." из меню "Help" приложения winhelp.exe. Вторая указывает пиктограмму, в которую превращается главное окно winhelp.exe при его минимизации. С остальными строками этой секции вы уже знакомы.
В секции [WINDOWS] помимо основного окна main описано вторичное окно grwnd, на которое есть ссылка из раздела оглавления. Помимо цвета не сворачиваемой области для вторичного окна следует указать координаты верхнего левого угла, ширину и высоту.
Секция MAP определяет номера контекста, которые будут использованы для ссылки при вызове функции WinHelp.
В секции CONFIG вызываются три макрокоманды: BrowseButtons, CB и RR.
Макрокоманда BrowseButtons добавляет кнопки просмотра последовательностей логически связанных разделов.
Макрокоманда CB (полное имя CreateButton) добавляет в окно Toolbar новую кнопку с надписью "Calc", предназначенную для запуска калькулятора calc.exe.
Макрокоманда RR (полное имя RegisterRoutine) предназначена для регистрации макрокоманды MsgBox, определенной в DLL-библиотеке hlpmore.dll.
Файл hlpmore/helpmwh.cpp
// ---------------------------------------- // Демонстрация использования функции WinHelp // ---------------------------------------- #define STRICT #include <windows.h>
#include <mem.h>
#include "helpmwh.hpp"
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK _export DlgProc(HWND, UINT, WPARAM, LPARAM);
char const szClassName[] = "HelpmWhClass"; char const szWindowTitle[] = "WinHelp Demo"; HACCEL hAccel; BOOL bF1 = FALSE; HWND hwndDlg = NULL; DLGPROC lpfnDlgProc; HINSTANCE hInst;
// ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
if(!InitApp(hInstance)) return FALSE;
hInst = hInstance;
hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
if(!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Загружаем таблицу акселераторов hAccel = LoadAccelerators(hInstance, "APP_ACCEL");
while(GetMessage(&msg, 0, 0, 0)) { if(!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; WNDCLASS wc;
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU"; wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szClassName;
aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { // Это сообщение поступает, если меню или модальная // диалоговая панель находится в состоянии ожидания case WM_ENTERIDLE: { // Пользователь выделил строку меню // Проверяем, была ли нажата клавиша <F1>
if((wParam == MSGF_MENU) && ((GetKeyState(VK_F1) & 0x8000) != 0)) { // Если была нажата клавиша <F1>
, устанавливаем // флаг bF1, а затем имитируем нажатие // клавиши <Enter>
bF1 = TRUE; PostMessage(hwnd, WM_KEYDOWN, VK_RETURN, 0l);
}
// Если клавиша <F1>
была нажата во время отображения // диалоговой панели, вызываем раздел оглавления // справочной системы else if((wParam == MSGF_DIALOGBOX) && ((GetKeyState(VK_F1) & 0x8000) != 0)) { hwndDlg = (HWND)LOWORD(lParam);
WinHelp(hwnd, "hlpmore.hlp", HELP_CONTENTS, 0L);
} break; }
case WM_COMMAND: { switch (wParam) { // Отображаем раздел оглавления справочной системы case CM_HELPINDEX: { WinHelp(hwnd, "hlpmore.hlp", HELP_CONTENTS, 0L);
return 0L; } case CM_HELPCOMMANDS: { // Если строка меню была выбрана клавишей <F1>
, // выводим справочную информацию, в противном // случае выполняем команду if(bF1) { WinHelp(hwnd, "hlpmore.hlp", HELP_CONTENTS, 0L);
bF1 = FALSE; } else WinHelp(hwnd, "hlpmore.hlp", HELP_CONTEXT, (DWORD)IDN_cmd);
return 0L; }
case CM_HELPKEYBOARD: { if(bF1) { WinHelp(hwnd, "hlpmore.hlp", HELP_CONTENTS, 0L);
bF1 = FALSE; } else WinHelp(hwnd, "hlpmore.hlp", HELP_CONTEXT, (DWORD)IDN_key);
return 0L; }
case CM_HELPUSING_HELP: { if(bF1) { WinHelp(hwnd, "hlpmore.hlp", HELP_CONTENTS, 0L);
bF1 = FALSE; } else WinHelp(hwnd, "hlpmore.hlp", HELP_HELPONHELP, 0L);
return 0L; }
case CM_HELPABOUT: { if(bF1) { WinHelp(hwnd, "hlpmore.hlp", HELP_CONTENTS, 0L);
bF1 = FALSE; } else { lpfnDlgProc = (DLGPROC)MakeProcInstance( (FARPROC)DlgProc, hInst);
DialogBox(hInst, "ABOUT_DIALOG", hwnd, lpfnDlgProc);
} return 0; }
case CM_FILEPRINTER_SETUP: case CM_FILEPAGE_SETUP: case CM_FILEPRINT: case CM_FILESAVEAS: case CM_FILESAVE: case CM_FILEOPEN: case CM_FILENEW: { if(bF1) { WinHelp(hwnd,"hlpmore.hlp", HELP_CONTEXT, (DWORD)IDN_file);
bF1 = FALSE; } else MessageBox(hwnd, "Меню File", "WinHelp Demo", MB_OK);
return 0; }
case CM_EDITPASTE: case CM_EDITCOPY: case CM_EDITCUT: case CM_EDITUNDO: { if(bF1) { WinHelp(hwnd,"hlpmore.hlp", HELP_CONTEXT, (DWORD)IDN_edit);
bF1 = FALSE; } else MessageBox(hwnd, "Меню Edit", "WinHelp Demo", MB_OK);
return 0; }
case CM_VIEWNORMAL: case CM_VIEWZOOM: { if(bF1) { WinHelp(hwnd,"hlpmore.hlp", HELP_CONTEXT, (DWORD)IDN_view);
bF1 = FALSE; } else MessageBox(hwnd, "Меню View", "WinHelp Demo", MB_OK);
return 0; }
case CM_FILEEXIT: { DestroyWindow(hwnd);
return 0; }
default: return 0; } }
case WM_DESTROY: { PostQuitMessage(0);
return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}
// ===================================== // Функция DldProc // ===================================== #pragma argsused BOOL CALLBACK _export DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_INITDIALOG: { return TRUE; }
case WM_COMMAND: { switch(wParam) { case IDOK: case IDCANCEL: { EndDialog(hdlg, 0);
return TRUE; } } } } return FALSE; }
В функции WinMain после обычной инициализации загружается таблица акселераторов, состоящая из одного определения. Комбинации клавиш <Shift+F1>
ставится в соответствие сообщение CM_HELPINDEX, попадающее в функцию окна приложения при выборе строки "Index" из меню "Help". Используя эту комбинацию клавиш, пользователь сможет отобразить раздел оглавления справочной системы.
Функция окна обрабатывает сообщение WM_ENTERIDLE, которое и помогает нам организовать контекстно-зависимую подсказку.
Это сообщение попадает в функцию окна приложения в том случае, если активизировано меню или диалоговая панель, и эти органы управления находятся в состоянии ожидания.
Если сообщение WM_ENTERIDLE попало в функцию окна в результате отображения меню, параметр wParam этого сообщения содержит значение MSGF_MENU.
Если же указанное выше сообщение явилось результатом отображения диалоговой панели, параметр wParam содержит значение MSGF_DIALOGBOX.
В обоих случаях младшее слово параметра lParam содержит идентификатор окна (при отображении меню) или диалоговой панели (при отображении диалоговой панели).
Чем же нам может помочь сообщение WM_ENTERIDLE для организации контекстно-зависимой подсказки?
При обработке этого сообщения приложение может проверить состояние клавиши <F1>
. Если эта клавиша была нажата, можно установить глобальный флаг запроса подсказки и поместить в очередь приложения сообщение WM_KEYDOWN с кодом клавиши <Enter>
.
Результат будет такой же, как если бы пользователь выбрал строку меню при помощи клавиатуры, вначале выделив ее, а затем нажав клавишу <Enter>
. Отличие заключается в том, что теперь установлен глобальный флаг запроса подсказки. Обработчик, запускаемый при выборе строки меню, может проверить этот флаг, и если он установлен, сбросить его и запросить контекстно-зависимую подсказку. Так как каждой строке меню соответствует свой индивидуальный обработчик, с организацией контекстно-зависимой подсказки не возникнет никаких проблем.
Именно по такому алгоритму и происходит обработка сообщения WM_COMMAND в нашем приложении.
Все необходимые константы определены в файле helpmwh.hpp (листинг4.4).
Файл hlpmore/helpmwh.hpp
#define CM_HELPABOUT 401 #define CM_HELPUSING_HELP 402 #define CM_HELPCOMMANDS 403 #define CM_HELPKEYBOARD 404 #define CM_HELPINDEX 405
#define CM_EDITPASTE 501 #define CM_EDITCOPY 502 #define CM_EDITCUT 503 #define CM_EDITUNDO 504
#define CM_VIEWNORMAL 601 #define CM_VIEWZOOM 602
#define CM_FILEEXIT 701 #define CM_FILEPRINTER_SETUP 702 #define CM_FILEPAGE_SETUP 703 #define CM_FILEPRINT 704 #define CM_FILESAVEAS 705 #define CM_FILESAVE 706 #define CM_FILEOPEN 707 #define CM_FILENEW 708
#define IDN_cmd 201 #define IDN_key 202 #define IDN_file 203 #define IDN_edit 204 #define IDN_view 205
Обратите внимание на константы, имя которых начинается с префикса IDN. Это номера контекста разделов справочной системы hlpmore.hlp, определенные в файле проекта hlpmore.hpj (листинг 4.2).
Ресурсы приложения (меню, пиктограмма, акселераторы и диалоговая панель) определены в файле helpmwh.rc (листинг 4.5).
Файл hlpmore/helpmwh.rc
#include "helpmwh.hpp"
APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New", CM_FILENEW MENUITEM "&Open...", CM_FILEOPEN MENUITEM "&Save", CM_FILESAVE MENUITEM "Save &as...", CM_FILESAVEAS MENUITEM SEPARATOR MENUITEM "&Print...", CM_FILEPRINT MENUITEM "Page se&tup...", CM_FILEPAGE_SETUP MENUITEM "P&rinter setup...", CM_FILEPRINTER_SETUP MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END
POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", CM_EDITUNDO MENUITEM "Cu&t\tCtrl+X", CM_EDITCUT MENUITEM "&Copy\tCtrl+C", CM_EDITCOPY MENUITEM "&Paste\tCtrl+V", CM_EDITPASTE END
POPUP "&View" BEGIN MENUITEM "&Normal", CM_VIEWNORMAL MENUITEM "&Zoom...", CM_VIEWZOOM END
POPUP "&Help" BEGIN MENUITEM "&Index\tF1", CM_HELPINDEX MENUITEM "&Keyboard", CM_HELPKEYBOARD MENUITEM "&Commands", CM_HELPCOMMANDS MENUITEM "&Using help", CM_HELPUSING_HELP MENUITEM SEPARATOR MENUITEM "&About...", CM_HELPABOUT END END
AppIcon ICON helpmwh.ico
APP_ACCEL ACCELERATORS BEGIN VK_F1, CM_HELPINDEX, VIRTKEY, SHIFT END
ABOUT_DIALOG DIALOG 25, 34, 152, 67 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "WinHelp Demo" BEGIN CTEXT "WinHelp Demo\n" "Приложение HELPMWH\n" "(C) Frolov A.V., 1995", -1, 28, 9, 117, 26, WS_CHILD | WS_VISIBLE | WS_GROUP ICON "AppIcon", -1, 6, 14, 16, 16, WS_CHILD | WS_VISIBLE DEFPUSHBUTTON "OK", IDOK, 56, 43, 36, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP END
Файл определения модуля приложения HELPMWH представлен в листинге 4.6.
Файл hlpmore/helpmwh.def
NAME HELPMWH DESCRIPTION 'Приложение HELPMWH, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл hlpmore/hlpmore.cpp
#define STRICT #include <windows.h>
#include <mem.h>
#include <time.h>
#include "hlpmore.h"
BOOL RegisterEWclass(HMODULE hModule);
extern "C" LRESULT CALLBACK _export EWWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
char const szClassName[] = "EWHlpMoreClass";
// ======================================================== // Функция LibMain // Получает управление только один раз при // загрузке DLL-библиотеки в память // ======================================================== #pragma argsused int FAR PASCAL LibMain(HINSTANCE hModule, WORD wDataSegment, WORD wHeapSize, LPSTR lpszCmdLine) { // После инициализации локальной области данных // функция LibEntry фиксирует сегмент данных. // Его необходимо расфиксировать. if(wHeapSize != 0) // Расфиксируем сегмент данных UnlockData(0);
// Регистрируем класс для встроенного окна if(!RegisterEWclass(hModule)) return FALSE; else // Возвращаем TRUE. Это означает, что инициализация // DLL-библиотеки выполнена успешно return TRUE; }
// ======================================================== // Функция WEP // Получает управление только один раз при // удалении DLL-библиотеки из памяти // ======================================================== #pragma argsused int FAR PASCAL WEP(int bSystemExit) { return 1; }
// ======================================================== // Функция MsgBox // Выводит на экран диалоговую панель с сообщением // ======================================================== extern "C" void FAR PASCAL _export MsgBox(HWND hwnd, LPSTR szMsg) { MessageBox(hwnd, szMsg, "Message from DLL", MB_OK);
}
// ======================================================== // Функция RegisterEWclass // Регистрация класса для встроенного окна // ======================================================== BOOL RegisterEWclass(HMODULE hModule) { ATOM aWndClass; WNDCLASS wc;
memset(&wc, 0, sizeof(wc));
// Класс окна доступен для всех приложений wc.style = CS_GLOBALCLASS;
wc.lpszMenuName = NULL; wc.lpfnWndProc = (WNDPROC) EWWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hModule; wc.hIcon = NULL; wc.hCursor = LoadCursor(hModule, "AppCursor");
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szClassName;
aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ======================================================== // Функция EWWndProc // Функция встроенного окна // ======================================================== extern "C" LRESULT CALLBACK _export EWWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc; TEXTMETRIC tm; int cxChar, cyChar; QCI qci; QRI qri; static BYTE szCustomData[80]; static BYTE szBuf[80];
#define CLOCK_TIMER 1
switch (msg) { // Создание встроенного окна case WM_CREATE: { // Копируем строку параметров qci = (QCI)((CREATESTRUCT FAR *)lParam)->
lpCreateParams; if(lstrlen(qci->
szAuthorData) < 80) lstrcpy(szCustomData, qci->
szAuthorData);
// Добавляем к встроенному окну рамку SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd,GWL_STYLE) | WS_BORDER);
// Создаем таймер для часов SetTimer(hwnd, CLOCK_TIMER, 1000, NULL);
return 0; }
// Сообщение от таймера case WM_TIMER: { // Перерисовываем встроенное окно InvalidateRect(hwnd, NULL, FALSE);
return 0; }
// Обработчик сообщения WM_PAINT case WM_PAINT: { int nBufSize; time_t t; struct tm *ltime; RECT rc;
hdc = BeginPaint(hwnd, &ps);
// Определяем время и его отдельные компоненты time(&t);
ltime = localtime(&t);
// Подготавливаем буфер, заполняя его // строкой с текущим временем nBufSize = wsprintf(szBuf, "%02d:%02d:%02d", ltime->
tm_hour, ltime->
tm_min, ltime->
tm_sec);
// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Получаем координаты и размер окна GetClientRect(hwnd, &rc);
// Выбираем цвет текста для часов SetTextColor(ps.hdc, RGB(0,100,0));
// Выводим время в центре окна DrawText(hdc, (LPSTR)szBuf, nBufSize, &rc, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_SINGLELINE);
EndPaint(hwnd, &ps);
return 0; }
// Обработчик этого сообщения должен определить // размеры встроенного окна case EWM_QUERYSIZE: { // Определяем метрики фиксированного шрифта hdc = GetDC(hwnd);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
GetTextMetrics(hdc, &tm);
cxChar = tm.tmMaxCharWidth; cyChar = tm.tmHeight + tm.tmExternalLeading; ReleaseDC(hwnd, hdc);
// Сохраняем размеры встроенного окна ((LPPOINT)lParam)->
x = 10 * cxChar; ((LPPOINT)lParam)->
y = 2 * cyChar;
return 1; }
// В ответ на это сообщение функция встроенного окна // должна вернуть представление содержимого окна // в виде текста или битового изображения case EWM_RENDER: { long lPtr = 0l;
switch(wParam) { // Представление содержимого в виде текста case CF_TEXT: { // Заказываем глобальный блок памяти HGLOBAL hglb = GlobalAlloc(GMEM_MOVEABLE, 50);
if(hglb) { // Фиксируем блок памяти LPSTR lpszTempBuf = (LPSTR)GlobalLock(hglb);
// Копируем строку, содержащую текущее время lstrcpy(lpszTempBuf,"\r\n");
lstrcat(lpszTempBuf,szBuf);
lstrcat(lpszTempBuf,"\r\n");
// Расфиксируем блок памяти GlobalUnlock(hglb);
// Возвращаем идентификатор блока памяти, // содержащего текстовое представление lPtr = (long)hglb; } break; }
// Представление содержимого // в виде битового изображения case CF_BITMAP: { POINT pt; RECT rc; HBITMAP hbmp, hbmpOld; HBRUSH hbrush;
// Сохраняем указатель на структуру RENDERINFO qri = (QRI)lParam;
// Создаем контекст отображения, совместимый // с экраном монитора hdc = CreateCompatibleDC(NULL);
// Определяем размеры встроенного окна SendMessage(hwnd, EWM_QUERYSIZE, (WPARAM)hdc, (long)(LPPOINT)&pt);
// Создаем битовое изображение, имеющее размеры, // равные размерам встроенного окна rc.left = rc.top = 0; rc.right = pt.x; rc.bottom = pt.y; hbmp = CreateCompatibleBitmap(qri->
hdc, pt.x, pt.y);
// Выбираем битовое изображение // в контекст отображения hbmpOld = (HBITMAP)SelectObject(hdc,hbmp);
// Закрашиваем битовое изображение цветом фона hbrush = CreateSolidBrush(GetBkColor(hdc));
FillRect(hdc, &rc, hbrush);
DeleteObject(hbrush);
// Рисуем рамку по периметру битового изображения Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
// Рисуем время в центре битового изображения DrawText(hdc, (LPSTR)szBuf, lstrlen(szBuf), &rc, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_SINGLELINE);
// Выбираем старое битовое изображение hbmp = (HBITMAP)SelectObject(hdc, hbmpOld);
DeleteDC(hdc);
// Возвращаем идентификатор созданного // битового изображения lPtr = (long)hbmp;
break; } }
return lPtr; }
// Обрабатываем сообщение от левой клавиши мыши case WM_LBUTTONDOWN: { if(IDYES == MessageBox(hwnd, "Запустить clock.exe?", "Help Sample", MB_YESNO | MB_ICONQUESTION)) WinExec("clock.exe", SW_SHOW);
#define IDN_CLOCK 200
// Во временном окне отображаем раздел справочной // системы с номером контекста, равным IDN_CLOCK WinHelp(hwnd, "hlpmore.hlp", HELP_CONTEXTPOPUP, (DWORD)IDN_CLOCK);
return 0; }
// При удалении встроенного окна уничтожаем таймер case WM_DESTROY: { KillTimer(hwnd, CLOCK_TIMER);
return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}
Функция WinMain получает управление при загрузке DLL-библиотеки в память. Ее основная задача - регистрация класса встроенного окна, для чего вызывается функция RegisterEWclass, определенная в этой же библиотеке.
Функция RegisterEWclass выполняет обычную процедуру регистрации класса, однако при регистрации используется стиль CS_GLOBALCLASS. Класс, зарегистрированный с этим стилем, доступен для использования всеми запущенными приложениями - это глобальный класс.
Далее в исходном тексте DLL-библиотеки определена функция MsgBox, которая не имеет никакого отношения к встроенным окнам и используется как дополнительная макрокоманда, вызываемая при помощи ссылки из раздела оглавления. Задача функции MsgBox заключается в отображении сообщения, полученного ей через второй параметр.
Функция EWWndProc выполняет роль функции встроенного окна. Она обрабатывает все сообщения, поступающие от приложения winhelp.exe при создании, в процессе работы и при удалении встроенного окна.
Обработчик сообщения WM_CREATE копирует строку параметров, добавляет к окну рамку и создает таймер с периодом работы 1 сек.
Каждую секунду от таймера в функцию встроенного окна приходит сообщение WM_TIMER. Обработка этого сообщения заключается в полной перерисовке содержимого встроенного окна.
Обработчик сообщения WM_PAINT вызывается при создании окна и затем периодически, раз в секунду. Он рисует в центре окна текстовую строку текущего времени.
Обработчик сообщения EWM_QUERYSIZE возвращает размер окна, определяя его на основании метрик фиксированного системного шрифта.
Как мы уже говорили, когда приложению winhelp.exe требуется получить текстовое или графическое представление содержимого встроенного окна, оно посылает функции окна сообщение EWM_RENDER.
При запросе текстового представления наше приложение предоставляет идентификатор блока, содержащего текстовую строку, в которой записано текущее время. При запросе графического представления приложение создает битовое изображение и рисует на нем тот же самый текст, обводя его рамкой по периметру окна.
Все выполняемые при этом действия были описаны нами ранее в предыдущих томах "Библиотеки системного программиста", поэтому они должны быть понятны без дополнительных объяснений.
Обратим ваше внимание на обработчик сообщения WM_LBUTTONDOWN.
Этот обработчик был определен для обеспечения возможности использования встроенного окна для организации чувствительной точки (в исходном тексте раздела встроенное окно нельзя использовать для организации ссылки или запуска макрокоманд).
Когда пользователь расположит курсор мыши над поверхностью встроенного окна и нажмет левую клавишу мыши, функция встроенного окна получит обычное сообщение WM_LBUTTONDOWN. Обработчик этого сообщения выводит диалоговую панель с предложением запустить приложение clock.exe, при необходимости запускает это приложение, а затем вызывает функцию WinHelp.
Функция WinHelp используется для организации гипертекстовой ссылки на раздел с номером контекста IDN_CLOCK.Раздел будет отображен во временном окне.
Идентификаторы констант, а также все необходимые структуры данных определены в файле hlpmore.h (листинг 4.8).
Файл hlpmore/hlpmore.h
#define EWM_RENDER 0x706A #define EWM_QUERYSIZE 0x706B #define EWM_ASKPALETTE 0x706C #define EWM_FINDNEWPALETTE 0x706D
typedef struct tagCreateInfo { short idMajVersion; short idMinVersion; LPSTR szFileName; LPSTR szAuthorData; HANDLE hfs; DWORD coFore; DWORD coBack; } EWDATA, FAR* QCI;
typedef struct tagRenderInfo { RECT rc; HDC hdc; } RENDERINFO, FAR* QRI;
Файл описания ресурсов содержит ссылку на файл hlpmore.cur (листинг4.9). Файл hlpmore.cur содержит курсор, который используется для встроенного окна.
Файл hlpmore/hlpmore.rc
AppCursor CURSOR hlpmore.cur
Файл определения модуля DLL-библиотеки hlpmore.dll приведен в листинге 4.10.
Файл hlpmore/hlpmore.def
LIBRARY HLPMORE DESCRIPTION 'DLL-библиотека HLPMORE, (C) 1995, Frolov A.V.' EXETYPE windows CODE preload moveable discardable DATA preload moveable single HEAPSIZE 1024
Файл vxdsrv\vxdsrv.asm
; --------------------------------------------------- ; Виртуальный драйвер VXDSRV ; Version 1.1 ; --------------------------------------------------- ; Copyright (C) Александр Фролов 1995 ; --------------------------------------------------- ; Работает совместно с приложением dos2win.exe ; и DLL-библиотекой d2w.dll ; ; Позволяет запускать приложения Windows из виртуальной ; машины MS-DOS, из командной строки Norton Commander или ; аналогичной оболочки, работающей на виртуальной ; машине MS-DOS ; ; Выполняет перехват функции 4B00h прерывания int 21h ; и сохраняет полученные этой функцией командную строку ; и строку параметров, а также определенные отдельно ; текущий диск и текущий каталог в области памяти, ; зарезервированной DLL-библиотекой d2w.dll. ; Затем драйвер вызывает функцию, определенную ; в этой библиотеке и посылающую сообщение приложению ; dos2win.exe. Приняв сообщение, приложение dos2win.exe ; запускает программу, пользуясь данными, полученными ; от виртуального драйвера ; ---------------------------------------------------
.386p include vmm.inc
; Идентификатор драйвера VXDSRV. VXDSRV_Id equ 8000h
HiVers equ 1 ; верхний номер версии драйвера LoVers equ 1 ; нижний номер версии драйвера Vers equ ((HiVers shl 8) or LoVers) CFlag equ 1
; =================================================== ; Заголовок виртуального драйвера ; =================================================== Declare_Virtual_Device VXDSRV, HiVers, LoVers, \ VXDSRV_Control, VXDSRV_Id, Undefined_Init_Order, \ VXDSRV_V86API_Handler, VXDSRV_PMAPI_Handler,
; =================================================== ; Инициализация в реальном режиме ; =================================================== VXD_REAL_INIT_SEG
RealInit proc near
; Вывод "рекламной" текстовой строки mov ah, 9 mov dx, offset VxD_Hello int 21h
; Признак успешной инициализации mov ax, Device_Load_Ok
; Страницы физической памяти не резервируются xor bx, bx
; Данные для каждого экземпляра виртуальной машины ; не резервируются xor si, si
; Значение, передаваемое процедуре Sys_Critical_Init xor edx, edx ret RealInit endp
VxD_Hello db '*VXDSRV* Copyright (C) Alexandr Frolov 1995' db 0dh, 0ah, '$' VXD_REAL_INIT_ENDS
; =================================================== ; Системная критическая инициализация ; =================================================== VXD_ICODE_SEG BeginProc VXDSRV_Sys_Crit_Init
; Устанавливаем фильтр для прерывания int 21h mov eax, 21h mov esi, offset32 V86_Int21_Handler VMMcall Hook_V86_Int_Chain
clc ; признак успешного завершения ret EndProc VXDSRV_Sys_Crit_Init VXD_ICODE_ENDS
; =================================================== ; Зафиксированный сегмент кода ; =================================================== VxD_LOCKED_CODE_SEG
; --------------------------------------------------- ; Определение управляющих процедур ; --------------------------------------------------- BeginProc VXDSRV_Control
; Процедура системной критической инициализации Control_Dispatch Sys_Critical_Init, VXDSRV_Sys_Crit_Init
clc ret EndProc VXDSRV_Control
VxD_LOCKED_CODE_ENDS
; =================================================== ; Сегмент данных ; =================================================== VxD_DATA_SEG
; --------------------------------------------------- ; Таблица адресов функций API драйвера ; --------------------------------------------------- VXDSRV_API_Call label dword
dd offset32 vxdapiGetVersion ; AX=0 dd offset32 vxdapiRegisterWnd ; AX=1 dd offset32 vxdapiUnregisterWnd ; AX=2
VXDSRV_API_MaxCall EQU ($ - VXDSRV_API_Call) / 4
CallbackSel dw 0h ; селектор функции обратного вызова CallbackOff dd 0h ; смещение функции обратного вызова V86CallFlag dd 0 ; вызов из виртуальной машины V86 CallbackBuf dd 0 ; адрес буфера для передачи строки
flatpCmdLine dd 0 ; адрес командной строки flatpParmLine dd 0 ; адрес строки параметров hSem dd 0 ; идентификатор семафора
nCurDisk db 0 ; текущий диск szCurPath db 64 dup(0) ; текущий каталог
; Сегмент буфера для получения текущего пути wPathSeg dw 0
; Соответствующий этому буферу FLAT-адрес flatpPathBuf dd 0
VxD_DATA_ENDS
; =================================================== ; Перемещаемый сегмент кода ; =================================================== VxD_CODE_SEG
; --------------------------------------------------- ; Вход API для виртуальных машин V86 ; --------------------------------------------------- BeginProc VXDSRV_V86API_Handler mov eax, 1 ; признак машины V86 mov V86CallFlag, eax
; Вызываем универсальное API драйвера call VXDSRV_API_Handler ret EndProc VXDSRV_V86API_Handler
; --------------------------------------------------- ; Вход API для виртуальных машин защищенного режима ; --------------------------------------------------- BeginProc VXDSRV_PMAPI_Handler mov eax, 0 ; признак защищенного режима mov V86CallFlag, eax
call VXDSRV_API_Handler ret EndProc VXDSRV_PMAPI_Handler
; --------------------------------------------------- ; Универсальное API драйвера, вызывается как для ; режима VM86, так и для защищенного режима ; --------------------------------------------------- BeginProc VXDSRV_API_Handler
; Загружаем номер функции API movzx eax, [ebp.Client_AX]
; Проверяем этот номер на допустимость cmp eax, VXDSRV_API_MaxCall jae short InvalidNumber
; В случае успеха сбрасываем флаг переноса и ; вызываем функцию по таблице адресов and [ebp.Client_EFlags], NOT CFlag call VXDSRV_API_Call[eax * 4] ret InvalidNumber: or [ebp.Client_EFlags], CFlag ret EndProc VXDSRV_API_Handler
; --------------------------------------------------- ; vxdapiGetVersion, номер = 0 ; Возвращает в AX номер версии: ; AH - старший номер, AL - младший номер ; --------------------------------------------------- BeginProc vxdapiGetVersion mov [ebp.Client_AX], Vers clc ; успешное завершение ret EndProc vxdapiGetVersion
; --------------------------------------------------- ; vxdapiRegisterWnd, номер = 1 ; --------------------------------------------------- BeginProc vxdapiRegisterWnd
; Можно вызывать только из защищенного режима mov eax, V86CallFlag cmp eax, 0 jnz short RW_CallFromRealMode
; Сохраняем смещение и селектор процедуры обратного ; вызова, расположенной в фиксированном сегменте ; кода DLL-библиотеки d2w.dll
movzx eax, [ebp.Client_CX] ; смещение mov [CallbackOff], eax mov ax, [ebp.Client_DX] ; селектор mov [CallbackSel], ax
; Преобразуем адрес буфера во flat-адрес
mov ax, (Client_SI shl 8) + Client_DI VMMcall Map_Flat mov [CallbackBuf], eax
; Создаем семафор с начальным значением 1 mov ecx, 1 VMMcall Create_Semaphore mov hSem, eax ; сохраняем идентификатор семафора
clc ret
RW_CallFromRealMode: stc ret EndProc vxdapiRegisterWnd
; --------------------------------------------------- ; vxdapiUnregister, номер = 2 ; --------------------------------------------------- BeginProc vxdapiUnregisterWnd
; Можно вызывать только из защищенного режима mov eax, V86CallFlag cmp eax, 0 jnz short UW_CallFromRealMode
mov eax, 0 mov [CallbackOff], eax mov [CallbackSel], ax mov [CallbackBuf], eax
; Уничтожаем семафор mov eax, hSem VMMcall Destroy_Semaphore
clc ret
UW_CallFromRealMode: stc ret EndProc vxdapiUnregisterWnd
; --------------------------------------------------- ; StrCpy, копирование строки ASCIIZ ; [ecx] - исходный адрес ; [edx] - адрес буфера ; --------------------------------------------------- BeginProc StrCpy push eax push ecx push edx
StrCpyLoop: mov al, [ecx] mov [edx], al cmp al, 0 jz short StrCpyEnd inc ecx inc edx jmp short StrCpyLoop StrCpyEnd:
pop edx pop ecx pop eax ret EndProc StrCpy
; --------------------------------------------------- ; CopyParm, копирование строки параметров ; --------------------------------------------------- BeginProc CopyParm push eax push ebx push ecx push edx
; Вычисляем адрес строки параметров в буфере mov edx, CallbackBuf add edx, 65 + 128
; Определяем размер строки параметров mov ecx, flatpParmLine movzx ebx, byte ptr [ecx] inc ecx
; Если параметров нет, закрываем строку нулем cmp ebx, 0 jz short ParmCopyEnd
; Цикл копирования строки параметров ParmCopyLoop: cmp ebx, 0 jz short ParmCopyEnd
mov al, [ecx] mov [edx], al
dec ebx inc ecx inc edx jmp short ParmCopyLoop
ParmCopyEnd:
; Закрываем строку нулем mov al, 0 mov [edx], al
pop edx pop ecx pop ebx pop eax ret EndProc CopyParm
; --------------------------------------------------- ; V86_Int21_Handler ; Фильтр для функции 4B00h прерывания INT 21h, ; вызываемого из виртуальной машины MS-DOS ; (эта функция выполняет запуск программы MS-DOS) ; --------------------------------------------------- BeginProc V86_Int21_Handler
pushad
; Проверяем номер функции. Нас интересует только ; запуск программ mov ax, word ptr [ebp.Client_AX] cmp ax, 4B00h jnz HandlerExit
; Если окно приложения dos2win не зарегистрировано, ; ничего не делаем mov eax, CallbackBuf cmp eax, 0 jz HandlerExit
; Если запускается программа MS-DOS, ничего ; не делаем call short IsWindowsApp jz HandlerExit
; Для исключения реентерабельных вызовов выполняем ; ожидание семафора mov eax, hSem mov ecx,(Block_Enable_Ints OR Block_Svc_If_Ints_Locked) VMMcall Wait_Semaphore
; Получаем текущий диск и каталог call short GetCurDir
; Сохраняем номер текущего диска mov edx, CallbackBuf mov al, nCurDisk mov byte ptr [edx], al
; Определяем FLAT-адрес командной строки mov ax, (Client_DS shl 8) + Client_DX VMMcall Map_Flat mov flatpCmdLine, eax
; Определяем FLAT-адрес блока EPB mov ax, (Client_ES shl 8) + Client_BX VMMcall Map_Flat
; Загружаем в DX:BX адрес строки параметров mov bx, [eax + 2] mov dx, [eax + 4]
; Определяем FLAT-адрес строки параметров
push dword ptr [ebp.Client_ES] push [ebp.Client_EBX]
mov [ebp.Client_ES], dx mov [ebp.Client_BX], bx mov ax, (Client_ES shl 8) + Client_BX VMMcall Map_Flat mov flatpParmLine, eax
pop [ebp.Client_EBX] pop dword ptr [ebp.Client_ES]
; Копируем командную строку в буфер, который ; находится в DLL-библиотеке d2w.dll mov ecx, flatpCmdLine mov edx, CallbackBuf add edx, 65 call short StrCpy
; Выполняем копирование строки параметров call short CopyParm
; Определяем идентификатор текущей VM VMMCall Get_Cur_VM_Handle mov edx, ebx
; Определяем идентификатор системной VM VMMcall Get_Sys_VM_Handle
; Планируем вызов функции обратного вызова mov esi, offset32 CallbackProc VMMcall Schedule_VM_Event
popad
; Если запускается приложение Windows, блокируем ; выполнение прерывания INT 21h в виртуальной ; машине MS-DOS clc
ret
HandlerExit: popad
; Если запускается программа MS-DOS, наш драйвер ; не мешает этому процессу stc
ret EndProc V86_Int21_Handler
; --------------------------------------------------- ; CallbackProc ; Функция обратного вызова ; Вызывается в системной VM по запросу фильтра ; прерывания INT 21h, установленного нашим драйвером ; --------------------------------------------------- BeginProc CallbackProc
; Сохраняем состояние системной VM Push_Client_State
; Начинаем вложенное выполнение VMMcall Begin_Nest_Exec
; Записываем в стек системной VM параметр - ; версию нашего VxD-драйвера mov ax, Vers VMMcall Simulate_Push
; Вызов функции обратного вызова, определенной ; в DLL-библиотеке d2w.dll mov edx, [CallbackOff] mov cx, [CallbackSel] VMMcall Simulate_Far_Call
; Выполняем вызов и восстановление состояния ; системной VM VMMcall Resume_Exec VMMcall End_Nest_Exec Pop_Client_State
; Сбрасываем семафор, разрешая обработку ; следующего прерывания INT 21h mov eax, hSem VMMcall Signal_Semaphore
ret EndProc CallbackProc
; --------------------------------------------------- ; GetCurDir ; Определение текущего диска и текущего каталога ; в виртуальной машине MS-DOS, выполняющей запуск ; программы с помощью прерывания INT 21h ; --------------------------------------------------- BeginProc GetCurDir
pushad
; Сохраняем состояние VM MS-DOS Push_Client_State
VMMcall Begin_Nest_Exec
; Определяем номер текущего диска в VM MS-DOS ; Используем для этого функцию 1900h прерывания ; INT 21h. Номер диска возвращается в регистре AL mov ax, 1900h mov word ptr [ebp.Client_AX], ax mov eax, 21h VMMcall Exec_Int
; Сохраняем номер текущего диска в VM MS-DOS mov ax, word ptr [ebp.Client_AX] mov nCurDisk, al
; Для определения текущего пути VM MS-DOS ; заказываем буфер размером 64 байта в адресном ; пространстве VM MS-DOS, пользуясь функцией 4800h ; прерывания INT 21h mov ax, 4800h mov word ptr [ebp.Client_AX], ax
; Размер буфера задается в параграфах (по 16 байт) mov ax, 0004h mov word ptr [ebp.Client_BX], ax mov eax, 21h VMMcall Exec_Int
; Сохраняем сегментную компоненту адреса ; (смещение полученного буфера всегда равно 0) mov ax, word ptr [ebp.Client_AX] mov wPathSeg, ax
; В маловероятном случае, когда в VM MS-DOS ; совсем нет свободной памяти (даже 64 байт), ; мы не заполняем строку параметров: ; если памяти нет, все равно нельзя запустить ; ни программу MS-DOS, ни приложение Windows mov ax, wPathSeg cmp ax, 0 jz DoNotGetPath
; Определяем FLAT-адрес полученного буфера mov [ebp.Client_ES], ax mov [ebp.Client_BX], 0 mov ax, (Client_ES shl 8) + Client_BX VMMcall Map_Flat mov flatpPathBuf, eax
; Получаем строку текущего каталога VM MS-DOS, ; для чего вызываем функцию 4700h прерывания ; INT 21h. Перед вызовом этой функции нужно ; предоставить в DS:SI адрес буфера размером ; 64 байта, в который и будет записана строка mov ax, wPathSeg mov word ptr [ebp.Client_DS], ax mov ax, 0 mov word ptr [ebp.Client_SI], ax mov ax, 0 mov word ptr [ebp.Client_DX], ax mov ax, 4700h mov word ptr [ebp.Client_AX], ax mov eax, 21h VMMcall Exec_Int
; Копируем строку текущего каталога в буфер ; CallbackBuf со смещением 1 байт mov ecx, flatpPathBuf mov edx, CallbackBuf add edx, 1 call short StrCpy
; Освобождаем буфер, полученный в адресном ; пространстве VM MS-DOS mov ax, wPathSeg mov word ptr [ebp.Client_ES], ax mov ax, 4900h mov word ptr [ebp.Client_AX], ax mov eax, 21h VMMcall Exec_Int
VMMcall End_Nest_Exec
DoNotGetPath:
Pop_Client_State popad ret EndProc GetCurDir
; --------------------------------------------------- ; IsWindowsApp ; Проверка файла запускаемой программы ; ; Функция возвращает флаг Z = 0, если запускается ; приложение Windows в формате NE или PE, и Z != 0, ; если запускается программа MS-DOS ; ; Входные параметры: ; [ebp.Client_DS] - сегмент буфера, в котором находится ; путь к запускаемой программе ; [ebp.Client_DX] - смещение буфера, в котором находится ; путь к запускаемой программе ; --------------------------------------------------- ; Copyright (C) Сергей Ноженко, 1995 ; --------------------------------------------------- BeginProc IsWindowsApp
Push_Client_State VMMcall Begin_Nest_Exec
; Индикатор, который будет равен нулю на выходе ; из функции, если запускаемый файл не содержит ; приложение Windows sub esi, esi
; Открываем файл с программой mov word ptr [ebp.Client_AX], 3D00h mov eax, 21h VMMcall Exec_Int
; Проверка ошибки test word ptr [ebp.Client_Flags], 1 jnz EndNest
; Сохраняем идентификатор файла mov ax, word ptr [ebp.Client_AX] push eax
; Заказываем 4 параграфа памяти mov word ptr [ebp.Client_AX], 4800h mov word ptr [ebp.Client_BX], 4 mov eax, 21h VMMcall Exec_Int
; Проверяем, выделена ли нам память mov ax, word ptr [ebp.Client_AX] or ax, ax jz CloseFile
; Читаем старый заголовок исполнимого файла ; (заголовок программы MS-DOS) ; Готовим ES для вызова функции освобождения ; памяти mov word ptr [ebp.Client_DS], ax mov word ptr [ebp.Client_ES], ax mov word ptr [ebp.Client_AX], 3F00h pop eax mov word ptr [ebp.Client_BX], ax mov word ptr [ebp.Client_CX], 40h ; читаем 40h байт mov word ptr [ebp.Client_DX], 0 mov eax, 21h VMMcall Exec_Int
; Проверка ошибки test word ptr [ebp.Client_Flags], 1 jnz FreeMem
; Если размер файла меньше 40h байт, это ; не приложение Windows cmp word ptr [ebp.Client_AX], 40h jl short FreeMem
; Получаем FLAT-адрес mov ax, (Client_DS shl 8) + Client_DX VMMcall Map_Flat
; Проверяем сигнатуру EXE-файла. Если это не ; EXE-файл, то мы имеем дело не с приложением ; Windows cmp word ptr [eax], "ZM" jne short FreeMem
; Проверяем смещение таблицы relocation table. ; Если оно меньше чем 40h, это программа MS-DOS cmp word ptr [eax + 18h], 40h jl short FreeMem
mov edi, eax
; Ищем заголовок NewEXE mov word ptr [ebp.Client_AX], 4200h mov bx, word ptr [eax + 3Ch] mov word ptr [ebp.Client_DX], bx mov bx, word ptr [eax + 3Eh] mov word ptr [ebp.Client_CX], bx mov eax, 21h VMMcall Exec_Int
test word ptr [ebp.Client_Flags], 1 jnz short FreeMem
; Читаем первое слово заголовка NewEXE mov word ptr [ebp.Client_AX], 3F00h mov word ptr [ebp.Client_CX], 2 mov word ptr [ebp.Client_DX], 0 mov eax, 21h VMMcall Exec_Int
test word ptr [ebp.Client_Flags], 1 jnz short FreeMem cmp word ptr [ebp.Client_AX], 2 jne short FreeMem
; Проверяем сигнатуру исполнимого сегмента. ; Допустимы сигнатуры "NE" (segmented executable) ; и "PE" (portable executable) cmp word ptr [edi], "EN" je short WinApp
cmp word ptr [edi], "EP" jne short FreeMem
WinApp:
; Устанавливаем индикатор - запускается ; приложение Windows inc esi
; Освобождаем память FreeMem: mov word ptr [ebp.Client_AX], 4900h mov eax, 21h VMMcall Exec_Int
; Закрываем файл CloseFile: mov word ptr [ebp.Client_AX], 3E00h mov eax, 21h VMMcall Exec_Int
EndNest: VMMcall End_Nest_Exec Pop_Client_State
or esi, esi ret EndProc IsWindowsApp
VxD_CODE_ENDS END
Заголовок виртуального драйвера VXDSRV мы описали раньше, поэтому не будем на нем останавливаться.
Файл vxdsrv\vxdsrv.def
library VXDSRV description 'VXDSRV Virtual Device, (C) A.V. Frolov, 1995' exetype DEV386 segments _LTEXT PRELOAD NONDISCARDABLE _LDATA PRELOAD NONDISCARDABLE _ITEXT CLASS 'ICODE' DISCARDABLE _IDATA CLASS 'ICODE' DISCARDABLE _TEXT CLASS 'PCODE' NONDISCARDABLE _DATA CLASS 'PCODE' NONDISCARDABLE exports VXDSRV_DDB @1
Обратите внимание, что тип загрузочного модуля указан в операторе exetype как DEV386. Далее, описаны все необходимые сегменты.
Виртуальный драйвер должен экспортировать только одну точку входа. Имя этой точки входа образуется автоматически макрокомандой определения драйвера добавлением к имени драйвера суффикса "_DDB". Номер точки входа должен быть равен 1.
Файл vxdsrv\m.bat
set include=g:\inc masm5 -p -w2 vxdsrv; link386 vxdsrv,vxdsrv.386,,,vxdsrv.def addhdr vxdsrv.386
Перед запуском ассемблера необходимо установить переменную среды include так, чтобы она содержала путь к каталогу с include-файлами.
Файл dos2win\dos2win.cpp
// ====================================================== // Приложение DOS2WIN // // Запуск приложений Windows из // коммандной строки MS-DOS // // Используется совместно с DLL-библиотекой // d2w.dll и VxD-драйвером VXDSRV.386 версии 1.1 // ------------------------------------------------------ // Copyright (C) 1995 Alexandr Frolov // ====================================================== #define STRICT #include <windows.h>
#include <mem.h>
#include <string.h>
#include <dir.h>
#include "dos2win.hpp" #include "vxdcall.hpp"
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
extern "C" void FAR PASCAL _export WinAppStart(HWND hwnd, LPSTR szPath);
extern "C" void FAR PASCAL _export RegisterWnd(HWND hwnd);
BOOL CALLBACK _export DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam);
char const szClassName[] = "DOS2WINClass"; char const szWindowTitle[] = "dos2win"; HINSTANCE hInst; HMENU hmenuSystemMenu;
struct LOADPARMS { WORD segEnv; // среда LPSTR lpszCmdLine; // коммандная строка LPWORD lpwShow; // режим отображения LPWORD lpwReserved; // зарезервировано }; struct LOADPARMS parms;
// Точка входа API VxD-драйвера VXDAPI vxd;
WORD awShow[2] = { 2, SW_SHOW }; LPSTR lpszCmd, lpszParm; char szBuf[256]; char szCurPath[128];
DLGPROC lpfnDlgProc;
// ====================================================== // Функция WinMain // ====================================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; HWND hwnd;
if(hPrevInstance) // только одна копия приложения return FALSE;
if(!InitApp(hInstance)) return FALSE;
hInst = hInstance;
hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
if(!hwnd) return FALSE;
// Получаем точку входа API VxD-драйвера vxd = vxdGetDeviceAPI(VXD_ID);
// Если VxD-драйвер не загружен, выводим // сообщение об ошибке if(vxd == NULL) { MessageBox(hwnd, "Error Loading DOS2WIN\n" "VxD Driver VXDSRV.386 not found", "DOS2WIN", MB_OK | MB_ICONHAND);
return FALSE; }
// Отображаем окно в виде пиктограммы ShowWindow(hwnd, SW_SHOWMINIMIZED);
UpdateWindow(hwnd);
while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam; }
// ====================================================== // Функция InitApp // Выполняет регистрацию класса окна // ====================================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; WNDCLASS wc;
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU"; wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "AppIcon");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szClassName;
aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ====================================================== // Функция WndProc // ====================================================== LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: { // Регистрируем окно приложения в DLL-библиотеке d2w.dll RegisterWnd(hwnd);
// Изменяем системное меню приложения hmenuSystemMenu = GetSystemMenu(hwnd, FALSE);
AppendMenu(hmenuSystemMenu, MF_SEPARATOR, 0, 0);
AppendMenu(hmenuSystemMenu, MF_BYCOMMAND | MF_ENABLED, CM_SYSABOUT, "&About...");
return 0; }
// Удаляем лишние строки из системного меню приложения case WM_INITMENUPOPUP: { RemoveMenu(hmenuSystemMenu, SC_RESTORE, MF_BYCOMMAND);
RemoveMenu(hmenuSystemMenu, SC_MAXIMIZE, MF_BYCOMMAND);
RemoveMenu(hmenuSystemMenu, SC_MINIMIZE, MF_BYCOMMAND);
RemoveMenu(hmenuSystemMenu, SC_SIZE, MF_BYCOMMAND);
break; }
case WM_SYSCOMMAND: { switch(wParam & 0xfff0) { // Блокируем изменение размеров окна case SC_RESTORE: case SC_MAXIMIZE: case SC_SIZE: return 0;
// Выводим диалоговую панель "About" case CM_SYSABOUT: { lpfnDlgProc = (DLGPROC)MakeProcInstance((FARPROC)DlgProc, hInst);
DialogBox(hInst, "ABOUT", hwnd, lpfnDlgProc);
FreeProcInstance((FARPROC)lpfnDlgProc);
return 0; }
default: return DefWindowProc(hwnd, msg, wParam, lParam);
} }
// Это сообщение посылается DLL-библиотекой d2w.dll, // когда VxD-драйвер фиксирует запуск программы любой // виртуальной машиной MS-DOS. // Параметр lParam содержит указатель на строку, // имеющую следующий формат: // // Смещение Размер Описание // 0 1 Номер дискового устройства, которое // было текущим при запуске программы // 1 64 Каталог, который был текущим // при запуске программы // 65 128 Путь к запускаемой программе MS-DOS // или к запускаемому приложению Windows // 193 128 параметры запускаемой программы // или приложения //
case WM_STARTWINAPP: { // Проверка указателя if(lParam == NULL) return 0;
// Путь к запускаемой программе lpszCmd = (LPSTR)lParam + 65;
// Указатель на строку параметров lpszParm = (LPSTR)lParam + 65 + 128;
// Формируем буфер параметров для функции LoadModule // Первый байт резервируем для размера строки lstrcpy(szBuf, (LPCSTR)" ");
lstrcat(szBuf, (LPCSTR)lpszParm);
// Записываем размер строки *szBuf = (BYTE)lstrlen(lpszParm);
// Заполняем структуру LOADPARMS if(lstrlen(lpszParm) != 0) parms.lpszCmdLine = (LPSTR)szBuf; else parms.lpszCmdLine = (LPSTR)"";
parms.segEnv = 0; parms.lpwShow = (LPWORD) awShow; parms.lpwReserved = (LPWORD) NULL;
// Устанавливаем такой же текущий диск, // какой был текущим при запуске программы // из виртуальной машины MS-DOS setdisk(*(LPSTR)lParam);
// Устанавливаем такой же текущий каталог, // какой был текущим при запуске программы // из виртуальной машины MS-DOS szCurPath[0] = (char)((*(LPSTR)lParam) + 'A');
lstrcpyn((LPSTR)szCurPath + 1, (LPSTR)":\\", 3);
lstrcat((LPSTR)szCurPath, (LPSTR)lParam + 1);
chdir(szCurPath);
// Выполняем попытку запуска программы. // Если запускается приложение Windows, оно // будет запущено. Если же был выполнен запуск // программы MS-DOS, функция LoadModule вернет // код ошибки, который мы игнорируем LoadModule(lpszCmd, &parms);
return 0; }
case WM_DESTROY: { PostQuitMessage(0);
return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}
// ====================================================== // Функция vxdGetDeviceAPI // Получение адреса точки входа API для // VxD-драйвера, идентификатор которого // задан параметром vxd_id // ====================================================== VXDAPI vxdGetDeviceAPI(unsigned short vxd_id) { unsigned axreg, dxreg;
asm push di asm push es asm mov ax, 0x1684 asm mov bx, vxd_id asm xor di, di asm mov es, di asm int 0x2f asm mov ax, di asm mov dx, es asm pop es asm pop di
asm mov axreg, ax asm mov dxreg, dx
return((VXDAPI)MAKELP(dxreg, axreg));
}
// ====================================================== // Функция DldProc // Обработка сообщений диалоговой панели "About" // ====================================================== #pragma argsused
BOOL CALLBACK _export DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_INITDIALOG: return TRUE;
case WM_COMMAND: { switch(wParam) { case IDOK: case IDCANCEL: { EndDialog(hdlg, 0);
return TRUE; } } } } return FALSE; }
В этом приложении нет ничего особенного, за исключением того, что его окно всегда отображается в виде пиктограммы.
На этапе инициализации приложение определяет точку входа виртуального драйвера VXDSRV и отказывается работать, если этот драйвер не загружен.
Во время обработки сообщения WM_CREATE приложение вызывает функцию RegisterWnd, которая находится в DLL-библиотеке d2w.dll. В качестве единственного параметра функции передается идентификатор главного окна приложения, необходимый для передачи приложению сообщения WM_STARTWINAPP.
Вместе с сообщением WM_STARTWINAPP через параметр lParam передается адрес буфера, содержащего всю информацию, необходимую для запуска приложения Windows.Исходный текст обработчика сообщения содержит описание формата буфера.
Обработчик сообщения WM_STARTWINAPP формирует командную строку и структуру параметров, необходимую для вызова функции LoadModule, с помощью которой выполняется запуск приложения.
Непосредственно перед запуском приложение DOS2WIN изменяет текущий диск и каталог в соответствии со значениями, переданными в первых двух полях буфера.
Файл dos2win.hpp содержит определение сообщения WM_STARTWINAPP, констант CM_SYSABOUT и VXD_ID (листинг 5.5).
Файл dos2win\dos2win.hpp
#define WM_STARTWINAPP (WM_USER + 100) #define CM_SYSABOUT 0x8880 #define VXD_ID 0x8000
В файле vxdcall.hpp (листинг 5.6) находятся символические константы для вызова программного интерфейса виртуального драйвера VXDSRV, определение типа VXDAPI (точка входа программного интерфейса виртуального драйвера) и прототип функции vxdGetDeviceAPI.
Файл dos2win\vxdcall.hpp
#define vxdapiGetVersion 0 #define vxdapiRegisterWnd 1 #define vxdapiUnregisterWnd 2
typedef unsigned long (far *VXDAPI)(void);
VXDAPI vxdGetDeviceAPI(unsigned short vxd_id);
Файл ресурсов приложения DOS2WIN приведен в листинге 5.7. В нем определены пиктограмма и диалоговая панель "About DOS2WIN".
Файл dos2win\dos2win.rc
#include "dos2win.hpp"
AppIcon ICON "dos2win.ico"
ABOUT DIALOG 24, 40, 151, 134 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About DOS2WIN" BEGIN ICON "APPICON", -1, 4, 7, 16, 16, WS_CHILD | WS_VISIBLE DEFPUSHBUTTON "OK", IDOK, 59, 112, 33, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP LTEXT "Starting Windows Application" " from MS-DOS Command Prompt\n\nVersion 1.1", -1, 30, 20, 120, 36, WS_CHILD | WS_VISIBLE | WS_GROUP LTEXT "DOS2WIN", -1, 30, 7, 97, 7, WS_CHILD | WS_VISIBLE | WS_GROUP CONTROL "", -1, "static", SS_BLACKRECT | WS_CHILD | WS_VISIBLE, 31, 58, 114, 1 LTEXT "Copyright © 1995 Alexandr Frolov" "\n\nfrolov@glas.apc.org", -1, 30, 65, 114, 28, WS_CHILD | WS_VISIBLE | WS_GROUP CONTROL "", -1, "static", SS_BLACKRECT | WS_CHILD | WS_VISIBLE, 31, 96, 114, 1 END
Файл определения модуля приложения представлен в листинге 5.8.
Файл dos2win\dos2win.def
NAME DOS2WIN DESCRIPTION 'Приложение DOS2WIN, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл dos2win\d2w.cpp
// ====================================================== // DLL-библиотека d2w.dll // // Используется совместно с приложением dos2win.exe // и VxD-драйвером VXDSRV.386 версии 1.1 // ------------------------------------------------------ // Copyright (C) 1995 Alexandr Frolov // ====================================================== #define STRICT #include <windows.h>
#include <mem.h>
#include "d2w.h" #include "vxdcall.hpp"
extern "C" void FAR PASCAL _export WinAppStart(WORD wVxDVersion);
extern "C" void FAR PASCAL _export RegisterWnd(HWND hwnd);
void UnregisterWnd(VXDAPI vxdEntry);
// Точка входа API VxD-драйвера VXDAPI vxdApi = NULL;
// Идентификатор окна приложения dos2win.exe HWND hwndDos2Win;
// В этот буфер VXD-драйвер VXDSRV.386 будет записывать // текущий диск, текущий каталог, путь к запускаемой // программе и параметры, использованные виртуальной // машиной MS-DOS в процессе запуска программы BYTE szCallbackBuf[350];
// Очередь запросов на запуск программ из VM MS-DOS // Организована в виде массива BYTE szCmd[6][350];
// Номер строки параметров в массиве szCmd int nCmdLine = 0;
// ======================================================== // Функция LibMain // Получает управление только один раз при // загрузке DLL-библиотеки в память // ======================================================== #pragma argsused int FAR PASCAL LibMain(HINSTANCE hModule, WORD wDataSegment, WORD wHeapSize, LPSTR lpszCmdLine) { if(wHeapSize != 0) UnlockData(0);
// Получаем точку входа API VxD-драйвера vxdApi = vxdGetDeviceAPI(VXD_ID);
return TRUE; }
// ======================================================== // Функция WEP // ======================================================== #pragma argsused int FAR PASCAL WEP(int bSystemExit) { // Если DLL-библиотека выгружается из памяти, // выполняем отключение VxD-драйвера if(vxdApi != NULL) UnregisterWnd(vxdApi);
return 1; }
// ======================================================== // Функция обратного вызова WinAppStart // Вызывается из VxD-драйвера VXDSRV.386 // Копирует строку, содержащую все характеристики // запускаемой программы в очередь запросов, затем // посылает сообщение WM_STARTWINAPP в приложение // dos2win.exe, которое выполняет запуск. // ======================================================== extern "C" void FAR PASCAL _export WinAppStart(WORD wVxDVersion) { // Проверяем версию VxD-драйвера, передаваемую // через параметр wVxDVersion if(wVxDVersion != 0x0101) return;
// Копируем текущий каталог, путь к запускаемой // программе и ее параметры _fmemcpy((LPVOID)szCmd[nCmdLine], (LPVOID)szCallbackBuf, 65 + 256);
// Посылаем сообщение приложению dos2win PostMessage(hwndDos2Win, WM_STARTWINAPP, 0, (LPARAM)szCmd[nCmdLine]);
// Очередь запросов организована в виде кольца nCmdLine++; if(nCmdLine >
5) nCmdLine = 0; }
// ======================================================== // Функция RegisterWnd // Регистрация окна приложения dos2win.exe // ======================================================== extern "C" void FAR PASCAL _export RegisterWnd(HWND hwnd) { if(vxdApi == NULL) return;
// Сохраняем идентификатор окна hwndDos2Win = hwnd; if(vxdApi == NULL) return;
// Вычисляем компоненты адреса функции обратного вызова unsigned sel = SELECTOROF((LPVOID)WinAppStart);
unsigned off = OFFSETOF((LPVOID)WinAppStart);
// Вычисляем компоненты адреса буфера szCallbackBuf unsigned bsel = SELECTOROF(szCallbackBuf);
unsigned boff = OFFSETOF(szCallbackBuf);
// Регистрируем функцию обратного вызова и // буфер szCallbackBuf в VxD-драйвере asm mov dx, sel asm mov cx, off asm mov si, bsel asm mov di, boff asm mov ax, vxdapiRegisterWnd (*vxdApi)();
}
// ======================================================== // Функция UnregisterWnd // Отключение VxD-драйвера // ======================================================== void UnregisterWnd(VXDAPI vxdEntry) { if(vxdApi == NULL) return;
asm mov ax, vxdapiUnregisterWnd (*vxdEntry)();
}
// ======================================================== // Функция vxdGetDeviceAPI // Получение адреса точки входа API для // VxD-драйвера, идентификатор которого // задан параметром vxd_id // ======================================================== VXDAPI vxdGetDeviceAPI(unsigned short vxd_id) { unsigned axreg, dxreg;
asm push di asm push es asm mov ax, 0x1684 asm mov bx, vxd_id asm xor di, di asm mov es, di asm int 0x2f asm mov ax, di asm mov dx, es asm pop es asm pop di
asm mov axreg, ax asm mov dxreg, dx
return((VXDAPI)MAKELP(dxreg, axreg));
}
В фиксированном сегменте данных библиотеки d2w.dll хранится идентификатор главного окна приложения dos2win.exe (переменная hwndDos2Win), который используется для посылки этому приложению сообщения WM_STARTWINAPP.
Кроме этого, в этом сегменте располагается буфер szCallbackBuf, адрес которого передается виртуальному драйверу при регистрации. Именно в этот буфер виртуальный драйвер записывает строку параметров, "подсмотренную" у функции прерывания INT21h, запускающей программы MS-DOS.
При начальной загрузке DLL-библиотеки в память функция LibMain получает адрес точки входа программного интерфейса виртуального драйвера VXDSRV, вызывая для этого функцию vxdGetDeviceAPI.
Напомним, что в процессе создания своего главного окна приложение dos2win.exe вызывает функцию RegisterWnd, определенную в библиотеке d2w.dll. Эта функция не только сохраняет полученный ей через параметр идентификатор главного окна, но и вызывает функцию регистрации vxdapiRegisterWnd из программного интерфейса драйвера VXDSRV.
В регистрах DX:CX функции регистрации драйвера передается адрес функции обратного вызова WinAppStart, определенной в DLL-библиотеке, а через регистры SI:DI - адрес буфера szCallbackBuf, в который драйвер будет записывать параметры запускаемого приложения Windows.
Функция WEP, которая вызывается при удалении DLL-библиотеки из памяти, вызывает функцию отмены регистрации UnregisterWnd, определенную в библиотеке d2w.dll. Задача функции UnregisterWnd заключается в простом вызове функции vxdapiUnregisterWnd из программного интерфейса виртуального драйвера.
Так как библиотека d2w.dll будет выгружена из памяти при завершении работы загрузившего ее приложения dos2win.exe, произойдет автоматическая отмена регистрации. Это означает, что после завершения dos2win.exe виртуальный драйвер VXDSRV будет отключен и, следовательно, запуск приложений Windows из командной строки виртуальной машины MS-DOS будет невозможен. Разумеется, до следующего запуска dos2win.exe!
Функция обратного вызова WinAppStart вызывается виртуальным драйвером.
Прежде всего, функция проверяет версию драйвера, которая передается ей в качестве параметра. Конечно, версию драйвера лучше было бы определить с помощью специально предназначенной для этого функции программного интерфейса драйвера, но мы использовали этот способ для иллюстрации возможности передачи параметров из виртуального драйвера.
На момент вызова функции WinAppStart буфер szCallbackBuf содержит строку параметров запускаемого приложения. Эта строка записывается в очередь, организованную в виде кольца на базе массива szCmd.
Вслед за этим в очередь сообщений главного окна приложения dos2win.exe посылается сообщение WM_STARTWINAPP. Это можно сделать только функцией PostMessage (или PostAppMessage), так как только эти функции допускают реентерабельный вызов.
Почему используется очередь?
Дело в том, что пользователь может запустить пакетный bat-файл, содержащий вызовы приложений Windows (да, теперь возможен и такой симбиоз между старой технологией запуска программ MS-DOS и приложениями новейшей операционной системы Windows). В этом случае функция WinAppStart будет вызываться очень часто, поэтому буфер szCallbackBuf будет перезаписываться новой информацией еще до завершения обработки старой.
Сообщение WM_STARTWINAPP и идентификатор виртуального драйвера определены в файле d2w.h (листинг 5.10).
Файл dos2win\d2w.h
#define WM_STARTWINAPP (WM_USER + 100) #define VXD_ID 0x8000
Файл определения модуля DLL-библиотеки d2w.dll представлен в листинге 5.11.
Файл dos2win\d2w.def
LIBRARY D2W DESCRIPTION 'DLL-библиотека D2W, (C) 1995, Frolov A.V.' EXETYPE windows CODE preload fixed DATA preload fixed single HEAPSIZE 1024
Файл wast\wastdrv.cpp
// ====================================================== // Загружаемый драйвер wastdrv.dll // Версия 1.0 // Используется совместно с приложением wast.exe // и VxD-драйвером VXDSRV.386 версии 1.1 // ------------------------------------------------------ // Copyright (C) 1995 Alexandr Frolov // ====================================================== #define STRICT #include <windows.h>
#include <mem.h>
#include <string.h>
#include "wastdrv.hpp" #include "wastvxd.hpp"
extern "C" LRESULT CALLBACK _export DriverProc(DWORD dwDiverId, HDRVR hDriver, UINT msg, LPARAM lParam1, LPARAM lParam2);
extern "C" void FAR PASCAL _export WinAppStart(WORD wVxDVersion);
BOOL RegisterTask(HTASK hTask);
void UnregisterTask(VXDAPI vxdEntry);
int CALLBACK _export DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam);
void AutoStartWAST(BOOL);
DWORD dwRc = 0L;
// Точка входа API VxD-драйвера VXDAPI vxdApi = NULL;
// Идентификатор задачи приложения wast.exe HTASK hTaskWAST = NULL;
// Буфер для передачи информации о запукаемой // программе BYTE szCallbackBuf[350];
// Очередь запросов на запуск программ из VM MS-DOS BYTE szCmd[6][350];
// Номер строки параметров в массиве szCmd int nCmdLine = 0;
// Флаг регистрации приложения wast.exe BOOL fTaskRegistered = FALSE;
HINSTANCE hMod; char szDriverName[] = "wastdrv"; char szTemp[80];
// ======================================================== // Функция LibMain // ======================================================== #pragma argsused int FAR PASCAL LibMain(HINSTANCE hModule, WORD wDataSegment, WORD wHeapSize, LPSTR lpszCmdLine) { hMod = hModule;
// Получаем точку входа API VxD-драйвера vxdApi = vxdGetDeviceAPI(VXD_ID);
return TRUE; }
// ======================================================== // Функция WEP // ======================================================== #pragma argsused int FAR PASCAL WEP(int bSystemExit) { // Если выгружается из памяти DLL-библиотека, // содержащая загружаемый драйвер, // выполняем отключение VxD-драйвера if(vxdApi != NULL) { if(fTaskRegistered) UnregisterTask(vxdApi);
} return 1; }
// ======================================================== // Функция обратного вызова WinAppStart // ======================================================== extern "C" void FAR PASCAL _export WinAppStart(WORD wVxDVersion) { if(wVxDVersion != 0x0101) return;
_fmemcpy((LPVOID)szCmd[nCmdLine], (LPVOID)szCallbackBuf, 65 + 256);
PostAppMessage(hTaskWAST, WM_STARTWINAPP, 0, (LPARAM)szCmd[nCmdLine]);
nCmdLine++; if(nCmdLine >
5) nCmdLine = 0; }
// ======================================================== // Функция RegisterTask // Регистрация приложения wast.exe // ======================================================== BOOL RegisterTask(HTASK hTask) { if(vxdApi == NULL) return FALSE;
// Устанавливаем флаг регистрации fTaskRegistered = TRUE;
// Сохраняем идентификатор окна hTaskWAST = hTask;
// Вычисляем компоненты адреса функции обратного вызова unsigned sel = SELECTOROF((LPVOID)WinAppStart);
unsigned off = OFFSETOF((LPVOID)WinAppStart);
// Вычисляем компоненты адреса буфера szCallbackBuf unsigned bsel = SELECTOROF(szCallbackBuf);
unsigned boff = OFFSETOF(szCallbackBuf);
// Регистрируем функцию обратного вызова и // буфер szCallbackBuf в VxD-драйвере
asm mov dx, sel asm mov cx, off asm mov si, bsel asm mov di, boff asm mov ax, vxdapiRegisterTask (*vxdApi)();
return TRUE; }
// ======================================================== // Функция UnregisterTask // Отключение VxD-драйвера // ======================================================== void UnregisterTask(VXDAPI vxdEntry) { if(vxdApi == NULL) return;
// Сбрасываем флаг регистрации fTaskRegistered = FALSE;
asm mov ax, vxdapiUnregisterTask (*vxdEntry)();
}
// ======================================================== // Функция vxdGetDeviceAPI // Получение адреса точки входа API для // VxD-драйвера, идентификатор которого // задан параметром vxd_id // ======================================================== VXDAPI vxdGetDeviceAPI(unsigned short vxd_id) { unsigned axreg, dxreg;
asm push ax asm push bx asm push di asm push es
asm mov ax, 0x1684 asm mov bx, vxd_id asm xor di, di asm mov es, di asm int 0x2f asm mov ax, di asm mov dx, es
asm pop es
asm mov axreg, ax asm mov dxreg, dx
asm pop di asm pop bx asm pop ax
return((VXDAPI)MAKELP(dxreg, axreg));
}
// ======================================================== // DriverProc // Функция загружаемого драйвера // ======================================================== extern "C" LRESULT CALLBACK _export DriverProc( DWORD dwDiverId, // идентификатор драйвера HDRVR hDriver, // идентификатор, который // используется для обращения к драйверу UINT msg, // код сообщения LPARAM lParam1, // первый параметр сообщения LPARAM lParam2) // второй параметр сообщения { switch(msg) { // Регистрация окна приложения wast.exe case UDRV_REGISTERTASK: { // Если виртуальный драйвер не загружен, // возвращаем признак ошибки FALSE if(vxdApi == NULL) { dwRc = (DWORD)FALSE; break; }
// Выполняется только в том случае, если // разрешен запуск приложений Windows из // командной строки MS-DOS if(GetPrivateProfileInt(szDriverName, "Enable", 0, "system.ini")) { dwRc = (DWORD)RegisterTask((HTASK)lParam1);
} break; }
// Отмена регистрации приложения wast.exe case UDRV_UNREGISTERTASK: { if(fTaskRegistered) UnregisterTask(vxdApi);
break; }
// Загрузка драйвера. Это первое сообщение, которое // получает драйвер case DRV_LOAD: { dwRc = 1l; break; }
// Это сообщение посылается драйверу перед тем, // как драйвер будет выгружен из памяти case DRV_FREE: { dwRc = 1l; break; }
// Открытие драйвера case DRV_OPEN: { dwRc = 1l; break; }
// Закрытие драйвера case DRV_CLOSE: { dwRc = 1l; break; }
// Разблокирование драйвера case DRV_ENABLE: { dwRc = 1l; break; }
// Блокирование драйвера case DRV_DISABLE: { dwRc = 1l; break; }
// Это сообщение посылается при установке драйвера case DRV_INSTALL: { // Создаем раздел [wastdrv] в файле system.ini // В этом разделе создаем строку: "Enable=1" WritePrivateProfileString(szDriverName, "Enable", "1", "system.ini");
// Дописываем имя приложения wast.exe в строке // "load=" раздела [windows] файла win.ini AutoStartWAST(TRUE);
// Запрашиваем перезапуск Windows dwRc = DRVCNF_RESTART; break; }
// Удаление драйвера case DRV_REMOVE: { // Если окно приложения wast.exe было // зарегистрировано, отменяем регистрацию if(fTaskRegistered) UnregisterTask(vxdApi);
// Посылаем приложению wast.exe сообщение // WM_UNLOADWAST, в ответ на которое оно // завершает свою работу if(hTaskWAST != NULL) PostAppMessage(hTaskWAST, WM_UNLOADWAST, 0, 0l);
// Идентификатор задачи wast.exe теперь // недействителен hTaskWAST = NULL;
// В разделе [wastdrv] файла system.ini // заменяем строку "Enable=1" на "Enable=0" WritePrivateProfileString(szDriverName, "Enable", "0", "system.ini");
// Отменяем автоматический запуск wast.exe AutoStartWAST(FALSE);
dwRc = 0l; break; }
// Это сообщение присылается как запрос на // возможность конфигурирования драйвера. // Так как наш драйвер можно конфигурировать, // возвращаем значение 1l case DRV_QUERYCONFIGURE: { dwRc = 1l; break; }
// Конфигурирование драйвера case DRV_CONFIGURE: { // Создаем диалоговую панель конфигурирования dwRc = DialogBox(hMod, "CONFIG", NULL, DlgProc);
break; }
// Все необработанные сообщения необходимо // передать функции DefDriverProc default: { return DefDriverProc(dwDiverId, hDriver, msg, lParam1, lParam2);
} } return dwRc; }
// ====================================================== // Функция DldProc // Обработка сообщений диалоговой панели конфигурирования // ====================================================== #pragma argsused
int CALLBACK _export DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { static BOOL fWasActive, fChanged;
switch(msg) { case WM_INITDIALOG: { // Определяем текущее состояние строки "Enable=" fWasActive = GetPrivateProfileInt(szDriverName, "Enable", 0, "system.ini");
// В соответствии с этим состоянием отмечаем // переключатель, управляющий возможностью запуска // приложений Windows из командной строки MS-DOS CheckDlgButton(hdlg, ID_ENABLE, fWasActive);
return TRUE; }
case WM_COMMAND: { switch(wParam) { case IDOK: { // Флаг изменения режима работы драйвера // при его конфигурировании fChanged = TRUE;
// Если переключатель находится во включенном // состоянии, разрешаем работу драйвера if(IsDlgButtonChecked(hdlg, ID_ENABLE)) { if(fWasActive) fChanged = FALSE;
// Заносим изменения в файл system.ini WritePrivateProfileString(szDriverName, "Enable", "1", "system.ini");
// Разрешаем автоматический запуск wast.exe AutoStartWAST(TRUE);
}
// Если переключатель находился в выключенном // состоянии, запрещаем работу драйвера else { if(!fWasActive) fChanged = FALSE; WritePrivateProfileString(szDriverName, "Enable", "0", "system.ini");
AutoStartWAST(FALSE);
}
// Если был изменен режим работы драйвера, // запрашиваем перезапуск Windows EndDialog(hdlg, fChanged ? DRVCNF_RESTART : DRVCNF_CANCEL);
return TRUE; }
// Отмена изменения конфигурации case IDCANCEL: { EndDialog(hdlg, DRVCNF_CANCEL);
return TRUE; } } } } return FALSE; }
// ====================================================== // Функция AutoStartWAST // В зависимости от значения параметра эта функция // дописывает имя приложения wast.exe в строке // "load=" раздела [windows] файла win.ini // или наоборот, удаляет имя wast.exe из этой строки // ====================================================== void AutoStartWAST(BOOL fStart) { static LPSTR lpszSubst;
// Получаем строку "load=..." во временный буфер GetProfileString("windows", "load", "", szTemp, 80);
// Ищем в этой строке имя приложения wast.exe lpszSubst = _fstrstr(szTemp, (LPSTR)"wast");
// Если параметр функции равен TRUE, добавляем // имя в строку if(fStart) { // Добавляем только в том случае, если этого // имени там нет if(lpszSubst == NULL) { lstrcat(szTemp, " ");
lstrcat(szTemp, "wast");
} }
// Если параметр функции равен FALSE, удаляем // имя приложения wast.exe из строки else { // Удаляем только в том случае, если это имя // там действительно есть if(lpszSubst != NULL) { _fstrcpy(lpszSubst, lpszSubst + _fstrlen(lpszSubst));
} }
// Сохраняем новое значение строки "load=..." WriteProfileString("windows", "load", szTemp);
}
Функция LibMain загружаемого драйвера аналогична такой же функции DLL-библиотеки d2w.dll. Она получает и сохраняет точку входа виртуального драйвера и дополнительно запоминает в глобальной переменной идентификатор модуля загружаемого драйвера.
Функция WEP отменяет регистрацию при выгрузке драйвера из памяти, вызывая функцию UnregisterTask. Эта функция полностью аналогична функции UnregisterWnd библиотеки d2w.dll, причину изменения названия вы узнаете чуть позже.
Функция WinAppStart выполняет ту же задачу, что и одноименная функция из библиотеки d2w.dll, однако делает она это немного по-другому.
Обратите внимание, что для посылки сообщения приложению wast.exe (аналогичного приложению dos2win.exe) мы использовали вместо функции PostMessage функцию PostAppMessage. Это связано с тем, что приложение wast.exe вообще не создает главного окна, хотя, тем не менее, имеет цикл обработки сообщений! (Разумеется, вы можете использовать и старый метод, использованный в приложении dos2win.exe).
В качестве первого параметра функции PostAppMessage необходимо передать идентификатор задачи wast.exe (а не идентификатор окна, которое, кстати, вообще не существует).
Идентификатор задачи сохраняется в глобальной переменной функцией RegisterTask, которая соответствует функции RegisterWnd библиотеки d2w.dll, но вызывается по-другому. Вызов этой функции выполняется из функции DriverProc при обработке пользовательского сообщения UDRV_REGISTERTASK, посылаемого загружаемому драйверу приложением wast.exe.
Аналогично, обработчик другого пользовательского сообщения UDRV_UNREGISTERTASK вызывает функцию UnregisterTask, действие которой полностью аналогично действию функции UnregisterWnd библиотеки d2w.dll.
Функция DriverProc обрабатывает все необходимые сообщения, реагируя на них соответствующим образом. Мы не будем повторять комментарии, приведенные в исходном тексте.Отметим только, что при установке загружаемого драйвера обработчик сообщения DRV_INSTALL дописывает имя приложения wast.exe в строке "load=" файла win.ini, обеспечивая автоматический запуск приложения wast.exe. Для этого он вызывает функцию AutoStartWAST с параметром TRUE.
При удалении драйвера обработчик сообщения отменяет автоматический запуск wast.exe, вызывая функцию AutoStartWAST с параметром FALSE. Перед этим выполняется отмена регистрации. В добавок приложению wast.exe посылается пользовательское сообщение WM_UNLOADWAST. Получив это сообщение, приложение wast.exe завершает свою работу.
Пользовательские сообщения для приложения wast.exe и для загружаемого драйвера, идентификатор виртуального драйвера, а также константа ID_ENABLE определены в файле wastdrv.hpp (листинг 5.13).
Файл wast\wastdrv.hpp
#define WM_STARTWINAPP (WM_USER + 100) #define WM_UNLOADWAST (WM_USER + 101) #define UDRV_REGISTERTASK (DRV_USER - 10) #define UDRV_UNREGISTERTASK (DRV_USER - 11) #define VXD_ID 0x8000 #define ID_ENABLE 101
Номера процедур программного интерфейса виртуального драйвера, точка входа программного интерфейса и прототип функции для получения точки входа определены в файле wastvxd.hpp (листинг 5.14).
Файл wast\wastvxd.hpp
#define vxdapiGetVersion 0 #define vxdapiRegisterTask 1 #define vxdapiUnregisterTask 2 typedef unsigned long (far *VXDAPI)(void);
VXDAPI vxdGetDeviceAPI(unsigned short vxd_id);
Файл ресурсов загружаемого драйвера (листинг 5.15) содержит определение пиктограммы и диалоговой панели конфигурирования драйвера.
Файл wast\wastdrv.rc
#include "wastdrv.hpp"
AppIcon ICON "wast.ico"
CONFIG DIALOG 11, 18, 167, 139 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Configure Driver WASTDRV" BEGIN ICON "APPICON", -1, 4, 7, 16, 16, WS_CHILD | WS_VISIBLE DEFPUSHBUTTON "OK", IDOK, 43, 118, 33, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP LTEXT "Version 1.0", -1, 30, 18, 120, 9, WS_CHILD | WS_VISIBLE | WS_GROUP CONTROL "", -1, "static", SS_BLACKRECT | WS_CHILD | WS_VISIBLE, 31, 36, 129, 1 LTEXT "Copyright © 1995 Alexandr Frolov" "\n\nfrolov@glas.apc.org", -1, 30, 41, 114, 28, WS_CHILD | WS_VISIBLE | WS_GROUP CONTROL "", -1, "static", SS_BLACKRECT | WS_CHILD | WS_VISIBLE, 31, 74, 129, 1 LTEXT "Installable Driver WASTDRV", -1, 30, 6, 108, 12, WS_CHILD | WS_VISIBLE | WS_GROUP CONTROL "", ID_ENABLE, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 31, 87, 10, 15 LTEXT "Enable Starting Windows Application from MS-DOS Command Prompt", -1, 43, 90, 124, 18, WS_CHILD | WS_VISIBLE | WS_GROUP PUSHBUTTON "Cancel", IDCANCEL, 91, 118, 32, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP END
Файл определения модуля загружаемого драйвера практически ничем не отличается от аналогичного файла для DLL-библиотеки d2w.dll:
Файл wast\wastdrv.def
LIBRARY WASTDRV DESCRIPTION 'Driver WASTDRV, (C) 1995, Frolov A.V.' EXETYPE windows CODE preload fixed DATA preload fixed single HEAPSIZE 1024
Для обеспечения возможности автоматической установки комплекса, состоящего из загружаемого драйвера wastdrv.dll, виртуального драйвера vxdsrv.386 и приложения wast.exe дистрибутивная дискета должна содержать помимо перечисленных файлов файл с именем oemsetup.inf (листинг 5.17).
Файл wast\oemsetup.inf
[disks] 1 = ., "WAST Installable Driver Disk 1"
[Installable.Drivers] wastdrvr=1:wastdrv.dll,"WAST", "WAST Installable Driver","1:vxdsrv.386",
[wastdrvr] 1:wast.exe
Секция [disks] этого файла описывает дистрибутивные дискеты. В нашем случае дистрибутив системы WAST состоит из одной дискеты с номером 1.
Диск называется "WAST Installable Driver Disk 1". Именно эта строка появится в списке при установке драйвера приложением Control Panel.
Секция [Installable.Drivers] описывает устанавливаемые драйверы.
Мы устанавливаем драйвер wastdrvr, который расположен на первом диске дистрибутива. Имя соответствующего файла - wastdrv.dll.
Тип драйвера, который появится в секции [drivers] файла system.ini, указан как WAST, поэтому в секцию [drivers] будет добавлена следующая строка:
[drivers] WAST=wastdrv.dll
В списке установленных драйверов, отображаемом приложением Control Panel, наш загружаемый драйвер будет показан с названием "WAST Installable Driver".
В добавок, на первом диске дистрибутива имеется виртуальный драйвер vxdsrv.386, который поставляется вместе с загружаемым драйвером и должен автоматически копироваться в системный каталог Windows, а также автоматически подключаться в секции [386Enh] файла system.ini.
В файле oemsetup.inf мы создали секцию [wastdrvr], в которой можно перечислить любые файлы, копируемые в системный каталог Windows в процессе установки. Мы копируем загрузочный модуль приложения wast.exe.
Файл wast\wast.cpp
// ====================================================== // Приложение WAST // ------------------------------------------------------ // Запуск приложений Windows из // командной строки MS-DOS // // Используется совместно с загружаемым драйвером // wastdrv.dll и VxD-драйвером VXDSRV.386 версии 1.1. // Запускается автоматически при загрузке Windows. // Автоматический запуск можно отменить, изменив // конфигурацию драйвера WASTDRVR при помощи приложения // Control Panel // ------------------------------------------------------ // Copyright (C) 1995 Александр Фролов, Сергей Ноженко // ====================================================== #define STRICT #include <windows.h>
#include <dir.h>
#include "wast.hpp"
BOOL InitApp(HINSTANCE);
static char szDriverName[] = "wastdrv.dll";
struct LOADPARMS { WORD segEnv; // среда LPSTR lpszCmdLine; // коммандная строка LPWORD lpwShow; // режим отображения LPWORD lpwReserved; // зарезервировано }; struct LOADPARMS parms;
WORD awShow[2] = { 2, SW_SHOW }; LPSTR lpszCmd, lpszParm; char szCurPath[128]; char szBuf[256]; // char szTempBuf[256];
// ====================================================== // Функция WinMain // ====================================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { if(hPrevInstance) // только одна копия приложения return FALSE;
// Открываем драйвер HDRVR hDriver = OpenDriver(szDriverName, NULL, NULL);
HTASK hTask;
if(hDriver) { // Получаем идентификатор текущей задачи hTask = GetCurrentTask();
// Регистрируем в драйвере текущую задачу BOOL rc = (BOOL)SendDriverMessage( hDriver, UDRV_REGISTERTASK, (LPARAM)hTask, NULL);
// Если не удалось зарегистрировать задачу, // виртуальный драйвер vxdsrv.386 не загружен if(!rc) { MessageBox(NULL, "Error Loading WAST\n" "Driver vxdsrv.386 not found", "Windows Application Starter", MB_OK | MB_ICONHAND);
return FALSE; } }
// Если не удалось открыть загружаемый драйвер, // выводим сообщение об ошибке else { MessageBox(NULL, "Error Loading WAST\n" "Driver wastdrv.dll not found", "Windows Application Starter", MB_OK | MB_ICONHAND);
return FALSE; }
MSG msg;
while(GetMessage(&msg, NULL, 0, 0)) { // Это сообщение посылается загружаемым драйвером // wastdrv.dll, когда VxD-драйвер фиксирует запуск // программы любой виртуальной машиной MS-DOS if (msg.message == WM_STARTWINAPP) { // Создаем и активируем временное невидимое окно, // переключая системную виртуальную машину из фонового // режима в основной режим HWND hDummyWnd = CreateWindow("static", "", WS_POPUP, 0, 0, 0, 0, HWND_DESKTOP, NULL, hInstance, NULL);
ShowWindow(hDummyWnd, SW_SHOW);
DestroyWindow(hDummyWnd);
// Адрес командной строки lpszCmd = (LPSTR)msg.lParam + 65;
// Адрес строки параметров lpszParm = (LPSTR)msg.lParam + 65 + 128;
// Устанавливаем текущий каталог setdisk(*LPSTR(msg.lParam));
szBuf[0] = *LPSTR(msg.lParam) + 'A'; szBuf[1] = ':'; szBuf[2] = '\\'; lstrcpy(szBuf + 3, LPSTR(msg.lParam) + 1);
chdir(szBuf);
// Готовим параметры для LoadModule lstrcpy(szBuf, (LPCSTR)" ");
lstrcat(szBuf, (LPCSTR)lpszParm);
*szBuf = (BYTE)lstrlen(lpszParm);
if(lstrlen(lpszParm) != 0) parms.lpszCmdLine = (LPSTR)szBuf; else parms.lpszCmdLine = (LPSTR)"";
parms.segEnv = 0; parms.lpwShow = (LPWORD) awShow; parms.lpwReserved = (LPWORD) NULL;
// Копируем командную строку и параметры // lstrcpy(szTempBuf, (LPSTR)msg.lParam + 65);
// lstrcat(szTempBuf, (LPSTR)msg.lParam + 65 + 128);
// Запускаем приложение Windows LoadModule(lpszCmd, &parms);
// WinExec(szTempBuf, SW_SHOW);
}
// Это сообщение поступает от загружаемого драйвера // при его удалении, выполняемом из приложения // Control Panel else if (msg.message == WM_UNLOADWAST) PostQuitMessage(0);
else DispatchMessage(&msg);
}
// Отменяем регистрацию текущей задачи SendDriverMessage(hDriver, UDRV_UNREGISTERTASK, (LPARAM)hTask, NULL);
// Закрываем драйвер CloseDriver(hDriver, NULL, NULL);
return msg.wParam; }
Первое, что делает функция WinMain - открывает загружаемый драйвер, вызывая для этого функцию OpenDriver.
Затем приложение получает идентификатор текущей задачи (т. е. свой идентификатор задачи), и регистрирует его в загружаемом драйвере, посылая тому сообщение UDRV_REGISTERTASK.
Далее запускается цикл обработки сообщений.
При обработке сообщения WM_STARTWINAPP выполняется запуск приложения Windows. Для быстрого переключения системной виртуальной машины из фонового режима в основной мы создаем окно с нулевыми размерами, а затем сразу же отображаем и удаляем его.
После этого запускаем приложение функцией LoadModule.
Вы можете также попробовать использовать для запуска функцию WinExec, для чего следует переставить соответствующим образом символы комментария.
При поступлении в очередь сообщения WM_UNLOADWAST работа приложения wast.exe завершается. Напомним, что это сообщение посылается приложению при удалении загружаемого драйвера.
Перед завершением своей работы приложение wast.exe отменяет регистрацию и закрывает загружаемый драйвер.
Все пользовательские сообщения определены в файле wast.hpp (листинг5.19).
Файл wast\wast.hpp
#define WM_STARTWINAPP (WM_USER + 100) #define WM_UNLOADWAST (WM_USER + 101) #define UDRV_REGISTERTASK (DRV_USER - 10) #define UDRV_UNREGISTERTASK (DRV_USER - 11)
Файл ресурсов приведен в листинге 5.20.
Файл wast\wast.rc
#include "wast.hpp" AppIcon ICON "wast.ico"
Файл описания модуля для приложения wast.exe вы сможете найти в листинге 5.21.
Файл wast\wast.def
NAME WAST DESCRIPTION 'WAST: Windows App Starter, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Литература
Фролов А. В., Фролов Г. В. Библиотека системного программиста. Т. 11-13. Операционная система Microsoft Windows 3.1 для программиста. М.: ДИАЛОГ-МИФИ, 1994
Фролов А. В., Фролов Г. В. Библиотека системного программиста. Т. 14. Операционная система Microsoft Windows 3.1 для программиста. Графический интерфейс GDI. М.: ДИАЛОГ-МИФИ, 1994
Фролов А. В., Фролов Г. В. Библиотека системного программиста. Т. 15. Мультимедиа для Windows. Руководство для программиста. М.: ДИАЛОГ-МИФИ, 1994
Фролов А. В., Фролов Г. В. Библиотека системного программиста. Т. 16. Программирование модемов и факс-модемов в MS-DOS и Windows. М.: ДИАЛОГ-МИФИ, 1994
Фролов А. В., Фролов Г. В. Библиотека системного программиста. Т. 6. Защищенный режим процессоров Intel 80286/80386/80486. М.: ДИАЛОГ-МИФИ, 1993
Фролов А. В., Фролов Г. В. Персональный компьютер - шаг за шагом. Т. 1. Введение в MS-DOS, MS Windows, MS Word for Windows. М.: ДИАЛОГ-МИФИ, 1994
Фролов А. В., Фролов Г. В. Персональный компьютер - шаг за шагом. Т. 2. Операционная система Microsoft Windows. Руководство пользователя. М.: ДИАЛОГ-МИФИ, 1994
Эллис М., Строуструп Б.. Справочное руководство по языку программирования C++ с комментариями. М.: МИР, 1992
Norton D. Writing Windows Device Drivers. Addison-Wesley Publishing Company, 1992
Petzold C. Programming Windows 3.1. Microsoft Press. One Microsoft Way. Redmont, Washington, 1992
Rector B. Developing Windows 3.1 Applications with Microsoft C/C++. SAMS, 1992
Windows. Справочник для программистов. Версия 3.0. Часть 1, 2. М.: "Научный центр", 1991
Макрокоманды
В этом разделе мы кратко рассмотрим средство расширения справочной системы Windows, позволяющее наращивать практически неограниченно ее и без того широкие возможности - макрокоманды.
Компилятор Help Compiler распознает значительное количество встроенных в справочную систему макрокоманд, с помощью которых можно модифицировать меню и Toolbar, добавляя или удаляя строки меню и кнопки, выполнять переходы между разделами и поиск разделов, запускать произвольные приложения Windows и вызывать функции из внешних DLL-библиотек.
Макрокоманда имеет имя и параметры, указанные в скобках. Параметр может быть текстовой строкой или числом (десятичным или шестнадцатиричным, в последнем случае перед числом должны находится символы 0x). Каждый параметр, заданный в виде текстовой строки, должен быть взят в кавычки:
ExecProgram("testapp.exe",0)
Допустимы вложенные вызовы макрокоманд, однако в этом случае для внутренних макрокоманд вместо обычных кавычек следует использовать символы (`) и ('):
CreateButton("start","&Start App","ExecProgram(`app.exe',0)")
Символ (`) находится на той же клавише, где и символ (~) ("тильда"). Эта клавиша находится возле клавиши <Tab>. Символ (') вы сможете найти на той клавише, где расположен символ двойной кавычки (").
Если вам нужно вставить в текстовую строку параметра макрокоманды символы ("), (`), ('), (\), то перед ними необходимо расположить символ (\).
Многооконный интерфейс MDI
1.1.
1.2.
1.3.
1.4.
1.5.
1.6.
1.7.
1.8.
1.9.
Как пользователь операционной системы Windows вы, наверное, хорошо знакомы с многооконным интерфейсом MDI (Multiple Document Interface), позволяющим в одном приложении работать одновременно с несколькими документами или с разными представлениями одного и того же документа. Этот интерфейс описан в руководстве по разработке интерфейса пользователя System Application Architecture Common User Access Advanced Interface Design Guide (SAA/CUA), созданном IBM. Интерфейс MDI использован в Windows, начиная с версии 3.0, в Windows NT, а также, разумеется, в графической оболочке Presentation Manager операционной системы OS/2.
В качестве примера приложения, использующего интерфейс MDI (MDI-приложения), можно привести текстовый процессор Microsoft Word for Windows версии 2.0. Рис. 1.1 иллюстрирует работу одновременно с двумя документами, один из которых находится в файле wadvance.doc, расположенном в текущем каталоге, а второй - в файле winmm-1.doc, расположенном в каталоге winmm.
Рис. 1.1. Работа одновременно с двумя документами
Посмотрите на рис. 1.2. Из этого рисунка видно, что текстовый процессор Microsoft Word for Windows способен также предоставить пользователю два различных представления одного и того же документа. В окне WINMM1.DOC:1 отображается обычное представление документа, а в окне WINMM1.DOC:2 - представление в режиме Outline (режим просмотра оглавления документа).
Рис. 1.2. Работа одновременно с двумя представлениями одного и того же документа
Любое MDI-приложение содержит в главном меню строку "Windows", при выборе которой на экране появляется временное (pop up) меню "Windows", предназначенное для управления окнами, отображающими различные документы или различные представления одного и того же документа (пока мы будем называть окно документов окном Document Window, позже уточним терминологию). На рис. 1.3. показано меню "Windows" приложения Program Manager.
Рис. 1.3.
Меню "Window" приложения Program Manager
Как правило, в меню "Windows" есть строки "Cascade" и "Tile", с помощью которых пользователь может расположить окна Document Window с перекрытием (друг за другом) или рядом друг с другом. Могут быть и другие строки, управляющие расположением окон Document Window. Например, если приложение допускает сворачивание окон Document Window в пиктограммы, в меню "Windows" присутствует строка "Arrange Icons", с помощью которой пользователь может упорядочить расположение пиктограмм свернутых окон.
Ниже разделительной черты в меню "Windows" находятся строки, соответствующие окнам Document Window. Если выбрать одну из этих строк, указанное окно будет активизировано и показано на первом плане.
Состав меню "Windows" может быть различным в различных MDI-приложениях, в чем вы можете убедиться сами. Однако в любом случае это меню позволяет вам автоматически упорядочить расположение окон Document Window и выбрать нужное окно из списка для активизации.
Поведение окон Document Window напоминает поведение дочерних окон. В частности, двигая их при помощи заголовка, вы не сможете переместить такие окна за пределы главного окна приложения. В то же время окна Document Window напоминают перекрывающиеся окна, так как их можно делать активными, при этом заголовок активного окна Document Window выделяется цветом.
Каждое окно Document Window имеет, как правило, системное меню (рис. 1.4) и кнопки изменения размера.
Рис. 1.4. Системное меню окна Document Window текстового процессора Microsoft Word for Windows версии 2.0
С помощью системного меню пользователь может изменять размеры или перемещать окно Document Window (строки "Restore", "Move", "Size", "Maximize", "Minimize"), закрывать окно (строка "Close"), передавать фокус ввода от одного окна Document Window другому (строка "Next Window") и выполнять другие действия, в зависимости от назначения приложения.
Для ускоренного доступа к функциям системного меню окна Document Window используются акселераторы. Вы можете активизировать системное меню окна Document Window мышью или при помощи комбинации клавиш <Alt"">.
Если окно Document Window сворачивается в пиктограмму (минимизируется), пиктограмма располагается в нижней части главного окна приложения (рис. 1.5).
Рис. 1.5. Окна Document Window могут быть свернуты в пиктограмму
Если же увеличить размеры окна Document Window до максимальных (при помощи кнопки максимизации или системного меню окна Document Window), внешний вид главного меню приложения изменится (рис. 1.6).
Рис. 1.6. Изменение главного меню приложения при максимальном увеличении размеров окна Document Window
Теперь в левой части главного меню находится пиктограмма системного меню окна Document Window, а в правой - пиктограмма восстановления размера окна Document Window.
Активное окно Document Window выделяется изменением цвета заголовка. Здесь используется тот же способ выделения, что и для главного окна активного приложения. Однако если активно MDI-приложение, выделяются сразу два окна - главное окно MDI-приложения и активное окно Document Window (если оно есть). Такая ситуация показана, например, на рис. 1.1.
Способ создания новых окон Document Window и удаления имеющихся полностью определяется приложением. Обычно новое окно Document Window создается при создании нового документа (строка "New" меню "File") или при загрузке уже существующего документа для редактирования или просмотра (строка "Open" меню "File").
Для того чтобы удалить окно Document Window, можно закрыть документ (строка "Close" меню "File") или закрыть само окно Document Window при помощи системного меню (если этот способ предусмотрен приложением).
Как видите, поведение MDI-приложения выглядит достаточно сложно. Вероятно, вы сможете создать такое приложение с использованием обычных окон, но для этого придется затратить немало усилий.
К счастью, операционная система Windows начиная с версии 3.0 имеет встроенную поддержку MDI-приложений, поэтому большинство из описанных выше свойств окон Document Window реализуется Windows, а не приложением.
Даже с учетом поддержки операционной системы, "полновесные" MDI-приложения выглядят достаточно громоздко. Примером может послужить приложение MULTIPAD, исходные тексты которого поставляются вместе с Microsoft SDK for Windows 3.1 и в составе примеров приложений системы разработки Microsoft Visual C++. Приложение MULTIPAD слишком сложно, для того чтобы начинать с него изучение интерфейса MDI, однако оно является хорошим примером того, как нужно делать подобные приложения. Мы рекомендуем вам после прочтения этой главы разобраться в том, как оно работает.
Не огорчайтесь, если вам покажется, что исходные тексты этого, в общем-то, простого приложения, очень сложны. На данном этапе для вас важно понять принципы, положенные в основу интерфейса MDI. В ближайших томах "Библиотеки системного программиста" мы научим вас создавать такие приложения с использованием библиотек классов Borland Object Windows и Microsoft Foundation Classes. Объем исходных текстов таких приложений, снабженных к тому же окнами Toolbar и Statusbar, намного меньше объема приложений, составленных на "чистом" Си. К тому же современные средства разработки позволяют создавать заготовку приложений автоматически, генерируя все необходимые исходные тексты.
Тем не менее, мы не считаем, что рассказ об интерфейсе MDI следует отложить "до лучших времен" (т. е. до тех времен, когда вы будете использовать в своих разработках готовые библиотеки классов). Несмотря на то, что библиотеки классов скрывают многие детали внутренних процессов операционной системы Windows, знание этих процессов позволит вам создавать приложения более эффективно и с меньшими затратами сил на поиск ошибок, которые возникают из-за непонимания происходящего.
Модель памяти FLAT
Для того чтобы использовать модель памяти FLAT, недостаточно отметить крестиком слово "Flat" при создании проекта в Borland C++ или Microsoft Visual C++. Единственная возможность, которая существует для разработчиков виртуальных драйверов, - пакетный ассемблер masm5.exe и редактор link386.exe, входящий в состав DDK for Windows 3.1. Отложив на время описание этого скудного набора средств, рассмотрим особенности модели памяти FLAT.
Новый формат дескриптора для процессора Intel 80386 и процессоров Intel более старших моделей позволяет создавать сегменты кода и данных, имеющих размер 4 Гбайт - больше, чем может потребоваться в настоящее время. Виртуальные драйверы всегда работают в модели памяти FLAT, предполагающей использование таких, с вашего позволения, сегментов.
Что дает виртуальным драйверам работа при такой организации памяти?
Виртуальные драйверы никогда не изменяют содержимое сегментных регистров, так как это не нужно. При помощи всего двух дескрипторов (описывающих сегменты кода и данных) виртуальные драйверы могут непосредственно адресовать всю виртуальную память, указывая лишь 32-разрядное смещение.
В качестве базового адреса в дескрипторе может находиться нулевое значение, и его едва ли нужно изменять - любая область памяти доступна так же легко, как в модели памяти TINY (если вы это еще помните, такая модель памяти с единственным сегментом используется com-программами MS-DOS).
Удобство FLAT-модели памяти для виртуальных драйверов заключается в том, что драйвер имеет прямой доступ к адресным пространствам всех одновременно запущенных виртуальных машин - как виртуальных машин MS-DOS, так и системной виртуальной машины, исполняющей приложения Windows. Так как виртуальный драйвер получает управление и выполняется в нулевом кольце защиты, он также имеет непосредственный доступ к системным областям памяти. Все это облегчает задачу организации взаимодействия между виртуальными машинами, доступ к аппаратуре и виртуализацию аппаратуры для совместного использования в многозадачной среде Windows.
Создает ли модель памяти FLAT какие-либо трудности для программиста по сравнению с обычными, 16-разрядными моделями памяти?
Практически никаких. Даже наоборот - возможность использования 32-разрядного смещения плюс отсутствие необходимости изменения содержимого сегментных регистров дают возможность упростить алгоритмы обработки данных.
Единственное, о чем необходимо помнить - это о том, что все регистры процессора являются 32-разрядными. В вашем распоряжении есть 8-разрядный регистры AL, AH, BL, BH, ..., 16-разрядные регистры AX, BX, CX,..., а также 32-разрядные регистры EAX, EBX, ECX и т. д.
Практически все регистры (кроме ESP) могут содержать 32-разрядное смещение. Следовательно, в них можно записывать адрес переменной. Под адресом здесь понимается короткий (NEAR) адрес, состоящий из одного смещения. В модели памяти FLAT нужно забыть про селекторы и сегменты.
В командах условных и безусловных переходов, а также при вызове подпрограмм командой call используйте ключевое слово short, так как используя короткое 32-разрядное смещение, можно "уйти" очень далеко.
Для загрузки 32-разрядного смещения в регистр указывайте ключевое слово offset32:
mov esi, offset32 V86_Int21_Handler
Ну и конечно, не оставляйте без внимания новые команды, доступные для процессоров 80386. Очень удобна, например, команда MOVZX, позволяющая загрузить в 32-разрядный регистр 8- или 16-разрядное значение, расширив его слева нулями:
movzx ebx, byte ptr [ecx]
Аналогичная команда MOVSX выполняет знаковое расширение.
Объем нашей книги не позволяет подробно описать все особенности 32-разрядного режима работы процессора, так что при необходимости обратитесь к дополнительной литературе.
Назначение атрибутов разделам справочной системы
Для назначения атрибутов разделам справочной системы вам необходимо освоить технику вставки подстрочных сносок в текстовом процессоре Microsoft Word for Windows.
О том, как работать с текстовым процессором Microsoft Word for Windows вы можете прочитать в нашей книге "Введение в MS-DOS, MS Windows, MS Word for Windows", которая вышла первым томом в серии "Персональный компьютер - шаг за шагом". Здесь мы приведем лишь самые минимальные сведения, необходимые для работы со сносками.
Подстрочная сноска - это текст, который располагается в нижней части страницы и обычно отделяется от основного текста горизонтальной чертой. В тексте сноска оформляется в виде специального символа, такого как "*", или числа.
Для вставки сноски вы должны выбрать из меню "Insert" текстового процессора Word for Windows строку "Footnote...". На экране появится диалоговая панель "Footnote" (рис. 4.14).
Рис. 4.14. Вставка сноски
В появившейся диалоговой панели в поле "Custom Footnote Mark" можно ввести символ, который будет использоваться для сноски, а можно использовать автоматическую нумерацию сносок.
При создании исходного текста справочной системы для назначения атрибутов используются сноски в виде символов, причем для каждого атрибута применяется свой символ:
Атрибут раздела | Символ |
Контекст | # |
Заголовок | $ |
Список ключевых слов | K |
Номер в последовательности просмотра | + |
Макрокоманда, получающая управление при отображении раздела | ! |
Тег компиляции | * |
В процессе назначения атрибута вам нужно вставить сноску, указав в поле "Custom Footnote Mark" (рис. 4.14) нужный символ.
Как только вы вставите сноску, экран текстового процессора окажется разделен на две части - в нижней части появится окно редактирования сносок, озаглавленное "Footnotes" (рис. 4.15).
Рис. 4.15. Окно редактирования сносок
Текст сноски (в нашем случае это атрибут раздела) вводится после соответствующего символа в окне редактирования сносок. Между строкой и символом сноски должен быть только один символ пробела.
При помощи кнопки "Close" вы можете закрыть окно редактирования сносок. Для того чтобы вновь открыть это окно, выберите из меню "View" строку "Footnotes".
Непосредственная вставка изображения
Самый простой способ включения графического изображения был использован в примере файла hlpfile.rtf. Мы подготовили изображение в графическом редакторе Paintbrush и вставили его через Clipboard непосредственно в текст, пользуясь текстовым процессором Microsoft Word for Windows.
Этот способ хорош в тех случаях, когда одно и то же изображение вставляется в текст справочной системы один-два раза. Дело в том, что сколько раз вы вставите в текст одну и ту же картинку, столько раз соответствующей ей массив данных будет записан в hlp-файл. Еще одно ограничение заключается в том, что вы можете вставить изображение только как символ в текстовую строку. При этом невозможно добиться эффекта "обтекания" изображения текстом.
Однако несомненное преимущество непосредственной вставки графики в исходный текст справочной системы заключается в том, что внешний вид исходного текста будет больше соответствовать результату компиляции.
Нестандартный формат данных
Для обмена данными между одновременно работающими копиями одного и того же приложения, а также для копирования фрагментов документа в рамках одной копии приложения не всегда удобно использовать стандартные форматы данных Clipboard. Дело в том, что внутренняя структура таких фрагментов может быть сложнее, чем обычный текст или битовое изображение.
Конечно, можно создать свой, нестандартный формат данных, пользуясь одним из стандартных форматов, например, текстовым CF_TEXT или каким либо еще, добавив к тексту или изображению дополнительные данные. Однако вставка пользователем фрагмента документа в таком "полустандартном" формате в произвольное приложение, не рассчитанное на этот формат, может привести к неожиданным результатам.
Поэтому лучшим решением будет создание собственного формата данных для записи в Clipboard.
Как создать свой формат данных для записи в Clipboard?
Для этого достаточно зарегистрировать формат данных при помощи функции RegisterClipboardFormat:
UINT WINAPI RegisterClipboardFormat(LPCSTR lpszFormatName);
В качестве параметра этой функции следует передать указатель на текстовую строку, закрытую двоичным нулем и содержащую имя регистрируемого вашим приложением нестандартного формата данных для Clipboard.
Функция возвращает нулевое значение при ошибке или идентификатор зарегистрированного формата данных, который можно использовать аналогично идентификаторам стандартных форматов в качестве параметра функции SetClipboardData.
Два различных приложения, две копии одного приложения могут зарегистрировать формат с одним и тем же именем, при этом функция RegisterClipboardFormat вернет один и тот же идентификатор формата. Поэтому два приложения всегда смогут "договориться", если они знают имя нестандартного формата данных.
При использовании нестандартного формата данных возникает проблема отображения этих данных приложениями, предназначенными для динамического просмотра содержимого Clipboard, а также проблема вставки нестандартных данных в приложения, не рассчитанные на этот формат данных.
Можно частично решить эту проблему, использовав так называемые форматы отображения (display formats) CF_DSPTEXT, CF_DSPBITMAP и CF_DSPMETAFILEPICT. Эти форматы предназначены для представления, соответственно, текстовых данных, битовых изображений и метафайлов. Обычные приложения не воспринимают форматы отображения, поэтому для них следует записать в Clipboard данные не только в форматах отображения, но и в стандартных форматах.
Форматы отображения не всегда позволяют точно изобразить данные, которые записаны в Clipboard в нестандартном формате. Например, текстовый процессор может записать в Clipboard текст и параметры его шрифтового оформления. Форматы CF_TEXT и CF_DSPTEXT не позволяют передать особенности шрифтового оформления, поэтому при просмотре содержимого Clipboard вы увидите "сырой" текст, набранный системным шрифтом.
Как же заставить приложение, предназначенное для просмотра Clipboard, максимально точно отобразить нестандартные данные, записанные приложением?
Для этого следует воспользоваться форматом данных CF_OWNERDISPLAY. Если ваше приложение при вызове функции SetClipboardData указывает этот формат данных, отображение в окне просмотра Clipboard будет выполнять то приложение, которое записало нестандартные данные.
Приложение, создавшее окно просмотра Clipboard, при отображении данных в формате CF_OWNERDISPLAY посылает владельцу Clipboard сообщения, связанные с отображением в окне просмотра.
Когда приложение-владелец Clipboard должен нарисовать нестандартные данные в окне просмотра, оно получает сообщение WM_PAINTCLIPBOARD. Отметим, что это сообщение посылает не Windows, а приложение, создавшее окно просмотра Clipboard (если оно рассчитано на отображение данных в формате CF_OWNERDISPLAY).
Параметр wParam сообщения WM_PAINTCLIPBOARD содержит идентификатор окна просмотра Clipboard, в котором нужно нарисовать данные.
Через параметр lParam передается идентификатор глобального блока памяти, содержащего структуру типа PAINTSTRUCT, определяющую внутреннюю область окна просмотра.
Перед использованием блок памяти следует зафиксировать функцией GlobalLock, а после использования - расфиксировать функцией GlobalUnlock.
Когда окно просмотра Clipboard изменяет свои размеры, владелец Clipboard, записавший туда данные в формате CF_OWNERDISPLAY, получит сообщение WM_SIZECLIPBOARD.
Параметр wParam сообщения WM_SIZECLIPBOARD содержит идентификатор окна просмотра Clipboard. Через параметр lParam передается идентификатор глобального блока памяти, содержащего структуру типа RECT, определяющую новые размеры внутренней области окна просмотра Clipboard.
Есть еще три сообщения, которые владелец Clipboard получает от окна просмотра Clipboard.
Сообщение WM_ACKCBFORMATNAME посылается окном просмотра, для того чтобы получить от владельца Clipboard имя нестандартного формата, использованное в процессе регистрации. Это имя будет отображено в списке доступных для отображения форматов данных.
Параметр lParam сообщения WM_ACKCBFORMATNAME содержит указатель на буфер, в который нужно скопировать текстовую строку имени нестандартного формата, закрытую двоичным нулем. Размер буфера задается параметром wParam.
Окно просмотра Clipboard может быть снабжено полосами просмотра. Поэтому владелец Clipboard может получить сообщения WM_HSCROLLCLIPBOARD и WM_VSCROLLCLIPBOARD. В процессе обработки сообщений владелец Clipboard может использовать функцию ScrollWindow для свертки окна просмотра.
Параметр wParam этих сообщений содержит идентификатор окна просмотра Clipboard. Через младшее слово параметра lParam передается код полосы просмотра (константы с префиксом имени SB, такие как SB_TOP, SB_BOTTOM, SB_LINEUP и т. п.). Старшее слово параметра lParam содержит значение позиции полосы просмотра.
Next()
Переход к следующему разделу в разделах, расположенных последовательно.