i21sys/Docu.Работа с DLL/creating-in-BB.odc

Создание DLL в Блэкбоксе
Для компиляции любого модуля Блэкбокса в нижеследующем тексте нужно выделить текст модуля (или просто ключевого слова MODULE, например, двойным кликом) и выполнить команду меню Dev, Compile Selection.
Здесь главный источник информации  документациядлямодуляDevLinker. 
Инструкции, приводимые ниже, содержат некоторое избыточное количество деталей в расчете на неопытных пользователей Блэкбокса.
NB По умолчанию Блэкбокс использует для передачи параметров соглашеия stdcall. Об этом важно помнить для правильного вызова DLL, созданных в Блэкбоксе, из C/C++.
Пример 0. Простая DLL
Предположим, нужно создать DLL с одной функцией из следующего модуля:

MODULE Epse21Dll0;

	PROCEDURE Add* (a, b: INTEGER): INTEGER;
	BEGIN
		RETURN  a + b
	END Add;
	
END Epse21Dll0.

Команда создания DLL из этого модуля такова:
DevLinker.LinkDll anyname.dll := Epse21Dll0# ~
Имя DLL (anyname.dll) не чувствительно к регистру. Это соглашение в MS Windows.
Тильда (~) отмечает конец списка модулей, которые должны быть скомпонованы в DLL.
Символ # указывает, что процедуры, экспортированные из этого модуля (в соответствии с правилами Блэкбокса) должны быть добавлены к списку экспорта получающейся DLL.
Вновь созданная DLL находится в папке Блэкбокса, и ее можно изучить с помощью программы DependencyWalker:



На картинке обведено имя экспортированной процедуры. 
Замечания
1. Имя процедуры, экспортированной из DLL, чувствительно к регистру. Если изменить Add на ADD, то имя в списке экспорта DLL тоже будет состоять из заглавных букв  ADD.
2. В списке экспорта получающейся DLL отсутствует информация об имени модуля. Это проектное решение Блэкбокса.
3. Модуль может содержать любое количество экспортированных процедур: они все появятся в списке экспорта DLL.
4. Отсутствует явное указание на то, что функция использует для передачи параметров соглашение stdcall.
Пример 1. DLL из двух модулей
Создадим DLL из вышеприведенного и следующего модулей:

MODULE Epse21Dll1;
	IMPORT  SYSTEM;

	PROCEDURE Add* (a, b: INTEGER): INTEGER;
	BEGIN
		RETURN  a - b
	END Add;
	
END Epse21Dll1.

Если нужно включить процедуру Sub в список экспорта DLL, то имя второго модуля тоже должно быть отмечено символом # в команде компоновщика:
DevLinker.LinkDll dll1.dll := Epse21Dll0# Epse21Dll1# ~
Программа Dependency Walker покажет в списке экспорта DLL как процедуру Add из первого модуля, таки Sub из второго (обведены):



Замечания
1. DLL выглядит в Dependency Walker так, как если бы она была создана из единственного модуля с двумя экспортированными функциями.
2. Если маркер # опустить после любого из имен двух модулей, то их экспортированные процедуры (экспортированные в смысле Компонентного Паскаля) не появятся списке экспорта DLL (это удобно, если в процедурах вызываются модули Блэкбокса, такие как Math).
3. Если переименовать Sub в Add, то список экспорта DLL будет содержать две функции с одинаковым именем:



NB Порядковые номера у двух функций разные, поэтому в принципе их можно различить в вызывающей программе. 
Пример 1ccall. Создание DLL с соглашением ccall
Если создается DLL для использования в программах на C/C++, то тогда может иметь смысл использование соглашения ccall. Тогда модуль на Компонентном Паскале должен импортировать SYSTEM, а перед именем процедуры нужно поставить [ccall]:

MODULE Epse21Dll1ccall;
	IMPORT SYSTEM;
	
	PROCEDURE [ccall] Sub* (a, b: INTEGER): INTEGER;
	BEGIN
		RETURN  a - b
	END Sub;
	
END Epse21Dll1ccall.

DevLinker.LinkDll dll1ccall.dll := Epse21Dll1ccall# ~
Пример 2. DLL из модуля, использующего NEW
Предположим, что нужно создать DLL, процедуры в котором используют оператор NEW для размещения в куче записей и массивов.
Пусть, например, процедура Add в нижеприведенном модуле размещает несколько мегабайт памяти при каждом вызове:

MODULE Epse21Dll2;

	PROCEDURE Add* (a, b: INTEGER): INTEGER;
		VAR  i: INTEGER;  t: POINTER TO ARRAY OF INTEGER;
	BEGIN
		FOR  i := 0  TO  1000  DO
			NEW( t, 1000 + i )
		END;
		RETURN  a + b
	END Add;
	
END Epse21Dll2.

