DLL
Создание DLL
Загружаем Lazarus и выбираем команду "Файл -> Создать…". Откроется окно, в котором нам нужно будет выбрать "Библиотека":
В результате будет создан проект, в котором будет находиться следующий код:
library Project1; {$mode objfpc}{$H+} uses Classes { you can add units after this }; begin end.
Нажмите кнопку "Сохранить всё", и сохраните проект под именем MyFirstDLL в папку 26-01. Как видите, первая строка кода автоматически изменилась на
library MyFirstDLL;
а в указанной папке появилось три файла: MyFirstDLL.lpi, MyFirstDLL.lpr и MyFirstDLL.lps. Вы можете сразу же попробовать скомпилировать проект командой "Запуск -> Компилировать" или кнопками <Ctrl+F9> и в результате получите четвертый файл MyFirstDLL.dll. Это и есть динамическая библиотека, которая пока ещё ничего не умеет делать.
В разделе uses, после модуля Classes, через запятую мы можем добавить и другие, необходимые нам модули, однако не забывайте, что это будет увеличивать результирующий размер библиотеки. Поэтому нужно включать только те модули, без которых действительно, не обойтись. Обратите внимание - после последнего модуля точку с запятой ставить не нужно, она стоит после комментария, который сообщает, что вы можете добавлять модули после этого, то есть Classes. Комментарий можете оставить, а можете и удалить.
Весь основной код библиотеки должен находиться над завершающими begin…end. Для примера научим наш модуль делать несколько полезных вещей:
- Переводить числа из обычных арабских в римские, и наоборот.
- Делать простую шифрацию указанной строки.
- Возвращать количество дней до дня рождения.
Поскольку нам придется иметь дело со строками, то следует иметь в виду вот что: в динамических библиотеках рекомендуют применять строковый тип PChar. Это связано с тем, что вашей DLL могут пользоваться программисты, создающие приложения на других платформах. В среде Windows тип PChar считается "родным", все WinAPI-функции используют этот тип. Внутри DLL-файла можно использовать любой строковый тип, но для передачи параметров и для получения результатов лучше использовать PChar.
Далее привожу полный код нашей библиотеки, после чего разберем некоторые моменты:
library MyFirstDLL; {$mode objfpc}{$H+} uses Classes, DateUtils, SysUtils { you can add units after this }; const R: array[1..13] of string[2] = ('I', 'IV', 'V', 'IX', 'X', 'XL', 'L', 'XC', 'C', 'CD', 'D', 'CM', 'M'); A: array[1..13] of Integer = (1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000); {шифрация/дешифрация} function Code(s: PChar; Key: integer): PChar; stdcall; var i: integer; ss: string; begin ss:= s; for i:= 1 to length(s) do ss[i]:= char(Ord(ss[i]) xor Key); Result:= PChar(ss); end; {дней до очередного дня рождения} function BeforeBirthday(Birthday:TDateTime): Integer; stdcall; var d,m,y: word; //день, месяц и год mydate: TDateTime; begin mydate:= Birthday; //разберем дату, изменим год на текущий и соберем обратно: DecodeDate(mydate, y, m, d); y:= YearOf(Date); mydate:= EncodeDate(y, m, d); //если ДР сегодня: if mydate = Date then Result:= 0 //если будет: else if mydate > Date then Result:= DaysBetween(Date, mydate) //если уже был: else begin //установим дату ДР на следующий год и снова посчитаем: y:= y + 1; mydate:= EncodeDate(y, m, d); Result:= DaysBetween(Date, mydate); end; //else end; {Арабские в римские} function ArToRom(N: integer): PChar; stdcall; var i: integer; s: string; begin s := ''; i := 13; while N > 0 do begin while A[i] > N do Dec(i); s := s + R[i]; Dec(N, A[i]); end; Result := PChar(s); end; {Римские в арабские} function RomToAr(s: PChar): Integer; stdcall; var i, p: Integer; begin Result := 0; i := 13; p := 1; while p <= Length(s) do begin while Copy(s, p, Length(R[i])) <> R[i] do begin Dec(i); if i = 0 then Exit; end; //while 2 Result := Result + A[i]; p := p + Length(R[i]); end; //while 1 end; exports Code name 'Code', BeforeBirthday name 'BeforeBirthday', ArToRom name 'ArToRom', RomToAr name 'RomToAr'; begin end.Листинг .
Разберем код. Прежде всего, мы включили в раздел uses два дополнительных модуля:
uses Classes, DateUtils, SysUtils { you can add units after this };
Эти модули были необходимы для работы с датами - для получения текущей даты, для разборки даты на составляющие (год, месяц, день) и на обратную сборку, а также для определения количества дней от одной даты до другой.
Далее, у нас описаны две константы:
const R: array[1..13] of string[2] = ('I', 'IV', 'V', 'IX', 'X', 'XL', 'L', 'XC', 'C', 'CD', 'D', 'CM', 'M'); A: array[1..13] of Integer = (1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000);
Эти константы представляют собой массивы - первый строковый, на два символа, второй - массив целых чисел. Оба массива нужны для замены типа цифр. Например, римская III соответствует арабской 3, римская X - это арабская 10, и так далее. Тут следует иметь в виду, что отрицательных римских цифр не бывает, и что минимальная цифра - единица. Кроме того, тип integer имеет максимальное значение 2 147 483 647. Вряд ли вам потребуется переводить на римские большую цифру - римские обозначения не так популярны и используются, в основном, в литературе, для обозначения номера главы или раздела. Однако, помните, что мы включаем в библиотеку только инструменты конвертирования, а проверку допустимых значений должен делать программист в использующей нашу DLL программе.
Пойдем дальше. Первой у нас описана функция кодирования / декодирования строк:
{шифрация/дешифрация} function Code(s: PChar; Key: integer): PChar; stdcall; var i: integer; ss: string; begin ss:= s; for i:= 1 to length(s) do ss[i]:= char(Ord(ss[i]) xor Key); Result:= PChar(ss); end;
Здесь мы прежде всего видим, что функция принимает параметры - строку типа PChar и целое число - ключ, и возвращает также строку PChar, хотя внутри самой функции используется тип String. Еще мы видим, что после объявления имени функции указывается ключевое слово stdcall, означающее, что для вызова этой функции будет использовано стандартное соглашение.
Чтобы в программе можно было использовать инструменты из динамических библиотек сторонних разработчиков, были разработаны специальные соглашения по вызову процедур. Эти соглашения определяют различные правила вызова подпрограмм: как будут передаваться параметры - через стек, через регистры, через динамическую память; кто ответственный за очистку стека - вызывающая или вызываемая программа и т.д. Так, если используется стек, то чтение будет происходить справа налево. То есть, последние загруженные данные будут считаны первыми.
По умолчанию, используется соглашение register. Его имеет смысл использовать тогда, когда вы создаете на Lazarus и DLL, и использующую её программу. Такой вызов работает быстрее, однако он почти не применяется, так как обычно программисты предпочитают использовать более гибкие соглашения stdcall и cdecl.