Main.asm для mycall (ассемблер)
Это основной файл приложения MyCall на ассемблере. Этот файл в текстовом формате вместе со всеми остальными файлами, необходимыми для компиляции приложения MyCall, содержится в zip-файле mycallab.zip (15913 байт). Имеется также Инструкция программиста.
Для получения комментариев щелкaйте по тексту или пользуйтесь групповым управлением:
if(dhtml){document.write("Все комментарии: [+][-] Открывать: [несколько]");}
;Включаемые файлы:
;@struct.inc - файл структурных макросов
;windows.inc - файл заголовков win32
;main.inc - файл заголовков приложения MyCall
include @struct.inc
include windows.inc
include main.inc
;ГЛАВНАЯ ФУНКЦИЯ ПРИЛОЖЕНИЯ
;///////////////////////////////////////////////////////////// WinMain
.data?
mw_class WNDCLASSEX{}
loop_message MSG{}
win_dim RECT{}
.const
mw_class_name db "MainWindowClass",0
my_message_name db "MyCallMessage",0
.code
WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show
mov dat_buffer,0
mov online,FALSE
;Получение дескриптора экземпляра приложения
;Необходимое действие, так как предполагается компиляция приложения без
;подключения runtime-библиотеки. Подробнее...
invoke GetModuleHandleA,NULL
mov hinst,eax
;Создание главного окна
;Обычное действие, с которого начинаются большинство приложений.
;Единственное отличие в том, что в качестве главного окна в MyCall
;используется окно диалога, описанного в файле ресурсов
;Регистрируется класс главного окна:
mov mw_class.cbSize,sizeof(WNDCLASSEX)
mov mw_class.style,NULL
mov mw_class.lpfnWndProc,offset superprocedure
mov mw_class.cbClsExtra,0
mov mw_class.cbWndExtra,DLGWINDOWEXTRA
mov eax,hinst
mov mw_class.hInstance,eax
invoke LoadIconA,eax,103
mov mw_class.hIcon,eax
mov mw_class.hIconSm,NULL
invoke LoadCursorA,NULL,IDC_ARROW
mov mw_class.hCursor,eax
mov mw_class.hbrBackground,COLOR_WINDOW
mov mw_class.lpszMenuName,NULL
mov mw_class.lpszClassName,offset mw_class_name
@if(ecx==3)
mov ecx,0
@endif
@endif
@if(ecx==0)
@if(ebx==0)
@if(byte ptr[esi]!=0)
@if(byte ptr[esi]!=1)
@push ecx,edx,ebx,esi
invoke SendMessageA,conn_window,CB_ADDSTRING,0,esi
@pop ecx,edx,ebx,esi
inc edx
@if(edx>=MAX_CON)
@break
@endif
@endif
@endif
@endif
@endif
inc ebx
inc esi
@endw
;По данным файла mycall. ini устанавливается прошлая позиция списка conn_window
@if(current_con>=edx)
mov current_con,0
@endif
invoke SendMessageA,conn_window,CB_SETCURSEL,current_con,0
;По состоянию списка conn_window формируются остальные списки
call change_con
;ЦИКЛ ОЖИДАНИЯ СООБЩЕНИЙ И ЗАВЕРШЕНИЕ ПРИЛОЖЕНИЯ
;Неотъемлемый элемент приложений для Windows. В MyCall никаких особенностей не имеет
;Поскольку MyCall собирается без runtime-библиотеки, для завершения работы
;обязательно использовать функцию ExitProcess. Подробнее...
msg_loop:
invoke GetMessageA,offset loop_message,NULL,0,0
@if(!eax)
invoke ExitProcess,loop_message.wParam
@endif
invoke TranslateMessage,offset loop_message
invoke DispatchMessageA,offset loop_message
jmp msg_loop
bad:
invoke fatal,eax
invoke ExitProcess,EXIT_COMMON_ERROR
WinMain ENDP
;ОКОННАЯ ПРОЦЕДУРА
;Стандартный элемент приложений для Windows. В MyCall особенностей не имеет.
;///////////////////////////////////////////////////////////// Оконная процедура
.data?
win_pos RECT{}
.code
superprocedure PROC window_from,message,w_param,l_param
mov eax,message
;Обработка сообщения WM_COMMAND, передаваемого элементами управления диалога
@if(eax==WM_COMMAND)
mov eax,w_param
mov cl,16
shr eax,cl
;Обработка уведомления CBN_SELCHANGE, передаваемого списками при изменении позиции:
;1000: список соединений conn_window. Изменяет содержание phon_window и user_window
;1001: список телефонов phon_window. Устанавливает новый телефон для текущего соединения con_phone[current_con]
;1002: список логинов user_window. Устанавливает новый логин для текущего соединения con_user[current_con]
@if(eax==CBN_SELCHANGE)
mov eax,w_param
and eax,0ffffh
@if(ax==1000)
mov eax,conn_window
and current_con,0ffh
mov esi,offset current_con
@elseif(ax==1001)
mov eax,phon_window
mov esi,offset con_phone
add esi,current_con
@elseif(ax==1002)
mov eax,user_window
mov esi,offset con_user
add esi,current_con
@else
jmp sp_nok
@endif
push esi
invoke SendMessageA,eax,CB_GETCURSEL,0,0
pop esi
@if(eax==CB_ERR)
xor eax,eax
@endif
mov [esi],al
call change_con
jmp sp_ok
@endif
; Обработка уведомления BN_CLICKED, передаваемого кнопкой при клике:
;в состоянии online=TRUE прекращает дозвон (разрывает соединение) и включает списки
;в состоянии online=FALSE отключает списки и начинает дозвон
@if(eax==BN_CLICKED)
mov eax,w_param
and eax,0ffffh
@if(eax==1003)
@if(online)
invoke EnableWindow,butt_window,FALSE
call ras_hangup
invoke EnableWindow,butt_window,TRUE
push FALSE
call disable_controls
@else
push TRUE
call disable_controls
call ras_dial
@endif
jmp sp_ok
@endif
@endif
jmp sp_nok
;Обработка сообщения WM_USER, используемого в MyCall нитью монитора разрыва.
;Обнаружив факт разрыва соединения, нить монитора передает WM_USER. Если был коннект,
;то включаются списки, в противном случает выполняется повторный дозвон
@elseif(eax==WM_USER)
call ras_hangup
@if(l_param)
push FALSE
call disable_controls
@else
call ras_dial
@endif
jmp sp_ok
;Обработка сообщения WM_MOVE: запоминание новой позиции окна
;для последующей записи ее в файл mycall.ini
@elseif(eax==WM_MOVE)
invoke GetWindowRect,main_window,offset win_pos
@if(eax)
push win_pos.left
pop main_win_left
push win_pos.top
pop main_win_top
@else
mov eax,l_param
and eax,0ffffh
mov main_win_left,eax
mov eax,l_param
mov cl,16
rcr eax,cl
and eax,0ffffh
mov main_win_top,eax
@endif
jmp sp_ok
;При завершении работы приложения записать файл mycall.ini,
;и освободить память
@elseif(eax==WM_DESTROY)
call save_ini
@if(dat_buffer)
invoke GlobalFree,dat_buffer
@endif
invoke PostQuitMessage,EXIT_NORMAL
jmp sp_ok
; При закрытии главного окна заблокировать кнопку
;и разорвать соединение
@elseif(eax==WM_CLOSE)
invoke EnableWindow,butt_window,FALSE
@if(online)
call ras_hangup
@endif
jmp sp_nok
;Обработка глобального оконного сообщения, используемого для
;взаимодействия экземпляров приложения. Получив это сообщение,
;экземпляр, запущенный первым, сообщает дескриптор своего главного окна.
;Сообщение, полученное вторым, принимает от первого дескриптор его главного окна,
;выводит его на первый план и завершается. Подробнее...
@if(eax==my_message)
mov ebx,w_param
@if(ebx!=main_window)
@if(!l_param)
invoke SendMessageA,HWND_BROADCAST,eax,main_window,1
@else
invoke SetForegroundWindow,w_param
invoke ExitProcess,EXIT_OVERLOADED
@endif
@endif
jmp sp_ok
@endif
;Возврат необработанных сообщений системе
sp_nok:
invoke DefWindowProcA,window_from,message,w_param,l_param
ret
;Завершение оконной процедуры для обработанных сообщений
sp_ok:
xor eax,eax
ret
superprocedure ENDP
;ЗАГРУЗКА ФАЙЛА ИНИЦИАЛИЗАЦИИ
;Файл mycall.ini хранит состояние списков и положение главного окна на момент
;завершения предыдущего сеанса работы приложения
;///////////////////////////////////////////////////////////// Загрузка файла инициализации
.data?
ini_filedd ?
buffer db INI_FILE_LENGTH dup(?)
bytes_readwrite dd ?
.const
ini_file_name db "mycall.ini",0
.code
load_ini PROC
;Инициализация глобальных переменных
mov main_win_left,MAIN_WIN_DEFAULT_LEFT
mov main_win_top,MAIN_WIN_DEFAULT_TOP
mov current_con,0
mov ebx,MAX_CON
@while(ebx)
dec ebx
mov [con_phone+ebx],0
mov [con_user+ebx],0
@endw
;Попытка загрузки файла mycall.ini
;В случае неудачи используются значения по умолчанию
invoke CreateFileA,offset ini_file_name,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
@if(eax!=INVALID_HANDLE_VALUE)
;Чтение файла mycall.ini
push eax
invoke ReadFile,eax,offset buffer,INI_FILE_LENGTH,offset bytes_readwrite,NULL
@if(eax)
;Приведение позиции окна к фактическим размерам экрана и запоминание
xor edx,edx
mov dx,[word ptr buffer]
push edx
invoke GetSystemMetrics,SM_CXSCREEN
sub eax,10
pop edx
@if(edx<=eax)
mov main_win_left,edx
@endif
xor edx,edx
mov dx,[word ptr buffer+2]
push edx
invoke GetSystemMetrics,SM_CYSCREEN
sub eax,10
pop edx
@if(edx<=eax)
mov main_win_top,edx
@endif
;Запоминание позиций списков телефонов и логинов
xor edx,edx
mov dl,[byte ptr buffer+4]
mov current_con,edx
xor ecx,ecx
xor ebx,ebx
@while(ecx<MAX_CON)
mov ax,[word ptr buffer+5+ebx]
mov [con_phone+ecx],al
mov [con_user+ecx],ah
add ebx,2
inc ecx
@endw
@endif
pop eax
invoke CloseHandle,eax
@endif
ret
load_ini ENDP
;СОХРАНЕНИЕ ФАЙЛА ИНИЦИАЛИЗАЦИИ mycall.ini
; В файле сохраняются положение главного окна на экране и позиции
;списков на момент завершения работы приложения
;============================================================= Сохранение файла инициализации
save_ini PROC
invoke CreateFileA,offset ini_file_name,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL
@if(eax!=INVALID_HANDLE_VALUE)
push eax
mov eax,main_win_left
mov [word ptr buffer],ax
mov eax,main_win_top
mov [word ptr buffer+2],ax
mov eax,current_con
mov [buffer+4],al
xor ecx,ecx
xor ebx,ebx
@while(ecx<MAX_CON)
mov al,[con_phone+ecx]
mov ah,[con_user+ecx]
mov [word ptr buffer+5+ebx],ax
add ebx,2
inc ecx
@endw
pop eax
push eax
invoke WriteFile,eax,offset buffer,INI_FILE_LENGTH,offset bytes_readwrite,NULL
pop eax
invoke CloseHandle,eax
@endif
ret
save_ini ENDP
;ЗАГРУЗКА ФАЙЛА ДАННЫХ mycall.dat
;Файл содержит имена соединений, телефоны и логины
;///////////////////////////////////////////////////////////// Загрузка файла данных
.data?
dat_file dd ?
dat_file_size dd ?
.const
dat_file_name db "mycall.txt",0
.code
load_data PROC
;Открытие файла, подготовка буфера для него и считывание файла в буфер
invoke CreateFileA,offset dat_file_name,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
@if(eax==INVALID_HANDLE_VALUE)
mov eax,FATAL_DAT_FILE_OPEN
jmp ld_out
@endif
mov dat_file,eax
invoke GetFileSize,eax,NULL
@if(eax==0ffffffffh)
mov eax,FATAL_DAT_FILE_SIZE
jmp ld_out
@endif
mov dat_file_size,eax
add eax,3
invoke GlobalAlloc,GMEM_FIXED,eax
@if(!eax)
mov eax,FATAL_DAT_BUF_ALLOC
jmp ld_out
@endif
mov dat_buffer,eax
invoke ReadFile,dat_file,eax,dat_file_size,offset bytes_readwrite,NULL
@if(!eax)
mov eax,FATAL_DAT_FILE_READ
jmp ld_out
@endif
;Замена слэшей, CR, LF и пробелов на 0h, добавление в конец 1h
xor ebx,ebx
mov esi,dat_buffer
@while(ebx<dat_file_size)
mov al,byte ptr [esi][ebx]
xor edx,edx
@if(al==0dh)
inc edx
@elseif(al==0ah)
inc edx
@elseif(al==' ')
inc edx
@elseif(al=='/')
inc edx
@endif
@if(edx)
mov byte ptr [esi][ebx],0
@endif
inc ebx
@endw
mov word ptr [esi][ebx],0
add ebx,2
mov byte ptr [esi][ebx],1
xor eax,eax
ld_out:
push eax
invoke CloseHandle,dat_file
pop eax
ret
load_data ENDP
;ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
;Используются при разборе содержимого буфера данных:
;skip_nz - пропуск всех символов, пока не встретится 00h
;skip_nz2 - пропуск всех символов, пока не встретится 0000h
;///////////////////////////////////////////////////////////// Пропуски
skip_nz PROC
@while(byte ptr[esi]!=0)
inc esi
@endw
nc esi
ret
skip_nz ENDP
;=============================================================
skip_nz2 PROC
@while(word ptr[esi]!=0)
inc esi
@endw
add esi,2
ret
skip_nz2 ENDP
;ИЗМЕНЕНИЕ СОЕДИНЕНИЯ
;Функция вызывается при выборе в списке соединений нового соединения
;///////////////////////////////////////////////////////////// Изменение соединения
change_con PROC
;Установка указателя на запись текущего соединения
xor edx,edx ;sub_string
xor ecx,ecx ;cur_con
mov esi,dat_buffer
@while(byte ptr[esi]!=1)
@if(ecx==current_con)
@break
@endif
@if(word ptr[esi]==0)
inc edx
@if(edx==3)
inc ecx
xor edx,edx
@endif
inc esi
@endif
inc esi
@endw
call skip_nz2
;Формирование списка телефонов для данного соединения и установка его позиции
push esi
invoke SendMessageA,phon_window,CB_RESETCONTENT,0,0
pop esi
xor edx,edx;number
@while(byte ptr[esi]!=0)
@push esi,edx
invoke SendMessageA,phon_window,CB_ADDSTRING,0,esi
@pop esi,edx
inc edx
call skip_nz
@endw
inc esi
mov ebx,current_con
@if(con_phone[ebx]>=dl)
mov byte ptr con_phone[ebx],0
@endif
xor eax,eax
mov al,con_phone[ebx]
invoke SendMessageA,phon_window,CB_SETCURSEL,eax,esi
; Формирование списка логинов для данного соединения и установка его позиции
invoke SendMessageA,user_window,CB_RESETCONTENT,0,0
xor edx,edx
@while(byte ptr[esi]!=0)
@push esi,edx
invoke SendMessageA,user_window,CB_ADDSTRING,0,esi
@pop esi,edx
inc edx
call skip_nz
call skip_nz
@endw
mov ebx,current_con
@if(con_user[ebx]>=dl)
mov byte ptr con_user[ebx],0
@endif
xor eax,eax
mov al,con_user[ebx]
invoke SendMessageA,user_window,CB_SETCURSEL,eax,esi
ret
change_con ENDP
;АВАРИЙНОЕ ЗАВЕРШЕНИЕ ПРИЛОЖЕНИЯ
;Вызывается в случае, когда продолжение работы приложения невозможно
;///////////////////////////////////////////////////////////// Фатальный аборт
.const
fatal_caption db "MyCall Error",0
fatal_txt db "Can't register main window class",0
db "Can't create main window",0
db "Can't open mycall.txt",0
db "Can't get size of mycall.txt",0
db "Can't allocate memory for mycall.txt",0
db "Can't read mycall.txt",0
db "Remote Access Service fatal error",0
.code
fatal PROC fatal_code
mov esi,offset fatal_txt
@while(fatal_code)
@while(byte ptr [esi])
inc esi
@endw
inc esi
dec fatal_code
@endw
invoke MessageBoxA,NULL,esi,offset fatal_caption,MB_OK OR MB_ICONERROR
ret
fatal ENDP
;В(Ы)КЛЮЧЕНИЕ ОРГАНОВ УПРАВЛЕНИЯ
;Меняет надпись на кнопке и активизирует списки в зависимости от того,
;находится приложение в режиме выбора или в режиме соединения
;///////////////////////////////////////////////////////////// Выключение органов управления
.const
ec_txt0 db "Call",0
ec_txt1 db "HangUp",0
.code
disable_controls PROC disable
mov eax,win_height
@if(disable)
add eax,12
@endif
invoke SetWindowPos,main_window,NULL,NULL,NULL,win_width,eax,SWP_NOMOVE OR SWP_NOZORDER
@if(disable)
mov eax,offset ec_txt1
@else
mov eax,offset ec_txt0
@endif
invoke SendMessageA,butt_window,WM_SETTEXT,0,eax
mov eax,disable
xor eax,1h
push eax
invoke EnableWindow,conn_window,eax
pop eax
push eax
invoke EnableWindow,phon_window,eax
pop eax
invoke EnableWindow,user_window,eax
ret
disable_controls ENDP
;ДОЗВОН
;///////////////////////////////////////////////////////////// Дозвон
.data?
ras_dial_params RASDIALPARAMS{}
.code
ras_dial PROC
;Подготовка структуры RASDIALPARAMS
mov ras_dial_params.dwSize,sizeof(RASDIALPARAMS)
mov ras_dial_params.szCallbackNumber,0
mov ras_dial_params.szDomain,0
mov esi,dat_buffer
xor ebx,ebx
@while(ebx!=current_con)
xor ecx,ecx
@while(ecx<3)
call skip_nz2
inc ecx
@endw
inc ebx
@endw
mov ras_dial_params.szEntryName,0
push esi
invoke lstrcpy,offset ras_dial_params.szEntryName,esi
pop esi
call skip_nz
mov ras_dial_params.szPhoneNumber,0
push esi
invoke lstrcpy,offset ras_dial_params.szPhoneNumber,esi
pop esi
call skip_nz
dec esi
@while(byte ptr[esi]==0)
inc esi
@endw
xor ecx,ecx
mov ebx,current_con
@while(con_phone[ebx]!=cl)
call skip_nz
inc ecx
@endw
push esi
invoke lstrcat,offset ras_dial_params.szPhoneNumber,esi
pop esi
call skip_nz2
xor ecx,ecx
mov ebx,current_con
@while(con_user[ebx]!=cl)
call skip_nz
call skip_nz
inc ecx
@endw
mov ras_dial_params.szUserName,0
push esi
invoke lstrcat,offset ras_dial_params.szUserName,esi
pop esi
call skip_nz
mov ras_dial_params.szPassword,0
push esi
invoke lstrcat,offset ras_dial_params.szPassword,esi
pop esi
;Дозвон
mov ras_conn,0
invoke RasDialA,0,0,offset ras_dial_params,0,ras_dial_func,offset ras_conn
@if(eax)
call ras_hangup
invoke fatal,FATAL_RAS
invoke ExitProcess,EXIT_RAS_ERROR
@else
mov online,TRUE
@endif
ret
ras_dial ENDP
;ФУНКЦИЯ КОНТРОЛЯ СОСТОЯНИЯ СОЕДИНЕНИЯ
;============================================================= RAS Callback function
.const
ras_state_text db "OpenPort",0
db "PortOpened",0
db "ConnectDevice",0
db "DeviceConnected",0
db "AllDevicesConnected",0
db "Authenticate",0
db "AuthNotify",0
db "AuthRetry",0
db "AuthCallback",0
db "AuthChangePassword",0
db "AuthProject",0
db "AuthLinkSpeed",0
db "AuthAck",0
db "ReAuthenticate",0
db "Authenticated",0
db "PrepareForCallback",0
db "WaitForModemReset",0
db "WaitForCallback",0
db "Projected",0
db "StartAuthentication",0
db "CallbackComplete",0
db "LogonNetwork",0
db "SubEntryConnected",0
db "SubEntryDisconnected",0
db "Interactive",0
db "RetryAuthentication",0
db "CallbackSetByCaller",0
db "PasswordExpired",0
db "Connected",0
db "Disconnected",0
db "Unknown state",0
.data?
ras_monitor_id dd ?
.code
ras_dial_func PROC PUBLIC type_of_event,rasconnstate,ras_error
push esi
@if(online)
;Показ состояния соединения в строке статуса
mov edx,rasconnstate
@if(edx>=RASCS_Connected)
sub edx,RASCS_Connected-28
@else
@if(edx>=RASCS_Interactive)
sub edx,RASCS_Interactive-24
@endif
@endif
@if(edx>30)
mov edx,30
@endif
push edx
mov esi,offset ras_state_text
@while(edx)
call skip_nz
dec edx
@endw
invoke SendMessageA,stat_window,WM_SETTEXT,0,esi
pop edx
;Если соединение установлено - запуск нити контроля
@if(rasconnstate==RASCS_Connected)
invoke CreateThread,NULL,0,offset ras_monitor_func,0,0,offset ras_monitor_id
; Если соединение не установлено - передача сообщения WM_USER для
;повтора дозвона
@elseif(rasconnstate==RASCS_Disconnected)
push edx
invoke PostMessageA,main_window,WM_USER,0,0
pop edx
@endif
@endif
pop esi
ret
ras_dial_func ENDP
;НИТЬ МОНИТОРА РАЗРЫВА
; Каждые 200 мс опрашивает состояние установленного соединения.
;При обнаружении факта разъединения посылает сообщение WM_USER
;и завершается
;============================================================= Нить монитора разрыва
.data?
ras_connect_status RASCONNSTATUS{}
.code
ras_monitor_func PROC PUBLIC param
rm_loop:
@if(online)
mov ras_connect_status.dwSize,sizeof(RASCONNSTATUS)
invoke RasGetConnectStatusA,ras_conn,offset ras_connect_status
@if(!eax)
@if(ras_connect_status.rasconnstate!=RASCS_Disconnected)
invoke Sleep,200
jmp rm_loop
@endif
@endif
@endif
invoke PostMessageA,main_window,WM_USER,0,1
ret
ras_monitor_func ENDP
;ПОЛОЖИТЬ ТРУБКУ
;Если происходит дозвон, или соединение установлено,
;дает команду на разрыв и ожидает, когда RAS ее выполнит
;///////////////////////////////////////////////////////////// Положить трубку
ras_hangup PROC
mov online,FALSE
@if(ras_conn)
invoke RasHangUpA,ras_conn
rh_loop:
mov ras_connect_status.dwSize,sizeof(RASCONNSTATUS)
invoke RasGetConnectStatusA,ras_conn,offset ras_connect_status
@if(eax!=ERROR_INVALID_HANDLE)
invoke Sleep,0
jmp rh_loop
@endif
@endif
mov ras_conn,0
invoke Sleep,1000
ret
ras_hangup ENDP
br>
;#############################################################
end