PC Magazine/RE logo (С) СК Пресс 11/95
PC Magazine July 1995, p. 317

Недокументированные функции справочной системы WinHelp, часть 1

Пит Девис, Джим Мишель


Справочная система Microsoft Windows Help удостоилась повышенного внимания прессы: за последние 18 месяцев ей были посвящены две книги, ежегодна конференция, привлекшая более 200 разработчиков, появился специализированный журнал (The WinHelp Journal) и многочисленные статьи в других изданиях, где обсуждались различные аспекты справочной системы, от самых простых до наиболее глубинных недокументированных свойств WinHelp.

Одна из малоизвестных и наиболее неверно понимаемых возможностей WinHelp - это обращения к функциям библиотек DLL от независимых поставщиков как к специально созданным макрокомандам. Защита HELP-файлов с помощью пароля, расширяемые страницы содержания, "водяные знаки" и множество средств поиска по ключевому слову - лишь несколько примеров функциональных возможностей, добавленных к WinHelp специально созданными макрокомандами.

Корпорация Microsoft опубликовала сведения о большей части интерфейса DLL WinHelp в руководстве по разработке специализированных HELP-систем (Help Authoring Guide), которое можно получить на компакт-диске Microsoft Developer Library, а также в файле HAG.ZIP форума WINSDK информационной службы CompuServe. В этой статье исследуются некоторые части интерфейса с DLL, которые не были документированы корпорацией Microsoft. Mы покажем, как использовать эти недокументированные средства для создания библиотеки DLL, в которой отмечается, кто и в какое врем обращался к файлу справочной системы.

Материал, изложенный в этой статье, применим только к версии 3.10.425 WINHELP.EXE, поставляемой с Windows3.1. Мы не проверяли, справедлива ли изложенна ниже информация по отношению к версии WINHELP4.0, которая будет поставляться в комплекте с Windows 95.

ВНУТРИ HELP-ФАЙЛА.

Один из недокументированных корпорацией Microsoft элементов справочной системы - внутренний формат файлов Windows Help (.HLP). Это досадное упущение, так как информация, получаемая из HLP-файла, необходима дл работы многих новейших функциональных средств (таких, как свертываемые страницы содержания). Формат HLP-файла был частично документирован - хотя и неофициально - Питом Девисом в разделе Undocumented Corner сентябрьского и октябрьского номеров журнала Dr. Dobb's Journal за 1993 г. Здесь мы не собираемся приводить детальное описание формата HLP-файла, а дадим лишь очень краткий обзор, чтобы облегчить понимание приведенного ниже материала.

Внутренняя структура HLP-файла в действительности представляет собой завершенную файловую систему с заголовком, каталогом и отдельными логическими файлами внутри HLP-файла (bag files), которые содержат текстовую информацию о предмете справки, ключевые слова и другие сведения, требующиеся для того, чтобы WinHelp могла вывести на экран содержимое ваших справочных файлов. Bag-файлы также используются для хранени растровых массивов, вызываемых с помощью ссылок, и файлов, имена которых перечисляются в разделе [BAGGAGE] проектного файла справочной системы. WinHelp предоставляет в ваше распоряжение ряд функций внешнего вызова (callback), позволяющих получить доступ к справочной файловой системе (Help File System, HFS) из ваших библиотек DLL примерно тем же способом, каким реализуются обращения к файловой системе DOS из прикладных программ Windows.

DLL И ФУНКЦИИ ВНЕШНЕГО ВЫЗОВА.

Библиотеки DLL, которые реализуют простые специализированные макрокоманды для справочной системы, ничем не отличаются от DLL, написанных для других языков. Например, функция DLL, служащая для выдачи звукового сигнала через громкоговоритель ПК, может быть вызвана либо из программы, составленной на языке Си, либо как макрокоманда справочной системы. Но, когда вы используете функции, специфические для WinHelp, такие, как поиск информации по заданной теме в справочном файле, вам следует подготовить DLL так, чтобы она могла работать совместно с WinHelp. В руководстве по разработке HELP-систем подробно описывается интерфейс DLL WinHelp и приводится несколько примеров библиотек DLL, которые взаимодействуют с WinHelp. Здесь мы рассмотрим основы справочной системы. Более подробную информацию вы можете получить в руководстве по разработке индивидуальных HELP-систем.

WinHelp обращается к DLL через функцию LDLLHandler(). Си-прототип этой функции выглядит следующим образом:

long _far _pascal _export LDLLHandler (WORD wMsg, LPARAM lParam1, LPARAM lParam2);

WinHelp передает сообщения в LDLLHandler(), чтобы проинформировать библиотеку DLL об определенных событиях WinHelp. WinHelp информирует DLL о таких событиях, как инициализация и завершение, переходы и активация окон. Когда DLL загружается в первый раз (это происходит во время первого обращения WinHelp к любой функции DLL), WinHelp посылает сообщение DW_WHATMSG в функцию LDLLHandler(); return-значение этой функции точно определяет события, о которых WinHelp должна извещать эту библиотеку.

По запросу DLL WinHelp пошлет сообщение DW_CALLBACKS и указатель на таблицу функций внешнего вызова, который может использоваться библиотекой для чтения файлов справочной системы и получения другой информации о текущем состоянии WinHelp. В документации Microsoft описываются 16 таких функций; их содержатся прототипы в файле DLL.H, входящем в руководство по разработке специализированных HELP-систем. Вот список документированных функций внешнего вызова:

Указатели на эти функции внешнего вызова хранятся в позициях 1 - по 16 таблиц. WinHelp передает их функции LDLLHandler() вместе с сообщением DW_CALLBACKS.

НЕДОКУМЕНТИРОВАННЫЕ ФУНКЦИИ ВНЕШНЕГО ВЫЗОВА WINHELP.

В руководстве по разработке специализированных HELP-систем не упамянуто, что существует еще девять дополнительных функций внешнего вызова, позволяющих создавать и изменять HELP-файлы, а также логические файлы. Адреса вызова этих функций хранятся в позициях 17 - 25 таблиц, которая передается функции LDLLHandler() вместе с сообщением DW_CALLBACKS. Си-определения функций и соответствующих им смещений в таблице показаны в листинге HELPDLL.H ниже. Вот описания недокументированных функций:

ОГРАНИЧЕНИЯ.

В отношении приведенных выше функций внешнего вызова, как и любых других недокументированных средств, фирма Microsoft не дает никаких гарантий, и они могут быть изменены или удалены в будущих версиях WinHelp. Добытые эмпирическим способом сведения показывают, что эти функции работают так, как я описал, но у вас может быть иная информация.

Одно из ограничений, накладываемых на использование недокументированных внешних вызовов, состоит в том, что вы не можете записывать или иным способом изменять открытый в данный момент HELP-файл. WinHelp открывает файлы только для чтения и устанавливает режим доступа так, что другие процессы могут читать, но не изменять файл. Это следует признать недостатком, потому что средства для изменения открытого файла облегчили бы возможность реализации многочисленных функций, в том числе шифрование тематических разделов справочной системы и динамическое изменение растровых массивов в HELP-файлах. На документированные функции внешнего вызова не накладывается подобных ограничений, поскольку они не изменяют содержимого HELP-файла.

