Консольное приложение. Тут в общем-то дело очень простое. Открываем Delphi, File-->New-->Other-->Console Application при этом мы имеем вот такую вот заготовочку:
program Project1; {$APPTYPE CONSOLE} uses SysUtils; begin { TODO -oUser -cConsole Main : Insert code here } end.
В общем-то тут всё понятно пишем свой код, на чистом паскале и периодически подключаем необходимые модули, по возможности перенося из модулей в прогу то что мы в них юзаем и отключаем их от проекта (это дает уменьшение размера). Ну что тут эще можно пояснить, мотод просто, как 5 копеек и проги на выходе получаются размером от 13,5кб и где-то 8кб если упаковать. . Вопрос встаёт в том что окошко таким методом не получить и всё равно придётся юзнуть функции системного API, а в этом и есть фишка следующего метода.
2) Приложение WinAPI. Для того что бы писать таким способом необходимо хоть немного представлять себе, как работает винда и что такое WinAPI, поэтому расшарьте эти вопросы, возьмите поудобочитабельней справочничек по функциям WinAPI, и можно считать, что вы готовы. Главная особенность использования этих функций состоит в том, что их необходимо импортировать из системы, но в делфи есть модуль windows, подключив к проекту, который большинство функций, типов и констант, нашей нелюбимой-куклы – винды, станут доступными как родные фишки делфи. Пишем мы проги соответственно в чистом dpr-е как и в прошлом методе, только убираем {$APPTYPE CONSOLE} что бы не вылетало чёрное DOS – окошко, которое нам в этом методе не пригодится т.к. мы будем писать оконное приложение. При мер того как создать окошко в АПИ, я бахну, смотрим и удивляемся: Program WinMin; uses windows; const WM_DESTROY = $0002; ID = PChar(32512); var wc : TWndClassEx; Wnd : Integer; Mesg : TMsg;
//Функция ловит сообщения окну и передаёт ему необходимые действия function WindowProc(wnd:Integer; Msg : Integer; Wparam:Longint; Lparam:Longint):LongInt; stdcall; Begin if msg=wm_destroy then //Если узер закрыл окно Begin postquitmessage(0);//Передаем окну сообщение о закрытии Result:=0; exit; End else Result:=DefWindowProc(wnd,msg,wparam,lparam);//Ловим дальше End;
Procedure RegCls; //Регистрация класса окна begin //Заполнение полей класса wc.cbSize:=sizeof(wc); wc.style:=cs_hredraw or cs_vredraw; wc.lpfnWndProc:=@WindowProc; wc.cbClsExtra:=0; wc.cbWndExtra:=0; wc.hInstance:=1; wc.hIcon:=LoadIcon(0,ID); wc.hCursor:=LoadCursor(0,ID); wc.hbrBackground:=COLOR_BTNFACE+1; wc.lpszMenuName:=nil; wc.lpszClassName:='TWnd'; RegisterClassEx(wc);//Регистрация класса end;
begin RegCls; //Коасс окна Wnd:=CreateWindowEx ( 0, 'TWnd', 'WinMin', ws_overlappedwindow, 100, 150, 400, 250, 0, 0, 1, nil); ShowWindow(Wnd,1); //Показать окно While GetMessage(Mesg,0,0,0) do//Получаем сообщения begin TranslateMessage(Mesg); {код} DispatchMessage(Mesg); end; end.
Вроде бы не так всё сложно. Получили окошко, примерно, такое как делфи создаёт по дефаулту, но весом в 14,5 кб.. Уже не плохо? Дальше будет страшней))) Разбираемся, думаем и переходим к следующей методу, кстати, оба следующих метода требуют усвоение этого, так, дальше писать придется, только, на АПИ, но плюс к этому примётся ещё и не много «поизвращяться», но думаю, дочитав статейку до этого момента нет смысла бросать.. Сразу залезу, вперед, и попробую вас немного взбодрить, тем фактом, что в конце этой статьи мы получим такое же окошко только весом 1264 байта!! Так что волю в кулак! Переходим дальше…
3) Приложение WinAPI без RTL. Этот способ разработки требует значительно больше усилий, но результат превосходит все ожидаемые от Delphi надежды. В чём суть метода. В том, что Delphi как язык, весьма и очень, совершенный даёт пользователю множество благ, которыми другие не могут похвастаться, например тип string и его конвертация в PChar и обратно, динамические массивы, функции выделения памяти, множество стандартный типов и констант. Всё это добро находится в двух модулях, которые нет необходимости подключать, тат как они подключаются автоматически - System и SysInit, эти вкусности называются RTL (Run Time Library). Эти модули, целиком включаясь в прогу, занимают в ней примерно 10кб. Я думаю, вы понимаете к чему я клоню, к тому, что от этих модулей надо избавиться и научиться обходиться без них… Вот на этом и основан этот принцип. Теперь перейдём конкретно к реализации. . Многие наверно по неопытности решили кокнуть файлы system.dcu и sysinit.dcu в папке: %ProgramFiles%\Borland\Delphi7\Lib\ и конечно же тем самым выбить делфи зубы, но это не лучшее решение, т.к. те, файлы пригодятся вам в других прогах, да и компилятору они просто необходимы для работы. Т.о. нам остаётся сделать свои файлы system.dcu и sysinit.dcu не включающие в себя ни чего лишнего, но удовлетворяющие потребности Delphi. Далее эти файлы необходимо размещать в папке с исходником, т.к. Delphi ищет модули в первую очередь в папке с сорцем, а потом уж в своих злачных местах. . Создаём модули system и sysinit. Создаем три текстовых файла: system.pas, sysinit.pas и compil.bat. Пишем в эти файлы следующие строки: В system.pas: unit System; interface procedure _HandleFinally; type TGUID = record D1: LongWord; D2: Word; D3: Word; D4: array [0..7] of Byte; end; PInitContext = ^TInitContext; TInitContext = record OuterContext: PInitContext; ExcFrame: Pointer; InitTable: pointer; InitCount: Integer; Module: pointer; DLLSaveEBP: Pointer; DLLSaveEBX: Pointer; DLLSaveESI: Pointer; DLLSaveEDI: Pointer; ExitProcessTLS: procedure; DLLInitState: Byte; end; implementation procedure _HandleFinally; asm end; end.
В sysinit.pas: unit SysInit; interface procedure _InitExe; procedure _halt0; var ModuleIsLib : Boolean; TlsIndex : Integer = -1; TlsLast : Byte; const PtrToNil : Pointer = nil; implementation procedure _InitExe; asm end; procedure ExitProcess(uExitCode: INTEGER); stdcall; external 'kernel32.dll' name 'ExitProcess'; procedure _halt0; begin ExitProcess(0); end; end.
Кладем все три файла в одну папку, запускаем файл compil.bat. После запуска файл compil.bat удаляется, а в появляется два файла: system.dcu и sysinit.dcu. Всё, эта часть окончена, необходимо только сохранить гденибудь в архиве оставшиеся в папке 4 файла, их вы сможете использовать не однократно, для этого достаточно их скопировать в каталог с исходником и открыть исходник в делфи, а далее просто пишем в делфи и в нем же компилируем. Что бы проверить работоспособность метода создайте файл 1.dpr со строками: Program mini; begin end.
Далее как оговаривалось киньте в папку с этим файлом те 4 файла что мы до этого создали и запустите проект: 1.dpr в делфи. Теперь жмём Ctrl-F9 и наблюдаем в этой папке файл 1.exe весом 3584 байта (3,5кб), а это как раз на 10кб меньше, чем тот же сорец откомпилированный без этих заморочек.. Теперь если этот exe упаковать замечательным пакером fsg 2.0 его размер составит около 1000 байт – нешуточное уменьшение. . Теперь осталось разобраться с тем как же, всё-таки, писать программы при использовании такого метода? В общем-то, программы пишутся так же как всегда, только не получится использовать то, что мы теряем, избавляясь от RTL, а это я уже перечислял и в первую очередь это тип string. При написании программ необходимо ограничиться типами: boolean, byte, word, integer, longint, pchar, char, cardinal, pointer. А так же массивы этих типов и записи с полями этого типа. Если, например функция, использует тип DWORD, его придётся заменить на другой 4 байтный тип, например integer, longint или cardinal, в зависимости от возможных значений. Как это делать вы не раз увидите в моих исходниках, и со временем будете делать это автоматически. Все стандартные операторы (условия, циклы), остаются. Приходится писать используя лишь функции WinAPI причем, каждую хоть один раз применяемую функцию, необходимо импортировать из системы, для того что бы её можно было в дальнейшем применять по всему коду. . Импорт функции осуществляется следующим образом: function (procedure) в скобках параметры и типов, как всегда, далее stdcall; external ‘имя библиотеки’ name ‘имя функции’; Некоторые функции требуют переноса из модуля типов и записей которыми они оперируют например функция CreatFile будет иметь такой импорт: //ТИПЫ type POverlapped = ^TOverlapped; TOverlapped = record Internal: Cardinal; InternalHigh: Cardinal; Offset: Cardinal; OffsetHigh: Cardinal; hEvent: Cardinal; end; PSecurityAttributes = ^TSecurityAttributes; TSecurityAttributes = record nLength: Cardinal; lpSecurityDescriptor: Pointer; bInheritHandle: Boolean; end; //Объявление function CreateFileA(lpFileName: PAnsiChar; dwDesiredAccess, dwShareMode: Cardinal; lpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: Cardinal; hTemplateFile: Cardinal): Cardinal; stdcall; external KERNEL32 name '_CreateFileA@28';
вот пример программы Hallo World, которая импортирует из user32.dll функцию MessageBoxA и с помощью неё выводит сообщение Hallo World.
Program Hallo; function MessageBoxA(hWnd: cardinal; lpText, lpCaption: PChar; uType: Cardinal): Integer; stdcall; external 'user32.dll' name 'MessageBoxA'; begin MessageBoxA(0,’Hallo World’,’Hallo’,0); end.
Как видите, не так и трудно... Но примеры будут и ещё, но они выходят за рамки этой статьи.. Переходим к разбору последнего метода этой статьи.
4) Приложение WinAPI c ассемблерной сборкой. Хочу напомнить, что все три предыдущих метода рассчитаны на создание исходника программы в файле проекта Delphi (.dpr), этот метод отличается от предыдущих тем, что он рассчитан на разработке в файле модуля (.pas) и его компиляцию. Принцип такой. Программа создаётся по всем правилам предыдущего метода, за исключением двух моментов, которые я сейчас рассмотрю: . Первое отличие – программа размещается в модуль и основная часть программы размещается в процедуру без параметров, объявление которой, указывается в разделе interface. Например если был исходник: Program example1;
Procedure pr1(a:integer); begin a:=a-123; end;
Procedure pr2(b:integer); begin b:=b-234; end;
var a,b,c:integer
begin c:=pr1(2345)+pr2(5678); end.
То при переделке в модуль получаем следующий исходник: UNIT Project1;
INTERFACE
Procedure Run;
IMPLEMENTATION
Procedure pr1(a:integer); begin a:=a-123; end;
Procedure pr2(b:integer); begin b:=b-234; end;
var a,b,c:integer
Procedure Run; begin c:=pr1(2345)+pr2(5678); end;
end.
. Как видите основная часть размещается в процедуру Run. Далее с помощью командной строки компилятора Delphi 3 (DCC32.exe) осуществляется компиляция модуля, но не в ехе, а в объектный файл СИ(.obj). Далее с помощью ассемблерного компоновщика (линкера) – Link.exe, осуществляется компоновка ехе из obj, с установкой точки входа на процедуру основной части, в нашем случае Run. . Второе отличие – в этом методе импорт функций осуществляется, немного не так, как в предыдущем методе, но разница не так велика. Отличие заключается в имени функции, при его объявлении. Дело в том что сборка ехе осуществляется линкером (об этом ниже), и поэтому бинарный код функций берется их ассемблерный библиотек .LIB все эти библиотеки необходимо брать из дистрибьютива MASM32 , и в этих же библиотеках необходимо поиском смотреть как необходимо писать имя функции. Например, нам нужно определить имя функции CreatFileA , нам необходимо взять библиотеку kernel32 .lib и поиском найти следующую строку: _CreateFileA@28, число 28 – это выделение памяти под функцию. По этой аналогии исходник, выше приводимого, Hallo World будет иметь вид: unit hello; interface Procedure Run; implementation function MessageBoxA(hWnd: cardinal; lpText, lpCaption: PChar; uType: Cardinal): Integer; stdcall; external 'user32.dll' name '_MessageBoxA@16'; const msg = 'Hallo'; Procedure Run; begin MessageBoxA(0,msg,msg,0); end; end.
. Вот и всё. Теперь поговорим о том, как на практике и какими инструментами такого рода компиляцию можно осуществить. Инструменты: 1)Компилятор а)DCC32.EXE - компилятор Delphi3 б)SYSTEM.DCU - модуль SYSTEM Delphi3 в)SYSINIT.DCU - модуль SYSINIT Delphi3
2)Компоновщик(Линкер) а)LINK.EXE - компоновщик(линкер) MASM32 б)MSPDB50.DLL - Библтотека линкера в)Набор необходимых библиотек, функции, которых используются в проекте. (файлы *.LIB)
Что бы создать ехе-шник необходимо сделать три действия: а)написать нормальный PAS, как я думаю вполне подробно пояснил. б)Откомпилировать obj для этого помещаем проект в папку с "инструментами” и выполнить командную строку: DCC32 -JP имя_проекта.pas. Выполнять это действие необходимо в CMD что бы можно было посмотреть ошибки и их исправить... Если все нормально компильнулось... необходимо проверить какчество obj, для этого открываем его листером или блокнотом и ищем строку с именем процедуры в которую мы располажили "основную часть" ... если все гуд то после имени процедуры будут символы:$qqrv... В нашем случае имя Run -->Run$qqrv..
Если этого не получились значит мы не правильно (не везде) заменили типы на разрешённые...
в)Откомпоновать ехе.. Для этого нам необходимо выполнить командную строку: Link ALING:4(8..16..32..64 - это приходится определять опытным путём... Обычно 4 хватает) /FORCE:UNRESOLVED /SUBSYSTEM:WINDOWS /MERGE:.data=.text /MERGE:.rdata=.text /ENTRY:Run(имя поцедуры)$qqrv /STUB:stub.bin перечисляем либы из которых нужно брать функции + имя odj файла... 2)Т.о. например для программы:
unit w; interface procedure Run; implementation function ShellExecute(hWnd: Cardinal; Operation, FileName, Parameters, Directory: PAnsiChar; ShowCmd: Integer): Cardinal; stdcall; external 'shell32.dll' name '_ShellExecuteA@24' procedure Run; begin ShellExecute(0,'','notepad','','',1); end; end.
размещённой в файле hello.pas
Мы используем командную строку:
для создания obj: dcc32 -JP hello.pas
для создания ехе:
LINK /ALIGN:4 /FORCE:UNRESOLVED /SUBSYSTEM:WINDOWS /MERGE:.data=.text /MERGE:.rdata=.text /ENTRY:Run$qqrv /STUB:stub.bin user32.lib hello.obj