Основы программирования на 32-битном ассемблере

 

Введение


Целью курсовой работы является изучение основ программирования на 32-битном ассемблере, при помощи которого необходимо разработать программу, демонстрирующую работу одного из разделов ОС WINDOWS.

В данном отчете отражена работа с потоками и процессамиWINDOWS, рассмотрены методы их создания, основанные на API-функциях. На основании описанных функций разработана программа.


1. Теоретическое обоснование


.1 Основные сведения


Основные положения программирования в ОС WINDOWS:

·Программирование в Windows основывается на использовании функций API (Application Program Interface, т.е. интерфейс программного приложения). Их количество достигает двух тысяч. Программа в значительной степени состоит из таких вызовов. Все взаимодействие с внешними устройствами и ресурсами операционной системы происходит посредством таких функций.

·Список функций API и их описание перечислен в файле WIN32.HLP, который поставляется, например, с пакетом Borland C++.

·Главным элементом программы в среде Windows является окно. Для каждого окна определяется своя процедура обработки сообщений.

·Окно может содержать элементы управления: кнопки, списки, окна редактирования и др. Эти элементы, по сути, также являются окнами, но обладающими особыми свойствами. События, происходящие с этими элементами (и самим окном), приводят к приходу сообщений в процедуру окна.

·Операционная система Windows использует линейную модель памяти. Другими словами, всю память можно рассматривать как один сегмент. Для программиста на языке ассемблера это означает, что адрес любой ячейки памяти будет определяться содержимым одного 32-битного регистра, например EBX.

·Следствием пункта 5 является то, что фактически нет ограничений в объеме данных, кода или стека (объеме локальных переменных). Выделение в тексте программы сегмента кода и сегмента данных является теперь простой формальностью, улучшающей читаемость программы.

·Операционная система Windows является многозадачной средой.

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


1.2 Основы вызова API-функций


Функции API в ассемблере вызываются способом, схожим с вызовом в языках высокого уровня. Для этого все передаваемые параметры передаются в стек в обратном порядке.

Например, функция:


intMessageBox ( HWNDhWnd, LPCTSTRlpText, LPCTSTRlpCaption, UINTuType );


будет вызвана в ассемблере по следующему шаблону:


includelib user32.libMessageBoxA@16:NEAR

МВ_ОК equ 0 DB "Неверный ввод! ",0 DB "Сообщение об ошибке.",0

HW DWORD ?МВ_ОК OFFSET STR1 OFFSET STR2 HW MessageBoxA@16


В таком вызове API функции MessageBox добавляется символ А, указывающий на ANSI-стандарт, символ @ в роли разделителя и число N в конце, обозначающий количество байт информации, которая передается в функцию через стек.

На замену стандартному вызову при помощи команды CALL в компиляторе MASM добавлена команда INVOKE. Вызовфункции:


PUSH par1par2par3par4NAME_PROC@N


Принимает вид:


INVOKE NAME_PROC, par4, par3, par2, par1


При этом функция объявляется в виде:


PROC PROTO :DWORD, :DWORD, :DWORD, :DWORD


Тогда код предыдущего примера выглядит следующим образом:


includelib user32.libPROTO :DWORD, :DWORD, :DWORD, :DWORD,

МВ_ОК equ 0 DB "Неверный ввод! ",0 DB "Сообщение об ошибке.",0

HW DWORD ?MessageBoxA,HW,OFFSET STR1,OFFSET STR2,MB_OK

Аналогичным образом задаются структуры. Так, структуре:


typedef struct tagMSG { // msghwnd;message;wParam;lParam;time;pt;

} MSG;

соответствуеткод:STRUCDD ?DD ?DD ?DD ?DD ?DD ?


1.3 Основы программирования в операционной системе Windows


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

·Регистрация класса окон

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

·Цикл обработки очереди сообщений

·Процедура главного окна

Регистрация класса окон. Регистрация класса окон осуществляется с помощью функции RegisterClassA, единственным параметром которой является указатель на структуру WNDCLASS, содержащую информацию об окне.

Создание окна. На основе зарегистрированного класса с помощью функции CreateWindowExA (или CreateWindowA) можно создать экземпляр окна. Как можно заметить, это весьма напоминает объектную модель программирования.

Цикл обработки очередей сообщений. Функция GetMessage() "отлавливает" очередное сообщение из ряда сообщений данного приложения и помещает его в структуру MSG.

