Copyright (C) А.Гавва V-0.4w май 2004

16. Интерфейс с другими языками

Обычно, не зависимо от того насколько хорош язык программирования, необходимо учитывать, что он должен сосуществовать с программным обеспечением которое написано с помощью других языков программирования. Разработчики Ады предусмотрели это и предлагают стандартные механизмы для осуществления связи с программами которые написаны на других языках.

При этом следует заметить, что различные компиляторы Ады могут расширять стандартные средства взаимодействия с другими языками программирования добавляя какие-либо дополнительные возможности, специфичные для конкретного компилятора. Поэтому, для получения более точных сведений, необходимо обратиться к справочному руководству используемого компилятора Ады.

16.1 Связь с другими языками в Ada83

Стандартным средством взаимодействия с другими языками в Ada83 является директива компилятора Interface, которая позволяет вызывать подпрограммы написанные на других языках программирования.

Предположим, что при работе в системе Unix, необходимо использовать команду kill. Для осуществления этого, необходимо выполнить следующее:


function kill( pid : in Integer; 
               sig : in Integer) return Integer;

pragma Interface(C, kill);

В данном случае, первый параметр директивы компилятора Interface - это язык вызываемой подпрограммы, а второй - имя подпрограммы под которым она (подпрограмма) известна в программе на Аде.

Пример пакета который импортирует функции написанные на Фортране может иметь следующий вид.


package MATHS is
        function sqrt(X : Float) return Float;
        function exp (X : Float) return Float;
private
        pragma Interface(Fortran, sqrt);
        pragma Interface(Fortran, exp);
end MATHS;

Необходимо заметить, что директива компилятора Interface не может быть использована с настраиваемыми подпрограммами.

16.2 Связь с другими языками в Ada95

Стандарт Ada95 внес в средства взаимодействия Ады с другими языками программирования некоторые изменения, которые облегчают использование Ады совместно с программным обеспечением написанным на других языках. Согласно стандарта Ada95, для организации взаимодействия с программным обеспечением, написанным на других языках программирования, можно использовать стандартные директивы компилятора Import, Export, Convention и Linker_Options. Кроме того, для описания трансляции типов данных между Адой и другими языками программирования, можно использовать предопределенную библиотеку - стандартный пакет Interfaces и его дочерние модули.

16.2.1 Директивы компилятора

Стандарт Ada95 содержит следующее описание стандартных директив компилятора Import, Export, Convention и Linker_Options:


pragma Import(
    [Convention =>] идентификатор_соглашения, [Entity =>] локальное_имя
 [, [External_Name =>] строковое_выражение]
 [, [Link_Name =>] строковое_выражение]);

pragma Export(
    [Convention =>] идентификатор_соглашения, [Entity =>] локальное_имя
 [, [External_Name =>] строковое_выражение]
 [, [Link_Name =>] строковое_выражение]);

pragma Convention ([Convention =>] идентификатор_соглашения,
                 [Entity =>] локальное_имя);

pragma Linker_Options (строковое_выражение);

При описании синтаксиса этих директив компилятора подразумевается, что:

Convention  -  обозначает язык или, точнее, соглашения (например, для вызова подпрограмм) используемые в конкpетном тpанслятоpе; в качестве идентификатор_соглашения могут использоваться:
  • Ada
  • Instrinsic
  • C
  • Fortran
  • Cobol
при этом, следует заметить, что обязательно поддерживаемыми являются только Ada и Instrinsic, кроме того, реализация конкретного компилятора может добавить любое другое значение
Entity  -  обозначает имя идентификатора (например, имя вызываемой подпpогpаммы) в Ада-пpогpамме
External_Name  -  обозначает имя идентификатора в чужом модуле (модуле написанном на другом языке программирования)
Link_Name  -  обозначает имя идентификатора с точки зpения линкера (pедактоpа связей)

Директива компилятора Import предназначена для импортирования объектов (подпрограмм или переменных), описанных на других языках программирования, в Ада-программу. С ее помощью можно вызывать подпрограммы или использовать переменные модулей которые написанны на других языках программирования.