Возможность динамического изменения HELP-файлов все же существует, но для этого вы должны синхронизировать два HELP-файла - переключиться на один из них и в это время изменить другой, а затем переключиться на тот, который был только что изменен. Этот метод мы используем в приводимой в качестве примера программе.

МНОЖЕСТВЕННЫЕ ЭКЗЕМПЛЯРЫ WINHELP.

Когда WinHelp передает адреса внешнего вызова в DLL, в действительности она передает адрес таблицы указателей на функции, которые были созданы с помощью функции MakeProcInstance(). Когда у вас одновременно выполняются несколько экземпляров WINHELP.EXE, каждый экземпляр имеет свою таблицу указателей на функции, которые он передает в библиотеки DLL. Если ваша DLL составлена таким образом, что не может обрабатывать несколько экземпляров WinHelp (а большинство DLL должны иметь такую возможность, так как иначе нельз предотвратить попытки WinHelp дважды загрузить одну и ту же библиотеку), то в конце концов при выполнении какого-то фрагмента кода DLL вы, скорее всего, узнаете об ошибке "General Protection Fault" (GPF). Например, предположим, что у вас есть DLL, которая получает указатели на функции внешнего вызова из WinHelp, чтобы получить доступ к HELP-файлам из этой библиотеки. Первый экземпляр WinHelp, загружающий DLL, передаст указатели на функции вашей библиотеки, копирующей указатели в собственное пространство памяти. Второй экземпляр WinHelp, загружающий DLL, передаст ей другой набор указателей на функции, которые (если ваша DLL не может работать с несколькими экземплярами WinHelp) будут записаны "поверх" указателей на функции первого экземпляра. Функционирование вашей DLL будет нормальным, за исключением того, что функция LGetInfo() может иногда принимать неверные return-значения. Однако если вы закроете второй экземпляр WinHelp, то указатели на функции, сохраненные в вашей DLL, станут ошибочными и вызов любой из них, по всей вероятности, приведет к ошибке GPF.

Обычно для работы с несколькими внешними вызовами применяется программный прием, использующий связанный список массивов функций внешнего вызова - одного дл каждого экземпляра - и некоторого фрагмента программного кода. Это необходимо, чтобы обеспечить ссылку на нужный экземпляр каждый раз, когда вы обращаетесь к одной из функций внешнего вызова. Реализующая эти функции программа может оказатьс довольно сложной, а ее приспособления к каждой из различных DLL после третьего или четвертого повторени может наскучить. Я ненавижу переделывать стандартные тексты для приспособления к конкретным ситуациям и потратил некоторое время на эксперименты. В итоге получился набор макроконструкций на языке Си и небольшой модуль во входном формате компоновщика, который позволяет работать с несколькими экземплярами при минимальных усилиях. Вы найдете эти макроконструкции в файле HELPDLL.H, который также содержит определения типов недокументированных функций внешнего вызова системы WinHelp и прототипов функций для программ, определенных в HELPDLL.C.

Логически HELPDLL.H и HELPDLL.C представляют собой расширения DLL.H, заголовочного файла библиотеки DLL системы WinHelp, распространяемого фирмой Microsoft в комплекте с руководством по разработке специализированных HELP-систем на компакт-диске Developer Network (сеть разработчика) и в файле HAG.ZIP, который можно загрузить по сети из информационной службы CompuServe. Поскольку HELPDLL.H содержит в себе DLL.H, вы можете заменить любые ссылки на DLL.H в ваших библиотеках DLL WinHelp ссылками на HELPDLL.H.