Что касается функции TranslateMessage, то ее компетенция касается сообщений WM_KEYDOWN и WM_KEYUP, которые транслируются в WM_CHAR и WM_DEADCHAR, а также WM_SYSKEYDOWN и WM_SYSKEYUP, преобразующиеся в WM_SYSCHAR и WM_SYSDEADCHAR. Смысл трансляции заключается не в замене, а в отправке дополнительных сообщений. Так, например, при нажатии и отпускании алфавитно-цифровой клавиши в окно сначала придет сообщение WM_KEYDOWN, затем WM_KEYUP, а затем уже WM_CHAR.

Как можно видеть, выход из цикла ожиданий имеет место только в том случае, если функция GetMessage возвращает 0. Это происходит только при получении сообщения о выходе (сообщение WM_QUIT). Таким образом, цикл ожидания играет двоякую роль: определенным образом преобразуются сообщения, предназначенные для какого-либо окна, и ожидается сообщение о выходе из программы.


MSG_LOOP:GetMessageA, OFFSET MSG,0,0,0AX, 0END_LOOPTranslateMessage,OFFSET MSGDispatchMessageA,OFFSET MSGMSG_LOOP_LOOP:ExitProcess,[MSG.MSWPARAM]

_ERR:END_LOOP

Процедура главного окна. Описывается следующим образом:

WNDPROC PROCEBPEBP, ESP ; теперь EBP указывает на вершину стека

PUSH EBXESIEDIDWORD PTR [EBP+14H]; LPARAM (lParam)DWORD PTR [EBP+10H]; WPARAM (wParam)DWORD PTR [EBP+0CH]; MES (message)DWORD PTR [EBP+08H]; HWND (hwnd)DefWindowProcA@16EDIESIEBXEBP16

WNDPROC ENDP


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

Пример создания диалогового окна без использования файла ресурсов:

·Опишем прототип

PROTO :DWORD,:DWORD,:DWORD,:DWORD


·Опишем диалоговое окно и его компоненты:


Dialog "Scrolling Caption in MASM32 Dialog", \

"MS Sans Serif",10, \_OVERLAPPED or \_SYSMENU or DS_CENTER, \

, \

,50,200,100, \


"Start thread",WS_TABSTOP,150,5,40,13,IDOK"Turn off", WS_TABSTOP, 150,20,40,13,IDSTOP"Cancel",WS_TABSTOP,150,35,40,13,IDCANCEL"MASM32 Dialog",SS_LEFT,5,5,60,9,100

·ВызовокнапроисходитспомощьюCallModalDialog: CallModalDialog hInstance,0,DlgProc,NULL

Где DlgProc - функция обработчика.


.4 Многозадачное программирование


Под процессом будем понимать объект, создаваемый операционной системой Windows обычно при загрузке исполняемого модуля и получающий в единоличное пользование:

1.Виртуальную память, выделяемую для него операционной системой.

2.Дескрипторы открываемых им файлов.

.Список загруженных им в его собственную память динамических модулей (DLL).

.Созданные им подпроцессы или потоки, исполняемые независимо друг от друга, в собственной памяти процесса.

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

Ваше приложение может создавать процессы, запустив ту или иную ЕХЕ-программу, которые будут работать независимо от основного приложения. Одновременно Ваше приложение может при необходимости удалить запущенное им приложение из памяти. Запустить приложение (создать процесс) можно при помощи функции CreateProcess. Сейчас мы дадим описание этой функции. Ниже объясняются ее параметры.

·1-й параметр - указывает на имя запускаемой программы. Имя может содержать полный путь к программе.

·2-й параметр - его значение зависит от того, является первый параметр NULL (0) или нет. Если первый параметр указывает на строку, то данный параметр трактуется как командная строка запуска (без имени программы). Если первый параметр равен NULL, то данный параметр рассматривается как командная строка, первый элемент которой представляет собой имя программы. Если путь к программе не указан, то функция CreateProcess осуществляет поиск программы по определенному алгоритму:

1.Поиск в каталоге, откуда была запущена программа.

2.Поиск в текущем каталоге.

.Поиск в системном каталоге (можно получить через GetSystemDirectory). Обычно системным каталогом является C:\WINDOWS\SYSTEM.

.Поиск в каталоге Windows (можно получить через GetWindowsDirectory). Обычно этим каталогом является C:\WINDOWS.

.Поиск в каталогах, перечисленных в параметре PATH окружения.

·3-й и 4-й параметры. Используются для задания атрибутов доступа порождаемого процесса. Обычно полагают равным 0.

·5-й параметр. Если этот параметр 0, то порождаемый процесс не наследует дескрипторы порождающего процесса, в противном случае порождаемый процесс наследует дескрипторы.

·6-й параметр. Может менять свойства порождаемого процесса. Если параметр равен нулю, то свойства задаются по умолчанию. В силу большого количества значений этого параметра, мы отсылаем желающих к соответствующим справочным руководствам.