Тогда в список модулей компоновщика нужно включить Kernel+ :
DevLinker.LinkDll dll2.dll := Kernel+ Epse21Dll2# ~
Заметим, что программа Dependence Walker выдает предупреждение для этой DLL. Однако, тест, приводимый ниже, должен работать.
Чтобы проверить, что получившаяся DLL не вызовет аварийной остановки, сделаем следующеее (см. подробнее ВызовDLLизБлэкбокса).
Скомпилируем следующий интерфейсный модуль для вызова получающейся DLL:

MODULE  Epse21Dll2Interface ["dll2"];
	PROCEDURE Add* ( a, b: INTEGER ): INTEGER;
END  Epse21Dll2Interface.

Следующий модуль проверяет DLL (можно следить за показанием Allocated Memory в правой половине статусной полоски Блэкбокса; убедитесь, что выбрана опция Double в диалоге Edit, Preferences...).

MODULE  Epse21Dll2Test;
	IMPORT  Dll2 := Epse21Dll2Interface,  StdLog;
	
	PROCEDURE Do*;
		VAR  a, b: INTEGER;
	BEGIN
		a := 1;
		FOR  b := 0  TO  1000  DO
			a := Dll2.Add( a, b )
		END;
		StdLog.Int( a );  StdLog.Ln
	END Do;
	
END  Epse21Dll2Test.

NB Прежде чем кликать по черному коммандеру ниже по тексту, заметим, что как только DLL загружен описываемым способом, ее уже нельзя выгружить. Поэтому если нужно изменить DLL, придется перезапустить весь Блэкбкос. (Чтобы избежать этого, см. раздел о динамической загрузке DLL в ВызовDLLизБлэкбокса.)
Epse21Dll2Test.Do
Пример 3. Массивы в качестве параметров
Рассмотрим функцию, возвращающую сумму элементов массива. 
Обычно процедура будет выглядеть примерно так:

	PROCEDURE Sum* ( IN a: ARRAY OF REAL ): REAL;
		VAR  i: INTEGER;  s: REAL;
	BEGIN
		s := 0;
		FOR  i := 0  TO  LEN(a) - 1  DO
			s := s + a[i]
		END;
		RETURN  s
	END Sum;
	
Однако при работе с массивами Блэкбокс использует скрытые дескрипторы, содержащие информацию о длине массива, размере и типе его элементов, и т.п. При этом в качестве аргумента в Sum передается адрес дескриптора, и именно поэтому длина массива может быть определена в коде с помощью LEN(a).
Другие системы программирования могут иметь другие соглашения. В фортране, C, C++ и т.п. массивы передаются в процедуры через адрес первого элемента. 
Это влечет две вещи:
1) длина массива должна передаваться в Блэкбокс через дополнительный параметр;
2) компилятору Блэкбокса нужно сообщить, что массив придет без обычного блэкбоксовского десприптора.
Тогда модуль может быть таким:

MODULE Epse21Dll3;
	IMPORT SYSTEM;
	
	PROCEDURE Sum* ( IN a: ARRAY [untagged] OF REAL;  len: INTEGER ): REAL;
		VAR  i: INTEGER;  s: REAL;
	BEGIN
		s := 0;
		FOR  i := 0  TO  len - 1  DO
			s := s + a[i]
		END;
		RETURN  s
	END Sum;
	
END Epse21Dll3.

DevLinker.LinkDll dll3.dll := Epse21Dll3# ~

Замечания 
1. Нумерация элементов в других языках может отличаться от используемой в Компонентном Паскале.
2. Вышеописанный механизм также работает с массивами, передаваемыми чере VAR/OUT параметры (например, когда массив нужно отсортировать и т.п.).
3. Тип REAL в Компонентном Паскале соответствует вещественным типам с двойной точностью в фортране и C/C++. Тип с одинарной точностью в КП  SHORTREAL.
4. Многомерные массивы в фортране располагаются в памяти по столбцам, т.е. a[0,0], a[1,0], и т.д. (первый индекс изменяется быстрее всех), тогда как в Блэкбоксе они хранятся по рядам, т.е. a[0,0], a[0,1], и т.д. (быстрее меняется последний индекс).
5. Нужна осторожность с тем, как передаются скалярные параметры вроде len: по значению или по ссылке. Если имеет место второе (что обычно для фортрана), то второй параметр должен быть объявлен с VAR:

MODULE Epse21Dll3var;
	IMPORT SYSTEM;
	
	PROCEDURE Sum* ( IN a: ARRAY [untagged] OF REAL;  VAR len: INTEGER ): REAL;
		VAR  i: INTEGER;  s: REAL;
	BEGIN
		s := 0;
		FOR  i := 0  TO  len - 1  DO
			s := s + a[i]
		END;
		RETURN  s
	END Sum;
	
END Epse21Dll3var.

DevLinker.LinkDll dll3var.dll := Epse21Dll3var# ~

NB По поводу более сложных случае (невыровненные записи полей и т.п.) следует обратиться к документу Особенностиплатформы.