Набор макроконструкций на языке Си и небольшой модуль во входном формате компоновщика обслуживают несколько экземпляров указателей на функции WinHelp при минимальных дополнительных усилиях. Также показаны Си-определения для девяти недокументированных функций внешнего вызова в системе WinHelp и соответствующие им смещения в таблице, передаваемой в LDLLHandler() вместе с сообщением DW_CALLBACK.
/* HELPDLL.H - дополнительные определения для библиотек DLL системы winHelp (c) Jim Mischel, 1994 */ #include "dll.h" // Включение стандартных определений // В DLL послано недокументированное сообщение #define DW_ACTIVATEWIN 11 // Смещения недокументированных функций внешнего вызова #define HE_FchsizeHF 17 #define HE_HFCreateFileHFS 18 #define HE_RCUnlinkFileHFS 19 #define HE_RCFlushHF 20 #define HE_LCBWriteHF 21 #define HE_RCRenameFileHFS 22 #define HE_RCAbandonHF 23 #define HE_HFSCreateFileSys 24 #define HE_RCDestroyFileSys 25 #define HE_Last 26 #define VPTR_Size (HE_Last * sizeof (VPTR)) // Определения типов для недокументированных внешних вызовов typedef BOOL (FAR PASCAL *LPFN_FCHSIZEHF)(HF f, LONG qlSize); typedef HF (FAR PASCAL *LPFN_HFCREATEFILEHFS) (HFS fs, LPSTR szName, BYTE bFlags); typedef RC (FAR PASCAL *LPFN_RCUNLINKFILEHFS)(HFS fs, LPSTR szName); typedef RC (FAR PASCAL *LPFN_RCFLUSHHF)(HF f); typedef LONG (FAR PASCAL *LPFN_LCBWRITEHF) (HF f, LPBYTE qb, LONG LCB); typedef RC (FAR PASCAL *LPFN_RCRENAMEFILEHFS) (HFS fs, LPSTR szOldName, LPSTR szNewName); typedef RC (FAR PASCAL *LPFN_RCABANDONHF)(HF f); typedef HFS (FAR PASCAL *LPFN_HFSCREATEFILESYS) (LPSTR szName, LONG Something); typedef RC (FAR PASCAL *LPFN_RCDESTROYFILESYS)(LPSTR szName); // Структура элементов DLL typedef struct tagwhDLLStruc { unsigned StackSeg; // Идентификатор уникального экземпляра VPTR FPtrs; // Таблица функций внешнего вызова void far * Extra; // Данные экземпляра, специфические для DLL struct tagwhDLLStruc *Next; // Указатель на следующий элемент // списка } whDLLStruc, far * whDLLPtr; typedef void (far *whDLLFreeFunc) (void far *p); // Функции, определенные в HELPDLL.C BOOL far pascal GetWinHelpCallbacks (VPTR VPtr, long lVeralon); void far Pascal EndWinHelpDLL (whDLLFreeFunc Func); whDLLPtr far pascal GetCurrentModule (void); #define CreateDLLInstance GetCurrentModule // Макрокоманды функций внешнего вызова в системе WinHelp #define HFSOpenSz(szName, fMode) \ ((LPFN_HFSOPENSZ)(GetCurrentModule())->FPtrs [HE_HfsOpenSz]) \ (szName, fMode) #define RCCloseHFS(fs) \ ((LPFN_RCCLOSEHFS)(GetCurrentModule())->FPtrs[HE_RcCloseHfs]) \ (fs) #define HFOpenHFS(fs, szName, bFlags) \ ((LPFN_HFOPENHFS)(GetCurrentModule())->FPtrs[HE_HfOpenHfs]) \ (fs, szName, bFlags) #deflne RCCloseHF(f) \ ((LPFN_RCCLOSEHF)(GetCurrentModule())->FPtrs [HE_RcCloseHf])(f) #define LCBReadHF(f, qb, LCB) \ ((LPFN_LCBREADHF)(GetCurrentModule())->FPtrs[HE_LcbReadHf]) \ (f, qb, LCB) #define LTellHF(f) \ ((LPFN_LTELLHF) (GetCurrentModule())->FPtrs[HE_LTellHf]) (f) #define LSeekHF(f, lOffset, wOrigin) \ ((LPFN_LSEEKHF) (GetCurrentModule())->FPtrs [HE_LSeekHf]) \ (f, lOffset, wOrigin) #define FEofHF(f) \ ((LPFN_FEOFHF) (GetCurrentModule())->FPtrs[HE_FEofHf]) (f) #define LCBSizeHF(f) \ ((LPFN_LCBSIZEHF)(GetCurrentModule())->FPtrs[HE_LcbSizeHf]) (f) #define FAccessHFS(f8, szName, bFlags) \ ((LPFN_FACCESSHFS) (GetCurrentModule())->FPtrs[HE_FAccessHfs]) \ (fs, szName, bFIags) #define ErrorW(nError) \ ((LPFN_ERRORW)(GetCurrentModule())->FPtrs[HE_ErrorW])(nError) #define ErrorSz(ErrMsg) \ ((LPFN_ERRORSZ)(GetCurrentModule())->FPtrs[HE_ErrorSz])(ErrMsg) #define LGetInfo(wItem, hwnd) \ ((LPFN_LGETINFO)(GetCurrentModule())->FPtrs[HE_GetInfo]) \ (wItem, hwnd) #define FApi(qchHelp, wCommand, ulData) \ ((LPFN_FAPI)(GetCurrentModule())->FPtrs[HE_API]) \ (qchHelp, wCommand, ulData) #define RCLLInfoFromHF(f, wOption, qFid, qlBase, qLCB) \ ((LPFN_RCLLINFOFROMHF ) \ (GetCurrentModule())->FPtrs[HE_RcLLInfoFromHf]) \ (f, wOption, qFid, qlBase, qLCB) #define RCLLInfoFromHFS(fs, szName, wOption, qFId, qlBase, qLCB) \ ((LPFN_RCLLINFOFROMHFS) \ (GetCurrentModule())->FPtrs[HE_RcLLInfoFromHfs]) \ (fs, szName, wOption, qFid, qlBase, qLCB) #define FChSizeHF(f, qlSize) \ ((LPFN_FCHSIZEHF)(GetCurrentModule())->FPtrs[HE_FChSizeHF]) \ (f, qlSize) #define HFCreateFileHFS(fs, szName, bFIags) \ ((LPFN_HFCREATEFILEHFS) \ (GetCurrentModule())->FPtrs[HE_HFCreateFileHFS]) \ (fs, szName, bFlags) #define RCUnlinkFileHFS(fs, szName) \ ((LPFN_RCUNLINKFILEHFS) \ (GetCurrentModule())->FPtrs[HE_RCUnlinkFileHFS])(fs, szName) #define RCFlushHF(f) \ ((LPFN_RCFLUSHHF)(GetCurrentModule())->FPtrs[HE_RCFlushHF])(f) #define LCBWriteHF(f, qb, LCB) \ ((LPFN_LCBWRITEHF) \ (GetCurrentModule())->FPtrs[HE_LCBWriteHF])(f, qb, LCB) #define RCRenameFileHFS(fB, szoldName, szNewName) \ ((LPFN_RCRENAMEFILEHFS) \ (GetCurrentModule())->FPtrs[HE_RCRenameFileHFS]) \ (fs, szOldName, szNewName) #define RCAbandonHF(f) \ ((LPFN_RCABANDONHF)(GetCurrentModule())->FPtrs[HE_RCAbandonHF]) \ (f) #define HFSCreateFileSys(szName, Something) \ ((LPFN_HFSCREATEFILESYS) \ (GetCurrentModule())->FPtrs[HE_HFSCreateFileSys]) \ (szName, Something) #define RCDestroyFileSys(szName) ((LPFN_RCDESTROYFILESYS) \ (GetCurrentModule())->FPtrs[HE_RCDestroyFileSys] ) \ (szName) // Определение LGetInfo #define LGetHInstance() (HlNSTANCE)LGetInfo (GI_HINSTANCE, 0) #define LGetMainHWnd() (HWND)LGetInfo (GI_MAINHWND, 0) #define LGetCurrHWnd() (HWND)LGetInfo (GI_CURRHWND, 0) #define LGetHFS(wnd) (HFS)LGetINFO (GI_HFS, wnd) #define LGetFGColor(wnd) LGetInfo (GI_FGCOLOR, wnd) #define LGetBKColor(wnd) LGetInfo (GI_BKCOLOR, wnd) #define LGetTopicNo(wnd) LGetInfo (GI_TOPICNO, wnd) char far * LGetHPath (HWND wnd); // В HELPDLL.C

Эти программы в сочетании с HELPDLL.H представляют собой расширение поставляемого фирмой Microsoft файла DLL.H, позволяя обслуживать несколько экземпляров WINHELP.EXE
/* HELPDLL.C -- Функции WinHelp DLL Helper (C) Jim Mischel, 1994 */ #include #include #include #include "helpdll.h" static whDLLStruc ModuleList = {0, NULL, NULL); static whDLLPtr CurrentModule = &ModuleList; // Внутренние программы связанного списка static void llPrepend (whDLLPtr p) { p->Next = ModuleList.Next; ModuleList.Next = p; } static whDLLPtr llFind (unsigned s) { whDLLPtr g = ModuleList.Next; while (g != NULL && q->StackSeg != s) q = q->Next; return q; } static void llUnlink (whDLLPtr p) { whDLLPtr q = &ModuleList; while (q->Next != p) q = q->Next; q->Next = p->Next; } static void llFree (whDLLPtr p, whDLLFreeFunc Func) { llUnlink (p); // Освободить любые другие области памяти, // используемые этой DLL if (Func \= NULL) Func (p->Extra); else if (p->Extra != NULL) free (p->Extra); free (p->Fptrs); free (p); } whDLLPtr far pascal GetCurrentModule (void) { #ifndef _BORLANDC_ unsigned _SS; asm mov _SS,ss #endif if (CurrentModule->StackSeg != _SS) if ((CurrentModule = llFind (_SS)) == NULL) { CurrentModule = malloc (sizeof (whDLLStruc)); CurrentModule->StackSeg = _SS; CurrentModule->Extra = NULL; CurrentModule->Next = NULL; CurrentModule->FPtrs = malloc (VPTR_Size); llPrepend (CurrentModule); } return CurrentModule; } // GetWinHelpCallbacks - копировать внутренние указатели // функций WinHelp BOOL far pascal GetWinHelpCallbacks (VPTR VPtr, long lVersion) { memmove (GetCurrentModule()->FPtrs, VPtr, vpTR_Size); return TRUE; } // EndWinHelpDLL - свободная память, используема // этим экземпляром void far pascal EndWinHelpDLL (whDLLFreeFunc Func) { llFree (GetCurrentModule (), Func); CurrentModule = &ModuleLlst; } /* LGetHPath -- получить префикс имени текущего HELP-файла и передать в вызывающую программу новую выделенную строку. Эта функция вызывает LGetInfo() и затем копирует полученную строку в новый указатель, который передается в вызывающую программу. Логический номер, получаемый из LGetInfo(), освобождается. Программа, вызывающая LGetHPath(), должна освободить указатель, переданный LGetHPath(). */ char far * LGetHPath (HWND wnd) { HGLOBAL Handle = (HGLOBAL)LGetInfo (GI_HPATH, wnd); char far *c = GlobalLock (Handle); char far *fname = NULL; if (c != NULL) { fname = strdup (c); GlobalUnlock (Handle); GlobalFree (Handle); } return fname; }

