![](/user_photo/1334_ivfwg.png)
Языки программирования С, С++
.pdf![](/html/1334/253/html_OO5wltpros.BbpE/htmlconvd-lLglPR481x1.jpg)
Трюки программирования
Нам в качестве примера вполне подойдёт Microsoft FlexGrid Control. Нажмите кнопку Insert для добавления его в проект, в появившемся окне Confirm Classes оставьте галочку только возле элемента CMSFlexGrid и смело жмите OK. В результате будут сформированы два файла msflexgrid.h и msflexgrid.cpp, большую часть содержимого которых нам придётся удалить. После всех изменений эти файлы будут иметь следующий вид:
msflexgrid.h
// msflexgrid.h
#ifndef __MSFLEXGRID_H__ #define __MSFLEXGRID_H__
#if _MSC_VER > 1000 #pragma once
#endif // _MSC_VER > 1000
#pragma warning(disable:4146) #import <MSFLXGRD.OCX>
class CMSFlexGrid : public CWnd
{
protected: DECLARE_DYNCREATE(CMSFlexGrid)
public:
MSFlexGridLib::IMSFlexGridPtr I; // доступ к интерфейсу void PreSubclassWindow (); // инициализация I
};
//{{AFX_INSERT_LOCATION}}
#endif
msflexgrid.cpp
// msflexgrid.cpp
#include "stdafx.h" #include "msflexgrid.h"
IMPLEMENT_DYNCREATE(CMSFlexGrid, CWnd)
477
![](/html/1334/253/html_OO5wltpros.BbpE/htmlconvd-lLglPR482x1.jpg)
Трюки программирования
void CMSFlexGrid::PreSubclassWindow ()
{
CWnd::PreSubclassWindow();
MSFlexGridLib::IMSFlexGrid *pInterface = NULL;
if (SUCCEEDED(GetControlUnknown() >QueryInterface(I.GetIID(),
(void**)&pInterface))) { ASSERT(pInterface != NULL); I.Attach(pInterface);
}
}
Теперь вставим элемент в любой диалог, например CAboutDlg. В диалог добавим переменную связанную с классом CMSFlexGrid и метод OnInitDialog, текст которого приведён ниже. При вызове диалога в наш FlexGrid будут добавлены два элемента:
BOOL CAboutDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_grid.I >AddItem("12345"); m_grid.I >AddItem("54321");
return TRUE;
}
В заключении, позволим ещё несколько замечаний.
Всегда внимательно изучайте файлы *.tlh. Отчасти они могут заменить документацию, а если её нет, то это единственный источник информации (кроме, конечно, OLE/COM Object Viewer).
Избегайте повторяющихся сложных конструкций. Например, можно написать так:
book >Worksheets >Item[1L] >Range["B2"] >FormulaR1C1 = "Строка 1"; book >Worksheets >Item[1L] >Range["C2"] >FormulaR1C1 = 12345L;
478
![](/html/1334/253/html_OO5wltpros.BbpE/htmlconvd-lLglPR483x1.jpg)
Трюки программирования
Но в данном случае вы получите неоправданное замедление из за лишнего межзадачного взаимодействия, а в случае
DCOM — сетевого взаимодействия. Лучше написать так:
_WorksheetPtr sheet = book >Worksheets >Item[1L]; sheet >Range["B2"] >FormulaR1C1 = "Строка 1"; sheet >Range["C2"] >FormulaR1C1 = 12345;
При работе с MS Office максимально используйте возможности VBA для подготовки и тестирования вашего кода.
Будьте внимательны с версиями библиотек типов. К примеру, в MS Word 2000 появилась новая версия метода Run. Старая тоже осталась, но она имеет теперь название RunOld. Если вы используете MS Word 2000 и вызываете метод Run, то забудьте о совместимости с MS Word 97 — метода с таким ID в MS Word 97 просто нет. Используйте вызов RunOld и проблем не будет, хотя если очень хочется можно всегда проверить номер версии MS Word.
Бывают глюки. Сразу заметим, что это не связано с самой директивой #import. Например, при использовании класса COleDispatchDriver с MSADODC.OCX всё прекрасно работало, а после того как стали использовать директиву #import, свойство ConnectionString отказалось возвращать значение. Дело в том, что директива #import генерирует обёртку, используя dual интерфейс объекта, а класс COleDispatchDriver вызывает ConnectionString
через IDispatch::Invoke. Ошибка, видимо, в реализации самого MSADODC.OCX. После изменения кода вызова свойства всё заработало:
inline _bstr_t IAdodc::GetConnectionString () { BSTR _result;
HRESULT _hr = _com_dispatch_propget(this,0x01,VT_BSTR,&_result); // HRESULT _hr = get_ConnectionString(&_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _bstr_t(_result, false);
}
В результате раскрутки библиотек типов MS Office, компилятор нагенерирует вам в выходной каталог проекта около 12 Mb исходников. Всё это он потом, естественно, будет компилировать. Если вы не являетесь счастливым обладателем
479
![](/html/1334/253/html_OO5wltpros.BbpE/htmlconvd-lLglPR484x1.jpg)
Трюки программирования
PIII, то наверняка заметите некоторые тормоза. В таких случаях надо стараться выносить в отдельный файл всю работу, связанную с подобными библиотеками типов. Кроме того, компилятор может генерировать обёртки классов каждый раз после внесения изменений в файл, в который включена директива #import. Представьте, что будет, если после каждого нажатия клавиши будут заново генерироваться все 12 Mb? Лучше вынести объявление директивы #import в отдельный файл и подключать его через #include.
Удачи в бою.
Создание системных ловушек Windows на Borland C++ Builder 5
Для начала определим, что именно мы хотим сделать.
Цель: написать программу, которая будет вызывающую хранитель экрана при перемещении курсора мыши в правый верхний угол и выдавать звуковой сигнал через встроенный динамик при переключении языка с клавиатуры.
Предполагается, что такая программа должна иметь небольшой размер. Поэтому будем писать её с использованием только WIN API.
Понятие ловушки
Ловушка (hook) — это механизм, который позволяет производить мониторинг сообщений системы и обрабатывать их до того как они достигнут целевой оконной процедуры.
Для обработки сообщений пишется специальная функция (Hook Procedure). Для начала срабатывания ловушки эту функцию следует специальным образом «подключить» к системе.
Если надо отслеживать сообщения всех потоков, а не только текущего, то ловушка должна быть глобальной. В этом случае функция ловушки должна находиться в DLL.
Таким образом, задача разбивается на две части:
●Написание DLL c функциями ловушки (их будет две: одна для клавиатуры, другая для мыши).
●Написание приложения, которое установит ловушку.
480
![](/html/1334/253/html_OO5wltpros.BbpE/htmlconvd-lLglPR485x1.jpg)
Трюки программирования
●Написание DLL.
●Создание пустой библиотеки.
С++ Builder имеет встроенный мастер по созданию DLL. Используем его, чтобы создать пустую библиотеку. Для этого надо выбрать пункт меню File New: В появившемся окне надо выбрать «DLL Wizard» и нажать кнопку «OК». В новом диалоге в разделе «Source Type» следует оставить значение по умолчанию — «C++». Во втором разделе надо снять все флажки. После нажатия кнопки «Ок» пустая библиотека будет создана.
Глобальные переменные и функция входа (DllEntryPoint)
Надо определить некоторые глобальные переменные, которые понадобятся в дальнейшем.
#define UP 1// Состояния клавиш #define DOWN 2
#define RESET 3
int iAltKey; // Здесь хранится состояние клавиш int iCtrlKey;
int iShiftKey;
int KEYBLAY;// Тип переключения языка
bool bSCRSAVEACTIVE;// Установлен ли ScreenSaver MOUSEHOOKSTRUCT* psMouseHook;// Для анализа сообщений от мыши
В функции DllEntryPoint надо написать код, подобный нижеприведённому:
if(reason==DLL_PROCESS_ATTACH)// Проецируем на адр. простр.
{ |
|
|
HKEY |
pOpenKey; |
|
char* cResult=""; |
// Узнаём как перекл. раскладка |
|
long |
lSize=2; |
|
KEYBLAY=3; |
|
if(RegOpenKey(HKEY_USERS,".Default\\keyboard layout\\toggle", &pOpenKey)==ERROR_SUCCESS)
{
RegQueryValue(pOpenKey,"",cResult,&lSize);
if(strcmp(cResult,"1")==0)
481
![](/html/1334/253/html_OO5wltpros.BbpE/htmlconvd-lLglPR486x1.jpg)
Трюки программирования
KEYBLAY=1; |
// |
Alt+Shift |
if(strcmp(cResult,"2")==0) |
|
|
KEYBLAY=2; |
// |
Ctrl+Shift |
RegCloseKey(pOpenKey);
}
else
MessageBox(0,"Не могу получить данные о способе" "переключения раскладки клавиатуры",
"Внимание!",MB_ICONERROR); // Есть ли активный хранитель экрана
if(!SystemParametersInfo(SPI_GETSCREENSAVEACTIVE,0,&bSCRSAVEAC TIVE,0))
MessageBox(0,"Не могу получить данные об установленном" "хранителе экрана", "Внимание!",MB_ICONERROR);
}
return 1;
Этот код позволяет узнать способ переключения языка и установить факт наличия активного хранителя экрана. Обратите внимание на то, что этот код выполняется только когда библиотека проецируется на адресное пространство процесса — проверяется условие (reason==DLL_PROCESS_ATTACH).
Функция ловушки клавиатуры
Функция ловушки в общем виде имеет следующий синтаксис:
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam),
где:
●HookProc — имя функции;
●nCode — код ловушки, его конкретные значения определяются типом ловушки;
●wParam, lParam — параметры с информацией о сообщении.
Вслучае нашей задачи функция должна определять состояние клавиш Alt, Ctrl и Shift (нажаты или отпущены). Информация об этом берётся из параметров wParam и lParam. После определения состояния клавиш надо сравнить его со способом переключения языка (определяется в функции входа).
482
![](/html/1334/253/html_OO5wltpros.BbpE/htmlconvd-lLglPR487x1.jpg)
Трюки программирования
Если текущая комбинация клавиш способна переключить язык, то надо выдать звуковой сигнал.
Всё это реализует примерно такой код:
LRESULT CALLBACK KeyboardHook(int nCode,WPARAM wParam,LPARAM lParam)
{// Ловушка клав. — биканье при перекл. раскладки
if((lParam>>31)&1) |
// Если |
клавиша нажата... |
|
switch(wParam) |
|
|
|
{// |
Определяем |
какая именно |
|
case |
VK_SHIFT: |
{iShiftKey=UP; |
break}; |
case VK_CONTROL: {iCtrlKey=UP; break}; case VK_MENU: {iAltKey=UP; break};
}
else// Если была отпущена...
switch(wParam)
{// Определяем какая именно
case VK_SHIFT: {iShiftKey=DOWN; break}; case VK_CONTROL: {iCtrlKey=DOWN; break}; case VK_MENU: {iAltKey=DOWN; break};
}
//
switch(KEYBLAY) |
// В зависимости от способа |
переключения раскладки |
|
{ |
|
case 1: // |
Alt+Shift |
{ |
|
if(iAltKey==DOWN && iShiftKey==UP) |
|
{ |
|
vfBeep(); |
|
iShiftKey=RESET; |
|
} |
|
if(iAltKey==UP |
&& iShiftKey==DOWN) |
{
vfBeep();
iAltKey=RESET;
}
((iAltKey==UP && iShiftKey==RESET)||(iAltKey==RESET && iShiftKey==UP))
{
483
![](/html/1334/253/html_OO5wltpros.BbpE/htmlconvd-lLglPR488x1.jpg)
Трюки программирования
iAltKey=RESET;
iShiftKey=RESET;
}
break;
}
//
case 2: // Ctrl+Shift
{
if(iCtrlKey==DOWN && iShiftKey==UP)
{
vfBeep();
iShiftKey=RESET;
}
if(iCtrlKey==UP && iShiftKey==DOWN)
{
vfBeep();
iCtrlKey=RESET;
}
if((iCtrlKey==UP && iShiftKey==RESET)||(iCtrlKey==RESET && iShiftKey==UP))
{
iCtrlKey=RESET;
iShiftKey=RESET;
}
}
}
return 0;
}
Звуковой сигнал выдаётся такой небольшой функцией: void vfBeep()
{// Биканье MessageBeep( 1);
MessageBeep( 1);// Два раза — для отчётливости
}
Функция ловушки мыши
Эта функция отслеживает движение курсора мыши, получает его координаты и сравнивает их с координатами правого верхнего угла экрана (0,0). Если эти координаты совпадают, то вызывается хранитель экрана. Для отслеживания движения
484
![](/html/1334/253/html_OO5wltpros.BbpE/htmlconvd-lLglPR489x1.jpg)
Трюки программирования
анализируется значение параметра wParam, а для отслеживания координат значение, находящееся в структуре типа MOUSEHOOKSTRUCT, на которую указывает lParam. Код, реализующий вышесказанное, примерно такой:
LRESULT CALLBACK MouseHook(int nCode,WPARAM wParam,LPARAM lParam)
{ // Ловушка мыши — включает хранитель когда в углу if(wParam==WM_MOUSEMOVE || wParam==WM_NCMOUSEMOVE)
{
psMouseHook=(MOUSEHOOKSTRUCT*)(lParam); if(psMouseHook >pt.x==0 && psMouseHook >pt.y==0) if(bSCRSAVEACTIVE) PostMessage(psMouseHook >hwnd,WM_SYSCOMMAND, SC_SCREENSAVE,0);
}
return 0;
}
Обратите внимание, что команда на активизацию хранителя посылается в окно, получающее сообщения от мыши:
PostMessage(psMouseHook >hwnd,WM_SYSCOMMAND, SC_SCREENSAVE ,0).
Теперь, когда функции ловушек написаны, надо сделать так, чтобы они были доступны из процессов, подключающих эту библиотеку. Для этого перед функцией входа следует добавить такой код:
extern "C" __declspec(dllexport) LRESULT CALLBACK KeyboardHook(int,WPARAM,LPARAM);
extern "C" __declspec(dllexport) LRESULT CALLBACK MouseHook(int,WPARAM,LPARAM);
Написание приложения, устанавливающего ловушку
Создание пустого приложения
Для создания пустого приложения воспользоваться встроенным мастером. Для этого надо использовать пункт меню File New: В появившемся окне необходимо выбрать «Console Wizard» и нажать кнопку «Ok». В новом диалоге в разделе «Source Type» следует оставить значение по умолчанию — «C++». Во втором разделе надо снять все флажки. По нажатию «Ок» приложение создаётся.
485
![](/html/1334/253/html_OO5wltpros.BbpE/htmlconvd-lLglPR490x1.jpg)
Трюки программирования
Создание главного окна
Следующий этап — это создание главного окна приложения. Сначала надо зарегистрировать класс окна. После этого создать окно. Всё это делает следующий код (описатель окна MainWnd определён глобально):
BOOL InitApplication(HINSTANCE hinstance,int nCmdShow) { // Создание главного окна
WNDCLASS wcx; // Класс окна wcx.style=NULL; wcx.lpfnWndProc=MainWndProc; wcx.cbClsExtra=0; wcx.cbWndExtra=0; wcx.hInstance=hinstance;
wcx.hIcon=LoadIcon(hinstance,"MAINICON"); wcx.hCursor=LoadCursor(NULL,IDC_ARROW); wcx.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE); wcx.lpszMenuName=NULL; wcx.lpszClassName="HookWndClass";
if(RegisterClass(&wcx)) |
// Регистрируем класс |
{ |
|
MainWnd=CreateWindow("HookWndClass","SSHook", /* Создаём окно */
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, NULL,NULL,hinstance,NULL); if(!MainWnd)
return FALSE;
return TRUE;
}
return false;
}
Обратите внимание на то, каким образом был получен значок класса:
wcx.hIcon=LoadIcon(hinstance,"MAINICON");
486