Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Языки программирования С, С++

.pdf
Скачиваний:
136
Добавлен:
01.05.2014
Размер:
1.43 Mб
Скачать

Трюки программирования

Нам в качестве примера вполне подойдёт 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

Трюки программирования

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

Трюки программирования

Но в данном случае вы получите неоправданное замедление из за лишнего межзадачного взаимодействия, а в случае

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

Трюки программирования

PIII, то наверняка заметите некоторые тормоза. В таких случаях надо стараться выносить в отдельный файл всю работу, связанную с подобными библиотеками типов. Кроме того, компилятор может генерировать обёртки классов каждый раз после внесения изменений в файл, в который включена директива #import. Представьте, что будет, если после каждого нажатия клавиши будут заново генерироваться все 12 Mb? Лучше вынести объявление директивы #import в отдельный файл и подключать его через #include.

Удачи в бою.

Создание системных ловушек Windows на Borland C++ Builder 5

Для начала определим, что именно мы хотим сделать.

Цель: написать программу, которая будет вызывающую хранитель экрана при перемещении курсора мыши в правый верхний угол и выдавать звуковой сигнал через встроенный динамик при переключении языка с клавиатуры.

Предполагается, что такая программа должна иметь небольшой размер. Поэтому будем писать её с использованием только WIN API.

Понятие ловушки

Ловушка (hook) — это механизм, который позволяет производить мониторинг сообщений системы и обрабатывать их до того как они достигнут целевой оконной процедуры.

Для обработки сообщений пишется специальная функция (Hook Procedure). Для начала срабатывания ловушки эту функцию следует специальным образом «подключить» к системе.

Если надо отслеживать сообщения всех потоков, а не только текущего, то ловушка должна быть глобальной. В этом случае функция ловушки должна находиться в DLL.

Таким образом, задача разбивается на две части:

Написание DLL c функциями ловушки (их будет две: одна для клавиатуры, другая для мыши).

Написание приложения, которое установит ловушку.

480

Трюки программирования

Написание DLL.

Создание пустой библиотеки.

С++ Builder имеет встроенный мастер по созданию DLL. Используем его, чтобы создать пустую библиотеку. Для этого надо выбрать пункт меню File New: В появившемся окне надо выбрать «DLL Wizard» и нажать кнопку «». В новом диалоге в разделе «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

Трюки программирования

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

Трюки программирования

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

Всё это реализует примерно такой код:

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

Трюки программирования

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

Трюки программирования

анализируется значение параметра 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

Трюки программирования

Создание главного окна

Следующий этап — это создание главного окна приложения. Сначала надо зарегистрировать класс окна. После этого создать окно. Всё это делает следующий код (описатель окна 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