Контроль версий файлов системы – большая бочка меда с ложкой дегтя
Проект переходит от стадии проектирования и планирования к стадии реализации. Когда в проекте работают несколько программистов над общими исходными файлами, начинается проблемы по поводу того, что программисты начинают мешать друг, другу внося изменения, в файлы, затирая чужие изменения. Так же встает проблема, как отслеживать самые последние версии файлов и распространять их между программистами.
Для того, что бы избежать этих проблем следует использовать программный продукт для контроля версий файлов. Существует большое количество коммерческих продуктов на эту тему. Но я бы рекомендовал некоммерческий продукт CVS.
Использование продуктов такого рода, конечно же, не решит все проблемы, но значительно облегчит и, следовательно, повысит продуктивность совместной работы программистов.
Принцип функционирования CVS довольно прост. Существует репозитарий (библиотека) всех исходных файлов проекта, там хранятся все версии каждого файла. Программисты могут подключиться к этому репозитарию и забрать с него самые последние версии исходных файлов проекта или любой его версии. Продукт CVS отвечает за синхронизацию локальных копий файлов проекта на машинах программистов с репозитарием и за разрешение конфликтов при совместном редактировании одного файла.
Руководителю проекта следует ввести одно очевидное правило работы с CVS для программистов: “Никогда не посылать в репозитарий файл заведомо не компилирующийся!”.
Краткие комментарии к динамической библиотеке
Процедура libEntry является точкой входа в динамическую библиотеку, её не надо объявлять как экспортируемую, загрузчик сам определяет её местонахождение. LibEntry может вызываться в четырёх случаях: при проецировании библиотеки в адресное пространство процесса (DLL_PROCESS_ATTACH); при первом вызове библиотеки из потока (DLL_THREAD_ATTACH), например, с помощью функции LoadLibrary; при выгрузке библиотеки потоком (DLL_THREAD_DETACH); при выгрузке библиотеки из адресного пространства процесса (DLL_PROCESS_DETACH).
В нашем примере обрабатывается только первое из событий DLL_PROCESS_ATTACH. При обработке данного события библиотека запрашивает версию OS сохраняет её, а также свой handle of instance.
Библиотека содержит только одну экспортируемую функцию, которая собственно не требует пояснений. Вы, пожалуй, можете обратить внимание на то, как производится запись преобразованных значений. Интересна система адресации посредством двух регистров общего назначения: ebx + ecx, она позволяет нам использовать регистр ecx одновременно и как счётчик и как составную часть адреса.
Пример 3. Оконное приложение
Файл dmenu.asm Ideal P586 Radix 16 Model flat
struc WndClassEx cbSize dd 0 style dd 0 lpfnWndProc dd 0 cbClsExtra dd 0 cbWndExtra dd 0 hInstance dd 0 hIcon dd 0 hCursor dd 0 hbrBackground dd 0 lpszMenuName dd 0 lpszClassName dd 0 hIconSm dd 0 ends WndClassEx
struc Point left dd 0 top dd 0 right dd 0 bottom dd 0 ends Point
struc msgStruc hwnd dd 0 message dd 0 wParam dd 0 lParam dd 0 time dd 0 pt Point <> ends msgStruc
MyMenu = 0065 ID_OPEN = 9C41 ID_SAVE = 9C42 ID_EXIT = 9C43 CS_VREDRAW = 0001 CS_HREDRAW = 0002 IDI_APPLICATION = 7F00 IDC_ARROW = 7F00 COLOR_WINDOW = 5 WS_EX_WINDOWEDGE = 00000100 WS_EX_CLIENTEDGE = 00000200 WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE OR WS_EX_CLIENTEDGE WS_OVERLAPPED = 00000000 WS_CAPTION = 00C00000 WS_SYSMENU = 00080000 WS_THICKFRAME = 00040000 WS_MINIMIZEBOX = 00020000 WS_MAXIMIZEBOX = 00010000 WS_OVERLAPPEDWINDOW = WS_OVERLAPPED OR \ WS_CAPTION OR \ WS_SYSMENU OR \ WS_THICKFRAME OR \ WS_MINIMIZEBOX OR \ WS_MAXIMIZEBOX CW_USEDEFAULT = 80000000 SW_SHOW = 5 WM_COMMAND = 0111 WM_DESTROY = 0002 WM_CLOSE = 0010 MB_OK = 0 PROCTYPE ptGetModuleHandle stdcall \ lpModuleName :dword PROCTYPE ptLoadIcon stdcall \ hInstance :dword, \ lpIconName :dword PROCTYPE ptLoadCursor stdcall \ hInstance :dword, \ lpCursorName :dword PROCTYPE ptLoadMenu stdcall \ hInstance :dword, \ lpMenuName :dword PROCTYPE ptRegisterClassEx stdcall \ lpwcx :dword PROCTYPE ptCreateWindowEx stdcall \ dwExStyle :dword, \ lpClassName :dword, \ lpWindowName :dword, \ dwStyle :dword, \ x :dword, \ y :dword, \ nWidth :dword, \ nHeight :dword, \ hWndParent :dword, \ hMenu :dword, \ hInstance :dword, \ lpParam :dword PROCTYPE ptShowWindow stdcall \ hWnd :dword, \ nCmdShow :dword PROCTYPE ptUpdateWindow stdcall \ hWnd :dword PROCTYPE ptGetMessage stdcall \ pMsg :dword, \ hWnd :dword, \ wMsgFilterMin :dword, \ wMsgFilterMax :dword PROCTYPE ptTranslateMessage stdcall \ lpMsg :dword PROCTYPE ptDispatchMessage stdcall \ pmsg :dword PROCTYPE ptSetMenu stdcall \ hWnd :dword, \ hMenu :dword PROCTYPE ptPostQuitMessage stdcall \ nExitCode :dword PROCTYPE ptDefWindowProc stdcall \ hWnd :dword, \ Msg :dword, \ wParam :dword, \ lParam :dword PROCTYPE ptSendMessage stdcall \ hWnd :dword, \ Msg :dword, \ wParam :dword, \ lParam :dword PROCTYPE ptMessageBox stdcall \ hWnd :dword, \ lpText :dword, \ lpCaption :dword, \ uType :dword PROCTYPE ptExitProcess stdcall \ exitCode :dword extrn GetModuleHandleA :ptGetModuleHandle extrn LoadIconA :ptLoadIcon extrn LoadCursorA :ptLoadCursor extrn RegisterClassExA :ptRegisterClassEx extrn LoadMenuA :ptLoadMenu extrn CreateWindowExA :ptCreateWindowEx extrn ShowWindow :ptShowWindow extrn UpdateWindow :ptUpdateWindow extrn GetMessageA :ptGetMessage extrn TranslateMessage :ptTranslateMessage extrn DispatchMessageA :ptDispatchMessage extrn SetMenu :ptSetMenu extrn PostQuitMessage :ptPostQuitMessage extrn DefWindowProcA :ptDefWindowProc extrn SendMessageA :ptSendMessage extrn MessageBoxA :ptMessageBox extrn ExitProcess :ptExitProcess UDataSeg hInst dd ? hWnd dd ? IFNDEF VER1 hMenu dd ? ENDIF DataSeg msg msgStruc <> classTitle db 'Menu demo', 0 wndTitle db 'Demo program', 0 msg_open_txt db 'You selected open', 0 msg_open_tlt db 'Open box', 0 msg_save_txt db 'You selected save', 0 msg_save_tlt db 'Save box', 0 CodeSeg Start: call GetModuleHandleA, 0 ; не обязательно, но желательно mov [hInst],eax sub esp,SIZE WndClassEx ; отведём место в стеке под структуру mov [(WndClassEx esp).cbSize],SIZE WndClassEx mov [(WndClassEx esp).style],CS_HREDRAW or CS_VREDRAW mov [(WndClassEx esp).lpfnWndProc],offset WndProc mov [(WndClassEx esp).cbWndExtra],0 mov [(WndClassEx esp).cbClsExtra],0 mov [(WndClassEx esp).hInstance],eax call LoadIconA, 0, IDI_APPLICATION mov [(WndClassEx esp).hIcon],eax call LoadCursorA, 0, IDC_ARROW mov [(WndClassEx esp).hCursor],eax mov [(WndClassEx esp).hbrBackground],COLOR_WINDOW IFDEF VER1 mov [(WndClassEx esp).lpszMenuName],MyMenu ELSE mov [(WndClassEx esp).lpszMenuName],0 ENDIF mov [(WndClassEx esp).lpszClassName],offset classTitle mov [(WndClassEx esp).hIconSm],0 call RegisterClassExA, esp ; зарегистрируем класс окна add esp,SIZE WndClassEx ; восстановим стек ; и создадим окно IFNDEF VER2 call CreateWindowExA, WS_EX_OVERLAPPEDWINDOW, \ extended window style offset classTitle, \ pointer to registered class name offset wndTitle,\ pointer to window name WS_OVERLAPPEDWINDOW, \ window style CW_USEDEFAULT, \ horizontal position of window CW_USEDEFAULT, \ vertical position of window CW_USEDEFAULT, \ window width CW_USEDEFAULT, \ window height 0, \ handle to parent or owner window 0, \ handle to menu, or child-window identifier [hInst], \ handle to application instance 0 ; pointer to window-creation data ELSE call LoadMenu, hInst, MyMenu mov [hMenu],eax call CreateWindowExA, WS_EX_OVERLAPPEDWINDOW, \ extended window style offset classTitle, \ pointer to registered class name offset wndTitle, \ pointer to window name WS_OVERLAPPEDWINDOW, \ window style CW_USEDEFAULT, \ horizontal position of window CW_USEDEFAULT, \ vertical position of window CW_USEDEFAULT, \ window width CW_USEDEFAULT, \ window height 0, \ handle to parent or owner window eax, \ handle to menu, or child-window identifier [hInst], \ handle to application instance 0 ; pointer to window-creation data ENDIF mov [hWnd],eax call ShowWindow, eax, SW_SHOW ; show window call UpdateWindow, [hWnd] ; redraw window IFDEF VER3 call LoadMenuA, [hInst], MyMenu mov [hMenu],eax call SetMenu, [hWnd], eax ENDIF msg_loop: call GetMessageA, offset msg, 0, 0, 0 or ax,ax jz exit call TranslateMessage, offset msg call DispatchMessageA, offset msg jmp msg_loop exit: call ExitProcess, 0 public stdcall WndProc proc WndProc stdcall arg @@hwnd: dword, @@msg: dword, @@wPar: dword, @@lPar: dword mov eax,[@@msg] cmp eax,WM_COMMAND je @@command cmp eax,WM_DESTROY jne @@default call PostQuitMessage, 0 xor eax,eax jmp @@ret @@default: call DefWindowProcA, [@@hwnd], [@@msg], [@@wPar], [@@lPar] @@ret: ret @@command: mov eax,[@@wPar] cmp eax,ID_OPEN je @@open cmp eax,ID_SAVE je @@save call SendMessageA, [@@hwnd], WM_CLOSE, 0, 0 xor eax,eax jmp @@ret @@open: mov eax, offset msg_open_txt mov edx, offset msg_open_tlt jmp @@mess @@save: mov eax, offset msg_save_txt mov edx, offset msg_save_tlt @@mess: call MessageBoxA, 0, eax, edx, MB_OK xor eax,eax jmp @@ret endp WndProc end Start
Комментарии к программе
Здесь мне хотелось в первую очередь продемонстрировать использование прототипов функций API Win32.
Конечно их (а также описание констант и структур из API Win32) следует вынести в отдельные подключаемые файлы, поскольку, скорее всего Вы будете использовать их и в других программах. Описание прототипов функций обеспечивает строгий контроль со стороны компилятора за количеством и типом параметров, передаваемых в функции. Это существенно облегчает жизнь программисту, позволяя избежать ошибок времени исполнения, тем более, что число параметров в некоторых функциях API Win32 весьма значительно. Существо данной программы заключается в демонстрации вариантов работы с оконным меню. Программу можно откомпилировать в трёх вариантах (версиях), указывая компилятору ключи VER2 или VER3 (по умолчанию используется ключ VER1). В первом варианте программы меню определяется на уровне класса окна и все окна данного класса будут иметь аналогичное меню. Во втором варианте, меню определяется при создании окна, как параметр функции CreateWindowEx. Класс окна не имеет меню и в данном случае, каждое окно этого класса может иметь своё собственное меню. Наконец, в третьем варианте, меню загружается после создания окна. Данный вариант показывает, как можно связать меню с уже созданным окном. Директивы условной компиляции позволяют включить все варианты в текст одной и той же программы. Подобная техника удобна не только для демонстрации, но и для отладки. Например, когда Вам требуется включить в программу новый фрагмент кода, то Вы можете применить данную технику, дабы не потерять функционирующий модуль. Ну, и конечно, применение директив условной компиляции - наиболее удобное средство тестирования различных решений (алгоритмов) на одном модуле. Представляет определённый интерес использование стековых фреймов и заполнение структур в стеке посредством регистра указателя стека (esp). Именно это продемонстрировано при заполнении структуры WndClassEx. Выделение места в стеке (фрейма) делается простым перемещением esp: sub esp,SIZE WndClassEx
Теперь мы можем обращаться к выделенной памяти используя всё тот же регистр указатель стека.При создании 16-битных приложений такой возможностью мы не обладали. Данный приём можно использовать внутри любой процедуры или даже произвольном месте программы. Накладные расходы на подобное выделение памяти минимальны, однако, следует учитывать, что размер стека ограничен и размещать большие объёмы данных в стеке вряд ли целесообразно. Для этих целей лучше использовать "кучи" (heap) или виртуальную память (virtual memory). Остальная часть программы достаточно тривиальна и не требует каких-либо пояснений. Возможно более интересным покажется тема использования макроопределений.
Краткие комментарии к программе
Сразу после метки Start, программа обращается к функции API Win32 GetModuleHandle для получения handle данного модуля (данный параметр чаще именуют как handle of instance). Получив handle, мы вызываем диалог, созданный либо вручную, либо с помощью какой-либо программы построителя ресурсов. Далее программа проверяет результат работы диалогового окна. Если пользователь вышел из диалога посредством нажатия клавиши OK, то приложение запускает MessageBox с текстом приветствия.
Диалоговая процедура обрабатывает следующие сообщения. При инициализации диалога (WM_INITDIALOG) она просит Windows установить фокус на поле ввода имени пользователя. Сообщение WM_COMMAND обрабатывается в таком порядке: делается проверка на код нажатия клавиши. Если была нажата клавиша OK, то пользовательский ввод копируется в переменную szValue, если же была нажата клавиша Cancel, то копирования не производится. Но и в том и другом случае вызывается функция окончания диалога: EndDialog. Остальные сообщения в группе WM_COMMAND просто игнорируются, предоставляя Windows действовать по умолчанию.
Вы можете сравнить приведённую программу с аналогичной программой, написанной на ЯВУ, разница в написании будет незначительна. Очевидно те, кто писал приложения на ассемблере под Windows 3.x, отметят тот факт, что исчезла необходимость в сложном и громоздком startup коде. Теперь приложение выглядит более просто и естественно.
Пример 2. Динамическая библиотека
Написание динамических библиотек под Win32 также значительно упростилось, по сравнению с тем, как это делалось под Windows 3.x. Исчезла необходимость вставлять startup код, а использование четырёх событий инициализации/деинициализации на уровне процессов и потоков, кажется логичным.
Рассмотрим простой пример динамической библиотеки, в которой всего одна функция, преобразования целого числа в строку в шестнадцатеричной системе счисления.
Файл mylib.asm Ideal P586 Radix 16 Model flat DLL_PROCESS_ATTACH extrn GetVersion: proc DataSeg hInst dd 0 OSVer dw 0 CodeSeg proc libEntry stdcall arg @@hInst :dword, @@rsn :dword, @@rsrv :dword cmp [@@rsn],DLL_PROCESS_ATTACH jne @@1 call GetVersion mov [OSVer],ax mov eax,[@@hInst] mov [hInst],eax @@1: mov eax,1 ret endP libEntry public stdcall Hex2Str proc Hex2Str stdcall arg @@num :dword, @@str :dword uses ebx mov eax,[@@num] mov ebx,[@@str] mov ecx,7 @@1: mov edx,eax shr eax,4 and edx,0F cmp edx,0A jae @@2 add edx,'0' jmp @@3 @@2: add edx,'A' - 0A @@3: mov [byte ebx + ecx],dl dec ecx jns @@1 mov [byte ebx + 8],0 ret endp Hex2Str end libEntry
Остальные файлы, которые необходимы для данного примера, можно найти в приложении 2.
Программная модель стрелка extern LArc
Программная модель стрелка extern LArc RiflemanTBL[]; class CRifleman : public LFsaAppl { public: int GetNumber();
void SetNumber(int n);
void SetLink(CRifleman *pFsaLeft, CRifleman *pFsaRigtht);
CRifleman *pFsaRightMan; CRifleman *pFsaLeftMan; CRifleman();
CRifleman(int n, CWnd* pW, LArc *pTBL=RiflemanTBL);
virtual ~CRifleman();
bool operator==(const CRifleman &var) const; bool operator<(const CRifleman &var) const; bool operator!=(const CRifleman &var) const; bool operator>
(const CRifleman &var) const; protected: CWnd* pParentWnd; CFireApp *pApp; // указатель на объект // основного класса программы int x1();
// Is fire? int x2();
// Is ready? int x3();
// Number is equal to zero? Shot! int x4();
// void y1();
// To place number. void y2();
// To reduce number by unit. void y3();
// Gunshot void y4();
// void y5();
// int nNumber; int nSaveNumber; int nLengthQueue; // Length of queue. int nCurrentQueue; // }; typedef vector<CRifleman*>
TIArrayRifleman; typedef vector<CRifleman*>
: :iterator TIIteratorRifleman; extern LArc RiflemanTBL[]; CRifleman::CRifleman():LFsaAppl() { } CRifleman::CRifleman(int n, CWnd* pW, LArc* pTBL): LFsaAppl(pTBL) { pParentWnd = pW; pFsaRightMan = NULL; pFsaLeftMan = NULL; nNumber = n; nLengthQueue = 5; nCurrentQueue = nLengthQueue; if (pParentWnd) { pApp = (CFireApp*)AfxGetApp();
FLoad(pApp->
pNetFsa,1);
} } bool CRifleman::operator==(const CRifleman &var) const { if (nNumber==var.nNumber) return true; else return false; } void CRifleman::SetLink(CRifleman * pFsaLeft, CRifleman * pFsaRigtht) { pFsaRightMan = pFsaRigtht; pFsaLeftMan = pFsaLeft; } LArc RiflemanTBL[] = { LArc("Сон", "Огонь", "x1", "y1"), LArc("Огонь", "Готов", "x2", "y2"), LArc("Готов", "Готов", "x3", "y2"), LArc("Готов", "Выстрел", "^x3", "y3y4"), LArc("Выстрел", "Выстрел", "x4", "y3y5"), LArc("Выстрел", "Сон", "^x4", "-"), LArc() }; int CRifleman::x1() { if (!pFsaLeftMan) return false; return string((pFsaLeftMan)- >
FGetState()) == "Огонь"; } int CRifleman::x2() { if (!pFsaRightMan) return true; else return string((pFsaRightMan)- >
FGetState()) == "Готов"; } int CRifleman::x3() { return nNumber; } int CRifleman::x4() { return nCurrentQueue; } void CRifleman::y1() { int n = pFsaLeftMan->
GetNumber();
SetNumber(n+1);
} void CRifleman::y2() { nNumber-; } void CRifleman::y3() { } void CRifleman::y4() { nCurrentQueue = nLengthQueue; } // формирование задержки между выстрелами void CRifleman::y5() { CFDelay *pCFDelay; pCFDelay = new CFDelay(200);
pCFDelay->
FCall(this);
nCurrentQueue-; }
Простейшее цифровое эхо
program echo; uses dsp_dma,getsbinf; {Ввод звука - 16 бит со знаком, вывод - 8 бит со знаком.} const BufSize = 2*1024; { размер буфера DMA } TimeConst = 156; { 156 - примерно 10 кГц } HalfBufToFill : integer = 0; { которая половина буфера DMA свободна } BothBuf : byte = 0; { индикатор заполнения обоих буферов } type RecBufType = array[0..BufSize-1]of integer; { для буфера DMA записи } PlayBufType = array[0..BufSize-1]of shortint; { для буфера DMA воспроизведения } var RecBuf : ^RecBufType; { буфер DMA для записи} PlayBuf : ^PlayBufType;{буфер DMA для воспроизведения} inpage, outpage : word; {страницы для буферов DMA} inoffset, outoffset : word; {смещения для буферов DMA} {$F+} procedure SBint;interrupt; {обработчик прерывания от звуковой платы} var intstat : integer; i : integer; begin Port[base + $04] := $82; {проверяем, по какому каналу пришло прерывание} intstat := Port[base + $05] and 3; BothBuf := BothBuf or intstat; if (intstat and 2 <>
0) then begin {16-битовый канал} i := Port[base + $0F]; end; if (intstat and 1 <>
0) then begin {8-битовый канал} i := Port[base + $0E]; end; if BothBuf = 3 then begin {если прошли прерывания от обоих каналов} for i := 0 to BufSize div 2 - 1 do PlayBuf^[HalfBufToFill*BufSize div 2 + i] := hi(RecBuf^[HalfBufToFill*BufSize div 2 + i]);
write(HalfBufToFill,#8);
{выводим на экран номер половинки буфера} HalfBufToFill := HalfBufToFill xor 1; BothBuf := 0; end; if (irq >
8) then {для IRQ 10, посылаем сигнал EOI во второй контроллер} Port[$A0] := $20; Port[$20] := $20; { посылаем EOI в первый контроллер} end; {$F-} var SkipLength : longint; {размер памяти до границы 64-Кбайт страницы} SkipBlock : pointer; begin writeln(' Эхо - Sound Blaster 16 в ', 'режиме full duplex');
writeln(' для завершения работы ', 'нажмите Enter');
GetBlasterInfo; {определяем характеристики карты} if (cardtype <>
6) then begin {Проверка, что на плате возможен full duplex} writeln(cardtype);
writeln( 'Для работы программы необходим Sound Blaster 16.');
halt; end; if (dma8 = dma16) then begin writeln('Ошибка: совпадение 8-битового и ', '16-битового каналов DMA.');
halt; end; SetMixer; {сброс DMAC и установки микшера} getmem(SkipBlock,16);
{проверка, чтобы буферы не пересекали границу 64К} SkipLength := $10000 - (seg(SkipBlock^) shl 4) - ofs(SkipBlock^);
freemem(SkipBlock,16);
if SkipLength >
3*BufSize then getmem(SkipBlock,SkipLength);
getmem(RecBuf,2*BufSize);
{выделение памяти для буфера записи} inpage := ((longint(seg(RecBuf^)) * 16) + ofs(RecBuf^)) div $10000; inoffset := ((longint(seg(RecBuf^)) * 16) + ofs(RecBuf^)) and $FFFF; getmem(PlayBuf,BufSize);
{выделение памяти для буфера воспроизведения} outpage := ((longint(seg(PlayBuf^)) * 16) + ofs(PlayBuf^)) div $10000; outoffset := ((longint(seg(PlayBuf^)) * 16) + ofs(PlayBuf^)) and $FFFF; fillchar(PlayBuf^,BufSize,0);
{очистка буфера воспроизведения} EnableInterrupt( @SBint);
SetupDMA(dma16,inpage,inoffset,BufSize, $54);
{DMA на ввод} SetupDSP($BE,$10,BufSize div 2,TimeConst);
{16 бит со знаком FIFO моно} SetupDMA(dma8,outpage,outoffset,BufSize, $58);
{DMA на вывод} SetupDSP($C6,$10,BufSize div 2,TimeConst);
{8 бит со знаком FIFO моно} readln; dspout($D5);
{приостанавливаем 16-битовый ввод-вывод} dspout($D0);
{приостанавливаем 8-битовый ввод-вывод} DisableInterrupt; freemem(PlayBuf,BufSize);
freemem(RecBuf,2*BufSize);
if SkipLength < 3*BufSize then freemem(SkipBlock,SkipLength);
end.
Сначала необходимо убедиться, что звуковая плата способна работать в режиме full duplex. Проще (и безопаснее) всего это сделать с помощью переменной окружения 'BLASTER'. Подобным способом следует определить и базовый адрес порта ввода-вывода, а также номера используемых IRQ и канала DMA. Программа, выполняющая разбор переменной окружения, приведена в листинге 2. Плата должна быть 6-го типа, а номера 8- и 16-разрядного каналов DMA - различаться.
Сценарий мониторинга для выполнения регистрации.
'Простой мониторинг для регистрации с использованием соединений VPN / RAS / Dynamic IP aSubnetList = Array("10.1.4.0/255.255.255.0", "10.1.4.0/255.255.252.0") bAllMatches = True ' Начало метки A Set Events = GetObject("winmgmts:\\.\root\cimv2")_ .ExecNotificationQuery ("SELECT TargetInstance.Name_ FROM __InstanceOperationEvent WITHIN 4 WHERE_ TargetInstance ISA 'Win32_NetworkAdapterConfiguration'")_ ' Конец метки A Do ' Начало метки B Set ConnectEvent = Events.nextevent ' Конец метки B If VarType(oConnectEvent.TargetInstance.Ipaddress(0)) = 8 Then ' Начало метки C bFoundMatch = SubnetMatch(aSubnetList, ConnectEvent.TargetInstance.Ipaddress(0), bAllMatches, aListofMatches) ' Конец метки C End If ' Начало метки D If bFoundMatch Then ' Конец метки D ' Начало метки E Set oShell = Createobject("wscript.shell") Set oNet = CreateObject("Wscript.Network") On Error Resume Next ' Начало метки F oNet.RemoveNetworkDrive "z:", True, True oNet.MapNetworkDrive "z:", "\\myserver\myshare" Err.clear RunCmd = oShell.Run("z:\logonscript.vbs", 1, True) Err.clear oNet.RemoveNetworkDrive "z:", True, True Err.clear ' Конец метки F On Error GoTo 0 ' Конец метки E bFoundMatch = False End If Loop Private Function SubnetMatch(aSubnetsToMatch, IPAddress, bAllMatches, aMatchList) For each subnetpair in aSubnetsToMatch pair = split(subnetpair, "/", 2) subnetoctets = split(pair(1), ".", 4) ipaddroctets = split(IPAddress, ".", 4) If pair(0) = join(Array(ipaddroctets(0) and subnetoctets(0), ipaddroctets(1) and subnetoctets(1), _ ipaddroctets(2) and subnetoctets(2), ipaddroctets(3) and subnetoctets(3)),".") Then SubnetMatch = True If MatchList = "" Then MatchList = MatchList & subnetpair Else MatchList = MatchList & ", " & subnetpair End If If not bAllMatches Then aMatchList = Array(subnetpair) Exit For End If
Модель командира class COfficer
Модель командира class COfficer : public CRifleman { public: COfficer();
virtual ~COfficer();
void SetCommand();
protected: CFireApp *pApp; // int x1();
// Is fire? void y1();
bool bCommandFire; }; extern LArc OfficerTBL[]; COfficer::COfficer():CRifleman (0,NULL,OfficerTBL) { bCommandFire = false; pApp = (CFireApp*)AfxGetApp();
// FLoad(pApp->
pNetFsa,1);
// подключить объект к КА-сети } COfficer::~COfficer() { } LArc OfficerTBL[] = { LArc("Сон", "Огонь", "x1", "y1"), LArc("Огонь", "Сон", "-", "-"), LArc() }; int COfficer::x1() { return bCommandFire; } void COfficer::y1() { bCommandFire = false; } void COfficer::SetCommand() { bCommandFire = true; }
Извлечение данных из переменной окружения
unit GetSBInf; interface var base :integer; { базовый адрес ввода-вывода} irq :integer; { номер IRQ } dma8 :integer; { 8-битный канал DMA } dma16 :integer; { 16-битный канал DMA } midi :integer; { порт MIDI } cardtype :integer; { номер типа платы } procedure GetBlasterInfo; {извлечение информации о плате} implementation uses dos; var s : string; {переменная окружения 'BLASTER'} e : byte; {позиция в этой строке} function str2hex:word; {преобразует последовательность hex-цифр в число} var val : word; begin val := 0; inc(e);
while (s[e] <>
' ') and (s[e] <>
char(0)) and (e <= length(s)) do begin case UpCase(s[e]) of '0'..'9' : val := val * 16 + (byte(s[e]) - byte('0'));
'A'..'F' : val := val * 16 + (byte(s[e]) - byte('A') + 10);
else begin writeln( 'Ошибка в цифровых параметрах переменной окружения');
halt; end; end; inc(e);
end; str2hex := val; end; procedure GetBlasterInfo; {информация о плате} begin s := getenv('BLASTER');
e := 1; if (length(s)>
0) then begin while (e < length(s)) do begin case UpCase(s[e]) of 'A':base := str2hex; 'I':irq := str2hex; 'D':dma8 := str2hex; 'H':dma16 := str2hex; 'P':midi := str2hex; 'T':cardtype := str2hex; end; {case} inc(e);
end; {while} end else begin writeln( 'Отсутствует переменная окружения BLASTER');
halt; end; {if} end; end.
Затем следует проинициализировать DSP (Digital Signal Processor - цифровой процессор сигналов) и установить режим микшера, для управления которым имеются два адреса портов: базовый+4 (для задания номера регистра) и базовый+5 (для записи/чтения нужной величины). Назначение регистров микшера приведено в табл. 1.
Несколько пояснений к табл. 1. Регистры до 2Еh включительно служат для совместимости с предыдущими моделями Sound Blaster, однако, поскольку глубина регулировки уровня в последних моделях возросла, необходимо ввести новые регистры. Старые дублируют старшие биты новых регистров того же назначения. Шаг регулировки громкости у старых регистров - 4 дБ, а у новых - 2 дБ. Появление регистра 3Сh позволяет отключить источники сигнала без изменения положения регуляторов уровня, а добавление регистров 3Dh-3Eh - подключать входные сигналы в любом порядке.
Например, можно подсоединить правый канал CD к левому звуковой платы, а правый канал линейного входа смешать с микрофоном и снова послать в правый канал. Кроме того, появились входные и выходные аттенюаторы с шагом 6 дБ и регуляторы тембра с шагом 2 дБ, а также стала возможной автоматическая регулировка уровня микрофонного входа. В случае монофонического сигнала все регулировки осуществляются по левому каналу.
D0 | Зарезервирован | 0 |
D1 | FIFO | 0 - выключен; 1 - включен |
D2 | Автоинициализация | 0 - режим одного цикла; 1 - режим с автоинициализацией |
D3 | Вид преобразования | 0 - цифроаналоговое (воспроизведение); 1 - аналого-цифровое (запись) |
D4-D7 | Разрядность | 1011 (Bh) - 16 разрядов; 1100 (Сh) - 8 разрядов |
Примечание: другие комбинации соответствуют остальным командам |
D0-D3 | Зарезервированы | 0000 |
D4 | Представление отсчетов | 0 - беззнаковое; 1 - знаковое |
D5 | Число каналов (-1) | 0 - моно; 1 - стерео |
D6-D7 | Зарезервированы | 00 |
D0 | 8-разрядный ввод-вывод |
D1 | 16-разрядный ввод-вывод |
D2 | Внешний MIDI-интерфейс (MPU-401) |
D3-D7 | Зарезервированы |
Если его окажется недостаточно, то нужно запросить всю память до конца данной страницы, чтобы начало свободной памяти (кучи) совпало с началом следующей, где будут размещены буферы. Для каждого из буферов определяются номер 64-Кбайт страницы и смещение в ней, которые надо затем сообщить контроллеру прямого доступа к памяти (DMAC). Процедуры работы с DMAC и цифровым сигнальным процессором (DSP) приведены в листинге 3. При инициализации режим работы контроллера необходимо записать в регистр 0Bh для 8-разрядного режима или в регистр D6h - для 16-разрядного. Значения отдельных битов этих регистров приведены в табл. 2. Запись и воспроизведение звука - процессы непрерывные и требующие одновременной работы как пары DMAC-звуковая плата, так и процессора для подготовки данных или их использования. Поэтому возникает вопрос, каким образом организовать работу, чтобы процессор и DMAC не мешали друг другу, используя одну и ту же область памяти. Выход был найден. Звуковой буфер стали делить на две части, причем в DMAC передается полная длина буфера, а в DSP звуковой платы - только половина ее. Тогда аппаратные прерывания будут генерироваться в начале и в середине периода воспроизведения всего буфера. А в случае, когда DMAC работает с первой половиной буфера, процессор может обрабатывать вторую, и наоборот.
Сценарий мониторинга
' Модификация для выполнения исключительно в соответствии с соглашением UNC Set oShell = Createobject("wscript.shell") On Error Resume Next RunCmd = oShell.Run("\\myserver\ myshare\mylogonscript.vbs", 1, True) Err.clear
On Error GoTo 0 End If Next If SubnetMatch Then aMatchList = split(matchlist, ",") End If End Function
int nNum, CSize sz, LArc
Модель пули extern LArc BulletTBL[]; class CBullet : public TBounce { public: void SetAddrMan (LFsaAppl *pFsaAppl);
CBullet();
CBullet(CWnd* pW, int nNum, CSize sz=CSize(10,10), LArc *pTBL=BulletTBL);
virtual ~CBullet();
void SetCenter(int x, int y);
void SetMove(int cx, int cy);
protected: int x1();
int x2();
int x3();
void y4();
protected: LFsaAppl *pFsaShot; }; typedef vector<CBullet*>
TIArrayBullet; typedef vector<CBullet*>
: :iterator TIIteratorBullet; CBullet::CBullet(CWnd* pW, int nNum, CSize sz, LArc *pTBL) :TBounce(pW, nNum, sz, pTBL) { pFsaShot = NULL; } CBullet::CBullet():TBounce() { pFsaShot = NULL; } CBullet::~CBullet() { } void CBullet::SetAddrMan(LFsaAppl * pFsaAppl) { pFsaShot = pFsaAppl; } // LArc BulletTBL[] = { LArc("st","b1", "x1", "y4"), LArc("b1","b1", "^x2", "y1"), LArc("b1","st", "x2", "y4"), LArc() }; int CBullet::x1() { if (!pFsaShot) return false; return string((pFsaShot)- >
FGetState()) == "выстрел"; } int CBullet::x2() { return m_ptCenter.y + m_sizeRadius.cy >
= rcClient.bottom; } int CBullet::x3() { return nNumBounce; } void CBullet::y4() { SetCenter(0,10);
} void CBullet::SetCenter(int x, int y) { if (y) m_ptCenter.y = y; if (x) m_ptCenter.x = x; } void CBullet::SetMove(int cx, int cy) { m_sizeMove.cx = cx; m_sizeMove.cy = cy; }
Работа с DSP и DMA
unit DSP_DMA; interface procedure DspOut(val:byte);
{выводит байт на DSP} procedure SetupDMA(dmach,page,ofs,DMAcount,dmacmd:word);
{установка режима DMA} procedure SetupDSP(dspcmd, mode, DSPcount, tc:word);
{установка режима DSP} procedure SetMixer; {сброс платы и выбор источника сигнала} procedure EnableInterrupt(newvect:pointer);
{установка векторов прерываний} procedure DisableInterrupt; {восстановление векторов прерываний} implementation uses getsbinf,crt,dos; var intvecsave :pointer; { старый вектор прерывания} intrnum :integer; { номер прерывания } intrmask :integer; { маска прерывания } { структура, содержащая данные контроллера DMA } type DmaPortRec = record addr,count,page : byte; end; const DmaPorts : array[0..7]of DmaPortRec = ( (addr:$00; count:$01; page:$87), {0} (addr:$02; count:$03; page:$83), {1} (addr:$04; count:$05; page:$81), {2 не используется} (addr:$06; count:$07; page:$82), {3} (addr:$00; count:$00; page:$00), {4 не используется} (addr:$C4; count:$C6; page:$8B), {5} (addr:$C8; count:$CA; page:$89), {6} (addr:$CC; count:$CE; page:$8A));
{7} procedure DspOut(val:byte);
{выводит байт в DSP} begin while (Port[base + $0C] and $80) <>
0 do; Port[base + $0C] := val; end; function DspIn:byte;{читает байт из DSP} begin while (Port[base + $0E] and $80) = 0 do; dspin := Port[base + $0A]; end; procedure SetupDMA(dmach,page,ofs,DMAcount,dmacmd:word);
{ Программирует контроллер DMA} { для 8- или 16-разрядного канала} var mask,mode,ff : byte; begin if (dmach < 4) then begin mask := $0A; mode := $0B; ff := $0C; end else begin mask := $D4; mode := $D6; ff := $D8; ofs := (ofs shr 1) + ((page and 1) shl 15);
end; Port[mask] := 4 or dmach; { маскируем DMA} Port[FF] := 0; { сбрасываем триггер-защелку} Port[mode] := dmacmd or (dmach and 3);
{ уст.режима DMA} Port[dmaports[dmach].addr] := lo(ofs);
{ младший байт адреса} Port[dmaports[dmach].addr] := hi(ofs);
{ старший байт} Port[dmaports[dmach].page] := page; { номер страницы} Port[dmaports[dmach].count] := lo(DMAcount-1);
{ младший байт счетчика} Port[dmaports[dmach].count] := hi(DMAcount-1);
{ старший байт} Port[mask] := (dmach and 3);
{ сброс бита маски} end; procedure SetupDSP(dspcmd, mode, DSPcount, tc:word);
{ Программирует DSP звуковой платы} begin DspOut($40);
{установка константы времени} DspOut(tc);
DspOut(dspcmd);
{команда Bx/Cx} DspOut(mode);
DspOut(lo(DSPcount-1));
DspOut(hi(DSPcount-1));
end; procedure SetMixer;{сброс платы и выбор источника сигнала} var val:byte; begin Port[base + $06] := 1; {сброс DSP} delay(1);
Port[base + $06] := 0; if (dspin <>
$AA) then {проверка готовности} writeln('Sound Blaster не готов.');
Port[base + $04] := $3D; Port[base + $05] := 1; { левый канал:источник сигнала - микрофон} { Port[base + $04] := $3E; {для моно - не обязательно} { Port[base + $05] := 1; } { правый канал:источник сигнала - микрофон} Port[base + $04] := $3C; Port[base + $05] := 0; { на выходе отключаем все, что можно} end; procedure EnableInterrupt(newvect:pointer);
{установка векторов прерываний} var intrmask1:word; begin if (irq < 8) then {вычисляем номера прерывания} intrnum := irq + 8 { для IRQ 0-7 прерывания 8-15.} else intrnum := irq - 8 + $70; { для IRQ 8-15 прерывания 70H-78H.} intrmask := 1 shl irq; {маска} GetIntVec(intrnum,intvecsave);
{ сохраняем старый вектор} SetIntVec(intrnum, newvect);
{ устанавливаем новый вектор} intrmask1 := intrmask; {разрешаем прерывания} Port[$21] := Port[$21] and not intrmask1; intrmask1 := intrmask1 shr 8; Port[$A1] := Port[$A1] and not intrmask1; end; procedure DisableInterrupt; {восстановление векторов прерываний} var intrmask1:word; begin intrmask1 := intrmask; {запрещаем прерывания} Port[$21] := Port[$21] or intrmask1; intrmask1 := intrmask1 shr 8; Port[$A1] := Port[$A1] or intrmask1; SetIntVec(intrnum,intvecsave);
{восстанавливаем вектор} end; end.
После программирования DMAC то же самое проделывается и с DSP звуковой платы.
Сначала надо установить частоту дискретизации, сообщив ему константу времени t = 256 - 1 000 000 / f,
где f - частота дискретизации. Затем следует задать команду на запись/воспроизведение звука. Для Sound Blaster 16 проще всего выбрать команды Bx/Cx, состоящие из четырех байтов: Command, Mode, LenLo, LenHi. Формат первого байта Command приведен в табл. 3, а второго байта Mode - в табл. 4. Байты LenLo и LenHi - младший и старший в соответствии с длиной передаваемого блока, уменьшенной на единицу. Команды Bx/Cx позволяют задавать как знаковый, так и беззнаковый вид представления отсчетов. При знаковом отсчет представляет собой целое число со знаком, принимающее значение 0 при отсутствии входного сигнала, при беззнаковом - целое число без знака, равное 80h для 8-разрядного режима и 8000h для 16-разрядного при отсутствии входного сигнала. Стандартом де-факто является представление 8-разрядных отсчетов в беззнаковой форме, а 16-разрядных - в знаковой, однако для упрощения процедуры преобразования в приводимой программе обе величины выбраны знаковыми.
14h | 8-разрядное воспроизведение через DMA без автоинициализации. Команда состоит из 3 байт, за ее кодом следует длина передаваемых данных, уменьшенная на 1 |
1Ch | 8-разрядное воспроизведение с автоинициализацией. Команда состоит из 1 байта, длина воспроизводимого блока задается командой 48h |
24h | 8-разрядная запись, аналогичная команде 14h |
2Ch | 8-разрядная запись с автоинициализацией, аналогичная 1Ch |
40h | Задание константы времени, 2 байта: после кода команды - константа |
41h | Задание частоты дискретизации вывода, 3 байта: после команды 2 байта частоты дискретизации в диапазоне 5000-45 000 Гц |
42h | Задание частоты дискретизации ввода, аналогичное 41h |
48h | Задание длины передаваемых данных, 3 байта, включая 2 байта данных. Определяет, по истечении какого объема переданных данных должно поступить прерывание от звуковой платы |
Bxh | 16-разрядный ввод-вывод |
Cxh | 8-разрядный ввод-вывод |
D0h | Пауза 8-разрядного ввода-вывода |
D1h | Выключение динамика |
D3h | Включение динамика |
D4h | Продолжение 8-разрядного ввода-вывода, приостановленного командой D0h |
D5h | Пауза 16-разрядного ввода-вывода |
D6h | Продолжение 16-разрядного ввода-вывода, приостановленного командой D5h |
D8h | После этой команды чтение из DSP возвращает статус динамика: 0 - выключен; FFh - включен |
D9h | Выход из 16-разрядного ввода-вывода с автоинициализацией |
DAh | Выход из 8-разрядного ввода-вывода с автоинициализацией |
E1h | После этой команды чтение 2 байт из DSP приведет к получению номера версии DSP, причем 1-й байт - старший, а 2-й - младший |
Затем процессор свободен для выполнения любой другой работы, например с экраном, как это практикуется в компьютерных играх. В данной же программе просто происходит ожидание ввода с клавиатуры. Однако время от времени работу процессора будут приостанавливать прерывания, поступающие со звуковой платы по окончании пересылки очередной порции данных. В задачу обработчика прерываний входит определение номера канала, по которому пришло прерывание. Дело в том, что и 8-разрядный, и 16-разрядный ввод-вывод, и даже внешний MIDI-интерфейс (MPU-401) генерируют одно и то же аппаратное прерывание. Для того чтобы различать их между собой, в адресном пространстве регистров микшера имеется порт номер 82h (регистр статуса прерываний), определяющий источник прерывания (табл. 5). Обработчик прерывания должен сообщить звуковой плате, что ее прерывание принято и обработано, для чего необходимо осуществить чтение из порта 0Eh или 0Fh для 8- либо 16-разрядного режимов соответственно. После прихода прерываний от канала записи и от канала воспроизведения можно считать, что соответствующие половины буферов записи и воспроизведения уже обработаны звуковой платой и пора копировать данные из одного буфера в другой. Так как в обоих случаях была выбрана одинаковая (знаковая) форма представления данных, то их преобразование сводится лишь к переписыванию старших байтов значений двухбайтовых звуковых отсчетов из входного буфера в выходной. По завершении отработки прерывания следует осведомить об этом контроллер прерываний (с учетом каскадирования). После нажатия на программа приостанавливает и 8-, и 16-разрядные операции ввода-вывода и восстанавливает векторы прерываний. Выше приведен выборочный список команд DSP, которые применяются при записи и воспроизведении звука (табл. 6). Здесь не рассматриваются непосредственный ввод-вывод, не использующий DMAC, ввод-вывод с компрессией и MIDI-команды. ОБ АВТОРЕ Андрианов Сергей Андреевич - канд. техн. наук; e-mail: или fidonet: 2:5017/11.40.
Модель цепи стрелков class CChainShot
Модель цепи стрелков class CChainShot { public: CChainShot(CWnd *pW);
virtual ~CChainShot();
void SetLink();
void SetCommand();
void OnSize(int cx, int cy);
CRifleman* GetAddrRifleman(int n);
CBullet* GetAddrBullet(int n);
protected: CWnd *pWnd; COfficer *pCOfficer; TIArrayRifleman IArrayRifleman; TIArrayBullet IArrayBullet; }; CChainShot::CChainShot(CWnd *pW) { pWnd = pW; pCOfficer = new COfficer();
for (int i=1; i<=4; i++) { IArrayRifleman.push_back(new CRifleman(i,pWnd));
IArrayBullet.push_back(new CBullet(pWnd,i));
} SetLink();
} CChainShot::~CChainShot() { if (pCOfficer) delete pCOfficer; TIIteratorRifleman iterRifleman = IArrayRifleman.begin();
while (iterRifleman != IArrayRifleman.end()) delete *iterRifleman++; IArrayRifleman.erase(IArrayRifleman.begin(), IArrayRifleman.end());
TIIteratorBullet iterBullet = IArrayBullet .begin();
while (iterBullet!=IArrayBullet.end()) delete *iterBullet++; IArrayBullet.erase(IArrayBullet.begin() ,IArrayBullet.end());
} void CChainShot::SetCommand() { if (pCOfficer) pCOfficer->
SetCommand();
} CRifleman* CChainShot::GetAddrRifleman(int n) { CRifleman* currentRifleman=NULL; CRifleman vs(n, NULL);
TIIteratorRifleman iterRifleman = IArrayRifleman.begin();
while (iterRifleman != IArrayRifleman.end()) { currentRifleman= *iterRifleman++; if (*currentRifleman==vs) break; } return currentRifleman; } CBullet* CChainShot::GetAddrBullet(int n) { CBullet* currentBullet=NULL; CBullet vs(NULL, n);
if (!IArrayBullet.empty()) { TIIteratorBullet iterBullet = IArrayBullet .begin();
while (iterBullet != IArrayBullet.end()) { currentBullet= *iterBullet++; if (*currentBullet==vs) break; } } return currentBullet; } void CChainShot::SetLink() { LFsaAppl *currentRifleman; TIIteratorRifleman iterRifleman = IArrayRifleman.begin();
int n =1; CRifleman *pFsaLeft = NULL; CRifleman *pFsaRight = NULL; while (iterRifleman != IArrayRifleman.end()) { if (n==1) { currentRifleman= *iterRifleman++; ((CRifleman*)currentRifleman)- >
SetNumber(n);
n++; pFsaLeft = pCOfficer; pFsaRight= *iterRifleman++; ((CRifleman*)pFsaRight)->
SetNumber(n);
n++; ((CRifleman*)currentRifleman)->
SetLink(pFsaLeft, pFsaRight);
} else { pFsaLeft = currentRifleman; if (iterRifleman != IArrayRifleman.end()) { currentRifleman = pFsaRight; pFsaRight= *iterRifleman++; ((CRifleman*)pFsaRight)->
SetNumber(n);
n++; ((CRifleman*)currentRifleman)->
SetLink(pFsaLeft, pFsaRight);
} } } pFsaLeft = currentRifleman; currentRifleman = pFsaRight; pFsaRight= NULL; ((CRifleman*)currentRifleman)- >
SetLink(pFsaLeft, pFsaRight);
TIIteratorBullet iterBullet = IArrayBullet.begin();
while (iterBullet != IArrayBullet.end()) { CBullet* currentBullet= *iterBullet++; CRifleman* pRf=GetAddrRifleman (currentBullet->
GetNum());
currentBullet->
SetAddrMan(pRf);
} } void CChainShot::OnSize(int cx, int cy) { int n=1; CBullet* currentBullet; TIIteratorBullet iterBullet = IArrayBullet.begin();
while (iterBullet != IArrayBullet.end()) { currentBullet= *iterBullet++; currentBullet->
Size(CSize(cx/n,cy/n));
currentBullet->
SetCenter(400/n-20,10);
currentBullet->
SetMove(0,1);
// currentBullet->
SizeBounce(CSize(20,20));
n++; } }
UINT nType, int cx, int
Объект окна-отображения void CFireView::OnFire() { pChainShot->
SetCommand();
} int CFireView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; pChainShot = new CChainShot(this);
CFireApp *pApp = (CFireApp*)AfxGetApp();
// pApp->
pNetFsa->
go_task();
// запуск КА-объекта return 0; } void CFireView::OnSize( UINT nType, int cx, int cy) { pChainShot->
OnSize(cx, cy);
CView::OnSize(nType, cx, cy);
} void CFireView::OnFast() { CFireApp *pApp = (CFireApp*)AfxGetApp();
pApp->
lCountTime=0; } void CFireView::OnSlow() { CFireApp *pApp = (CFireApp*)AfxGetApp();
pApp->
lCountTime=1000; }
Действие y3 модели стрелка void
Действие y3 модели стрелка void CRifleman::y3() { CRect r; pParentWnd->
GetClientRect(r);
CSize cz = r.Size();
int x1, y1; x1=cz.cx/nSaveNumber; y1= cz.cy/nSaveNumber; CBullet *currentBullet = new CBullet(pParentWnd, 0);
// задание начального положения пули и ее размеров currentBullet->
SetCenter(x1-50,10);
currentBullet->
SetMove(0,3);
// интервал между пулями // currentBullet->
SetMove(nCurrentQueue,3);
// стрельба // веером currentBullet->
SizeBounce(CSize(2,5));
// передача адреса стрелка новой пуле currentBullet->
SetAddrMan(this);
// currentBullet->
FCall(this);
}
Таблица переходов
Таблица переходов "автоматной" пули LArc BulletTBL[] = { LArc("st","b1", "x1x3", "y4"), LArc("st","b1", "^x3", "y4"), LArc("b1","b1", "^x2", "y1"), LArc("b1","st", "x2x3", "y4"), LArc("b1","00", "x2^x3","-"), LArc() };
Литература
[1] Объектные технологии построения распределенных информационных систем, Юрий Пуха,
[2] Симфония CORBA, Марина Аншина,
[3] В ожидании CORBA 3.0, Сергей Орлик, О
[4] Компонентная объектная модель Javabeans, Владимир Галатенко, Александр Таранов,
[5] CORBA/IIOP и Java RMI. Основные возможности в сравнении, Юрий Пуха,
Alan K. Melby,Eight Types of Translation Technology // ATA, Hilton Head, November 1998 Олег Сонин, MT или TM// Компьютерная неделя N26-27(200-201).- М., 1999 Martin Volk: The Automatic Translation of Idioms. Machine Translation vs. Translation Memory Systems. In: Nico Weber (ed.): Machine Translation: Theory, Applications, and Evaluation. An assessment of the state of the art. St. Augustin: gardez-Verlag. 1998. The Universal Networking Language (UNL) Specifications Version 3.0// UNU/IAS/UNL Center, August 2000.
Гамма Э. Приемы объектно-ориентированного проектирования (паттерны проектирования). - Санкт-Петербург: Издательство «Питер», 2001. – 368с. Цимбал А. Технология CORBA для профессионалов. – Санкт-Петербург: Издательство «Питер», 2001. – 624с.
Schoch, G. MetaBASE by gs-soft - Ingneiburo G.Schoch, 1996 Henderson, K. Delphi Database Developer's Guide. - SAMS Publishing, 1996 Горин C.B., Тандоев А.Ю. Применение CASE-средства ERwin 2.0 для информационного моделирования в системах обработки данных - СУБД, 1996, N 3 c. 26-40
Interface Ltd. (авторизованный учебный центр Logic Works и Borland)
тел./факс (095)135-55-00, 135-25-19, e-mail:
Любченко В.С. О бильярде с Microsoft C++ 5.0. // Мир ПК, 1998, № 1, с. 202.
Трахтенброт Б.А. Алгоритмы и вычислительные автоматы. М.: Советское радио, 1974. 200 с.
Любченко В.С. Новые песни о главном-II. // Мир ПК, 1998, № 7, с. 112.
Ла Мот А., Ратклиф Д., Тайлер Д. Секреты программирования игр /Пер. с англ. СПб: Питер, 1995. 720 с.
Макроопределения
Мне достаточно редко приходилось серьёзно заниматься разработкой макроопределений при программировании под DOS. В Win32 ситуация принципиально иная. Здесь грамотно написанные макроопределения способны не только облегчить чтение и восприятие программ, но и реально облегчить жизнь программистов. Дело в том, что в Win32 фрагменты кода часто повторяются, имея при этом не принципиальные отличия. Наиболее показательна, в этом смысле, оконная и/или диалоговая процедура. И в том и другом случае мы определяем вид сообщения и передаём управление тому участку кода, который отвечает за обработку полученного сообщения. Если в программе активно используются диалоговые окна, то аналогичные фрагменты кода сильно перегрузят программу, сделав её малопригодной для восприятия. Применение макроопределений в таких ситуациях более чем оправдано. В качестве основы для макроопределения, занимающегося диспетчеризацией поступающих сообщений на обработчиков, может послужить следующее описание.
Пример макроопределений macro MessageVector message1, message2:REST IFNB <message1> dd message1 dd offset @@&message1 @@VecCount = @@VecCount + 1 MessageVector message2 ENDIF endm MessageVector macro WndMessages VecName, message1, message2:REST @@VecCount = 0 DataSeg label @@&VecName dword MessageVector message1, message2 @@&VecName&Cnt = @@VecCount CodeSeg mov ecx,@@&VecName&Cnt mov eax,[@@msg] @@&VecName&_1: dec ecx js @@default cmp eax,[dword ecx * 8 + offset @@&VecName] jne @@&VecName&_1 jmp [dword ecx + offset @@&VecName + 4] @@default: call DefWindowProcA, [@@hWnd], [@@msg], [@@wPar], [@@lPar] @@ret: ret @@ret_false: xor eax,eax jmp @@ret @@ret_true: mov eax,-1 dec eax jmp @@ret endm WndMessage
Комментарии к макроопределениям
При написании процедуры окна Вы можете использовать макроопределение WndMessages, указав в списке параметров те сообщения, обработку которых намерены осуществить. Тогда процедура окна примет вид: proc WndProc stdcall arg @@hWnd: dword, @@msg: dword, @@wPar: dword, @@lPar: dword WndMessages WndVector, WM_CREATE, WM_SIZE, WM_PAINT, WM_CLOSE, WM_DESTROY @@WM_CREATE: ; здесь обрабатываем сообщение WM_CREATE @@WM_SIZE: ; здесь обрабатываем сообщение WM_SIZE @@WM_PAINT: ; здесь обрабатываем сообщение WM_PAINT @@WM_CLOSE: ; здесь обрабатываем сообщение WM_CLOSE @@WM_DESTROY: ; здесь обрабатываем сообщение WM_DESTROY endp WndProc
Обработку каждого сообщения можно завершить тремя способами: вернуть значение TRUE, для этого необходимо использовать переход на метку @@ret_true; вернуть значение FALSE, для этого необходимо использовать переход на метку @@ret_false; перейти на обработку по умолчанию, для этого необходимо сделать переход на метку @@default.
Отметьте, что все перечисленные метки определены в макро WndMessages и Вам не следует определять их заново в теле процедуры.
Теперь давайте разберёмся, что происходит при вызове макроопределения WndMessages.
Вначале производится обнуление счётчика параметров самого макроопределения (число этих параметров может быть произвольным). Теперь в сегменте данных создадим метку с тем именем, которое передано в макроопределение в качестве первого параметра. Имя метки формируется путём конкатенации символов @@ и названия вектора. Достигается это за счёт использования оператора &. Например, если передать имя TestLabel, то название метки примет вид: @@TestLabel. Сразу за объявлением метки вызывается другое макроопределение MessageVector, в которое передаются все остальные параметры, которые должны быть ничем иным, как списком сообщений, подлежащих обработке в процедуре окна. Структура макроопределения MessageVector проста и бесхитростна. Она извлекает первый параметр и в ячейку памяти формата dword заносит код сообщения. В следующую ячейку памяти формата dword записывается адрес метки обработчика, имя которой формируется по описанному выше правилу. Счётчик сообщений увеличивается на единицу. Далее следует рекурсивный вызов с передачей ещё не зарегистрированных сообщений, и так продолжается до тех пор, пока список сообщений не будет исчерпан. Сейчас в макроопределении WndMessage можно начинать обработку. Теперь существо обработки, скорее всего, будет понятно без дополнительных пояснений. Обработка сообщений в Windows не является линейной, а, как правило, представляет собой иерархию. Например, сообщение WM_COMMAND может заключать в себе множество сообщений поступающих от меню и/или других управляющих элементов. Следовательно, данную методику можно с успехом применить и для других уровней каскада и даже несколько упростить её. Действительно, не в наших силах исправить код сообщений, поступающих в процедуру окна или диалога, но выбор последовательности констант, назначаемых пунктам меню или управляющим элементам (controls) остаётся за нами. В этом случае нет нужды в дополнительном поле, которое сохраняет код сообщения. Тогда каждый элемент вектора будет содержать только адрес обработчика, а найти нужный элемент весьма просто.Из полученной константы, пришедшей в сообщении, вычитается идентификатор первого пункта меню или первого управляющего элемента, это и будет номер нужного элемента вектора. Остаётся только сделать переход на обработчик. Вообще тема макроопределений весьма поучительна и обширна. Мне редко доводится видеть грамотное использование макросов и это досадно, поскольку с их помощью можно сделать работу в ассемблере значительно проще и приятнее.
Машинный перевод
Данный способ перевода заключается в алгоритмической обработке исходного текста, в ходе которой происходит разбор сегментов, выделяются отдельные термины и отношения между ними, после чего осуществляется замена всех терминов на соответствующие термины целевого языка в нужной форме и взаиморасположении. Машинный перевод (MachineTranslation) применим только в очень узком контексте и требует значительного постредактирования переведенного текста.
Масштабируемость
Использование распределенных инстанций Flora/C+ для исполнения приложений означает принципиальную возможность реализации масштабируемых решений, а наличие встроенных средств слабого и сильного взаимодействия между объектами дерева объектов обеспечивает приемлемую трудоёмкость реализации соответствующих распределенных приложений.
Механизм сборки всей системы – кто написал этот не компилирующийся файл?!
Процессу сборки следует уделить внимание в первые же дни реализации проекта. Дело в том, что программисты предпочитают работать в разных оболочках для редактирования и компиляции исходных кодов от простого текстового редактора notepad до сложного и многофункционального решения, такого как Together. Вследствие чего придется каждому из них постоянно возиться с настройками процесса компиляции. И это будет постоянной проблемой, так как по мере реализации проекта процесс сборки системы усложняется и обычно кардинально меняется.
Руководителю проекта следует внедрить единый для всех инструмент для сборки проекта и разместить сценарий сборки системы в репозитарии CVS. Таким образом, любой из программистов, получив посредством CVS последнюю версию сценария сборки, сможет без проблем собрать всю систему у себя на рабочем месте и в любой момент модернизировать этот сценарий в случае изменения сценария сборки подсистемы, которую он реализует. Возможно, что один из программистов лучше всех разбирается в сценарии сборки, и другие обращаются к нему за помощью при изменении сценария.
Сборку системы следует производить в начале рабочего дня, после того как все программисты отослали последние версии своих файлов на CVS. К сожалению, после сборки и запуска системы выявляются конфликты и ошибки, на устранение которых уходит какое-то время (рискну сказать - четверть рабочего дня).
Менеджер проектов
Файлы, образующие приложение - формы и модули - собраны в проект. Менеджер проектов показывает списки файлов и модулей приложения и позволяет осуществ ять навигацию между ними. Можно вызвать менеджер проектов , выбрав пункт меню View/Project Manager. По умолчанию вновь созданный проект получает имя Project1.cpp.
Рис.5. Менеджер проектов
По умолчанию проект первоначально содержит файлы для одной формы и исходного кода одного модуля. Однако большинство проектов содержат несколько форм и модулей. Чтобы добавить модуль или форму к проекту, нужно щелкнуть правой кнопкой мыши и выбрать пункт New Form из контекстного меню. Можно также добавлять существующие формы и модули к проекту, используя кнопку Add контекстного меню менеджера проектов и выбирая модуль или форму, которую нужно добавить. Формы и модули можно удалить в любой момент в течение разработки проекта. Однако, из-за того, что форма связана всегда с модулем, нельзя удалить одно без удаления другого, за исключением случая, когда модуль не имеет связи с формой. Удалить модуль из проекта можно, используя кнопку Remove менеджера проектов.
Если выбрать кнопку Options в менеджере проектов, откроется диалоговая панель опций проекта, в которой можно выбрать главную форму приложения, определить, какие формы будут создаваться динамически, каковы параметры компиляции модулей (в том числе созданных в Delphi 2.0, так как C++ Builder может включать их в проекты) и компоновки.
Рис. 6. Установка опций проекта
Важным элементом среды разработки C++ Builder является контекстное меню, появляющееся при нажатии на правую клавишу мыши и предлагающее быстрый доступ к наиболее часто используемым командам.
Разумеется, C++ Builder обладает встроенной системой контекстно-зависимой помощи, доступной для любого элемента интерфейса и являющейся обширным источником справочной информации о C++ Builder.
MetaBASE как средство решения проблем доступа к метаданным
MetaBASE представляет собой набор утилит и визуальных компонент для Delphi 1.0,2.0,2.01,3.0, выпущенный компанией gs-soft и поставляемый в комплекте с ERwin (Logic Works). Назначение этого набора - предоставить объектно-ориентированный доступ к модели данных ERwin в процессе разработки и выполнения клиентских приложений, создаваемых с помощью Delphi. Осуществляется этот доступ за счет создания специализированного словаря данных (в терминологии авторов продукта - Metamodel), отличного от словаря данных Delphi 2.0, который, c одной стороны, поддерживает двунаправленный обмен метаданными с ER-диаграммой формата .erx с помощью специальной утилиты, а, с другой стороны, доступен для использования набором поставляемых в комплекте визуальных компонент для доступа к данным, которые, в свою очередь, являются полноценной заменой стандартным компонентам из комплекта поставки Delphi, хотя и не исключают их использования в приложении. Отметим, что пользователи 16-разрядной версии Delphi, количество которых в нашей стране еще, видимо, долго будет достаточно велико, при использовании MetaBASE получают отсутствующий в этой версии, но для многих желанный словарь данных.
За счет этого метаданные постоянно доступны в процессе разработки и выполнения приложения. Поэтому возможна модификация модели данных и, соответственно, серверной части информационной системы без модификации клиентских приложений, так как визуальные компоненты MetaBASE, используемые в приложении, адаптируются к изменениям в модели данных.. При этом повышается скорость разработки приложений и упрощается модернизация и сопровождение информационной системы даже в случае сложных моделей данных, так как программист в этом случае избавлен от необходимости написания кода, реализующего бизнес-логику приложения.
Методы
Метод является функцией, которая связана с компонентом, и которая объявляется как часть объекта. Создавая обработчики событий, можно вызывать методы, используя следующую нотацию: ->, например: Edit1->Show();
Отметим, что при создании формы связанные с ней модуль и заголовочный файл с расширением *.h генерируются обязательно, тогда как при создании нового модуля он не обязан быть связан с формой (например, если в нем содержатся процедуры расчетов). Имена формы и модуля можно изменить, причем желательно сделать это сразу после создания, пока на них не появилось много ссылок в других формах и модулях.
Метрики и измерения
Данный миф утверждает возможность умозаключений, "хорошее" ли ПО разработано, на основе цифровых оценок, относящихся как непосредственно к коду, так и к процессу его разработки. Но можно ли точно определить, что значит "хороший" код?
Для большинства специалистов код хорош тогда, когда он реализует вычисление необходимой функции желаемым способом (например, корректно и с ограничениями, накладываемыми исполнением в реальном времени). При этом понятие "хороший" не связано напрямую с тем, как код структурирован и тем более с тем, как он выглядит - оно прежде всего относится к семантике функции, реализованной данным фрагментом кода.
Так как структурные методы семантику не измеряют, они не могут показать, насколько код хорош . В еще большей степени это относится к метрикам процессов. Одно время считалось, что метрики могут измерять семантику, но это оказалось не так. К тому же, как выяснилось, уже собранные метрики не так просто интерпретировать в практических терминах, что необходимо для реального улучшения процесса разработки. Интересно, что метрики кода больше подходят для оценки качества процесса разработки, чем самого кода.
Важно также понимать, что метрики обеспечивают косвенную меру, вообще говоря, неизмеряемых свойств. Например, невозможно измерить "тестируемость" и "сопровождаемость" программы. Зато легко установить количество строк кода. Ясно, что программа длиной в одну строку будет иметь лучшие характеристики тестируемости и сопровождаемости по сравнению с программой в миллион строк, и метрика "число строк кода" это покажет. Лучше следовать эмпирическому правилу (для многих неочевидному): метрики не могут дать универсальных рецептов построения качественного ПО; они обеспечивают лишь направление дальнейшего поиска решений.
Мягкое аварийное переключение (Graceful failover)
Для обработки ситуаций, когда адрес подсети как будто соответствует заданным критериям корпоративной сети, но там, где в соответствии со сценарием должен находиться сценарий регистрации, последний отсутствует, используется мягкое аварийное переключение. Этот термин означает, что если сценарий мониторинга для выполнения регистрации не может запустить сценарий регистрации, все сообщения об ошибках подавляются, и выполнение сценария продолжается. Мягкое аварийное переключение, позволяющее продолжать выполнение сценария в тех случаях, когда обнаружить сценарий регистрации невозможно, обеспечивается операторами On Error Resume Next и Err.clear в метке E.
Одна из ситуаций, предусматривающих использование мягкого аварийного переключения, возникает при разрыве сетевого соединения. Служба WMI извещает об этом событии сценарий мониторинга для выполнения регистрации, поскольку класс _InstanceOperationEvent фиксирует события удаления. Возвращаемый объект содержит атрибуты только что удаленного соединения.
Еще одна аварийная ситуация возникает в том случае, когда компьютер, выполняющий сценарий мониторинга для осуществления регистрации, подключен к сети, IP-адрес которой соответствует адресу целевой сети, но, тем не менее, данная сеть целевой сетью не является (это может быть сеть Internet-провайдера или сеть другой корпорации). В такой ситуации сценарий попытается запустить сценарий регистрации, но не сможет его отыскать.
Наконец, бывают ситуации, когда совпадения связаны с устройствами, не подключенными к корпоративной сети. К примеру, нередко типы соединений через инфракрасные порты имеют IP-адреса, которые могут совпадать с адресами заданных корпоративных подсетей.
Вам придется изменить те строки Листинга 1 и Листинга 2, где содержатся ссылки на совместно используемое имя и на имя сценария мониторинга для выполнения регистрации так, чтобы они соответствовали используемым в сети именам. Возможно, вы сочтете необходимым на время тестирования снабдить строку On Error Resume Next комментарием, чтобы иметь возможность перехватывать ошибки в значениях параметров.
Многоуровневая модель памяти переводов
Представление данных
Структура реально используемых ныне реализаций памяти перевода является одноуровневой и подразумевает наличие упорядоченного списка языковых пар. С введением механизма выделения в парах общих частей подобная организация данных окажется неудобной. Действительно, для любых двух пересекающихся языковых пар в базе будет создаваться дополнительный элемент, содержание которого будет полностью дублироваться в обеих языковых парах. От избыточности удастся избавиться, если удалить вынесенную общую часть из обеих пар, а на ее место поставить ссылку на вновь созданную языковую пару (рис. 1).
Рис. 1
Повторяя данную процедуру каждый раз, когда обнаруживается очередное пересечение, мы, в конечном счете, получим направленный граф, узлами которого являются языковые пары, а дугами- отношения включения.
Еще одной оптимизацией является разделение исходного и целевого сегментов каждой языковой пары. Это имеет смысл, поскольку не так уж редки случаи, когда одна и та же исходная фраза переводится на целевой язык по-разному, что порождает две языковые пары с одинаковым первым элементом. Помимо этого, пересечения исходных и целевых сегментов не всегда изоморфны, и их результаты не обязательно образуют языковую пару.
Поэтому разделим языковые пары, и получим два ориентированных графа, "синхронизированных" друг относительно друга (рис. 2). При этом отношения, связывающие вершины разных графов, могут быть различной местности, но обязательно обладают свойством симметричности.
Рис. 2
Развивая идею дальше, вспомним, что в нашем распоряжении уже имеется аппарат, позволяющий формализовать представленную схему. Это- объектно-ориентированный подход. В самом деле, каждому сегменту в базе может быть сопоставлен отдельный класс, отношение включения по своим свойствам идентично отношению наследования, а горизонтальные связи, формирующие языковые пары могут быть заданы механизмом ассоциирования, либо введением в класс метода Translate ("перевести").
Тем не менее, предложенная модель пока что не описывает внутреннюю структуру сегментов, которую необходимо анализировать при создании новой языковой пары на основе пересечения существующих.
Для решения этой задачи разобьем все сегменты на отдельные слова (по пробелам). Теперь каждый сегмент может быть представлен классом, являющимся производным от всех слов, образующих текст сегмента. При этом совершенно необязательно иметь перевод для каждого слова, поскольку это будет отражено лишь отсутствием соответствующей связи между узлами графов, а метод Translate будет возвращать "нулевое" значение. Памятуя о том, что в состав среды перевода помимо памяти переводов входит также терминологический словарь, деление сегментов можно осуществлять не по словам, а по терминам. Наличие терминологического словаря дает и еще одну возможность. Для каждого вхождения термина в сегмент можно определить его начальную форму, то есть ту, в которой он входит в базу словаря. Эта начальная форма послужит как бы абстрактным базовым классом для каждого вхождения термина, а конкретное вхождение будет содержать определение значений заданных в базовом классе атрибутов: род, число, падеж и т. п. Последнее нововведение подталкивает нас к мысли о том, терминологический словарь можно слить воедино с памятью переводов, представив, тем самым, все ресурсы переводчика в виде универсальной модели (рис. 3).
Модели взаимодействия программ при помощи сообщений
Системы очередей сообщений позволяют программам отправлять и получать данные, не соединяясь друг с другом напрямую. Приложения изолируются друг от друга транспортным слоем, который состоит из менеджеров очередей, обеспечивающих коммуникации. С помощью очередей сообщений можно реализовать как традиционные модели взаимодействия программ (клиент-сервер), так и модели, которые создаются только при помощи сервиса сообщений.
Архитектура клиент/сервер. Запросы клиента передаются в виде сообщений в серверную очередь. После обработки запроса приложение-сервер отправляет ответ в виде нового сообщения в очередь, указанную клиентом.
Асинхронное взаимодействие. При использовании очередей сообщений взаимодействующие приложения не обязательно должны быть одновременно активны. Программа может отправить сообщение другому приложению, которое обработает его, когда сочтет нужным. Отправив сообщение, программа может не ожидать ответа, а продолжать выполнять другие задачи.
Запуск программы. Сообщение, посланное одной прикладной программой, может инициировать старт другой. В таком случае по команде программы-монитора запускается приложение-адресат, которое обрабатывает сообщение.
Параллельная и распределенная обработка. Исполнение прикладного процесса может быть распределено между несколькими прикладными программами. Старт, координация между программами и консолидация результатов обработки на разных системах реализуются путем пересылки сообщений через очереди.
Архитектура публикация-подписка. Прикладные программы-публикаторы посылают в виде сообщения менеджеру очередей свою информацию с указанием темы. Другие приложения - подписчики присылают заявки на информацию по темам. Полученные публикации распределяются в соответствии с темами между подписчиками через очереди сообщений специальной программой - брокером.
Мониторинг ASE
Мониторы Sybase Central собирают информацию о характеристиках ASE и отображают ее в виде графиков и таблиц. Системные администраторы и DBA могут использовать эту информацию для: Определения потенциально узких мест Исследования текущих проблем Определения объектов, вовлеченных в конфликт Определения объектов, которые можно улучшить, используя кэш или назначая устройство Тонкой настройки ASE, приложений и хранимых процедур для лучшей производительности Анализа структуры данных и индексов
Мониторы
ASE Plug-in для Sybase Central содержит 14 мониторов. ASE release 11.5.1 будет включать дополнительно Process Current SQL Statement Monitor, который будет показывать SQL-выражения и план запроса, выполняющиеся в данный момент в выбранном процессе.
Наименование монитора | Описание |
Application Activity Monitor | Показывает информацию о ресурсах верхнего уровня для запущенных в данный момент приложений. |
Cache Monitor | Показывает информацию о процедурном кэше и кэше данных. |
Data Cache Monitor | Показывает общую загрузку и уровни эффективности 10 наиболее активных буферов данных (включая поименованные кеши данных и встроенный кеш). |
Device I/O Monitor | Показывает буфер (не страничный) загрузки ввода/вывода устройств, определенных для ASE. |
Engine Activity Monitor | Показывает текущую загрузку CPU в разрезе задач обработки данных. |
Memory Utilization Monitor | Отображает график распределения памяти. |
Network Activity Monitor | Показывает значения и размер коммуникационных пакетов, использующихся для связи ASE и клиентов, а также некоторые параметры сетевого трафика. |
Object Lock Status Monitor | Показывает детальную информацию о текущих блокировках. |
Object Page I/O Monitor | Показывает статистику физических и логических страницах ввода/вывода таблиц, влючая системные и временные, и индексов. |
Performance Summary Monitor | Отображает обобщенные показатели производительности. |
Performance Trends Monitor | Графическое отображение наиболее значимых статистических выборок параметров производительности, определяемых пользователем. |
Process Activity Monitor | Показывает информацию о ресурсах текущего процесса. |
Stored Procedure Activity Monitor | Отображает метрику выполняемых в данный момент процедур и триггеров. |
Transaction Activity Monitor | Выводит обобщенную информацию о транзакциях, выполняемых под управлением ASE. |
На рисунках приведены примеры Performance Summary Monitor и Object Lock Status Monitor
Performance Summary Monitor
Object Lock Status Monitor
Morfolog.shtml
Компьютерный морфологический разбор слов русского языка.
Ермолаев Д.С., Москва,
Ключевые слова. Разбор текста на русском языке компьютером. Интерфейсы на естественном языке. Морфологический разбор слов. Морфология слов русского языка. Интеллектуальный поиск текстов.
Применение данной статьи важно для тех, кто хочет сделать интерфейс к своей программе на естественном языке или сделать интеллектуальный поиск информации. Для этого нужно в первую очередь сделать морфологический анализ слов текста. Тогда не нужно будет иметь обширный словарь слов в разных словоформах. Достаточно запомнить основное слово в словаре, а входной поток слов подвергать морфологическому анализу, с тем, чтобы все слова преобразовать к начальным словоформам.
Пример. пользователь ввел в базу знаний свою информацию "фирма РиК. наша фирма продает тару картонную". Модуль морфологического разбора преобразует эту информацию к следующему виду: "фирма. РиК. мой фирма продать тара картонный". С точки зрения смысла получилась бессмыслица. Но для компьютера - самый раз, это будет видно дальше. Теперь, другой пользователь вводит для поисковой системы запрос "продает тару картонную". Этот запрос будет так же преобразован в "продать тара картонный". И теперь исполнив простой поиск по совпадению, система поиска выдаст ранее запомненную информацию: "фирма Рик. продать...". Однако здесь было бы лучше запомнить первоначальную информацию клиента с правильными словоформами и выдать только её.
Морфология слов русского языка определяется по аффиксу - окончанию и суффиксу слова. Назовем это правило правилом морфологического разбора. Однако есть слова, которые имеют окончание, подходящее для некоторой формы слова, но являются совершенно другой формой. Например, "-ать" говорит что слово есть глагол (прыгать, бежать). Но есть слово "кровать", которое есть существительное. Значит, из правила морфологического разбора есть исключения. Так же есть слова, которые не изменяют свою форму.
Например, предлоги, "не", наречия, "столь" и т.д. Значит, есть дополнения к правилу морфологического разбора. Эти дополнения можно представить как исключения из правила. Таким образом мы пришли к определенному логическому описанию морфологического разбора слов. Для создания компьютерной программы здесь лучше всего подойдет логический язык программирования. Рассмотри два из них. Пример программы морфологического разбора слов на логическом языке программирования ПРОЛОГ.
------------------------------------ /* программа по распознаванию морфологии слов русского языка */ /* по окончанию слова */ /* язык программирования ПРОЛОГ */ domains Слово = string predicates морфология(Слово,Слово Основа) nondeterm исключение(Слово,Слово Основа) nondeterm правило(Слово Аффикс, Слово АффиксОсновы) nondeterm аффикс(Слово Корень, Слово, Слово Аффикс) clauses /* база знаний */ /* исключения из правила разбора слова для "неправильных" слов */ исключение("рек","река"). исключение("сел","сесть"). /* правила разбора для правильных слов */ /* для глаголов */ правило("нули","ать"). правило("нул","ать"). правило("еть","ать"). правило("ает","ать"). правило("ал","ать"). правило("ул","ать"). правило("ули","ать"). /* для прилагательных */ правило("вая","вый"). правило("вые","вый"). правило("ая","ой"). правило("ие","ой"). правило("ую","ой"). /* предикат осуществляющий перебор всех вариантов */ /* аффиксов для этого слова */ аффикс("",Аффикс,Аффикс). аффикс(Корень,Слово,Аффикс):- frontchar(Слово,Буква,Слово1), аффикс(Корень1,Слово1,Аффикс), frontchar(Корень,Буква,Корень1). /* сначала просмотри все исключения */ морфология(Слово,Осн):- исключение(Слово,Осн),!. /* если не удачно, то переберем все аффиксы слова */ морфология(Слово,Осн):- аффикс(Корень,Слово,Аффикс), правило(Аффикс,АффиксиОсн), concat(Корень,АффиксиОсн,Осн),!. /* если неудачно, то значит слово несклоняемо */ морфология(Слово,Слово):-!. /* вызов процедури морфологического разбора */ Goal морфология("зеленую",Слово).
Ответ ПРОЛОГА: Слово = "зеленый"
Как видно, в программе всего 13 строчек, а остальное база знаний. Теперь посмотрим как справится с этой задачей РЕФАЛ. Пример на логическом языке программирования РЕФАЛ - 5:
----------------------- /* программа по распознаванию морфологии слов руссского языка */ /* по окончанию и приставке слова */ /* язык программирования РЕФАЛ 5 */ /* автор Ермолаев Д.С. dimonas_long@yahoo.com */ /* ввод одного слова с консоли */ $ENTRY Go { = <Prout <Question (<Card>) >>; }; /* таблица1. слова, которые имеют неправильное окончание */ WordsMissTable { = ( ('сел') 'сесть' ) ( ('рек') 'чего' ) } ; /* таблица2. окончания, по которым можно определить основу */ CompletionTable { = /* для глаголов */ ( ('нули') 'ать') ( ('нул') 'ать') ( ('ает') 'ать') ( ('еть') 'ать') ( ('еч') 'ать') ( ('ал') 'ать') ( ('ел') 'ать') /* для прилагательных */ ( ('вые') 'вый') ( ('вая') 'вый') ( ('ая') 'ой') ( ('ие') 'ой') ( ('ую') 'ой') }; /* сама программа распознавания морфологической формы слова */ Question { /* берем слово и ищем подходящее по шаблону в таблице1 */ (e.Word), <WordsMissTable>: e.L((e.Word)e.Qst)e.R = e.Qst; /* иначе, бере окончание слова и ищем по шаблону в таблице2 */ (e.1 e.End), <CompletionTable>: e.L((e.End)e.Qst)e.R = e.1 e.Qst ; /* иначе, слово неизменяемо */ (e.1) = e.1; };
Программа на РЕФАЛЕ состоит из трех предложений! Интересно, сколько бы предложений программы пришлось бы написать для решения такой задачи на алгоритмическом языке? Например С++? Ермолаев Д.С., Москва,
Можно ли из программы на Visual Basic создать рабочую книгу Excel?
Q: Можно ли из программы на Visual Basic создать рабочую книгу Excel?
A: Да, можно…..
Пример того, как из Visual Basic'a через OLE запустить Excel, и создать рабочую книгу...
' CreateXlBook
' Вызывает MS Excel, создает рабочую книгу с именем sWbName с одним
' единственным рабочим листом. Рабочая книга будет сохранена в каталоге
' sDirName. В случае успеха возвращает True, в противном случае - False.
'
Public Function CreateXlBook(sWbName As String, sDirName) As Boolean
' MS Excel hidden instance
Dim objXLApp As Object
Dim objWbNewBook As Object
CreateXlBook = False
Set objXLApp = CreateObject("Excel.Application")
If objXLApp Is Nothing Then Exit Function
' В новой рабочей книге создавать только один рабочий лист
objXLApp.SheetsInNewWorkbook = 1
Set objWbNewBook = objXLApp.Workbooks.Add
If objWbNewBook Is Nothing Then Exit Function
' Сохраняем книгу
If vbNullString = Dir(sDirName, vbDirectory) Then Exit Function
objWbNewBook.SaveAs (sDirName + "\" + sWbName + ".xls")
CreateXlBook = True
' Освобождение памяти
Set objWbNewBook = Nothing
objXLApp.Quit
Set objXLApp = Nothing
CreateXlBook = True
End Function
Hint: Tested and approved with MS Visual Basic 4.0 Enterprise Edition
Coрyright(c) 1997 by Andrew Kirienko.
E-Mail:
FidoNet: 2:5020/239.21
А также огромное спасибо:
Michael Zemlaynukha, (2:5015/4.9@FidoNet, )
- за полезные замечания и здоровую критику этого FAQ'а
Написание DLL.
Создание пустой библиотеки.
С++ Builder имеет встроенный мастер по созданию DLL. Используем его, чтобы создать пустую библиотеку. Для этого надо выбрать пункт меню File->New: В появившемся окне надо выбрать "DLL Wizard" и нажать кнопку "Ok". В новом диалоге в разделе "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) 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,&bSCRSAVEACTIVE,0)) MessageBox(0,"Не могу получить данные об установленном" "хранителе экрана", "Внимание!",MB_ICONERROR); } return 1;
Этот код позволяет узнать способ переключения языка и установить факт наличия активного хранителя экрана. Обратите внимание на то, что этот код выполняется только когда библиотека проецируется на адресное пространство процесса - проверяется условие (reason==DLL_PROCESS_ATTACH). Если вас интересуют подробности, то их можно узнать в разделе справки "Win32 Programmer's Reference" в подразделе "DllEntryPoint".
Написание хороших журнальных записей
Если можно использовать `cvs diff', чтобы получить точное содержание любого изменения, то зачем тогда придумывать еще журнальную запись о нем? Очевидно, что журнальные записи короче, чем тексты изменений, и позволяют читателю получить общее понимание изменения без необходимости углубляться в детали.
Однако же, хорошая запись в журнале описывает причину, по которой было сделано изменение. Например, плохая журнальная запись для редакции 1.7 может звучать как "Преобразовать `t' к нижнему регистру". Это правильно, но бесполезно -- `cvs diff' предоставляет точно ту же информацию, и гораздо яснее. Гораздо лучшей журнальной записью было бы "Сделать эту проверку независящей от регистра", потому что это гораздо яснее описывает причину любому, кто понимает, что происходит в коде --- клиенты HTTP должны игнорировать регистр букв при анализе заголовков ответа от сервера.
Написание приложения, устанавливающего ловушку.
Создание пустого приложения.
Для создания пустого приложения воспользоваться встроенным мастером. Для этого надо использовать пункт меню File->New: В появившемся окне необходимо выбрать "Console Wizard" и нажать кнопку "Ok". В новом диалоге в разделе "Source Type" следует оставить значение по умолчанию - "C++". Во втором разделе надо снять все флажки. По нажатию "Ок" приложение создаётся.
Создание главного окна.
Следующий этап - это создание главного окна приложения. Сначала надо зарегистрировать класс окна. После этого создать окно (подробности можно найти в "Win32 Programmer's Reference" в подразделах "RegisterClass" и "CreateWindow"). Всё это делает следующий код (описатель окна 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"); Для того, чтобы это получилось надо включить в проект файл ресурсов (*.res), в котором должен находиться значок с именем "MAINICON".
Это окно никогда не появится на экране, поэтому оно имеет размеры и координаты, устанавливаемые по умолчанию. Оконная процедура такого окна необычайно проста: LRESULT CALLBACK MainWndProc(HWND hwnd,UINT uMsg,WPARAM wParam, LPARAM lParam) {// Оконная процедура switch (uMsg) { case WM_DESTROY:{PostQuitMessage(0); break;} //------------ case MYWM_NOTIFY: { if(lParam==WM_RBUTTONUP) PostQuitMessage(0); break; // Правый щелчок на значке - завершаем } default: return DefWindowProc(hwnd,uMsg,wParam,lParam); } return 0; }
Размещение значка в системной области.
Возникает естественный вопрос: если окно приложения никогда не появится на экране, то каким образом пользователь может управлять им (например, закрыть)? Для индикации работы приложения и для управления его работой поместим значок в системную область панели задач.
Делается это следующей функцией:
void vfSetTrayIcon(HINSTANCE hInst) { // Значок в Tray char* pszTip="Хранитель экрана и раскладка";// Это просто Hint NotIconD.cbSize=sizeof(NOTIFYICONDATA); NotIconD.hWnd=MainWnd; NotIconD.uID=IDC_MYICON; NotIconD.uFlags=NIF_MESSAGE|NIF_ICON|NIF_TIP; NotIconD.uCallbackMessage=MYWM_NOTIFY; NotIconD.hIcon=LoadIcon(hInst,"MAINICON"); lstrcpyn(NotIconD.szTip,pszTip,sizeof(NotIconD.szTip)); Shell_NotifyIcon(NIM_ADD,&NotIconD); }
Для корректной работы функции предварительно нужно определить уникальный номер значка (параметр NotIconD.uID) и его сообщение (параметр NotIconD.uCallbackMessage). Делаем это в области определения глобальных переменных:
#define MYWM_NOTIFY (WM_APP+100) #define IDC_MYICON 1006
Сообщение значка будет обрабатываться в оконной процедуре главного окна (NotIconD.hWnd=MainWnd):
case MYWM_NOTIFY: { if(lParam==WM_RBUTTONUP) PostQuitMessage(0); break; // Правый щелчок на значке - завершаем }
Этот код просто завершает работу приложения по щелчку правой кнопкой мыши на значке. При завершении работы значок надо удалить:
void vfResetTrayIcon() {// Удаляем значок Shell_NotifyIcon(NIM_DELETE,&NotIconD); }
Установка и снятие ловушек.
Для получения доступа в функциям ловушки надо определить указатели на эти функции:
LRESULT CALLBACK (__stdcall *pKeybHook)(int,WPARAM,LPARAM); LRESULT CALLBACK (__stdcall *pMouseHook)(int,WPARAM,LPARAM);
После этого спроецируем написанную DLL на адресное пространство процесса:
hLib=LoadLibrary("SSHook.dll");
(hLib описан как HINSTANCE hLib). После этого мы должны получить доступ к функциям ловушек:
(void*)pKeybHook=GetProcAddress(hLib,"KeyboardHook"); (void*)pMouseHook=GetProcAddress(hLib,"MouseHook");
Теперь всё готово к постановке ловушек. Устанавливаются они с помощью функции SetWindowsHookEx:
hKeybHook=SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)(pKeybHook),hLib,0); hMouseHook=SetWindowsHookEx(WH_MOUSE,(HOOKPROC)(pMouseHook), hLib,0);
(hKeybHook и hMouseHook описаны как HHOOK hKeybHook; HOOK hMouseHook;)
Первый параметр - тип ловушки (в данном случае первая ловушка для клавиатуры, вторая - для мыши).Второй - адрес процедуры ловушки. Третий - описатель DLL-библиотеки. Последний параметр - идентификатор потока, для которого будет установлена ловушка. Если этот параметр равен нулю (как в нашем случае), то ловушка устанавливается для всех потоков. После установки ловушек они начинают работать. При завершении работы приложения следует их снять и отключить DLL. Делается это так:
UnhookWindowsHookEx(hKeybHook); UnhookWindowsHookEx(hMouseHook); // Завершаем FreeLibrary(hLib);
Настройка вашего репозитория
CVS хранит все изменения в данном проекте в дереве каталогов, называемом "репозиторием" (repository). Перед тем, как начать использовать CVS, вам необходимо настроить переменную среды CVSROOT так, чтобы она указывала на каталог репозитория. Тот, кто ответственен за управление конфигурацией вашего проекта, вероятно, знает, что именно должна содержать в себе эта переменная; возможно даже, что переменная CVSROOT уже установлена глобально.
В любом случае, на нашей системе репозиторий находится в `/usr/src/master'. В этом случае вам следует ввести команды ` setenv CVSROOT /usr/src/master '
если ваш командный интерпретатор -- csh или порожден от него, или ` CVSROOT=/usr/src/master export CVSROOT
если это Bash или какой-либой другой вариант Bourne shell.
Если вы забудете сделать это, CVS пожалуется, если вы попытаетесь запустить его: $ cvs checkout httpc cvs checkout: No CVSROOT specified! Please use the `-d' option cvs [checkout aborted]: or set the CVSROOT environment variable. $
Назначение
IBM Rational Rapid Developer это новый продукт, официально представленный компанией IBM в текущем (2003) году. Основное его назначение - помогать разработчикам быстро создавать, развертывать и отлаживать комплексные и многоуровневые Интернет приложения, основанные на использовании широкого круга технологий J2EE. Продукт сочетает большие возможности визуального моделирования и автоматического конструирования приложений. Приложения, построенные с помощью Rapid Developer, являются надежными, масштабируемыми и безопасными. Что является очень важным, продукт предоставляет максимальные возможности по использованию в новых проектах уже существующих наработок, таких как UML модели, схемы баз данных, действующие бизнес-системы или подсистемы, системы обработки сообщений, Web сервисы и др.
IBM Rational Rapid Developer позволяет создавать и вести единое проектное пространство для всей разрабатываемой системы во всех ее конфигурациях. С этой информационной системой пользователь сможет работать как с обычного компьютера (HTML доступ или Java приложение), так и с мобильных устройств с ограниченными функциональными возможностями (сотовые телефоны, карманные компьютеры, другие CDC или CLDC устройства). Например, с помощью Rational Rapid Developer легко спроектировать и реализовать обычный WAP-сайт, который позволит пользователю осуществлять доступ к Интернет-сайту с обычного мобильного телефона по протоколу WAP.
Некоторые подходы к отладке распределенных приложений
При отладке распределенного приложения в целом нужно представлять общее его состояние, которое включает структуры данных, распределенные по нескольким платформам. Кроме того, необходимо иметь протокол взаимодействия задач в системе.
Взаимодействие задач, исполняемых на разных процессорах, можно протоколировать, используя вместо стандартных функции связи, передающие необходимую информацию менеджеру. Чем более полной является эта информация, тем проще менеджеру с ней работать, но тем большее влияние на работу системы оказывает сеанс отладки, в результате чего могут возникать новые динамические ошибки. В [9] описана система DARTS (Debug Assistant for Real-Time Systems). С ее помощью можно проводить полноценный сеанс отладки без наличия какой-либо отладочной информации в приложении. Для этого необходимо правильно сопоставить полученный от системы поток событий с исходными текстами приложения. Этот процесс происходит в 2 этапа: разбор исходных текстов и непосредственно само сопоставление.
При разборе исходного текста для каждой задачи генерируется последовательность следующих программных элементов: системные вызовы; условные конструкции; циклы; вызовы функций, описанных в программе; библиотечные вызовы.
После трассировки приложения необходима полная информация для полученного потока событий с целью его дальнейшей отладки. Для этого происходит такое сопоставление: системные вызовы сравниваются по именам и параметрам; условная конструкция считается обнаруженной в протоколе, если присутствует один из вариантов; цикл считается найденным, если он присутствует в протоколе 0 и более раз (каждый раз ищется максимальное число его вхождений); программные вызовы идентифицируются по вхождению в протокол тела подпрограммы; для каждой библиотечной функции строится набор возможных последовательностей системных вызовов. Функция считается присутствующей в протоколе, если обнаружена некоторая последовательность из ее характеристического набора.
В результате получается набор гипотез о ходе выполнения приложения (включая вызовы функций, время и процессор).
Информация может уточняться, если задавать некоторые интервалы выполнения (например, протоколирование выполнения конкретной задачи). После таких уточнений получаем символьную и строковую информацию о произошедших событиях. Еще один подход к отладке распределенных приложений предложен в [16]. Описанный там отладчик Ariadne позволяет проверять, произошла ли некоторая заданная для конкретной задачи последовательность событий. Механизм проверки осуществлен следующим образом. Сперва создается граф хода выполнения приложения, построенный на протоколе работы приложения. Затем пользователь задает цепи - последовательности событий, которые будут искаться в графе хода выполнения приложения. Следующим шагом является создание p-цепей - это цепи, для которых указаны конкретные задачи, где ожидается возникновение данной последовательности событий. В итоге формируется шаблон поиска - pt-цепи, которые представляют собой логические композиции p-цепей. Если в графе хода выполнения встречается pt-цепь, то считается, что запрос удовлетворен, и возвращается так называемое абстрактное событие - подграф, содержащий встретившиеся экземпляры событий. Эти экземпляры удаляются из графа хода выполнения, и анализ событий продолжается. Если все pt-цепи присутствуют в графе, то тест считается успешно завершенным. Ввиду асинхронности выполнения ошибка может состоять в том, что нарушен порядок возникновения абстрактных событий. Для локализации таких ошибок в Ariadne реализованы следующие соотношения между абстрактными событиями:
A предшествует B, если существует зависимость некоторого события из B от некоторого события из A; A параллельно B, если события в A и в B независимы; A перекрывает B, если существует как зависимость события из A от события из B, так и обратная зависимость.
Проверка полученных абстрактных событий на соответствие этим соотношениям позволяет выявлять ошибки, связанные с асинхронностью. В [26] излагается способ отладки РСРВ посредством моделирования системы сетями Петри с временными ограничениями (timing constraint Petri nets, TCPN).
TCPN - это граф ; где P - множество позиций; T - множество переходов; F - множество дуг, соединяющих позиции и переходы; C - множество целочисленных пар (TCmin(pt),TCmax(pt)), где pt может быть и позицией, и переходом; D - множество чисел FIRE(pt), обозначающих время срабатывания pt; и М - множество маркеров. Говорят, что переход t разрешен, если каждая из входных позиций содержит по крайней мере один маркер. Если к моменту Т0 переход t разрешен, то он может сработать в течении времени от Т0 + ТCmin(t) до T0 + TCmax(t). Переход t сработал успешно, если он продолжался не более FIRE(t) временных единиц, иначе происходит срабатывание других переходов. В случае, когда не срабатывает ни один из переходов, все маркеры остаются на своих местах. Таким образом локализуются ошибки в РСРВ. Одной из серьезных ошибок, связанных с работой распределенного приложения в системе реального времени, является недетерминированность. Ее суть заключается в том, что при разных запусках приложения при одних и тех же входных данных получаются разные результаты. В [8] описан подход к обнаружению недетерминированности в системах, использующих в качестве связи между задачами сообщения. В таких системах недетерминированность может быть вызвана либо задержками сообщений, либо сменой алгоритма планирования. Следует отметить, что в приложении может быть специально заложена некая недетерминированность, поэтому нужно такой случай выделять. Предлагается такая стратегия обнаружения ошибочной недетерминированности:
для каждого сообщения определяется некоторый идентификатор; при получении сообщения идентификатор обрабатывается, и создается некоторая, специфическая для получившей задачи, интерпретация сообщения; совершается проверка, удовлетворяет ли эта интерпретация некоторому порядку получения сообщений данной задачей. Такой подход позволяет обходить случаи встроенной недетерминированности путем определения одинаковой интерпретации для соответствующих сообщений.
Некоторые выводы
Итак, на сегодняшний день мы имеем средство, в полной мере использующее открытую архитектуру Delphi, позволяющее эффективно использовать модель данных в клиентских приложениях (в том числе и во время выполнения), экономя силы и время при модернизации и сопровождении информационной системы. Конечно, использование такого средства может иметь некоторые отрицательные последствия. Например, если модель данных хранится на сервере, число обращений к серверу с целью обращения к ней может оказаться достаточно велико. Но, возможно, этот фактор во многих случаях окажется менее важен, чем эффективность разработки и модернизации информационной системы и возможность комплексного использования средств проектирования баз данных, особенно в случае сжатых сроков выполнения проектов.
Задать вопросы о MetaBASE и получить trial-версию этого продукта можно в компании Interface Ltd, авторизованном учебном центре Logic Works и Borland, по телефонам (095)135-55-00, 135-25-19, e-mail:
New Insight Technologies for Maximum Productivity and Ease-of-Use
Delphi 3 позволяет сосредоточиться на проблемах бизнеса предприятия и проблемах его развития. Созданные, чтобы помочь разработчикам сфокусироваться на решаемых ими проблемах предметной области, ActiveInsight, BusinessInsight и CodeInsight представляют собой новые технологии для быстрого создания элементов управления ActiveX, систем поддержки принятия решений, автоматизации программирования.
"Возрастающая продуктивность Delphi 3 выгодна как индивидуальным разработчикам, так и целым организациям", - заявил Джон Адамс (John Adams), главный консультант Arthur Andersen's Advanced Technology Group. - "Новые технологии Insight изменили мои методы создания приложений как для моих личных проектов, так и для нужд предприятий моих клиентов."
Независимость от операционной системы
Flora/C+ разработана с применением специальных технологий, в частности, использована технология микроядра (системозависимых кодов) и метод раскрутки (разработка). Эти технологии обеспечили полную независимость Flora/C+ и её приложений от операционных систем и платформ, на которых они исполняются.
О синхронизации процессов в среде Windows
- Запасайтесь, дьяволы, гробами, сейчас стрелять буду.
М. Зощенко. Нервные люди
Статья "О бильярде с Microsoft C++ 5.0" [1] положила начало знакомству с практическим применением технологии конечных автоматов в рамках Visual C++. В этой технологии особое внимание уделяется параллельным процессам, в основе которых на уровне единичного процесса (программа, оператор, объект и т.п.) лежит модель конечного автомата (КА), а на уровне множества процессов - сетевая автоматная модель.
В статье [1] рассматривались представленные объектами-мячиками независимые параллельные процессы. Здесь мы обсудим взаимодействие и синхронизацию процессов на примере известной задачи Майхилла об одновременной стрельбе [2], превратив безобидные мячики в пули и добавив к ним стрелков.
Задача Майхилла - еще один (наряду с задачей RS-триггера [3]) пример решения нетривиальных проблем создания сложных систем. Справившись с ней, мы научимся организовывать взаимодействие параллельно работающих компонентов сложных программных комплексов в жестких условиях.
Об авторe
Николай Игнатович - эксперт компании IBM. С ним можно связаться по электронной почте по адресу:
Вячеслав Селиверстович Любченко - программист, в "Мире ПК" опубликован ряд его статей. Е-mail:
Джеффри Воас (Jeffrey Voas) - работает в компании Reliable Software Technologies. С ним можно связаться по эл. почте:
Jeffrey Voas, "Software Quality`s Eight Greatest Myths", - IEEE Software, September/October 1999, pp. 118-120. Reprinted with permission, Copyright IEEE, 1999. All rights reserved.
Объединение изменений
Так как каждый разработчик использует свой собственный рабочий каталог, изменения, которые вы делаете в своем каталоги, не становятся автоматически видимыми всем остальным в вашей команде. CVS не публикует изменений, пока они не закончены. Когда вы протестируете изменения, вы должны "зафиксировать" (commit) их в репозитории и сделать их доступными остальным. Мы опишем команду cvs commit далее.
Однако, что если другой разработчик изменил тот же файл, что и вы, и, может быть, даже изменил те же самые строки? Чьи изменения будет использованы? Обычно ответить на этот вопрос автоматически невозможно, и CVS совершенно точно некомпетентен, чтобы принимать такие решения.
Поэтому перед тем, как фиксировать ваши изменения, CVS требует, чтобы исходные тексты были синхронизированы со всеми изменениями, которые сделали остальные члены группы. Команда cvs update позаботится об этом: $ cvs update cvs update: Updating . U Makefile RCS file: /u/src/master/httpc/httpc.c,v retrieving revision 1.6 retrieving revision 1.7 Merging differences between 1.6 and 1.7 into httpc.c M httpc.c $
Рассмотрим пример строка за строкой: `U Makefile'
Строка вида `U файл' означает, что файл просто был обновлен; кто-то еще внес в этот файл изменения, и CVS скопировал измененный файл в ваш рабочий каталог. `RCS file:... retrieving revision 1.6 retrieving revision 1.7 Merging differences between 1.6 and 1.7 into httpc.c'
Это сообщение означает, что кто-то еще изменил `httpc.c'; CVS объединила их изменения с вашими и не обнаружила текстуальных конфликтов. Цифры `1.6' и `1.7' -- это номера редакции (revision numbers), используемые для обозначения определенных точек истории файла. Заметьте, что CVS объединяет изменения только в вашей рабочей копии; репозиторий и рабочие каталоги других разработчиков остаются нетронутыми. От вас требуется протестировать объединенный текст и убедиться, что он верен. `M httpc.c'
Строка вида `M файл' означает, что файл был модифицирован вами и содержит изменения, которые еще не стали видны другим разработчикам. Это -- изменения, которые вам следует зафиксировать.
Так как CVS объединила чьи-то еще изменения с вашими исходными текстами, следует убедиться, что они все еще работают: $ make gcc -g -Wall -lnsl -lsocket httpc.c -o httpc $ httpc GET http://www.cyclic.com ...HTML text for Cyclic Software's home page follows... $
Объектно-ориентированное окружение
На всех стадиях жизни приложения. во время разработки, отладки, тестирования и исполнения на компьютере пользователя, его форма существования. дерево взаимодействующих между собой и с внешним миром объектов, сохраняется неизменным. Какие-либо этапы перехода приложения из стадии разработки в стадию исполнения концептуально отсутствуют.
Свойства дерева объектов приложения сохраняются даже в процессе исполнения и могут быть доступны в том же виде, в котором они были созданы разработчиком.
Объектные модели
Итак, в моделях CORBA и COM клиент получает обслуживание от сервера объекта, запрашивая метод, заданный в интерфейсе объекта. Важной характеристикой обеих технологий является четкое разграничение интерфейсов, которые - суть абстрактный набор связанных методов, и конкретных реализаций этих методов. Клиенту доступно описание интерфейса объекта, через которое он получает доступ к методам, то есть функциональности данного объекта. Детали реализации методов от клиента полностью изолированы. Метод вызывается по ссылке, и реальные действия выполняются в адресном пространстве объекта.
Однако за этим фундаментальным сходством начинаются значительные различия между конкретным воплощением в двух моделях понятий объектов, интерфейсов, вызова по ссылке и т.д. На них и остановимся.
В CORBA интерфейс объекта задается с помощью определенного OMG языка описания интерфейсов (Interface Definition Language, IDL). Тип объекта - это тип его интерфейса. Интерфейс идентифицируется именем, представленным цепочкой символов. В модели CORBA определен базовый тип для всех объектов - CORBA::Object. Объект поддерживает тип своего непосредственного интерфейса и, по принципу наследования, все его базовые типы.
В СОМ объект характеризуется своим классом. Класс - это реализация некоторого множества интерфейсов. Множественное наследование интерфейсов не поддерживается, вместо этого объект может иметь несколько интерфейсов одновременно. В СОМ интерфейс может определяться путем наследования другого интерфейса. Для всех интерфейсов существует базовый интерфейс - IUknown. Для того чтобы перейти от интерфейса базового типа к унаследованному интерфейсу или от одного из интерфейсов объекта к другому, клиент должен вызывать метод QueryInterface, определенный в базовом интерфейсе IUknown. Интересно отметить, что возможность описания нескольких интерфейсов для одного объекта должна появиться теперь и в CORBA; анонсированная в сентябре прошлого года версия CORBA 3.0 в числе важных нововведений содержит и концепцию Multiple Interface [3].
Для идентификации классов и интерфейсов СОМ используются те же универсальные уникальные идентификаторы (UUID), которые приняты для идентификации интерфейсов в спецификации DCE RPC.
Можно применять и символьные обозначения интерфейса, но затем они должны быть транслированы в надлежащий идентификатор UUID. Объект в СОМ - это экземпляр класса. Клиент получает доступ к объекту с помощью указателя на один из его интерфейсов (interface pointer). Связь между классом и множеством поддерживаемых им интерфейсов достаточно произвольна. Не существует заранее заданного отношения между идентификатором класса и конкретным набором интерфейсов, и разные экземпляры класса могут поддерживать разные подмножества интерфейсов. Идентификатор класса ссылается на конкретную реализацию, и фактический набор интерфейсов для данной реализации становится окончательно известен только на стадии выполнения. Все эти особенности вытекают из существа модели СОМ, которая реализует интеграцию объектов на уровне двоичных кодов. Двоичное представление интерфейса объекта в СОМ идентично виртуальной таблице функций языка Си++. Клиент имеет доступ к интерфейсу посредством указателя на такую таблицу, а все детали реализации скрыты от него. Поэтому можно прозрачно для пользователя вносить изменения в реализацию объекта, если при этом не меняется интерфейс. Заимствованная из Си++ модель интеграции на двоичном уровне с помощью таблиц функций имеет как свои плюсы, так и минусы. Невидимая пользователю возможность замены реализаций объектов и высокая эффективность вызовов методов в пределах одного адресного пространства по аналогии с вызовом виртуальной функции в С++ - очевидные положительные стороны такой модели. С другой стороны, интеграция на уровне двоичных кодов возможна только на одной аппаратно-операционной платформе. Невозможность множественного наследования интерфейсов в СОМ - следствие использования таблиц функций. Интерфейс представляется указателем на одну таблицу, а множественное наследование повлекло бы за собой связь конкретного интерфейса с несколькими таблицами, то есть понадобилось бы несколько указателей для одного интерфейса. В СОМ принята более громоздкая, по сравнению с CORBA, структура логических связей объекта и его интерфейсов. Конечно, определение интерфейсов с помощью двоичных таблиц не зависит от конкретного языка программирования.
Но поддержка различных языков должна опираться на механизм вызовов виртуальных функций, который непосредственно применим только к Си++, а для всех остальных языков будет подвергаться определенной предварительной обработке. Поэтому по числу поддерживаемых языков СОМ уступает CORBA, в которой предусмотрен гибкий механизм отображения описаний интерфейсов в терминах IDL в соответствующие структуры того или иного языка программирования. Различие между языками IDL по версии OMG и Microsoft - одно из наиболее значительных для двух объектных моделей. В CORBA язык описания интерфейсов - важнейшая часть архитектуры, основа схемы интеграции объектов. Все интерфейсы и типы данных определяются на IDL. Различные языки программирования поддерживаются благодаря заданным отображениям между описаниям типов данных на IDL в соответствующие определения на конкретном языке. CORBA IDL задает определения, которые могут отображаться в множество различных языков, не требуя при этом никаких изменений от целевого языка. Эти отображения реализуются компилятором IDL, который генерирует исходные коды на нужном языке. В настоящий момент поддерживается отображение в Си, Си++, SmallTalk, Ada95, Visual Basic, Кобол Cobol и Java. Сам IDL синтаксически напоминает декларации типов в Си++, но отнюдь не идентичен этому языку. Если в модели интеграции объектов CORBA язык IDL играет фундаментальную роль, то Microsoft IDL (MIDL) - лишь один из возможных способов определения интерфейсов объекта. В спецификации СОМ подчеркивается, что эта модель реализует интеграцию на двоичном уровне, поэтому все спецификации и стандарты, относящиеся к уровню исходных текстов компонентов, к которым следует причислить и MIDL, рассматриваются как вспомогательные и не могут оказывать решающего влияния на общую архитектуру системы. MIDL - не более чем полезный инструментарий для написания интерфейсов. В отличие от CORBA IDL, MIDL, являющийся расширением DCE RPC IDL, не определяет общего набора типов данных, доступных различным языкам программирования.
На MIDL можно определить интерфейсы и типы данных, которые поймут программы на Си и Си++, но для Visual Basic и Java этого уже сделать нельзя. Проблему частично обещает решить новая спецификация СОМ+, которая предоставит возможности встраивания средств определения СОМ-интерфейсов в инструментарий для языков типа Visual Basic и Visual C++. Обе объектные модели предусматривают ситуацию, когда у клиента уже в процессе выполнения программы возникает потребность использовать те или иные объекты. В этом случае у клиента не будет скомпилированных команд для вызова методов, поскольку интерфейс объекта не был известен на этапе компиляции. Поэтому ему потребуются специальные интерфейсы динамического вызова. В CORBA механизм DII (Dynamic Invocation Interface) опирается на Interface Repositоry, который содержит написанные на IDL определения типов данных и интерфейсов. Конкретная реализация такого репозитария зависит от разработчика брокера объектных запросов ORB. Помимо динамических вызовов, репозитарий интерфейсов в CORBA может использоваться для систематической организации и хранения данных определенного проекта. В СОМ те же функции выполняют интерфейс Dispatch и библиотеки типов Type Libraries. CORBA и СОМ абсолютно по-разному подходят к проблемам идентификации (identity) объектов и их сохранения в долговременной памяти (persistance). CORBA вводит понятие объектной ссылки (object reference), которая уникальным образом идентифицирует объект в сети. Тем самым экземпляру объекта дается право на существование в течение некоторого времени. Объекты могут активироваться, сохраняться в долговременную память, позже вновь реактивироваться и деактивироваться, и при этом объектная ссылка будет указывать все время на одно и то же конкретное воплощение объекта. Для особо значимых объектов, предназначенных для длительного использования, объектные ссылки могут интегрироваться со службой каталогов или службой имен. Механизм долговременного хранения, то есть сохранения состояния объекта в долговременной памяти для дальнейшей его реактивации, в CORBA абсолютно прозрачен для клиента.
Клиент не имеет никаких легальных средств обнаружить: куда и каким образом сохраняется экземпляр объекта. Такой подход соответствует концепции сокрытия деталей реализации объекта от клиента. В СОМ понятие объектной ссылки отсутствует. Ближайший аналог - это механизм moniker ("кличка"), обеспечивающий преобразование символьного имени объекта в указатель интерфейса. Этот механизм действует для тех объектов, которые сохраняются в долговременной памяти. Два же активных объекта считаются идентичными, если для них совпадают указатели на интерфейс IUknown. Для долговременного хранения в СОМ поддерживаются две модели. Первая и изначальная модель предоставляет клиенту возможность управлять хранением объекта. В интерфейсе объекта, предназначенного для долговременного хранения, поддерживаются возможные типы носителей. Если необходимо восстановить объект из долговременной памяти, клиент явным образом указывает, где был сохранен экземпляр объекта и запрашивает его активацию. Однако, такая модель эффективна, скорее всего, только в настольной среде или в небольшой локальной сети, где управление хранением документов, к примеру, может быть доверено пользователю файловой системы. Другой, более поздний вариант сохранения в долговременную память в СОМ предусматривает использование Microsoft Transaction Server (MTS), который обеспечивает управление хранением со стороны сервера.
Обеспечение целостности данных и синхронизации изменений
MQSeries - это транзакционное программное средство, которое может объединять группу операций по посылке и приему сообщений в единую транзакцию. Прикладная программа помечает часть своих получаемых и отравляемых сообщений специальной опцией - "участвующие в транзакции". До момента выполнения приложением команды на завершение транзакции MQCMIT, посланные им сообщения фактически "невидимы" для других приложений, а полученные реально не удаляются из очередей. В случае выполнения приложением команды на откат транзакции (MQBACK), очереди восстанавливаются к состоянию на начало транзакции.
Менеджер очередей MQSeries может играть роль менеджера ресурсов, поддерживающего XA интерфейс, а может участвовать в распределенной транзакции под управлением таких мониторов транзакций как CICS, Encina, Tuxedo. Продукты MQSeries, начиная с версии 5, сами могут быть координаторами распределенных транзакций с двухфазной фиксацией.
Обеспечение необходимой защиты передаваемых данных
Для обеспечения безопасности при использовании приложениями системы очередей сообщений необходимо знать, какое приложение послало то или иное сообщение: отслеживать, кто получает данное сообщение; обеспечивать идентификацию удаленных менеджеров, а также контролировать выполнение административных команд. Сама по себе система MQSeries не имеет собственных специальных модулей шифрования сообщений, но зато позволяет подсоединять внешние компоненты обеспечения безопасности.
Административные команды и доступ к объектам менеджера очередей. MQSeries позволяет контролировать исполнение административных команд и доступ к объектам менеджера очередей - к самому менеджеру и очередям. На большинстве платформ имеется менеджер безопасности, который позволяет определить и разграничить доступ к объектам. Кроме того, поскольку MQSeries предоставляет документированный интерфейс для управления функциями безопасности, можно создать собственный менеджер безопасности.
Безопасность в каналах передачи сообщений. Для защиты информации, которую передают друг другу менеджеры очередей, MQSeries поддерживает возможности подключения специально разрабатываемых модулей. Например, два менеджера очередей, устанавливающих связь по каналу, могут с помощью дополнительных программ опознать друг друга. Кроме того, сообщения, передаваемые по каналу, могут шифроваться перед передачей и расшифровываться при приеме.
Безопасность приложений. Интерфейс MQI предоставляет приложениям средства идентификации себя (приложения и платформы) и пользователя. Идентификационная информация содержится в заголовке сообщения, передается вместе с ним и только привилегированные приложения могут ее изменять. Приложения могут использовать эту информацию для дополнительной проверки получаемых сообщений.
Обработка конфликтов
Как уже упоминалось, команда `cvs update' объединяет изменения, сделанные другими разработчиками, с исходными текстами в вашем рабочем каталоге. Если вы отредактировали файл одновременно с кем-то другим, CVS объединит ваши изменения.
Довольно легко представить себе, как работает объединение, если изменения были совершены в разных участках файла, но что если вы оба изменили одну и ту же строку? CVS называет эту ситуацию конфликт и предоставляет вам самому разобраться с ним.
Например, предположим, что вы добавили некую проверку на ошибки в код, определяющий адрес сервера. Перед фиксированием изменений вы должны запустить `cvs update', чтобы синхронизировать ваш рабочий каталог с репозиторием: $ cvs update cvs update: Updating . RCS file: /u/src/master/httpc/httpc.c,v retrieving revision 1.8 retrieving revision 1.9 Merging differences between 1.8 and 1.9 into httpc.c rcsmerge: warning: conflicts during merge cvs update: conflicts found in httpc.c C httpc.c $
В этом случае другой разработчик изменил тот же участок файла, что и вы, поэтому CVS жалуется на конфликт. Вместо того, чтобы напечатать M httpc.c, как это обычно происходит, CVS печатает C httpc.c, что означает наличие конфликта в этом файле.
Чтобы справиться с конфликтом, откройте этот файл в редакторе. CVS обозначает конфликтующий текст так: /* Look up the IP address of the host. */ host_info = gethostbyname (hostname); <<<<<<< httpc.c if (! host_info) { fprintf(stderr, "%s: host not found: %s\n", progname, hostname); exit(1); } ======= if (! host_info) { printf("httpc: no host"); exit(1); } >>>>>>> 1.9 sock = socket (PF_INET, SOCK_STREAM, 0);
Текст вашего рабочего файла появляется сверху, после символов <<<. Внизу находится конфликтующий код другого разработчика. Номер редакции 1.9 указывает, что конфликтующее изменение было внесено в версии 1.9 этого файла, упрощая проверку журнальных записей или просто выяснение изменений с помощью `cvs diff'.
Когда вы решите, как именно справиться с конфликтом, уберите маркеры из кода и отредактируйте его.
Система состоит из трех частей: клиентское приложение (GUI или Web), сервер приложений и источник данных (СУБД, XML и т.д.). Идеология системы строится на трех вещах: фактах, метамодели и безопасности. Факты- это так называемые бизнес-объекты из предметной области, с которой будет работать система. Метамодель - это описание этих бизнес-объектов. Безопасность - это описание прав доступа к фактам и метамодели. Диаграмма пакетов системы изображена на рис. 1.1. Следует обратить внимание на функциональную значимость метамодели в этой системе. Обычно при реализации большого количества типов бизнес-объектов (фактов) для каждого факта ставится в соответствие класс. Для того, чтобы повысить степень повторного использования и упростить механизм поддержки большого числа типов фактов в системе, следует для всех фактов выделить всего один или два класса, а структуру фактов описать в метамодели. Таким образом, при изменении структуры фактов не нужно будет менять исходные коды, а достаточно будет поправить информацию в источнике данных, например СУБД, откуда берет данные метамодель.
Рис. 1.1 Диаграмма зависимости между пакетами
Клиентская часть состоит из 10 пакетов. Пакет view отвечает за ее внешний вид. Пакет mediator сопрягает виды приложения. Пакет model отвечает за внутреннее представление данных приложения. Пакет controller содержит классы, работающие с моделью данных приложения. Пакет model.fact представляет структуры фактов, которыми обменивается клиентское приложение с сервером приложений. Пакет model.meta представляет структуры описывающих факты, т.е. метамодель, которыми обменивается клиентское приложение с сервером приложений. Пакет model.security представляет структуры, описывающие безопасность доступа к фактам и метамодели, которыми также обменивается клиентское приложение с сервером приложений. Пакеты source.fact, source.meta и source.security отвечают за взаимодействие между клиентским приложением и сервером приложений и поддерживают между ними обмен фактами (model.fact), метаданными (model.meta) и безопасностью (model.security) не зависимо от используемой разработчиком распределенной технологии.
На самом деле описанная выше проблема отнюдь не определяется особенностью VB - эта общий вопрос для всех современных Windows-приложений. Она является следствием реализации компонентной модели при создании программ. (Речь идет о понимании "компонентов" в широком плане, как любых автономных файлов, а не в конкретной архитектуры типа COM или CORBA). Суть такой модели - использование одних и тех же копий общих компонентов для различных приложений. При этом решаются две очень важные задачи - минимизация объемов программ и повышение управляемости программной системой в целом (в частности, файл с ошибкой нужно заменить в одном месте, а не во всех программах). Первым примером такого глобального компонента является сама операционная система Windows, куда постепенно перетекают многие элементы прикладных программ, например в виде наборов WAPI. Одним из результатов этого стало то, что прикладная программа стала фактически приложением к Windows (вот такая версия смены терминов!), его функциональным расширением, потеряв автономность, которая была ей присуща во времена DOS. Однако "плюсы" не бывают без "минусов" и последние довольно сильно проявляются по мере усложнения Windows-систем. Прежде всего, теоретическая предпосылка об обязательной совместимости версий компонентов "снизу-вверх" на практике реализуется с большим трудом, особенно когда они вообще создаются разными разработчиками (такое редко, но бывает). Тем более известно, что каждая новая версия исправляет старые ошибки, но добавляет новые, которые могут оказаться критичными для уже имеющихся программ. Не очень приятным моментом является рост требований к ресурсам со стороны новых версий компонентов при том, что их новые функции для старых программ не нужны. Разделение программных компонентов на общие и локальные вызывает трудности в решении двух вопросов:
какие компоненты входят в состав данного приложения? какие приложения реально используют данный компонент?
В этой связи представляется, что одним из не очень удачных решений в Windows является автоматическое объявление OCX и многих DLL общими элементами и помещение их в один системный каталог SYSTEM.
Проще всего создать макрос с помошью команды Сервис->Макрос->Начать запись. Все действия пользователя до нажатия кнопки Стоп записываются в макрос и воспроизводятся при запуске этого макроса. Такой способ не позволяет организовывать циклы и выдавать сообщения пользователю, поэтому для написания полноценной программы необходимо отредактировать записанный макрос. Для этого в Word 6.0 и 7.0 необходимо выбрать команду Сервис ->Макрос-> Изменить. (Сервис->Макрос->Редактор VisualBasic в Word97).
Полное описание команд WordBasic поставляется вместе с Word, но не устанавливается по умолчанию. Если Вы не можете отыскать этот раздел в Вашей справочной системе, значит необходимо установить его с дистрибутивного диска Word.
В отличие от других отраслей, разработка программного обеспечения требует, чтобы прикладные модули были скомпилированы и слинкованы с другими зависимыми частями приложения. Всякий раз, когда разработчик хочет использовать в приложении другую логику или новые возможности, для того, чтобы эти изменения вступили в силу, ему или ей необходимо модифицировать и перекомпилировать первичное приложение. В производстве такое ограничение недопустимо. Можете ли вы себе представить, что вам пришлось бы переделывать автомобильный двигатель, если бы вы захотели заменить ваши шины от изготовителя на более совершенные? Это могло бы пролиться золотым дождем на механиков, но чрезмерные эксплуатационные издержки приведут к уменьшению спроса на автомобили, а от этого пострадают все: потребители, производители автомобилей, те же механики. Фактически, одним из основных факторов успеха промышленной революции стала способность взаимозаменяемости деталей машин, т.е. использование компонентов. Сегодня мы не задумываясь заменяем компоненты и добавляем новые принадлежности в наши автомобили. Автомобили ничего "не знают" о шинах, которые они используют. Шины имеют свойства (ширина колеса и пр.). Если свойства у различных шин совпадают, то эти шины взаимозаменяемы. Светильник ничего "не знает" о лампах, которые в нем используются. Если параметры ламп (диаметр завинчивающейся части) удовлетворяют требованиям изготовителя осветительного прибора, то эти лампы взаимозаменяемы. Давно ли индустрия программного обеспечения стала догонять остальную часть мира и строить компоненты, которые понятия не имеют о том, как они будут использоваться ? Для отрасли, которая считается передовой, мы действительно плетемся в хвосте. На первый взгляд, динамически подключаемые библиотеки (DLL) обеспечивают решение указанных выше проблем. Следующая выдуманная история покажет, что это не так. Предположим, вам нужно разработать приложение для компании Acme Gas Tanks. Приложение будет показывать уровень бензина в новом престижном топливном баке Acme на 1000 галлонов.
Инструментарий AWT (Abstract Windowing Toolkit) начал поставляться с самой первой версией Java. Он использует родные для платформ компоненты GUI (т.е. Win32 API для Windows и библиотеку Motif для Unix), обеспечивая таким образом переносную обертку. Это значит, что внешний вид и поведение AWT-программ будет отличаться на различных платформах, потому что именно они занимаются отрисовкой и управлением компонентов GUI. Это противоречит кросс-платформенной философии Java и может быть объяснено тем, что первая версия AWT была разработана за четырнадцать дней.
По этой и другим причинам AWT был дополнен инструментарием Swing. Swing использует AWT (и, следовательно, низкоуровневые библиотеки) только лишь для базовых операций: создания прямоугольных окон, управления событиями и отрисовки графических примитивов. Всем остальным, включая отрисовку компонентов GUI, занимается Swing. Это решает проблему отличающегося внешнего вида и поведения приложений на различных платформах. Но из-за реализации Swing-инструментария средствами Java его производительность оставляет желать лучшего. В результате Swing-программы медлительны не только во время интенсивных вычислений, но и при отрисовке элементов пользовательского интерфейса. Как уже говорилось, ничто не вызывает у пользователей такого раздражения, как большое время отклика интерфейса программ. Странно наблюдать за медлительностью перерисовки Swing -кнопки на современном оборудовании. Хотя с ростом производительности оборудования эта ситуация будет постепенно улучшаться, сложным пользовательским интерфейсам, созданным с помощью Swing, всегда будет свойственна медлительность.
При разработке инструментария Qt был использован тот же самый подход: низкоуровневые библиотеки используются только лишь для базовых операций, а отрисовкой элементов GUI занимается непосредственно Qt. Благодаря этому инструментарий Qt приобретает все преимущества Swing (например, схожесть поведения и внешнего вида приложений на различных платформах), и не имеет проблем, связанных с низкой производительностью, так как разработан на C++ и откомпилирован в машинный код. Интерфейс, созданный с помощью Qt, отличается быстрой работой, и, благодаря использованию кеширования, может быть быстрее интерфейса, разработанного стандартными средствами. Теоретически, оптимизированная не-Qt программа должна быть быстрее аналогичной Qt-программы; но на практике для оптимизации не-Qt программы потребуется больше усилий и мастерства, чем для создания оптимизированной Qt-программы.
И Qt, и Swing поддерживают технику стилей, которая позволяет программам независимо от платформы использовать один из стилей интерфейса. Это становится возможным благодаря тому, что отрисовкой элементов GUI занимаются непосредственно Qt и Swing. Вместе с Qt поставляются стили, которые эмулируют внешний вид Win32, Motif, MacOS X Aqua (в Macintosh-версии), и даже стиль, эмулирующий внешний вид Swing-программ.
В этом случае, так как ваша обработка ошибок определенно лучше, то просто отбросьте чужой вариант, оставив такой код: /* Look up the IP address of the host. */ host_info = gethostbyname (hostname); if (! host_info) { fprintf(stderr, "%s: host not found: %s\n", progname, hostname); exit(1); } sock = socket (PF_INET, SOCK_STREAM, 0);
Теперь протестируйте изменения и зафиксируйте их: $ make gcc -g -Wall -Wmissing-prototypes -lnsl -lsocket httpc.c -o httpc $ httpc GET http://www.cyclic.com HTTP/1.0 200 Document follows Date: Thu, 31 Oct 1996 23:04:06 GMT ... $ httpc GET http://www.frobnitz.com httpc: host not found: www.frobnitz.com $ cvs commit httpc.c
Важно понимать, что именно CVS считает конфликтом. CVS не понимает семантики вашей программы, он обращается с исходным кодом просто как с деревом текстовых файлов. Если один разработчик добавляет новый аргумент в функцию и исправляет все ее вызовы, пока другой разработчик одновременно добавляет новый вызов этой функции, и не передает ей этот новый аргумент, что определенно является конфликтом -- два изменения несовместимы -- но CVS не сообщит об этом. Его понимание конфликтов строго текстуально. На практике, однако, конфликты случаются редко. Обычно они происходят потому, что два человека пытаются справиться с одной и той же проблемой, от недостатка взаимодействия между разработчиками, или от разногласий по поводу архитектуры программы. Правильной распределение задач между разработчиками уменьшает вероятность конфликтов. Многие системы контроля версий позволяют разработчику блокировать файл, предотвращая внесение в него изменений до тех пор, пока его собственные изменения не будут зафиксированы. Несмотря на то, что блокировки уместны в некоторых ситуациях, это не всегда подход лучше, чем подход CVS. Изменения обычно объединяются без проблем, а разработчики иногда забывают убрать блокировку, в обоих случаях явное блокирование приводит к ненужным задержкам. Более того, блокировки предотвращают только текстуальные конфликты -- они ничего не могут поделать с семантическими конфликтами типа вышеописанного -- когда два разработчика редактируют разные файлы. Footnotes Интересно, почему это вообще не -u? Прим.перев. Все же лучше использовать -u. Прим. перев.
Общая компонентная модель
Другими словами, на основе них следует делать стабы (stub) [2].
Сервер приложений состоит из 9 пакетов. Пакеты model.fact, model.meta, model.security такие же, как на стороне клиентского приложения. Они служат value-объектами обмена информацией между сервером приложений и клиентским приложением. Пакеты source.fact, source.meta и source.security на стороне сервера отвечают за взаимодействие между клиентским приложением и сервером приложений. Другими словами, на основе них следует делать скелетоны (skeleton) [2]. Пакет server.datasource отвечает за поддержку разных типов источников данных, в которых хранятся факты. Пакет server.factdao отвечает за взаимодействие с фактами для разных типов источников данных. Пакет server.kernel управляет функционированием сервера приложений, связывая воедино все пакеты серверной части.
В роли источника данных, как уже говорилось, может выступать СУБД или другое решение для доступа и хранения данных. Скорость работы с источником естественно зависит от его типа. В пакете server.factdao скорость можно поднять, например, за счет стратегии кэширования.
Общая проблема модели Windows-приложений
Общие замечания.
Обзор
Во-первых, вы создаете индикатор уровня на основе ActiveX(tm), который имеет три отметки: текущий уровень топлива в баке, минимально возможный безопасный уровень и максимально возможный безопасный уровень. Вы пишете DLL, назвав ее GasTankLevelGetterDLL, которая имеет следующие функции:
long GetLowestPossibleSafeLevel(); long GetHighestPossibleSafeLevel (); long GetCurrentLevel();
Естественно, GasTankLevelGetterDLL поддерживает возможность некоторого устройства непрерывно считывать данные о количестве топлива в новом топливном баке Acme. Ваше приложение работает превосходно и не "глючит".
Пару недель спустя, мистер Ричи Рич ( Richy Rich ) вызывает вас к себе и сообщает, что ваш ActiveX для индикации уровня является самой красивой вещью, которую он когда-либо видeл в своей жизни. Ричи говорит вам, что хочет использовать его для контроля уровня в своем аквариуме на 5000 галлонов. Он заявляет, что индикатор должен показывать те же три уровня, что и для топливного бака. Вы говорите ему, что зайдете к нему завтра, а пока подумаете над его предложением.
На следующий день вы приходите к мысли, называть все DLL, которые реализуют те самые три функции, хотя и с различной внутренней обработкой, одинаково - LevelGetterDLL. Проблема контроля уровня воды в аквариуме мистера Ричи решена. Он проверяет ваше приложение 24 часа в сутки, чтобы убедиться, что его рыбки находятся в полной безопасности. Вы также передаете новую версию LevelGetterDLL Acme. Другие компании связываются с вами на предмет использования вашего ActiveX индикатора уровня. Вы отвечаете им: "Нет проблем! Возьмите эти три функции, назовите вашу DLL LevelGetterDLL, и все готово." Вам необходимо всего лишь один раз перекомпилировать ваше приложение, чтобы оно поддерживало новую версию LevelGetterDLL, но поскольку во всем мире все называют свои DLL одинаково (LevelGetterDLL) и используют одинаковые неизменные три метода, то все работает превосходно, и вам никогда не придется перекомпилировать ваше приложение снова.
Вы возвращаетесь домой, чувствуя себя немножко гением.
На следующий день, открыв The Wall Street Journal , вы обнаруживаете, что Ричи Рич разбился на своем вертолете. По дороге в штаб-квартиру Rich Inc. ему не хватило топлива. Похоже, Ричи был клиентом Acme и запускал оба приложения на своем компьютере одновременно. Приложение 1 было то самое, которое вы разработали с использованием LevelGetterDLL для контроля уровня в его аквариуме. Приложение 2 было сделано по заказу Acme для контроля уровня топлива, в нем использовалась та же версия LevelGetterDLL, которая была установлена на вертолете Ричи. И хотя Ричи запускал оба приложения, Приложение 2 для топливных баков Acme использовало DLL LevelGetterDLL для аквариума и показывало уровни 5000-галлонного аквариума вместо 1000-галлонного топливного бака, поскольку версия для аквариума была установлена на компьютер последней. Ричи ничего не знал о том, что его вертолету не хватит топлива. Rich Inc. подает в суд на Acme, которая, в свою очередь, подает в суд на вас. Другие компании, которым вы посоветовали ваше решение, также подают на вас в суд. Если бы вы использовали Component Object Model (COM), Ричи Рич был бы жив, и вам не пришлось бы садиться на скамью подсудимых.
Правило
Если две или более DLL предоставляют одинаковые функции (immutability), вы можете использовать любую из этих DLL. Однако одно приложение не может использовать сразу несколько DLL, как и не могут одновременно несколько таких DLL находиться на одном и том же компьютере. Технология COM решает эту проблему. Два сервера COM с идентичными интерфейсами (и следовательно методами) могут использоваться двумя различными приложениями и могут находиться на одном и том же компьютере, поскольку они имеют различные идентификаторы CLSID, и, следовательно, различны на бинарном уровне. Кроме того, технически эти два сервера COM взаимозаменяемы.
Отсутствие "взаимозаменяемых деталей" (компонентов) присуще индустрии программных разработок в силу ее относительно молодого возраста.
Однако, подобно индустриальной революции, создавшей независимые детали машин, технология COM реализует это через программные компоненты. Понимая смысл CLSID и неизменности интерфейсов, можно написать законченный plug-in без какого-либо знания о клиенте. Это означает, что Приложение 1 может использовать или Plug-In1 или Plug-In2. Еще лучше, чтобы Приложение 1 могло динамически переключать Plug-In1 и Plug-In2. Проектирование приложений, использующих динамически заменяемые вставки (plug-ins) сделает для программной индустрии то же самое, что сделали детали машин и механизмов для промышленной революции.
Восторгаясь Active Template Library (ATL) и Distributed COM (DCOM), мы постепенно забываем, что лежало в основе появления COM. Способность DCOM использовать удаленный вызов процедур (remote procedure calls, RPC) выстраивать данные (marshaling) воодушевляет ( и, возможно, является одной из причин роста популярности COM за последние 12 месяцев), однако это не главное, почему была разработана технология COM. Главное, ради чего создавалась COM, - предоставить производителям программ возможность встраивать новые функциональные части в существующие приложения без перестраивания этих приложений. Компоненты COM должны быть спроектированы как взаимозаменяемые вставки (plug-ins), независимо от того, является ли компонент COM локально подключаемой DLL или удаленно запускаемым сервером.
Описание AWT, Swing и Qt
Описание бизнес-процессов
С помощью Rapid Developer аналитик проекта может описывать бизнес-логику создаваемой информационной системы в терминах бизнес-процессов. Для этого необходимо на дереве объектов Архитектора приложений выделить необходимый класс и в правом фрейме перейти на страничку "Processes".
Количество бизнес-процессов, реализуемых любым классом не ограничено. Описание бизнес-процесса может быть любой степени сложности. Это может быть обычный текст, помещенный в поле "To do", или специфичная графическая диаграмма (вызывается при нажатии кнопки "Process Architect" для выделенного бизнес-процесса в списке "Process list").
Рис. 5..
Ошибки в системах реального времени
Отмеченные выше методы отладки позволяют выявлять и устранять ошибки следующего характера:
Ошибки в программном обеспечении, влекущие неправильное выполнение задачи (безотносительно времени). Обычные ошибки, обнаруживаемые средствами активной отладки. Эти средства будут рассмотрены в разделе 2. Ошибки в ОСРВ: ошибки планирования, синхронизации и связи.Для отладки, в этом случае, надо использовать один из способов мониторинга. Подробно средства мониторинга описаны в разделе 3. Логические ошибки, связанные с асинхронностью. Пример такого рода ошибок и способы их устранения будут приведены в разделе 4. Ошибки, связанные с тем, что данные задачи были изменены другой задачей. Локализацию ошибок лучше проводить, используя мониторинг, а именно: осуществлять периодический контроль целостности данных, временно запрещать другим задачам доступ к некоторым участкам кода или данных.
Oсновные архитектурные принципы
Основное назначение CORBA и COM - поддержка разработки и развертывания сложных объектно-ориентированных прикладных систем.
Для чего нужны эти модели? Любого отдельно взятого объектно-ориентированного языка недостаточно для написания распределенных вычислительных систем. Очень часто различные компоненты программной системы требуют реализации на разных языках и, возможно, разных аппаратных платформах. С помощью объектных моделей множество объектов приложения, в том числе и на различных платформах, взаимодействуют друг с другом и реализуют бизнес-процессы, создавая видимость единого целого.
Функции CORBA и COM - это функции промежуточного программного обеспечения объектной среды. Для того чтобы обеспечить взаимодействие объектов и их интеграцию в цельную систему, архитектура промежуточного уровня должна реализовать несколько базовых принципов. Независимость от физического размещения объекта. Компоненты программного обеспечения не обязаны находиться в одном исполняемом файле, выполняться в рамках одного процесса или размещаться на одной аппаратной системе. Независимость от платформы. Компоненты могут выполняться на различных аппаратных и операционных платформах, взаимодействуя друг с другом в рамках единой системы. Независимость от языка программирования. Различия в языках, которые используются при создании компонентов, не препятствуют их взаимодействию друг с другом.
CORBA и COM во многом различны, однако сходны в том, каким образом в них достигается реализация этих принципов. Это клиент-серверные технологии, в которых функциональность объекта предоставляется клиенту посредством обращения к абстрактным интерфейсам. Интерфейс определяет набор методов, которые реализуют функции, присущие данному классу объектов. Интерфейс дает клиенту возможность только вызывать тот или иной метод, скрывая от него все детали его реализации.
Рис.1. Механизм вызова удаленной процедуры
Клиент получает доступ к объекту только путем вызова метода, определенного в интерфейсе объекта. Это означает, что реальные действия выполняются в адресном пространстве объекта, возможно, удаленном по отношению к процессу клиента.
Сокрытие деталей реализации и позволяет в конечном итоге добиться слаженного взаимодействия компонентов в независимости от того, где и на какой платформе они реализованы и какой язык программирования для этого использовался. В обеих технологиях взаимодействие между клиентским процессом и сервером объекта, то есть процессом, который порождает и обслуживает экземпляры объекта, использует механизм объектный вариант вызова удаленной процедуры (RPC, remote procedure call). На рис. 1 показана типичная структура RPC - старейшей из технологий промежуточного программного обеспечения. Механизм RPC реализует схему передачи сообщений, в соответствии с которой в распределенном клиент-серверном приложении процедура-клиент передает специальное сообщение с параметрами вызова по сети в удаленную серверную процедуру, а результаты ее выполнения возвращаются в другом сообщении клиентскому процессу. Для того чтобы реализовать эту схему, на стороне клиента и на стороне сервера поддерживаются специальные компоненты, носящие название клиентский и серверный суррогаты (client stub и server stub). Для того чтобы вызвать ту или иную функцию, клиент обращается к клиентскому суррогату, который упаковывает аргументы в сообщение-запрос и передает их на транспортный уровень соединения. Серверный суррогат распаковывает полученное сообщение и в соответствии с переданными аргументами вызывает нужную функцию, или нужный метод объекта, если речь идет об объектном варианте RPC. В СОМ клиентский суррогат называется proxy, а серверный - stub. В CORBA клиентский суррогат не имеет специального названия, а серверный обозначают термином skeleton. Параметры вызова могут формироваться в отличной от серверной языковой и операционной среде, поэтому на клиентский и серверный суррогаты возлагаются функции преобразования аргументов и результатов в универсальное, не зависящее от конкретной архитектуры представление. Тем самым достигается возможность взаимодействия клиента и сервера на различных платформах. Строго говоря, рассуждая о вызове удаленных объектов и используя при этом аббревиатуру СОМ, мы не вполне точны.
Взаимодействие объектов на разных узлах сети реализовано в расширенном варианте этой технологии, Distributed COM (DCOM), который, в свою очередь, базируется на объектном расширении спецификации DCE RPC. DCOM появилась в 1996 году вместе с операционной системой Windows NT 4.0.
Поэтому OMG пошла по пути разработки единых спецификаций, на основе которых компании имели возможность создавать собственные реализации. Рис. 3. Архитектура Common Object Request Broker Architecture Ядром архитектуры CORBA (рис. 3) является брокер объектных запросов (Object Request Broker, ORB). Это объектная шина, по которой в стиле, напоминающем классический механизм RPC, происходит взаимодействие локальных и удаленных объектов. В отличие от СОМ, ORB не опирается непосредственно на механизм RPC, но работает по тем же принципам. Помимо самого вызова метода удаленного объекта, ORB отвечает за поиск реализации объекта, его подготовку к получению и обработке запроса, передачу запроса и доставку результатов клиенту. Кроме того, CORBA включает в себя несколько групп реализаций объектов, а именно прикладные объекты, объектные службы, общие средства и домены. Прикладные объекты (Application Objects) представляют собой реализации объектов для конкретных пользовательских приложений, например, объекты для поддержки специфических бизнес-процессов. Реализации объектов, предоставляющие общие для любой объектно-ориентированной среды возможности, входят в категорию объектных служб (CORBA services): служба имен, служба событий, служба сохранения в долговременной памяти, служба транзакций и т.д. Общие средства (CORBA facilities)- это реализации объектов, необходимые для большого числа приложений, например, поддержка составных документов, потоков заданий и др. В CORBA есть также понятие домена; реализации объектов домена (CORBAdomains) предназначены для приложений вертикальных рынков - здравоохранения, страхования, финансового рынка, производственных отраслей и т.д. С момента появления первой ее версии в октябре 1991 года архитектура CORBA постоянно совершенствуется, чему способствуют строго регламентированные процессы принятия новых стандартов в OMG. Принимаемые стандарты открыты, и любая фирма может присоединиться к консорциуму и предложить свою технологию для стандартизации.
Основные модули Crystal Reports.
Crystal Reports состоит из следующих основных компонентов:
Report Designer (файл CRW.EXE для 16 - разрядной версии и CRW32.EXE для 32- разрядной) - основной модуль, который позволяет разрабатывать отчеты и открывать *.rpt -файлы. Прочие компоненты (перечисленные ниже) играют вспомогательную роль.
Query Designer (CQW.DLL /CQW32.DLL) - инструмент для создания SQL- запросов (описан в третьей статье серии).
Data Dictionary (CDW. EXE / CDW.EXE) - словарь типовых решений для быстрой разработки отчетов. Словари хранятся в файлах *.DC5. Работа со словарями Crystal описана во второй статье серии.
Report Engine (CRPE.DLL / CRPE32.DLL) - API, дающий возможность разработчикам интегрировать отчеты в их собственные приложения. Создав отчет в Report Designer, можно просматривать его в приложениях, используя Report Engine. С помощью Report Engine можно во время выполнения устанавливать условия группировки данных, стили графиков, местоположение БД и многое другое.