·7-й параметр. Является указателем на буфер, содержащий параметры среды. Если параметр равен 0, то порождаемый процесс наследует параметры среды порождающего процесса.

·8-й параметр. Задает текущее устройство и каталог для порождаемого процесса. Если параметр равен NULL, порождаемый процесс наследует текущее устройство и каталог порождающего процесса.

·9-й параметр. Представляет указатель на структуру, которая содержит информацию об окне создаваемого процесса. Ниже будут рассмотрены поля этой структуры.

·10-й параметр. Указывает на структуру, заполняемую при выполнении запуск приложения. Вот эта структура:


·PROCINF STRUC

·hProcess DD ? ; дескриптор созданного процесса.

·hThread DD ? ; дескриптор главного потока нового процесса.

·Idproc DD ? ; идентификатор созданного процесса.

·idThr DD ? ; идентификатор главного потока нового процесса.

PROCINF ENDS


Основное отличие дескриптора от идентификатора заключается в том, что дескриптор уникален лишь в пределах данного процесса, идентификатор же является глобальной величиной. Посредством идентификатора может быть найдена база данных текущего процесса. У читателя, я думаю, сразу возникнет вопрос: а чем дескриптор приложения, который мы получаем при помощи функции GetModuleHandle, от только что упомянутых величин? Дескриптор приложения, или дескриптор модуля есть величина локальная, т.е. действующая в пределах данного процесса и, как правило, равная адресу загрузки модуля в виртуальное адресное пространство. Дескриптор модуля имеется у любого модуля, загруженного в память, в том числе и у подчиненных DLL-библиотек.

Рассмотрим теперь структуру, на которую указывает 9-й параметр функции CreateProcess. Вот эта структура:


STARTUP STRUCDD 0DD 0DD 0DD 0DD 0DD 0DD 0DD 0DD 0DD 0DD 0DD 0DW 0DW 0DD 0DD 0DD 0DD 0ENDS


Итак, разберем смысл полей этой структуры.- размер данной структуры в байтах. Заполняется обязательно.- резерв, должно быть равно нулю.- имя рабочего стола (и рабочей станции). Имеет смысл только для Windows NT.- название окна для консольных приложений, создающих свое окно. Для остальных приложений должно быть равно 0.- координата X левого верхнего угла окна.- координата Y левого верхнего угла окна.- размер окна по X.- размер окна по Y.- размер буфера консоли по X.- размер буфера консоли по Y.- начальный цвет текста. Имеет значение только для консольных приложений.- флаг значения полей. Вот значение этого флага.


Макро-значение флагаЗначение константыСмысл значенияSTARTF_USESHOWWINDOW1hРазрешить поле dwShowWindowSTARTF_USESIZE2hРазрешить dwXSize и dwYSizeSTARTF_USEPOSITI0N4hРазрешить dwX и dwYSTARTF_USECOUNTCHARS8hРазрешить dwXCountChars и dwYCountCharsSTARTF_USEFILLATTR1BUTE10hРазрешить dwFillAttributeSTARTF_FORCEONFEEDBACK40hВключить возврат курсораSTARTF_FORCEOFFFEEDBACK80hВыключить возврат курсораSTARTF_USESTDHANDLES100hРазрешить hStdInput- определяет способ отображения окна.- резерв, должно быть равно 0.- дескриптор ввода (для консоли).- дескриптор вывода (для консоли).- дескриптор вывода сообщения об ошибке (для консоли).

Поток может быть создан при помощи функции CreateThread. Рассмотрим параметры этой функции.

·1-й параметр. Указатель на структуру атрибутов доступа. Имеет значение только для Windows NT. Обычно полагается NULL.

·2-й параметр. Размер стека потока. Если параметр равен нулю, то берется размер стека по умолчанию, равный размеру стека родительского потока.

·3-й параметр. Указатель на потоковую функцию, с вызова которой начинается исполнение потока.

·4-й параметр. Параметр для потоковой функции.

·5-й параметр. Флаг, определяющий состояние потока. Если флаг равен 0, то выполнение потока начинается немедленно. Если значение флага потока равно CREATE_SUSPENDED (4H), то поток находится в состояние ожидания и запускается по выполнению функции ResumeThread.

·6-й параметр. Указатель на переменную, куда будет помещен дескриптор потока.

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

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

Для взаимодействия между потоками могут использоваться:

1.Семафоры

2.События

.Критические секции

.Глобальные переменные

.Сообщения

Семафор представляет собой глобальный объект, позволяющий синхронизировать работу двух или нескольких процессов или потоков. Для программиста семафор - это просто счетчик. Если счетчик равен N, это означает, что к ресурсу имеют доступ N процессов.