КАК ЭТО РАБОТАЕТ.

Центральная часть HELPDLL.H и HELPDLL.C, обеспечивающая работу с несколькими экземплярами WinHelp, - функция GetCurrentModule(). Эта функци составляет связанный список экземпляров WinHelp, загрузивших DLL, содержащий также указатели на функции внешнего вызова каждого экземпляра и разнообразную информацию о DLL для каждого экземпляра. Структуры в списке выглядят следующим образом:

typedef struct tagwhDLLStruc { unsigned StackSeg; VPTR FPtrs; void far * Extra; struct tagwhDLLStruc *Next; } whDLLStruc, far * whDLLPtr;

Для идентификации конкретного экземпляра WinHelp мы применяли не внутренний идентификатор handle, а значение сегмента стека, потому что столкнулись с трудностями при использовании функции GetCurrentTask() в одной из первых редакций Windows NT.

Поле FPtrs - просто указатель на блок памяти, который был выделен для хранения указателей на функции для данного экземпляра WinHelp. Переменная Extra может быть использована DLL для того, чтобы выделить отдельный участок памяти в распоряжение каждого экземпляра WinHelp. Функция EndWinHelpDLL() в HELPDLL.C автоматически освободит эту память, или, если необходимо, ваша DLL может передать в EndWinHelpDLL() указатель на функцию, которая освободит ранее выделенную память.

Функция GetCurrentModule() получает текущее значение сегмента стека и находит в списке структуру, соответствующую этому значению сегмента стека. Если такой структуры не существует, то GetCurrentModule() выделяет память и инициализирует новую структуру с таким значением сегмента стека. В любом случае GetCurrentModule() передает в вызывающую программу указатель на структуру, которая содержит данные дл текущего экземпляра WinHelp.

Функция GetWinHelpCallbacks(), которую ваша функци LDLLHandler() должна вызвать при получении сообщени DW_CALLBACKS из WinHelp, вызывает функцию GetCurrentModule(), чтобы создать структуру для нового экземпляра WinHelp в связанном списке, а затем копирует адрес функции внешнего вызова из таблицы, переданной WinHelp.

Оставшаяся часть HELPDLL.H содержит макроконструкции на языке Си, которые делают процедуру обращения к функциям внешнего вызова относительно безболезненной. Эти макроконструкции получают указатель на структуру данных текущего экземпляра WinHelp и затем вызывают функцию из принадлежащего экземпляру списка функций внешнего вызова. Вот, например, определение LCBWriteHF:

#define LCBWriteHF(f, qb, LCB) \ ((LPFN_LCBWRITEHF) (GetCurrent\ Module())->FPtrs[HE_LCBWriteHF])\ (f, qb, LCB)
Код, который генерируется, когда вы пишете
LCBWriteHF(f, (LPBYTE)Buffer, BufferSize);
эквивалентен следующему фрагменту исходного текста:
whDLLPtr p = GetCurrentModule(); LPFN_LCBWRITEHF WriteHF = p->Fptrs [HE_LCBWriteHF]; WriteHF(f, (LPBYTE)Buffer, BufferSize);

Вызов GetCurrentModule() при каждом обращении к функции внешнего вызова несколько замедляет программу, однако программы, вызывающие эти функции, обычно не предъявляют особых требований к производительности, а альтернативный подход сопряжен с многочисленными обращениями функции GetCurrentModule(), разбросанными по всей вашей программе. Поэтому последний прием крайне запутан, и его трудно реализовать без ошибок.

ПРИМЕР.

HACCESS.C представляет собой WinHelp DLL, реализующую журнал регистрации обращений к HELP-файлу, который запрашивает имя пользователя при загрузке файла диалоговой документации. Программа добавляет введенное имя и текущее время в логический файл, входящий в состав данного HELP-файла. Регистратор доступа реализован в специально созданной макрокоманде AddAccess(). Другая макрокоманда, ShowAccess(), может быть вызвана, чтобы вывести на экран список лиц, обращавшихся к HELP-файлу.

Чтобы протестировать HACCESS, вы должны выполнить компиляцию файлов HACCESS.C и HELPDLL.C и скомпоновать их с файлами описания ресурсов - HACCESS.RC и HACCESS.RH, - которые содержат идентификаторы ресурсов.

Используйте HELP-компилятор (HC.EXE), чтобы создать тестовый HELP-файл - ACCTEST.HLP - из файлов ACCTEST.HPJ и ACCTEST.RTF. Раздел [CONFIG] в проектном файле ACCTEST.HPJ определяет специально созданные макрокоманды и вызывает функцию AddAccess(), чтобы получить имя пользователя и записать имя и текущее время в логический файл регестрации ACCESS.LOG, который содержится в ACCTEST.HLP. Единственный тематический элемент в ACCTEST.RTF содержит "горячую" точку, при "нажатии" на которую вызывается макрокоманда ShowAccess(), выводящая на экран список лиц, получавших доступ к HELP-файлу.

Для того чтобы функция AddAccess() могла работать, она должна загрузить другой HELP-файл, добавить информацию о доступе в ACCTEST.HLP и затем перезагрузить ACCTEST.HLP. Загруженный рабочий HELP-файл - ACCESS.HLP - создается HELP-компилятором из файлов ACCESS.HPJ и ACCESS.RTF. Все эти файлы могут быть загружены из сети ZiffNet (в службе CompuServe); вы найдете файл HACC.ZIP в разделе Power Programming Library форума Programming Forum (GO ZNT:PROGRAM).

Файл HACC.ZIP можно также переписать с ftp-сервера ftp.pcmag.ziff.com/pcmag/Issue Archives/v14_1995 и извлечь из файла v14n13.zip.