Директива компилятора Export предназначена для экспортирования объектов (подпрограмм или переменных), написанных на Аде, для их использования в модулях, написанных на других языках программирования. Следует заметить, что в модулях, написанных на других языках программирования, может потребоваться выполнение вызовов adainit и adafinal для осуществления правильной инициализации и деструктуризации импортированного Ада-кода.

Директива компилятора Convention позволяет указать компилятору на необходимость использования определенных языковых соглашений для какого-либо объекта (подпрограммы или переменной), который впоследствии будет либо импортирован, либо экспортирован.

Директива компилятора Linker_Options предназначена для передачи дополнительных опций линкеру (редактору связей). Содержимое строкового параметра этой директивы будет зависеть от используемого линкера.

16.2.2 Интерфейсные пакеты

Стандарт Ada95 определяет интерфейс взаимодействия с языками программирования C, COBOL и Fortran. Для облегчения осуществления связи программ Ады с этими языками программирования существует стандартно определенная иерархия пакетов, состоящия из пакета Interfaces и его дочерних модулей:


package Interfaces
package Interfaces.C
package Interfaces.C.Pointers
package Interfaces.C.Strings
package Interfaces.COBOL
package Interfaces.Fortran

Эти интерфейсные пакеты обеспечивают достаточно мощные средства для взаимодействия с другими языками программирования.

16.3 Взаимодействие с программами написанными на C

Обсуждение взаимодействия с программами написанными на C полагается на описания которые содержатся в стандартном пакете Interfaces.C. В частности, этот пакет предусматривает средства преобразования объектов с типами языка C в объектыс типами Ады, и обратно.

16.3.1 Численные и символьные типы

С помощью использования следующих типов, переменные или результаты выражений Ады могут быть конвертированы в форму, совместимую с языком C, и обратно:

целочисленные типы    символьные типы    вещественные типы
Int    Char    C_Float
Short    Wchar_T    Double
Long         Long_Double
Size_T          

Например, для передачи целочисленного значения переменной Item как параметра для функции языка C которая ожидает получить параметр типа long double можно использовать следующее выражение Ады Long_Double(Item).

16.3.2 Строки языка C

Описание массива символов языка C имеет следующий вид:


type  Char_Array  is array (Size_T range <>) of aliased Char;

Для представления строк, в языке C используются массивы символов заканчивающиеся нулевым символом. Нулевой символ используется как индикатор конца строки. Таким образом, описание строки Name, содержащей текст "Vasya", которая может быть передана как параметр для функции языка C может иметь следующий вид:


Name  : constant  Char_Array := "Vasya" & nul;

Примечательно, что nul используется для представления нулевого символа (индикатор конца строки) и не является зарезервированным словом Ады null.

Для выполнения символьной конверсии можно использовать функции:


function To_C (Item: in Character) return Char;
function To_Ada (Item: in Char) return Character;

Для выполнения строковой конверсии можно использовать функции:


function To_C (Item       : in String;
               Append_Nul : in Boolean := True) return Char_Array;
function To_Ada (Item     : in Char_Array;
                 Trim_Nul : in Boolean := True) return String;

Не сложно догадаться, что функции с именами To_C должны использоваться для преобразования переменных в форму совместимую с языком C, а функии с именами To_Ada - обратно.

16.3.3 Примеры организации взаимодействия с C

Простым примером импорта C-функции может служить пример использования функции read системы UNIX в процедуре, написанной на Аде:


procedure Read( File_descriptor : in     Integer;
                Buffer          : in out String;
                No_Bytes        : in     Integer;
                No_Read         :    out Integer) is

    function Read( File_descriptor : Integer;
                   Buffer          : System.Address;
                   No_Bytes        : Integer) return Integer;

    pragma Import(C, read, "read");

