Опубликован: 25.06.2014 | Уровень: для всех | Доступ: платный | ВУЗ: Учебный центр "ANIT Texno Inform"
Лекция 23:

DLL

Вызовы register и pascal передают параметры слева направо, то есть первый параметр слева вычисляется и передается в первую очередь, а последний параметр справа - вычисляется и передается последним. Вызовы cdecl и stdcall передают параметры наоборот, справа налево. В таблице ниже указаны способы вызова процедур:

Таблица 26.1. Сведения по соглашениям вызова подпрограмм
Команда вызова Обработка параметров Ответственный за очистку стека Разрешена ли передача параметров через регистры
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);
    

После чего собрали дату обратно. Но это только полдела. Ведь может быть три варианта:

  1. День рождения сегодня. В этом случае мы возвращаем 0 дней до дня рождения.
  2. Дня рождения в этом году еще не было. Мы возвращаем количество дней от текущей даты до даты дня рождения, для этого используем функцию DaysBetween, которая возвращает разницу в днях между двумя указанными датами. Кстати, при желании вы можете использовать и другие подобные функции: YearsBetween - возвращает количество лет между двумя датами, MonthBetween - количество месяцев, WeeksBetween - количество недель, HoursBetween - количество часов, MinutesBetween - количество минут, SecondsBetween - количество секунд и MilliSecondsBetween - количество миллисекунд. Только имейте в виду, что если вам нужно получить разницу в часах, минутах, секундах или миллисекундах, то вместо Date, которая возвращает только текущую дату вам нужно будет использовать Now, которая возвращает текущие дату и время.
  3. День рождения в этом году уже был. В этом случае нам нужна новая дата - дата дня рождения в следующем году. Для этого нам опять нужно "разобрать" дату, прибавить к году единицу и вновь собрать её. После чего возвращаем оставшееся количество дней от текущей даты до новой даты дня рождения.

Функции перевода римских цифр в арабские и обратно, для экономии места предлагаю рассмотреть самостоятельно - у нас ведь лекция про создание и вызов 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.

Инга Готфрид
Инга Готфрид
Александр Скрябнев
Александр Скрябнев

Через WMI, или используя утилиту wmic? А может есть еще какие более простые пути...

Нина Фисенко
Нина Фисенко
Россия, Саратов, Саратовский государственный университет им Чернышевского, 1972
Галина Талисман
Галина Талисман
Россия