HACCESS.H - это библиотека WinHelp DLL, котора реализует журнал регистрации обращений к HELP-файлу, запрашивая имя пользователя при загрузке файла диалоговой документации. Программа добавляет введенное имя и текущее время в логический файл, входящий в состав этого HELP-файла.
/* HACCESS.C - библиотека DLL для учета обращений к HELP-файлу (c) Jim Mischel, Pete Devis, 1995 */ #include #include #include #include "helpdll.h" #include "haccess.rh" #define TEMPORARY_FILE "access.hip" #define ACCESS_BAGFILE "access.log" // Дескриптор экземпляра static HINSTANCE HInstance; static char FileToModify[128]; static int AccessFlag = 0; static int ModFlag = 0; int _far _pascal _export LibMaln (HINSTANCE hInst, WORD wDataSeg, WORD cbHeap, LPSTR lpchCmdLine) { HInstance = hInst; return TRUE; } static void FilIListbox (HWND hDlg) { HFS hfs; HF hf; char *filename; char *Buffer; char *c; long FileSize; int Counter; HWND hwndListbox = GetDlgItem (hDlg, IDC_LISTBOX1); filename = LGetHPath (LGetCurrHWnd ()); // Попытка открыть HELP-файл if ((hfs = HFSOpenSz (filename, fFSOpenReadOnly)) == 0) return; free (filename); // Попытка открыть логический файл if ((hf = HFOpenHFS (hfs, ACCESS_BAGFILE, fFSOpenReadOnly)) == 0) goto CloseHFS; // Получить размер файла и выделить буфер FileSize = LCBSizeHF (hf); Buffer = malloc (FileSize); // Чтение содержимого файла в буфер LCBReadHF (hf, (LPBYTE)Buffer, FileSize); // Теперь добавить каждую строку в окно списка c = Buffer; Counter = 0; while (Counter < FileSize) { SendMessage (hwndListbox, LB_ADDSTRING, 0, (LPARAM)c); Counter += lstrlen (c)+1; c += latrlen (c)+1; } free (Buffer); RCCloseHF (hf); CloseHFS: RCCloseHFS (hfs); } LRESULT FAR PASCAL DisplayDlgProc (HWND hDlg, WORD message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG : FillListbox (hDlg); return TRUE; case WM_COMMAND : switch (wParam) { case IDOK : EndDialog (hDlg, IDOK); break; } break; } return FALSE; } void _far _pascal _export ShowAccess (void) { DLGPROC dIgproc; // получить им dlgproc = (DLGPROC)MakeProcInstance ((FARPROC)DisplayDlgProc, HInstance); DialogBox (HInstance, "DisplayDlg", LGetCurrHWnd (), dlgproc); FreeProcInstance ((FARPROC)dlgproc); } static char AccessString[61]; LRESULT FAR PASCAL NameDlgProc (HWND hDlg, WORD message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG : { HWND EditBox = GetDlgItem (hDlg, IDC_EDIT1); // Очистить окно ввода и установить максимальную // длину текста SetWindowText (EditBox, ""); SendMessage (EditBox, EM_LIMITTEXT, 30, 0L); return TRUE; } case WM_COMMAND : switch (wParam) { case IDOK ; GetDlgItemText (hDlg, IDC_EDIT1, AccessString, 30); EndDialog (hDlg, IDOK); break; } break; } return FALSE; void _far _pascal _export AddAccess (void) { char *c; DLGPROC dlgproc; time_t ttime; if (AccessFlag) ( AccessFlag = 0; return; } // Получить им dlgproc = (DLGPROC)MakeProcInstance ((FARPROC)NameDlgProc, HInstance); DialogBox (HInstance, "AccessDlg", LGetCurrHWnd (), dlgproc); FreeProcInstance ((FARPROC)dlgproc); // Добавить дату и врем Istrcat (AccessString, " "); ttime = time (NULL); lstrcat (AccessString, asctime (localtime (&ttime))); // Удалить символ новой строки AccessString[lstrlen(AccessString)-1] = '\0'; c = LGetHPath (LGetCurrHWnd ()); lstrcpy (FileToModify, c); free (c); AccessFlag = 1; ModFlag = 1; FApi (TEMPORARY_FILE, HELP_FORCEFILE, 0L); } static void UpdateAccess (char *Filename) { HFS hfs; HF hf; if (ModFlag--) return; // Попытка открыть HELP-файл if ((hfs = HFSOpenSz (Filename, fFSOpenReadWrite)) == 0) return; // Попытка открыть логический файл if ((hf = HFOpenHFS (hfs, ACCESS_BAGFILE, fFSOpenReadWrite)) == 0) { // Он нe существует, создать его hf = HFCreateFileHFS (hfs, ACCESS_BAGFILE, fFSOpenReadWrite); } // Поиск конца файла LSeekHF (hf, 0, wFSSeekEnd); // Запись строки LCBWriteHF (hf, (LPBYTE)AccessString, lstrlen (AccessString)+1); RCCloseHF (hf); CloseHFS: RCCloseHFS (hfs); } /* LDLLHandler -- ответ на сообщение WinHelp */ long _far _pascal _export LDLLHandler (WORD wMsg, LONG lParam1, LONG lParam2) { switch (wMsg) { /* Передаваемые в вызывающую программу значение указывает типы сообщений, которые вы хотите получать. */ case DW_WHATMSG : return (DC_CALLBACKS | DC_INITTERM | DC_JUMP); /* Получить адреса внешних вызовов */ case DW_CALLBACKS : GetWinHelpCallbacks ((VPTR)lParam1, lParam2); return TRUE; case DW_INIT : return TRUE; /* Получено непосредственно перед тем, как DLL выгружена */ case DW_TERM : EndWinHelpDLL (NULL); return TRUE; /* Получено после завершения перехода */ case DW_ENDJUMP : if (AccessFlag) { UpdateAccess (FileToModify); FApi (FileToModify, HELP_FORCEFILE, 0); } return TRUE; /* Игнорировать любые другие сообщения */ default : return FALSE; } }
PC Magazine/Russian Edition спецвыпуск 1/1996 (45) PC Magazine September 12, 1995, p.536

Недокументированные функции справочной системы WinHelp, часть 2

Пит Девис, Джим Мишель


Здесь мы расскажем о том, как заставить систему WinHelp делать вещи, для которых она вовсе не предназначалась. В первой части (PC Magazine/Russian Edition, 11/95, c. 168) мы обсуждали разнообразные недокументированные аспекты WinHelp. Теперь мы продолжим этот разговор и, используя некоторые недокументированные возможности Windows, заставим WinHelp делать некоторые вещи, для которых она вовсе не предназначалась.

Сначала вы узнаете об использовании интерфейса прикладного программирования Windows API из WinHelp. Нельзя сказать, что соответствующие действия были полностью недокументированы, но в документации для SDK не разъясняется, с какой целью и каким образом вы можете использовать эту возможность. Затем мы узнаем, как наделить WinHelp дополнительной функциональностью, даже если речь идет о HELP-файлах, авторство которых вам не принадлежит. Мы покажем, как "вклиниться" в чужую программу.

Прежде чем начать, предупреждаем вас о двух ловушках, которых следует остерегаться. Во-первых, описанные здесь приемы либо изменятся, либо их применение станет невозможным в Win32-версиях WinHelp, в том числе в Windows 95; во-вторых, как всегда обсуждая недокументированные функции, нельзя быть уверенным, что они сохранятся во всех будущих версиях. Краткий обзор WinHelp 4.0 - версии, которая будет поставляться вместе с Windows 95, - см. во врезке "Что нового в WinHelp 4.0".

ИСПОЛЬЗОВАНИЕ WINDOWS API ИЗ WINHELP

Большинство разработчиков WinHelp осведомлены о возможности строить библиотеки DLL, к которым можно обращаться из HELP-файла, но можно также использовать Windows API из файла проекта HELP-системы или из тематических файлов, не создавая своих собственных DLL. Достаточно просто зарегистрировать подпрограммы из Windows DLL, содержащие вызовы API. Например, если вы хотите использовать функцию SetWindowLong() нужно просто зарегистрировать эту подпрограмму с помощью функции RegisterRoutine(), и затем вызывать ее как обычную макрокоманду. Функция RegisterRoutine() имеет следующий синтаксис:

RegisterRoutine (имя_DLL, имя_функции, параметры)

Параметры RegisterRoutine():

Следующая запись регистрирует подпрограмму SetWindowLong():