begin
    No_Read := Read(File_descriptor,
                    Buffer(Buffer'First)'Address,
                    No_Bytes);
end Read;

Проблема связи с C возникает в ситуации, когда необходимо взаимодействовать с функциями которые имеют список параметров с переменным числом параметров неоднородного типа. Примером такой функции может служить C-функция printf. Необходимо отметить, что подобные функции, по своей природе, не надежны, и не существует удовлетворительного способа решения этой проблемы.

Однако, Ада позволяет взаимодействовать с функциями, имеющими переменное число параметров, но при этом тип параметров однороден. Для взаимодействие с такими функциями можно использовать типы неограниченных (unconstrained) массивов.

Предположим, что существует функция, написанная на C, описание которой имеет следующий вид:


void something(*int[]);

Мы можем использовать эту функцию в Аде следующим образом:


type Vector is array(Natural range <>) of Integer;

procedure Something(Item : Vector) is

    function C_Something(Address : System.Address);
    pragma Import(C, C_Something, "something");

begin
    if Item'Length = 0 then
        C_Something(System.Null_Address);
    else
        C_Something(Item(Item'First)'Address);
    end if;
end Something;

Рассмотрим более сложный пример, который демонстрирует использование C-функции execv системы UNIX, описанную в C следующим образом:


int execv(const char *path, char *const argv[]);

В данном случае, дополнительную сложность вызывает необходимость трансляции Ада-строк в массивы символов C-стиля. Перед тем как описывать непосредственную реализацию, необходимо сделать некоторые описания:


----------------------------------------------------------
type String_Ptr is access all String;
type String_Array is array(Natural range <>) of String_Ptr;

function execv( Path     : String;
                Arg_List : String_Array) return Interfaces.C.Int;

---------------------------------------------------------
-- execv заменяет текущий процесс на новый.
-- Список аргументов передается как параметры командной
-- строки для вызываемой программы.
--
-- Для вызова этой подпрограммы можно:
--
-- Option2 : aliased String := "-b";
-- Option3 : aliased String := "-c";
-- Option4 : String := "Cxy";
-- Result  : Interfaces.C.Int;
-- ...
-- Result := execv(Path => "some_program",
--                 -- построение массива указателей на строки...
--                 argv => String_Array'(new String'("some_program"),
--                                       new String'("-a"),
--                                       Option2'Unchecked_Access,
--                                       Option3'Unchecked_Access,
--                                       new String'('-' & Option4));
--
-- Допустимо использовать любую комбинацию
-- динамически размещаемых строк и 'Unchecked_Access
-- к aliased переменным.
-- Однако, необходимо отметить, что нельзя выполнить
-- "some_String"'Access, поскольку Ада требует имя,
-- а не значение, для отрибута 'Access

------------------------------------------------------------

Теперь, реализация может быть выполнена следующим образом:


function execv( Path : String;
                argv : String_Array ) return Interfaces.C.Int is

    Package C renames Interfaces.C;
    Package Strings renames Interfaces.C.Strings;

    C_Path  : constant Strings.Chars_Ptr(1..Path'Length + 1)
                                     := Strings.New_String(Path);

    type Char_Star_Array is array(1..argv'Length + 1) of
                                        Strings.Char_Array_Ptr;

    C_Argv : Char_Star_Array;
    Index  : Integer;
    Result : C.int;

    ------------------------------------------------------------
    function C_Execv( Path       : Strings.Char_Ptr;
                      C_Arg_List : Strings.Char_Ptr) return C.int;
    pragma Import(C, C_Execv, "execv");
    ------------------------------------------------------------

begin

    -- установка массива указателей на строки

    Index := 0;
    for I in argv'Range loop
            Index := Index + 1;
            C_Argv(Index) := Strings.New_String(argv(I).all));
    end loop;


    -- добавление C-значения null в конец массива адресов

    C_Argv(C_Argv'Last) := Strings.Null_Ptr;


    -- передача адресов первых элементов каждого параметра,
    -- как это ожидает C

    Result := C_Execv( C_Path(1)'Address, C_Argv(1)'Address));


    -- освобождение памяти, поскольку часто это не выполняется

    for I in argv'Range loop
            Strings.Free(argv(I));
    end loop;

    Strings.Free(C_Path);

    return Result;
end execv;

Примечательно, что передается адрес первого элемента массива, а не адрес самого массива. Массивы, описываемые как неограниченные, зачастую содержат вектор с дополнительной информацией, которая включает верхнюю и нижнюю границу массива.


Copyright (C) А.Гавва V-0.4w май 2004