Сначала при помощи функции CreateSemaphore создается семафор и его дескриптор присваивается глобальной переменной. Перед попыткой обращения к ресурсам, доступ к которым необходимо ограничить, поток должен вызвать функцию WaitForSingleObject. При открытии доступа функция возвращает 0. По окончании работы с ресурсом следует вызвать функцию ReleaseSemaphore. Тем самым увеличивается счетчик доступа на 1. С помощью семафора можно регулировать количество потоков, которые одновременно могут иметь доступ к ресурсу. Максимальное значение счетчика как раз и определяет, сколько потоков могут получить доступ к ресурсу одновременно.

События. Событие является объектом, очень похожим на семафор, но в несколько видоизмененном виде.- создает объект-событие. Параметры функции.
1-й параметр. Имеет тот же смысл, что и первый параметр функции CreateSemaphore. Обычно полагается равным NULL.
-параметр. Если параметр не равен нулю, то событие может быть сброшено при помощи функции ResetEvent. Иначе событие сбрасывается при доступе к нему какого либо процесса.

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

-й параметр. Указатель на строку, которая содержит имя события.

Ожидание события осуществляется, как и в случае с семафором, функцией WaitForSingleObject.

Критические секции. Понятие критической секции позволяет уберечь определенные области программы так, чтобы в этой области программы в данный момент времени исполнялся бы только один поток. Рассмотрим функции для работы с критической секцией.- данная функция создает объект под названием критическая секция. Параметры функции.

·1-й параметр. Указатель на структуру, указанную ниже. Поля данной структуры используются только внутренними процедурами, и смысл их безразличен.


·CRITICAL_SECTION STRUCT

·DebugInfo DWORD ?

·LockCount LONG ?

·RecursionCount LONG ?

·OwningThread HANDLE ?

·LockSemaphore HANDLE ?

·SpinCount DWORD ?

CRITICAL_SECTION ENDS

EnterCriticalSection - войти в критическую секцию.

После выполнения этой функции данный поток становится владельцем данной секции. Следующий поток, вызвав данную функцию, будет находиться в состоянии ожидания. Параметр функции такой же, что и в предыдущей функции.- покинуть критическую секцию. После этого второй поток, который был остановлен функцией EnterCriticalSection, станет владельцем критической секции. Параметр функции LeaveCriticalSection такой же, как и у предыдущих функций.- удалить объект "критическая секция". Параметр аналогичен предыдущим.

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

Часть процедуры, выводящей очередной символ, сделана критической, поэтому доступ к выводу в окно в данный момент времени имеет только один поток.


2. Демонстрационная программа


.1 Листинг


; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

.486 ;

.model flat, stdcall ;casemap :none ;

; Подключаем файлы

; ~~~~~~~~~~~~~..\include\windows.inc..\include\user32.inc..\include\kernel32.inc..\macros\macros.asm..\include\masm32.inc

; Библиотеки

; ~~~~~~~~~..\lib\user32.lib..\lib\kernel32.lib..\lib\masm32.lib..\include\dialogs.inc

; Описание прототиповPROTO :DWORD,:DWORD,:DWORD,:DWORD_caption PROTO :DWORD

; Дата сегмент

.datadb "Потоков было запущенно",0

prog db "c:\windows\notepad.exe",0db "c:\windows\regedit.exe",0PROCESS_INFORMATION <>

.data?dd ?dd ?dd ?DWORD ?dd ?dd ?db 20 dup(?)dd ?

; Константы

.constequ 102equ 103equ 104equ 105equ 107_STOP_THREAD equ WM_USER+1_STOP_THREAD2 equ WM_USER+1

; ««««««««««««««««««««««««««««««КОД««««««««««««««««««««««««««««««««««

.code

:hInstance, FUNC(GetModuleHandle,NULL)

;Описываем диалог"Курсовая работа по ОС, выполнил Григорьев Ю.Н. 8-78-3", \ ;

"MS Sans Serif",10, \ ; Шрифт и его размер

WS_OVERLAPPED or \ ; Стиль_SYSMENU or DS_CENTER, \ ; диалогового окна

7, \ ; Число элементов управляения

,50,200,100, \ ; x y координаты

; Размер буффера

;Описываем кнопки"Start thread",WS_TABSTOP,150,5,40,13,IDOK

DlgButton "End thread",WS_TABSTOP,100,5,50,13,IDOK1"End thread2",WS_TABSTOP,100,20,50,13,IDOK2"Thr. started", WS_TABSTOP, 150,20,40,13,IDSTOP"Start note", WS_TABSTOP, 150,35,40,13,IDPROC"Start regedit", WS_TABSTOP, 150,50,40,13,IDPROC1"Cancel", WS_TABSTOP,150,65,40,13,IDCANCEL