RegisterRoutine() ("user.exe","SetWindowLong", "uiU")

Однако на применение вызовов API налагаютс серьезные ограничения. Поскольку вы не можете создавать переменные в файле проекта HELP-системы или RTF-файле, то вам негде хранить return-значения функций. Однако, проявив творческий подход к встраиванию функций, вы можете использовать значения, передаваемые из функций API. Следующий пример может быть добавлен в раздел [CONFIG] вашего файла проекта HELP-системы, чтобы заблокировать элемент меню File | Print Topic (Файл | Печать Темы) в WinHelp:

RegisterRoutine() ("user.exe", "EnableMenuItem", "uuu") RegisterRoutine() ("user.exe", "GetSubMenu", "u=ui") RegisterRoutine() ("user.exe", "GetMenu", "u=u") RegisterRoutine() ("user.exe", "GetActiveWindow", "u=") EnableMenuItem(GetSubMenu(GetMenu(GetActiveWindow()), 0), 2, 1027)

Фактически, вы можете по своему желанию либо заблокировать, либо активизировать любые элементы меню. Хорошим применением для этого приема было бы запрещение вывода на печать и копирования в буфер обмена определенных тем, содержащих "конфиденциальную" информацию (хотя распечатка экрана была бы по-прежнему возможна). Ядро процедуры - вызов функции EnableMenuItem(). 0 - параметр функции GetSubMenu(), уведомляющий функцию EnableMenuItem() о том, с каким меню вы работаете. Далее перечислены значения этого параметра при работе с WinHelp:

0=File 1=Edit 2=Bookmark 3=Help

2 - индекс элемента меню (первый элемент имеет нулевой индекс), с которым вы работаете. Разделители считаются, поэтому Print Topic имеет номер 2.

1027 - флаг операции (action flag) для элемента меню. Полезны следующие значения:

1024=enabled (активирован) 1025=grayed (затенен) 1026=disabled (заблокирован) 1027=grayed and disabled (затенен и заблокирован)

Хотите заблокировать системное меню, свертывать и увеличивать до полного размера кнопки? Это просто. Добавьте в ваш файл HPJ следующие команды:

RegisterRoutine() ("user.exe", "SetWindowLong", "uiU") RegisterRoutine() ("user.exe", "SetWindowPos", "uuiiiiu") SetWindowLong(hwndContext,-16, 0x16000000) SettWindowPos(hwndContext,0,0,0,0,0,0x27)

С помощью функции SetWindowLong() мы устанавливаем стиль окна (GWL_STYLE=-16). Мы назначаем ему стиль WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN. Наконец, мы вызываем SetWindowPos(), чтобы сделанные изменения вступили в силу. Мы передаем 0 для параметра hwndInsertAfter и всех x- и y-координат для окна. Мы не хотим изменять позицию или порядок наложения окон друг на друга на экране (z-order). 27h - комбинаци следующих битовых флагов: SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED.

Таковы лишь некоторые вещи, которые можно сделать с помощью Windows API из WinHelp. Одно из основных ограничений состоит в том, что вы не можете использовать никаких переменных для хранени return-значений, функций. Возможное решение этой проблемы - создать DLL, которая будет работать с переменными. Это позволило бы сохранять значения, передаваемые вызовами API, и использовать их в других вызовах.

Вот как могут выглядеть такие функциональные средства. Предположим, что вы имеете следующие две функции:

void SaveIntVar(LPSTR VarName, int VarValue); int GetIntVar(LPSTR VarName);

Теперь вы можете написать программу, подобную приведенной ниже:

SaveIntVar("hWnd", GetDeskTopWindow()); MessageBox(GetTopWindow(GetIntVar("hWnd")), "Message", "Notice", 0);

Этот же результат может быть достигнут с помощью встроенных вызовов, но в данном случае преследовалась цель показать, каким образом может работать такой набор подпрограмм. Его реализация не составит труда дл программиста среднего уровня. Конечно, вам понадобитс отдельный набор подпрограмм для разных типов переменных, но все программы будут довольно похожи.

ВЫЗОВ ЭКСПОРТИРУЕМЫХ ПОДПРОГРАММ WINHELP

Одной из проблем, связанных с WinHelp, всегда было то, что для подключения DLL вам нужно было быть автором HELP-файла. Вы не можете расширить функциональные возможности любого экземпляра WinHelp. Но, воспользовавшись книгой Andrew Schulman, Dave Maxey, Matt Pietrek, "Undocumented Windows" ("Недокументированные возможности Windows", Эндрю Шульман, Дейв Макси и Матт Петрек), вышедшей в издательстве Addison-Wesley, вы можете обойти это препятствие.

В нашем примере мы хотим, чтобы WinHelp загрузила DLL и установила связь с нашей функцией LDLLHandler(). Чтобы сделать это, вам нужно найти способ заставить WinHelp вызвать функцию из этой DLL. Например, в нашей DLL содержится функция LoadHELPHOOK(). Следующие вызовы WinHelp()API загрузят DLL:

WinHelp(hWnd, "helpfile.hlp", HELP_COMMAND, (DWORD) (LPSTR) "RR('HELPHOOK', 'LoadHELPHOOK', "); WinHelp(hWnd, "helpfile.hlp", HELP_COMMAND, (DWORD) (LPSTR) "LoadHELPHOOK()");

Этот подход работает прекрасно, если только экземпляр WinHelp не был запущен с помощью WinExec() вместо WinHelp()API. В последнем случае вам придетс запустить второй экземпляр WinHelp с помощью WinHelp()API. Такое решение выглядит в лучшем случае неряшливо. Другой вариант - использовать функцию FApi(), которая экспортируется WinHelp. FApi() имеет те же функциональные возможности, что и WinHelp()API, отличие состоит в том, что она работает независимо от того, каким способом была запущена WinHelp. Остаетс единственная проблема - для получения адреса FApi() нужно, чтобы функция LDLLHandler() получила адреса косвенных вызовов из WinHelp. Ситуация кажетс безвыходной. Но так ли это?

Почему бы для получения адреса функции FApi() из WinHelp не воспользоваться функцией GetProcAddress()? Первая проблема состоит в том, что вы можете использовать функцию GetProcAddress() только дл получения адреса функции в DLL, но не в EXE-файле. (Вспомните, что USER.EXE, GDI.EXE и т. д. в сущности представляют собой DLL.) Нет проблем: мы просто на секунду превратим WinHelp() в DLL, выполним GetProcAddress() и затем вновь превратим его в задачу. Вот где нам пригодится книга "Undocumented Windows". Открыв страницу 231, мы увидим, что слово со смещением 0x000C элемента таблицы модулей (module table) содержит бит, который устанавливается или сбрасывается в зависимости от того, представляет ли собой данный модуль DLL или задачу. Как раз этот бит и проверяетс функцией GetProcAddress().

Теперь все, что нам нужно, - это дескриптор (handle) модуля. К сожалению, для этого недостаточно просто вызвать GetModuleHandle() c помощью WINHELP.EXE. Трудность состоит в том, что может существовать несколько экземпляров WinHelp. Ваш подход к этой задаче зависит от того, чего вы хотите достичь. Мы решаем ее, пристраивая нашу DLL к каждому выполняемому в данный момент экземпляру WinHelp.

HHOOK.C

