DLL
Вызовы register и pascal передают параметры слева направо, то есть первый параметр слева вычисляется и передается в первую очередь, а последний параметр справа - вычисляется и передается последним. Вызовы cdecl и stdcall передают параметры наоборот, справа налево. В таблице ниже указаны способы вызова процедур:
Команда вызова | Обработка параметров | Ответственный за очистку стека | Разрешена ли передача параметров через регистры |
---|---|---|---|
Register | Слева-направо | Подпрограмма | Да |
Pascal | Слева-направо | Подпрограмма | Нет |
Cdecl | Справа-налево | Вызывающая программа | Нет |
Stdcall | Справа-налево | Подпрограмма | Нет |
Совсем уж влезать в дебри машинного языка, пожалуй, не стоит, запомните только несколько рекомендаций. Для программ на Windows чаще всего используют соглашение stdcall. А если вы создаете DLL, которую затем могут использовать Си-программисты, то указывайте соглашение cdecl.
ss:= s;
не просто присваивает переменной ss переданную в параметре строку, она одновременно делает преобразование типов из PChar в String. Если вы не забыли, такие преобразования можно делать явно, например:
ss:= String(s); s:= PChar(ss);
В нашей функции преобразование из PChar в String не указано, так как оно происходит по умолчанию, однако обратное преобразование все же было сделать необходимо:
Result:= PChar(ss);
Внутри подпрограммы устроен цикл
for i:= 1 to length(s) do ss[i]:= char(Ord(ss[i]) xor Key);
Здесь, очередному символу строки ss присваивается результат работы побитовой операции XOR. Кстати, шифрование текста обычно делают именно с помощью XOR, наш пример только более простой. Что в данном случае делает XOR? Оператор XOR используется для инвертирования определённых битов числа (еще говорят, что XOR - операция исключающего ИЛИ). Для отдельных битов оператор XOR работает следующим образом (смотреть слева направо):
0 xor 0 = 0 0 xor 1 = 1 1 xor 0 = 1 1 xor 1 = 0
То есть, если два аргумента равны, XOR возвращает False (для отдельных битов - ноль). А если аргументы различаются, XOR возвращает True (или 1 для битов).
Каждый символ таблицы ASCII имеет собственное числовое обозначение. Так, английская "A" имеет номер 65, английская "B" - 66, и так далее, это всё мы обсуждали в "Символы и строки" . Компьютер работает только в двоичной системе, для него "A" - это не десятичная 65, а двоичная 0100 0001. Так вот, XOR обрабатывает не всё число целиком, а отдельно каждый его бит. В нашем примере слева от XOR указан очередной символ строки, а справа - число-ключ, от которого зависит изменение бита символа. Учитывая, что в таблице ASCII может быть максимум, 255 символов, слишком большой ключ указывать не стоит, во избежание ошибок. 10 вполне достаточно, хотя можете и поэкспериментировать (у меня после значения ключа 31 русские буквы при шифрации начинали искажаться, так что выбирайте значения от 1 до 30). Таким образом, мы каждый символ строки кодируем в совершенно другой символ. В более сложных системах шифрования ключ может меняться от бита к биту.
Когда все символы строки обработаны, строка преобразуется в PChar, и результат возвращается обратно в программу.
Следующей у нас идет функция подсчета количества дней до следующего дня рождения. День рождения указывается в параметре, который имеет тип TDateTime. Чтобы подсчитать количество дней до очередного дня рождения, нам требуется получить новую дату - день и месяц дня рождения, а год мы должны указать текущий. Для этого мы вначале "разбираем" дату на составляющие:
DecodeDate(mydate, y, m, d);
Так мы получили отдельно, год, месяц и день. Затем мы изменили год на текущий:
y:= YearOf(Date);
После чего собрали дату обратно. Но это только полдела. Ведь может быть три варианта:
- День рождения сегодня. В этом случае мы возвращаем 0 дней до дня рождения.
- Дня рождения в этом году еще не было. Мы возвращаем количество дней от текущей даты до даты дня рождения, для этого используем функцию DaysBetween, которая возвращает разницу в днях между двумя указанными датами. Кстати, при желании вы можете использовать и другие подобные функции: YearsBetween - возвращает количество лет между двумя датами, MonthBetween - количество месяцев, WeeksBetween - количество недель, HoursBetween - количество часов, MinutesBetween - количество минут, SecondsBetween - количество секунд и MilliSecondsBetween - количество миллисекунд. Только имейте в виду, что если вам нужно получить разницу в часах, минутах, секундах или миллисекундах, то вместо Date, которая возвращает только текущую дату вам нужно будет использовать Now, которая возвращает текущие дату и время.
- День рождения в этом году уже был. В этом случае нам нужна новая дата - дата дня рождения в следующем году. Для этого нам опять нужно "разобрать" дату, прибавить к году единицу и вновь собрать её. После чего возвращаем оставшееся количество дней от текущей даты до новой даты дня рождения.
Функции перевода римских цифр в арабские и обратно, для экономии места предлагаю рассмотреть самостоятельно - у нас ведь лекция про создание и вызов DLL, а не про синтаксис Паскаля. Если вы внимательно изучали предыдущие лекции, то сможете разобраться с синтаксисом. До этого мы не рассматривали только процедуру декремента Dec, которая уменьшает значения аргумента. По умолчанию, она уменьшает аргумент на единицу. Или можно указать аргумент и через запятую, значение, на которое нужно уменьшить аргумент:
Dec(i); //уменьшает i на единицу Dec(N, A[i]); //уменьшает N на значение A[i]
С остальным синтаксисом вы должны быть уже знакомы.
В самом конце библиотеки у нас указан список функций, которые можно будет вызывать из внешних программ:
exports Code name 'Code', BeforeBirthday name 'BeforeBirthday', ArToRom name 'ArToRom', RomToAr name 'RomToAr';
В библиотеке может быть и больше процедур и функций, в списке exports нужно указывать только те, которые будут доступны извне. У этих же функций указывались и соглашения вызовов. В нашем случае, все имеющиеся функции должны быть доступны извне.
Теперь о способе описаний списка. Директива
Code name 'Code',
говорит о том, что наша функция Code должна вызываться из внешней программы по имени Code. Для чего это сделано? Допустим, во внешней программе уже есть функция Code, тогда мы получим конфликт имен. В этом случае мы могли бы вызывать подпрограмму под другим именем, например:
Code name 'NewCode',
Тогда во внешней программе мы запрашивали бы не функцию Code, а функцию NewCode.
Кроме того, в списке exports можно указывать передачу подпрограмм не по именам, а по индексам, например, так:
exports Code, BeforeBirthday, ArToRom, RomToAr;
Тогда бы подпрограммам автоматически были бы присвоены индексы, от 1 до 4. Но индексы можно присвоить и принудительно, с помощью директивы index, например, так:
exports Code index 1, BeforeBirthday index 2, ArToRom index 3, RomToAr index 4;
Что нам дают индексы? В среде Windows подпрограммы по индексам вызываются чуть быстрее, однако запоминать, под каким индексом находится нужная подпрограмма сложнее. Кроме того, использование индексации имеет смысл только в Windows, так как другие ОС формируют индексы автоматически. В общем, рекомендуется обращаться к подпрограммам по именам, как мы и сделали в нашей библиотеке.
На этом работа с динамической библиотекой закончена. Поскольку кнопка Run на панели инструментов для DLL недоступна, нажмите <Ctrl+F9> или выберите команду "Запуск -> Компилировать". В результате получите готовую библиотеку - файл MyFirstDLL.dll.