;Вызов окнаhInstance,0,DlgProc,NULL


; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

;Процедура обработчика диалогового окнаproc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

;Разбор сообщенияuMsgWM_INITDIALOG ; на созданииm hWnd, hWin; копируем дескрипторthreadCount, 0;счетчик потоков = 0

Case WM_COMMAND ; разбор сообщений кнопокwParamIDOKthreadSwitch,0 ; маска для работы потоков

;Если = 0 то работа разрешенаeax,OFFSET thread_proc1 ; в регистр адрес процедуры потока

invoke CreateThread,NULL,NULL,eax,NULL,NULL,ADDR ThreadID ; вызываем поток


;;;IDOK1eax,OFFSET thread_proc3 ; вызов второго вида потокаCreateThread,NULL,NULL,eax,NULL,NULL,ADDR ThreadIDIDOK2eax,OFFSET thread_proc4 ; вызов второго вида потокаCreateThread,NULL,NULL,eax,NULL,NULL,ADDR ThreadID

;;;IDCANCEL ;quit_dialog ; закрыть диалогIDSTOP ;остановка потоковeax,OFFSET thread_proc2 ; вызов второго вида потокаCreateThread,NULL,NULL,eax,NULL,NULL,ADDR ThreadIDIDPROC ; создание процессаGetStartupInfo,ADDR startInfo ; получаем информацию

;вызываем процессCreateProcess,NULL,offset prog,NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS,NULL,NULL,ADDR startInfo,ADDR processInfoIDPROC1; создание процессаGetStartupInfo,ADDR startInfo ; получаем информацию

;вызываем процессCreateProcess,NULL,offset prog1,NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS,NULL,NULL,ADDR startInfo,ADDR processInfo

EndSwWM_STOP_THREADthreadSwitch,1 ; по данному сообщению маска для потоков = 1

Case WM_STOP_THREAD2TerminateThread,IDOK,12 ; завершаем потоки 2 методомWM_CLOSE_dialog:EndDialog,hWin,0 ; закрываем диалог


return 0

endp

; процедура первого вида потока

; она будет крутить (скроллить) заголовок_proc1 proc

;локальные переменныеtlen :DWORDbuffer[128]:BYTEpbuf :DWORDcharCount :DWORDcharCount,0pbuf, ptr$(buffer)threadCount, 1

@loop:GetWindowText,hWnd,pbuf,128 ; берем текстrotate_caption,pbuf;крутим на 1 символSetWindowText,hWnd,pbuf;обновляем текстSleep, 50;заддержка

.if threadSwitch == 1;если запрет

jmp @stopThread;выход из цикла

.endif

jmp @loop

@stopThread:_proc1 endp

; процедура второго вида потока_proc2 proc

invoke dwtoa,threadCount,ADDR textBuffer ; число в строкуMessageBox,hWnd,ADDR textBuffer,ADDR textBufferCaption,MB_OK ; выводим сколько потоков запущенноSendMessage,hWnd,NULL,NULL,NULL ;_proc2 endp

;_proc3 procSendMessage,hWnd,WM_STOP_THREAD,NULL,NULL ; отсылаем сообщениеthreadCount,0 ; вычитаем_proc3 endp

_proc4 procSendMessage,hWnd,WM_STOP_THREAD2,NULL,NULLthreadCount,0 ; вычитаем_proc4 endp

; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

PROLOGUE:NONEEPILOGUE:NONE

; процедура прокрутки на 1 символ_caption proc pbuf:DWORD

edx, [esp+4] ; грузим строкуcl, [edx] ; первый байтedx, 1 ; переходим к следующему


@rewrite:al, [edx] ; читаем символedx, 1 ; увеличиваем аддресal, al ; проверяем на 0@last_rewrite ; если конец строки то на выход[edx-2], al ; запись символа@rewrite


@last_rewrite:[edx-2], cl ; запись последнего символаBYTE PTR [edx-1], 0 ; нуль терминатор


ret 4 ; баланс стека

_caption endp

PROLOGUE:PrologueDefEPILOGUE:EpilogueDef

; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««start

секция функция идентификатор windows

2.2 Демонстрация работы



Вывод


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


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

Больше работ по теме:

КОНТАКТНЫЙ EMAIL: [email protected]

Скачать реферат © 2017 | Пользовательское соглашение

Скачать      Реферат

ПРОФЕССИОНАЛЬНАЯ ПОМОЩЬ СТУДЕНТАМ