Вызывая функции TaskFirst() и TaskNext() из TOOLHELP.DLL, мы просматриваем список задач Task List и ищем дескрипторы модулей и дескрипторы экземпляров, принадлежащие WinHelp. Это происходит в функции HookAll() (см. ниже).

Теперь, вооружившись дескрипторами модулей и экземпляров, мы можем превратить WinHelp в DLL, вызвать GetProcAddress(), вновь превратить WinHelp в EXE-файл и затем дать указание WinHelp установить связь с нашей DLL. Все это происходит в функции HookDLL().

Единственный необычный аспект программы состоит в том, что нам приходится выполнять вызов на языке ассемблера. Причина этого - необходимость поместить при вызове FApi() в регистр AX селектор сегмента данных. Это, в свою очередь, объясняется тем, что FApi() - косвенно вызываемая функция. Более подробное объяснение выходит за рамки данной статьи, поэтому я ограничусь описанием параметров.

Во-первых, мы загружаем 0 в регистр AX и дважды помещаем его в стек. Это действие передает NULL вместо имени HELP-файла: поскольку мы не хотим, чтобы WinHelp изменял файлы, нам не нужно передавать имя файла. Затем мы передаем 0x102. Это параметр wCommand макрокоманды HELP_COMMAND. Иными словами, мы указываем WinHelp, что последний параметр представляет собой строку, содержащую макрокоманду, которую мы хотим исполнить. Заключительный параметр - адрес переменной Command, которая представляет собой строку, содержащую нашу макрокоманду. Последние два шага загружают в AX дескриптор экземпляра (который совпадает с селектором сегмента данных WinHelp), и, наконец, вызываем FApi().

/* Вызов FApi() */ _asm { mov ax, 0 push ax push ax mov ax, 102h push ax lea ax, Command push ds push ax mov ax,hInst call lpfn_FAPI }

Это все, что требуется для подключения DLL к WinHelp. Единственное, что вам теперь нужно, - это DLL.

HELPHOOK.DLL

Зарегистрировав подпрограмму LoadHookDLL() из библиотеки HELPHOOK.DLL и вызывая LoadHookDLL(), мы заставляем WinHelp искать LoadHookHandler() в HELPHOOK.DLL. Собственно функция LoadHookDLL() ничего не делает. Это обычный способ заставить WinHelp проверить наличие LDLLHandler() в DLL.

HELPHOOK выполняет не всю работу. Это надуманный пример, однако он иллюстрирует принцип этого способа. HELPHOOK просто устанавливает LDLLHandler() и получает обратные вызовы с помощью GetWinHelpCallbacks(). (Более подробная информация приведена в статье "Недокументированные функции справочной системы WinHelp, Часть 1" PC Magazine/Russian Edition, 11/95, c.168) Затем HELPHOOK вызывает макрокоманду LGetHPath() из HELPDLL.C, чтобы получить путь доступа к текущему HELP-файлу. Затем всплывает окно сообщения: "Hooked into: d:\path\helpfile.hlp", где "d:\path\helpfile.hlp" - путь доступа и имя HELP-файла, к которому мы присоединились.

СОБИРАЯ ВСЕ ВОЕДИНО

Теперь у нас есть прикладная программа и DLL. Скопируйте DLL в каталог \WINDOWS\SYSTEM. После этого любой HELP-файл наверняка сможет найти ее. (Убедитесь, что HELPHOOK.DLL пока еще не присутствует в вашем каталоге \WINDOWS\SYSTEM.) Теперь запустите один или более экземпляров WinHelp с любыми HELP-файлами по вашему усмотрению. После того как вы их откроете, запустите HHOOK.EXE. Этим вы присоедините DLL ко всем экземплярам WinHelp. Для каждого экземпляра WinHelp на экране появится окно с сообщением, извещающим вас, что к данному экземпляру присоединена DLL.

Очевидно, что такого рода приемы могут использоваться в массе прикладных программ. Информаци о программировании WinHelp стала доступна широкой общественности лишь в прошлом году или около того. Поскольку WinHelp - система замкнутая, она иной раз требует творческих подходов. Мы надеемся, что вам пригодятся описанные здесь приемы программирования, которые, возможно, подскажут вам идеи еще более творческих проектов WinHelp. Нас всегда интересует знать, что люди делают с WinHelp, поэтому, если вы разрабатываете что-то действительно необычное, напишите нам и поделитесь своим опытом.


Эта программа отыскивает все экземпляры WinHelp и связывает каждый из них с HELPHOOK.DLL
/* HELPHOOK.C By Pete Davis & Jim Mischel PC Magazine Vol. 14, No. 15 */ #include #include #lnclude "hhook.h" int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { static char azAppName [] = "HelpHook"; MSG mag; HWND hwnd; WNDCLASS wndclass; if (!hPrevInstance) { wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = MainWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon (hInstance, szAppName); wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass.hbrBackground = GetStockObject (WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = azAppName; RegisterClass (&wndclass); } hwnd = CreateWindow (azAppName, "HelpHooker", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow (hwnd, SW_HIDE); UpdateWindow (hwnd); while (GetMesaage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam; } void HookDLL(HMODULE hModule, HINSTANCE hInat) { LPBYTE lpModule; long (FAR PASCAL *lpfn_FAPI) (LPSTR, WORD, DWORD); char Command [256]; /* Преобразовать WinHelp в DLL */ lpModule = GlobalLock(hModule); lpModule += 0x0D; *lpModule = *lpModule | 0x80; /* Получить адрес экспортируемой функции FApi() */ (FARPROC) lpfn_FAPI = GetProcAddress(hInst, "EXPFAPI"); /* Вновь преобразовать WinHelp в .EXE-файл */ *lpModule = *lpModule & Ox7F; GlobalUnlock(hModule); lstrcpy(Command, "RR('helphook.dll', 'LOADHOOKDLL', 'S' )"); lstrcat (Command, ";LoadHookDLL(qchPath)"); /* Call FApiO */ _asm { mov ax, 0; push ax push ax mov ax, 102h push ax lea ax, Command push ds push ax mov ax, hInst call lpfn_FAPI } } void HookAll() { TASKENTRY TE; TE.dwSize = sizeof(TASKENTRY); TaskFirst (&TE); if (!TE.hModule) { MessageBox(NULL, "TaskFirst function call failed", "Error", MB_OK); return; } while (TE.hNext) { if (!lstrcmp(TE.szModule, "WINHELP")) HookDLL(TE.hModule, TE.hInst); TaskNext (&TE); } } long _far _pascal _export MainWndProc (HWND hwnd, UINT message, UINT wParam, LONG lParam) { HINSTANCE hInstDLL; int i; switch (message) { case WM_CREATE: hInstDLL = LoadLibrary ("HELPHOOK. DLL"); HookAll(); for (i=1; i<=20; i++) Yield(); FreeLibrary(hInstDLL); PostMessage(hwnd, WM_DESTROY, 0, 0L); Yield(); break; case WM_DESTROY : PostQuitMessage (0); return 0; } return DefWindowProc (hwnd, message, wParam, lParam); }
В этой программе используется простая функци LDLLHandler(), которая извещает вас о том, что она присоединена к HELP-файлу, и сообщает его имя.
/* HELPHOOK. By Pete Davis & Jim Mischel PC Magazine Vol. 14 No. 15 #include #include "helpdll.h" #include "helphook.h" int FAR PASCAL LibMain (HINSTANCE hInst, WORD wDataSeg, WORD cbHeap, LPSTR lpchCmdLine) { return 1; } int FAR PASCAL _export WEP(int nParam) { return 1; } /* Пустая функция, просто удостоверяющая тот факт, что LDLLHandler загружена */ void FAR PASCAL _export LoadHookDLL(LPSTR lpPath) { return; } /* LDLLHandler -- отвечает на сообщения WinHelp */ long FAR PASCAL _export LDLLHandIer (WORD wMsg, LONG lParaml, LONG lParam2) { static char Message [256]; LPSTR lpPath; switch (wMsg) { case DW_WHATMSG : return (DC_CALLBACKS | DC_INITTERM); /* Получить адреса обратных вызовов */ case DW_CALLBACKS : GetWinHelpCallbacks ((VPTR)lParam1, lParam2); lpPath = LGetHPath(NULL); lstrcpy(Mesaage, "Hooked into: "); lstrcat (Message, lpPath); MessageBox(NULL, Message, "Notice", MB_OK); return TRUE; case DW_lNIT : return TRUE; case DW_TERM : EndWinHelpDLL (NULL); return TRUE; /* Игнорировать все прочие сообщения */ default : return FALSE; } }
Что нового в WinHelp 4.0
Пит Девис

Microsoft Windows 95 будет обладать новым механизмом диалоговой документации - WinHelp 4.0. Хотя новый механизм основывается на прежнем коде, он подвергс полному пересмотру. Пользовательский интерфейс и внешний вид самой программы WinHelp в значительной степени изменены.

Наверное, наиболее заметным новым функциональным средством станет закладка-содержание (Contents Tab). В сущности, Contents Tab - это содержание вашего HELP-файла, представленное в виде разворачиваемой/ свертываемой древовидной тематической структуры. Это очень полезное нововведение поможет пользователям в навигации по большим HELP-файлам.

С помощью такой закладки пользователи получили возможность распечатывать сразу несколько страниц, вместо того чтобы печатать их по одной. Выбрав из Contents Tab "книгу", пользователи могут распечатать все темы, находящиеся под закладкой.

Родственное средство - набор макрокоманд печати, которые позволяют предоставить пользователям выбор из нескольких тем для печати. Диапазон тем дл печати должен быть определен автором HELP-системы; пока еще не существует средств, разрешающих пользователям определять произвольный диапазон или группу тем для печати.

Одна из наиболее полезных новых функциональных возможностей - закладка поиска (Find Tab). Это почти то же самое, что поиск по ключевым словам в WinHelp 3.x. Самое большое отличие состоит в возможности дл пользователя (не автора HELP-системы) соединять списки ключевых слов из разных HELP-файлов в единый список, делая возможным поиск по ключевому слову в нескольких HELP-файлах.

Еще одна группа функциональных средств, за которую разработчики WinHelp ратовали много лет, предназначена для работы с 256-цветными растрами. До сих пор для этого требовалось либо написать собственную WinHelp DLL, либо купить ее у независимой фирмы. WinHelp 4.0 предоставляет средства для работы не только с 256-цветными, но также и с 24-разрядными растрами. Это позволит разработчикам WinHelp поставлять со своими HELP-файлами растровые изображения намного более высокого качества.

Помимо этого, WinHelp теперь распознает "прозрачные" растры. В предыдущих версиях WinHelp наложение растра на тематический экран приводило к тому, что фон растра подавлял фон тематического экрана. Поэтому если ваше окно темы имело синий фон, а растр - белый фон, то на экране отображались белый прямоугольник с вашим растром внутри и синий фон. Такой эффект не всегда желателен. WinHelp 4.0 позволяет вам сделать фон "прозрачным", так что белый фон растра автоматически исключается из изображения. Вместо белого прямоугольника вы просто увидите ваш растр на фоне, принадлежащем теме.

Еще одно новое дополнение - специализированные кнопки. Такая кнопка - стандартная кнопка Windows, которую можно ввести в любое место текста в своем HELP-файле. Автор HELP-системы может снабдить кнопку текстом и поставить ей в соответствие макрокоманду. В прошлом единственный простой способ сделать это состоял в том, чтобы создать растр, имеющий вид кнопки, загрузить его в SHED (Segmented Hypergraphic Editor - сегментированный гиперграфический редактор) и создать "горячую точку" (hot spot), включающую весь растр целиком. Специализированные кнопки будут закодированы в ваш RTF-файл примерно так же, как кодируются растровые изображения.

Усовершенствования вторичных окон в WinHelp 4.0 потрясающи. В предыдущих версиях возможности вторичных окон были очень ограниченными. Например, вторичные окна не имели меню и кнопочных линеек. Число одновременно открытых вторичных окон также было ограничено одним. WinHelp 4.0 устраняет эти ограничения.

Согласно сведениям,полученным из фирмы Microsoft, разработчики WinHelp могут теперь быть уверенными в том, что они получат полную документацию по всем макрокомандам WinHelp. WinHelp 3.1 имел довольно много недокументированных макрокоманд. Все эти команд, по-видимому, будут документированы, равно как и все нововведения. Кроме того, все макрокоманды WinHelp 3.1 - даже недокументированные - будут перенесены в WinHelp 4.0. Это означает, что ваши HELP-файлы должны быть полностью совместимы с WinHelp 4.0.

Говоря о совместимости, следует отметить еще одну особенность: для обеспечения максимальной возможной совместимости WinHelp 4.0 будет компоноваться с 16-разрядными DLL. Поэтому если вы имеете старые 16-разрядные DLL, которые позволяют вам, например, воспроизводить AVI-файлы в WinHelp, то они должны прекрасно работать и с WinHelp 4.0. Насколько гладко, сейчас сказать трудно. Я видел довольно много безукоризнено работавших DLL и только одну, попытка исполнения которой окончилась неудачей.

WinHelp 4.0 имеет также в своем составе новый Project Editor (редактор проектов). Хотя этот пакет и уступает коммерческим специализированным пакетам создания HELP-систем, он представляет собой значительное улучшение по сравнению со старыми средствами, которые требовали ручного ввода файлов проекта HELP-систем. Редактор проектов HELP не помогает строить RTF-файлы, но выполняет любой нужный вам вид работы с HPJ-файлами. Многие новые средства WinHelp 4.0 в точности отвечают запросам пользователей. А все вместе они предоставляют большие возможности и гибкость функций как дл авторов, так и для пользователей HELP-систем.

Пит Девис - независимый специалист, работающий по контрактам, преимущественно для фирмы Wextech Systems. В настоящее время он пишет книгу, предварительно озаглавленную Undocumented Windows File Formats ("Недокументированные форматы файлов Windows"), которая должна выйти летом 1995 г. в издательстве R&D Publications. Адрес Пита Девиса в службе CompuServe - 71644,3570.

Джим Мишель - программист в фирме Cinematronics (Остин, шт. Техас), занимающейся разработкой игровых программ. Он автор книги The Developer's Guide to WINHELP.EXE (WINHELP.EXE: Руководство программиста), которая выпущена издательством John Wiley & Sons. Адрес Джима Мишеля в службе CompuServe - 75300,2525.