Главная » Java » Согласование с C и C++

0

Согласование Java c языком С происходит довольно прямолинейно. Для стыковки родных методов с вызовами C используется сгенерированный  заголовочный файл, содержащий все необходимые объявления типов и сигнатуры функций, а также программные “заглушки” на C, которые помогают runtime-системе  Java вызывать эти методы.

Мы рассмотрим только основные моменты согласования. Программа, которая

используется здесь в качестве примера, представляет собой текст класса LockableClass из пакета local: /К сожалению, мы не смогли воспользоваться соглашением об именах пакетов, поскольку это привело бы к удлинению идентификаторов  и затруднило бы работу с книгой./

package local;

import java.io.*;

class LockableFile extends File { LockableFile(String path) {

super(path);

}

// допустимые параметры lock()

public final static int READ = 0, WRITE = 1;

public native void lock(int type) throws IOException; public native void unlock() throws IOException; private int fd = -1;

static { System.load("LockableFile");

}

}

После того, как эта программа будет обработана компилятором Java, следует сгенерировать заголовочный файл с помощью утилиты javah, передав ей имя класса, для которого создается данный файл. Утилита сгенерирует файл, содержащий все объявления и определения на языке C, необходимые для стыковки. В нашем примере команда будет выглядеть следующим образом:

javah local.LockableFile

Имя сгенерированного  файла определяется по имени класса, в соответствии с описанной ниже схемой согласования имен. В нашем случае заголовочный файл будет называться local_LockableFile.h. Содержимое этого заголовочного файла и реализация его родных методов будут приведены ниже в данной главе.

Процесс согласования с C++ выглядит так же. Фактически, согласование с C++ сводится к включению сгенерированного  заголовочного файла в объявление extern " C":

"C":

extern "C" {

# include local_LockableFile.h

}

Символы, которые используются во время выполнения программы для вызова оболочек родных методов, создаются в пространстве имен С, а не так называемых “преобразованных”  (mangled) имен C++, поэтому родные методы должны быть написаны на C. /На самом деле это не совсем так —приложив некоторые усилия, опытный программист сможет обойти это ограничение, но для простоты описания и реализации будет лучше согласиться с ним./ Основное следствие заключается в том, что вы не сможете использовать перегрузку методов C++ для реализации родных методов. В сущности, правильнее было бы сказать, что для родных методов прямая стыковка с C++ вообще не используется, однако возможна косвенная стыковка за счет вызовов функций C в C++. Разумеется, согласование программ на Java с C++ может быть улучшено, и несомненно это будет сделано в будущих версиях.

Реализуя родные методы на C или C++, вы должны связать откомпилированный код с приложением на Java; для этого необходимо создать библиотеку динамического связывания и соединить ее со своей программой. Чаще всего программист выделяет статический блок, наподобие приведенного выше в классе LockableFile, а затем вызывает один из двух статических методов класса System, предназначенных  для загрузки библиотек:

public synchronized void load(String pathname)

Загружает динамическую библиотеку, расположенную по заданному полному имени pathname. В некоторых случаях полное имя модифицируется в соответствии с требованиями локальной системы. Если файл не обнаружен или не найдены символы, необходимые для работы библиотеки, возбуждается исключение UnsatisfiedLinkError.

public synchronized void loadLibrary(String libname)

Загружает динамическую библиотеку с указанным именем libname. Вызов LoadLibrary должен осуществляться в статическом инициализаторе первого загружаемого класса (то есть класса, содержащего вызываемый метод main). Попытки многократной загрузки одной и той же библиотеки игнорируются. Если библиотека не найдена, возбуждается исключение UnsatisfiedLinkError.

Метод load требует указания полного имени файла, однако во время разработки лучше пользоваться именно этой версией, поскольку она нормально сообщает о неопределенных символах. Метод loadLibrary переводит любую ошибку, включая неопределенные

символы, в ошибку “библиотека не найдена”. Если в библиотеке присутствуют

неопределенные символы, то подобный перевод скроет от вас важную информацию об истинной причине происходящего. Если вы будете пользоваться методом load до того момента, когда родные методы заработают, после чего замените его методом loadLibrary, то сможете добиться более подробной информации в ходе разработки и переносимости кода после ее завершения.

Методы загрузки библиотек класса System представляют собой сокращения для вызова тех же самых методов класса Runtime, представляющего  текущий runtime-контекст.

А.2.1 Имена

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

Аналогично переводятся и имена типов — за тем исключением, что перед именем типа ставится префикс Class. В программе на C тип для LockableFile будет называться Classlocal_LockableFile. Для каждого класса также необходим дескриптор (handle), поскольку он используется для внутреннего представления ссылок. Имя дескриптора совпадает с именем класса, но вместо префикса Class используется префикс H. Таким образом, тип дескриптора для LockableFile будет называться Hlocal_LockableFile.

Символы имен, относящиеся к набору ASCII (символы, меньшие или равные \u007f) переходят в C и в имена пакетов без изменений. Все остальные символы переводятся в вид _0dddd, где d — цифры, используемые в символьном представлении Java. Например, символ г (код \u00e3) будет представлен идентификатором  _000e3. Косая черта (/) в имени пакета переходит в символ подчеркивания (_).

А.2.2 Методы

Каждый родной метод представляется в виде функции. Например, метод lock будет называться local_LockableFile_lock и иметь соответствующие параметры. Первый параметр функции — это дескриптор объекта, для которого вызывается метод (ссылка this). Для статических методов такой дескриптор всегда равен null. Ниже дескрипторы рассматриваются более подробно.

Для вызова методов нужен дополнительный  слой в виде файлов-заглушек  (stub files).

Последние также генерируются утилитой javah, но с параметром -stubs:

javah -stubs local.LockableFile

Такая команда генерирует исходный файл на языке C, который должен быть скомпилирован и загружен в динамическую библиотеку вместе в реализациями родных методов. Имя этого файла совпадает с именем заголовочного файла, однако расширение h изменяется на c — в нашем случае это будет файл local_LockableFile.c.

А.2.3 Типы

В приведенной ниже таблице показано соответствие между примитивными типами Java и типами языка C, когда они используются в качестве параметров методов или полей (согласование типов для массивов рассматривается ниже).

Тип Java

Тип C

boolean

long

byte

long

short

long

int

long

long

int64_t

float

float

double

double

char

long

Классы Java представляются в языке C структурами (struct). Все нестатические поля класса являются членами структуры, а их имена совпадают с именами в Java (за исключением символов Unicode, отображаемых в эквиваленты _0dddd). Это означает, что класс, содержащий родные методы, не может иметь два нестатических поля с одинаковыми именами; в противном случае структура на языке C содержала бы члены с одинаковыми именами, что запрещено.

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

Каждая статическая константа с атрибутом final представлена константой #define с префиксами (именем пакета и класса). Статические поля, не являющиеся final, не переводятся ни во что. Например, тип и константы для класса LockableFile определяются в заголовочном файле следующим образом:

typedef struct Classlocal_LockableFile {

struct Hjava_lang_String *path;

/* недоступное статическое поле: separator */

/* недоступное статическое поле: separatorChar */

/* недоступное статическое поле: pathSeparator */

/* недоступное статическое поле: pathSeparatorChar */

#define local_LockableFile_READ 0L

#define local_LockableFile_WRITE 1L

long fd;

} Classlocal_LockableFile;

Ссылки на объекты представляются типом “дескриптор”, в составном имени которого Class

заменяется на H. Ссылка на класс LockableFile будет называться Hlocal_LockableFile. Макрос unhand получает дескриптор и возвращает указатель на структуру, которая представляется этим дескриптором.

Ниже приведены сигнатуры функций языка C, в которых ниже мы определим родные методы класса LockableFile:

extern void local_Lockable_File_lock(

struct Hlocal_LockableFile *, long);

extern void local_Lockable_File_unlock(

struct Hlocal_LockableFile *);

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

А.2.5 Средства безопасности

Средства безопасности языка Java не имеют аналогов в C. Вы полностью отвечаете за работу программы на C (как это обычно бывает) без какой-либо автоматической помощи со стороны Java.

А.2.6 Работа с памятью

Родные методы могут создавать новые объекты Java с помощью функций, описанных ниже.

А.3 Пример

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

#include "local_LockableFile.h"

#include <<javaString.h>>

#include <<fcntl.h>>

#include <<errno.h>>

void local_LockableFile_lock(

struct Hlocal_LockableFile *this_h, long mode)

{

Classlocal_LockableFile *this = unhand(this_h);

struct flock lock;

if (this->>fd == -1 && !open_fd(this))

return;       /* произошла ошибка */

if (!setup_lock(&lock, mode))

return;

if(fcntl(this->>fd, F_SETLKW, &lock) == -1) SignalError(EE(),

"java/io/IOException", strerror(errno));

}

Сначала мы включаем нужные заголовочные файлы — сгенерированный  файл для

LockableFile, вспомогательный  заголовочный файл для работы со строками

<<javastring.h>>,  системный заголовочный файл <<fcntl.h>>, определяющий вызовы для осуществления блокировки в POSIX, и системный заголовочный файл <<errno.h>> для обработки ошибок, полученных в ходе вызовов системных функций.

Первая строка в реализации метода lock переводит дескриптор this_h в указатель на структуру Classlocal_LockableFile, для чего используется макрос unhand, который возвращает объект, соответствующий  данному дескриптору. Ссылкам null в Java ставится в соответствие указатели на дескрипторы, которые также равны NULL. Чтобы убедиться в том, что передача ссылки null не приведет к ошибкам, необходимо проверить дескриптор перед тем, как вызывать для него макрос unhand — это демонстрируется  в последующих примерах.

Во второй строке local_LockableFile_lock объявляется структура flock. Структура flock используется для работы с блокировкой в POSIX. Реализации open_fd и setup_lock приводятся в разделе “Внутреннее строение LockableFile”.

Далее мы проверяем, имеется ли файловый дескриптор. Если он отсутствует и функция open_fd не может открыть файл, видимо, было возбуждено исключение, сигнализирующее об ошибке, поэтому мы просто выходим из функции. Если же дескриптор имеется, то структуру flock необходимо подготовить вызовом внутренней функции setup_lock. Вызов функции также может закончиться неудачей (например, если mode имеет недопустимое значение) и возбуждением исключения — и в этом случае мы выходим из функции. Функции open_fd и setup_lock являются частью кода, специфичного для POSIX.

Затем мы пытаемся заблокировать файл с помощью режима F_SETLKW функции POSIX с именем fcntl, который при необходимости ожидает возможности блокировки. Если fcntl возвращает -1, то попытка блокировки оказалась неудачной, и мы возбуждаем исключение, вызывая runtime-функцию  Java с именем SignalError:

void SignalError(ExecEnv *exenu, char *type, char *constructor)

Сигнализирует о том, что после выхода из родного метода должно быть возбуждено исключение. Структура exenu обычно возвращается функцией EE и представляет текущее состояние среды. Параметр type является полным именем класса возбуждаемого объекта-исключения, в котором каждая точка (.) заменяется чертой (/). Последний параметр содержит строковое описание исключения или NULL при его отсутствии.

В нашем случае используется значение функции POSIX с именем strerror, которая возвращает строку с описанием номера ошибки. Практически это самое лучшее, что мы можем сделать для описания ошибок в родных методах.

Функция SignalError лишь подготавливает исключение; она не возбуждает его. В языке C исключения не предусмотрены, поэтому возбудить их из программы невозможно. После выхода из функции, содержащей реализацию родного метода, runtime-система проверяет флаги и по ним определяет, был ли получен сигнал о возбуждении исключения. Если такой сигнал получен, то runtime-система возбуждает исключение. Подобная схема позволяет в программе на C выполнить необходимые завершающие действия после “возбуждения” исключения. В функции local_LockableFile_lock такие действия не требуются, поэтому после “возбуждения” исключения мы просто выходим из нее.

Реализация unlock выглядит проще. Мы подготавливаем структуру flock и вызываем fcntl для режима F_UNLCK (снятие блокировки). И снова при неудаче возбуждается исключение, содержащее строку с описанием ошибки:

void local_LockableFile_unlock(

struct Hlocal_LockableFile *this_h)

{

Classlocal_LockableFile *this = unhand(this_h);

struct flock lock;

lock.l_whence = lock.l_start = lock.l_len = 0;

lock.l_type = F_UNLCK;

if (fcntl(this->>fd, F_SETLKW, &lock) == -1) SignalError(EE(),

"java/io/IOException", strerror(errno));

}

В класс LockableFile можно внести ряд усовершенствований. Например, создать функцию для проверки того, заблокирован ли файл и если да, то каким программным потоком. Можно сконструировать специальный вариант lock без ожидания, который лишь осуществляет блокировку, если она возможна, а в противном случае завершает свою работу. Или создать отдельные исключения для каждой ошибки, чтобы ошибка “файл не существует” отличалась от “доступ запрещен”.

Упражнение А.1

Если у вас имеется доступ к системе, отличной от POSIX и поддерживающей  блокировку файлов, реализуйте класс LockableFile с использованием ее механизмов.

Упражнение А.2

Если вы работаете только с POSIX-совместимыми системами или выполнили упражнение

А.1, включите в класс описанные выше возможности.

А.3.1 Внутреннее строение LockableFile

Для полноты картины приведем текст внутренних функций, используемых классом LockableFile. Статические функции open_fd и setup_lock используются при реализации родных методов lock и unlock:

static int open_fd(Classlocal_LockableFile *this)

{

char *path = allocCString(this->>path);

if ((this->>fd = open(path, O_RDWR)) == -1) SignalError(EE(),

"java/io/IOException", strerror(errno));

free(path);     /* больше не требуется */

return (this->>fd != -1);

}

static int setup_lock(

struct flock *lock, long mode)

{

lock->>l_whence = lock->>l_start = lock->>l_len = 0;

switch (mode) {

case local_LockableFile_READ: lock->>l_type = F_RDLCK; break;

case local_LockableFile_WRITE: lock->>l_type = F_WRLCK; break;

default:

SignalError(EE(), "java/lang/IllegalArgumentException",  NULL);

return 0;

}

return 1;

}

А.4 Строки

Родные методы часто должны использовать строковые объекты String. Заголовочный файл <<javaString.h>>  определяет несколько функций, помогающих в решении этой задачи. Все функции, преобразующие объекты String языка Java в строки C, переносят в них только младшие 8 бит символов Unicode. Эти функции работают с дескрипторами, которые передаются родным методам, хранятся в структурах языка C или создаются с помощью приведенных ниже функций.

char *allocCString(Hjava_lang_String *str)

Вызывает функцию malloc языка C для создания буфера, размер которого достаточен для хранения строки. Когда вы закончите работать со строкой, не забудьте вызвать free для возвращенного указателя.

char *javaString2CString(Hjava_lang_String *str, char buffer[], int length)

Копирует до length символов из str в buffer. Размещение и освобождение буфера лежит на совести программиста. Для удобства функция возвращает buffer.

char *makeCString(Hjava_lang_String *str)

Возвращает строку языка C, которая может быть уничтожена сборщиком мусора. Сборщик мусора в поисках ссылок сканирует не только объекты Java, но и данные C, поэтому строка не может быть уничтожена им в случае, если указатель на нее используется в родном методе.

int javaStringLength(Hjava_lang_String *str)

Возвращает длину строки Java. Функции передается параметр-дескриптор.

unicode *javaString2unicode(Hjava_lang_String *str, unicode *buf, int len)

Копирует до len символов Unicode из str в буфер buf. Тип данных unicode

определяется при включении файла <<native.h>> и представляет собой

16-разрядное символьное значение.

Для создания новых объектов используется функция makeJavaString:

Hjava_lang_String  *makeJavaString(char *str, int len)

Возвращает дескриптор нового строкового объекта, созданного на основе строки str языка C, с использованием начальных len байтов строки. Длина не должна учитывать нуль-байт, завершающий строку в языке C.

Если какой-либо из этих методов столкнется с ошибкой (например, нехваткой памяти), он вызывает SignalError с соответствующим  исключением и возвращает NULL. В этом случае необходимо выполнить нужные завершающие действия и выйти из функции.

Ниже показано, как можно написать родной метод с использованием  функции strxfrm, соответствующей  стандарту ANSI C, которая по обычной 8-разрядной строке стандарта Latin-1 создает порядковую строку, соответствующую локальному языковому контексту. Созданная строка может использоваться для сравнения исходной строки с другими строками. Локальный языковой контекст определяет порядок сортировки принятый во французском языке, норвежском, испанском и т. д. Сортировка строк с помощью функции strxfrm выполняется следующим образом:

1.  Вызовите strxfrm для двух строк, чтобы создать для каждой из них порядковую строку.

1.  Сравните порядковые строки функцией strcmp, которая возвращает отрицательное, равное нулю или положительное число, если первая строка соответственно меньше, равна или больше второй.

1.  Упорядочите исходные строки в зависимости от результатов вызова strcmp для двух порядковых строк.

Эта процедура может пригодиться, если вашей программе часто приходится сортировать строки в соответствии с локальным языковым порядком. Другая возможность заключается в том, чтобы применить strcoll вместо strcmp. Вызов функции strcoll обходится дороже,

чем обращение к strcmp, но он не требует хранения порядковых строк для их последующего использования.

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

package local;

public class LocalString {

/** возвращает порядковую строку для str */

public native static String xfrm(String str);

/** сортирует массив в соответствии с локальным контекстом */

public native static String[] sort(String[] input);

static { System.loadLibrary("LocalString");

}

}

Метод sort мы рассмотрим в следующем разделе.

Вот один из способов реализации xfrm: HString *

local_LocalString_xfrm(

struct Hlocal_LocalString *this_h, Hjava_lang_String *str_h)

{

Hjava_lang_String *retval;

char *str, *xfrm;

size_t xfrm_len;

set_locale();

str = allocString(str_h);

if (str == Null)

return NULL; /* функция allocString() вызвала SignalError */

xfrm_len = strxfrm(Null, str, 0);

if ((xfrm = (char *)malloc(xfrm_len + 1)) == NULL) { SignalError(EE(),

"java/lang/OutOfMemorytException",  NULL);

return NULL;

}

strxfrm(xfrm, str, xfrm_len);

retval = makeJavaString(xfrm, xfrm_len);

free(xfrm); free(str); return retval;

}

Первое, что мы должны сделать – это настроить локальный контекст функцией set_locale:

#include <<locale.l>>

void set_locale()

{

static int done_set = 0;

if (!done_set) { setlocale(LC_COLLATE, ""); done_set++;

}

}

Функция setlocale устанавливает алгоритм сравнения строк для локального контекста, заданного переменными среды, на что указывает параметр "". После выхода из set_locale мы выделяем место под новую строку языка C, содержимое которой совпадает с содержимым передаваемого параметра, и проверяем возможные ошибки. Далее мы используем вариант функции strxfrm, который возвращает количество символов для порядковой строки, выделяем буфер, рассчитанный на данное количество символов плюс один символ для нуль-байта, и заполняем буфер функцией strxfrm. Затем мы вызываем makeJavaString, чтобы создать новый объект String, содержащий порядковую строку. Перед тем, как возвращать ее, необходимо освободить выделенную память. Наконец, мы возвращаем объект String, содержащий порядковую строку.

Ничто не может помешать программе на C модифицировать символы в структуре Classjava_lang_String, соответствующей  объекту String языка Java. Тем не менее, это нарушает гарантию того, что объекты String доступны только для чтения, на которую часто полагаются runtime-система Java, сгенерированный  программный код и классы. Например, два объекта String с одинаковым содержимым часто могут совместно использовать одну и ту же область памяти. Модификация одного объекта String не только нарушает гарантию — она может привести к одновременной модификации других объектов String.

Упражнение А.3

Напишите родной метод, который выводит содержимое объекта String.

Упражнение А.4

Напишите родной метод, который возвращает системную строку, недоступную классам

Java — например, имя текущего рабочего каталога или папки.

Упражнение А.5

Напишите класс, который использует родные методы для того, чтобы предоставить программам на Java доступ к какой-нибудь библиотеке вашей системы.

А.5 Массивы

Массивы в Java являются типизированными  — они могут состоять из значений примитивного типа (массив int) или из объектов класса. Тип массива Java учитывается при его переводе в C. Существуют специальные типы массивов для примитивных значений и универсальный тип для массивов, содержащих объекты. Каждый массив в языке C представлен структурой следующего вида:

typedef struct { CType *body;

} ArrayOfJavaType;

В каждой структуре имеется поле с именем body, указывающее на элементы массива; CType — тип языка C, которому соответствует тип элементов массива, а JavaType — имя типа в языке Java. В таблице показано, как происходит перевод различных массивов из Java в C:

Тип массива в Java

Имя структуры

Тип body

Тип размещаемого массива в C

boolean

ArrayOfInt

long

T_BOOLEAN

byte

ArrayOfByte

char

T_BYTE

short

ArrayOfShort

short

T_SHORT

int

ArrayOfInt

long

T_INT

long

ArrayOfLong

int64_t

T_LONG

float

ArrayOfFloat

float

T_FLOAT

double

ArrayOfDouble

double

T_DOUBLE

char

ArrayOfChar

unicode

T_CHAR

Object

ArrayOfObject

Hobject

T_CLASS

Доступ к элементам массива осуществляется стандартным образом, в виде body[i]; максимальный индекс на единицу меньше количества элементов в массиве. Функция obj_Length возвращает количество элементов в заданном массиве.

Для создания массива используется функция ArrayAlloc:

Handle *ArrayAlloc(int  type, int size)

Создает новый массив заданного типа type, который должен быть одним из типов в приведенной выше таблице. Если параметр type равен T_CLASS,

создается один дополнительный  элемент, указывающий на объект класса,

соответствующего  типу элементов массива.

Приведем в качестве примера функцию, которая создает массив объектов заданного типа:

HArrayOfObject *

alloc_class_array( char *type, int cnt)

{

HArrayOfObject *retval;

retval = (HArrayOfObject *)ArrayAlloc(T_CLASS, cnt);

if (retval == NULL) { SignalError(EE(),

"java/lang/OutOfMemorytException",  NULL);

return NULL;

}

unhand(retval)->>body[cnt] =

(HObject *)FindClass(EE(), type, TRUE);

return retval;

}

Сначала мы пытаемся создать массив типа T_CLASS и проверяем, удалось ли нам это. Затем – получаем объект Class для заданного типа. Функция FindClass получает в качестве параметров среду выполнения, имя типа в виде строки языка C и логическое значение, которое определяет необходимость загрузки класса в том случае, если он не был ранее загружен. Функция EE возвращает текущее состояние среды выполнения.

Объект, возвращаемый функцией FindClass, вставляется после конца массива и используется runtime-системой  для проверки того, что каждый заносимый в массив элемент относится к правильному типу. Чтобы создать массив, который может содержать любые объекты, следует воспользоваться классом "java/lang/Object".

Реализация LocalString.sort показывает, как работает вся эта инфраструктура. Сначала давайте посмотрим, как реализована сама функция local_LocalString_sort:

#include "local_LocalString.h"

#include <<stdlib.h>>

#include ,javaString.h>>

HArrayOfString *

local_LocalString_sort(

struct Hlocal_LocalString *this_h, HArrayOfString *strngs_h)

{

ClassArrayOfString *in_strings; HArrayOfString *retval = NULL; ClassArrayOfString *retstrs; char **string = NULL;

int i, str_cnt;

if (strings_h ==NULL) { /* проверить ссылку */ SignalError(EE(),

"java/lang/NullPointerException", "null array");

return NULL;

}

set_locale();

in_strings = unhand(strings_h);

str_cnt = obj_length(strings_h);

strings = (char **)malloc(str_cnt * sizeof *strings);

if (strings == NULL) { SignalError(EE(),

"java/lang/OutOfMemorytException",  NULL);

return NULL;

}

for (i = 0; i << str_cnt; i++) {

if (in_strings->>body[i] == NULL) { SignalError(EE(), "java/lang/NullPointerException", "Null string in array");

goto cleanup;

}

strings[i] = makeCString(in_strings->>body[i]);

if (strings[i] == NULL)

goto cleanup; /* функция SignalError() уже вызвана */

}

qsort(strings, str_cnt, sizeof *strings, cmp);

retval = (HArrayOfString *)

alloc_class_array("java/lang/String",  str_cnt);

retstrs = unhand(retval);

for (i = 0; i << str_cnt; i++) {

retstrs->>body[i] =

makeJavaString(strings[i], strlen(strings[i]));

}

cleanup: free(strings); return retval;

}

Сначала мы проверяем, действительно ли был передан массив. Затем устанавливается локальный контекст, как это делалось в LocalString.xfrm.

Затем — создаем строковый массив, в котором будет храниться содержимое сортируемых объектов String. Для создания строкового массива необходимо знать, сколько строк в него входит — это число получается вызовом obj_length для дескриптора строкового массива. Затем мы используем функцию malloc для выделения памяти под указатели и проверяем возвращаемое ею значение.

Проверка ошибок чрезвычайно важна. Родные методы, не анализирующие возможные ошибки, нарушают те гарантии безопасности, которые Java предоставляет программистам. Например, если бы мы пропустили проверку равенства ссылки null, то при попытке использования этого указателя вместо возбуждения исключения

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

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

Теперь массив strings содержит эквиваленты исходных строк в языке C. Мы вызываем стандартную библиотечную функцию C с именем qsort, чтобы отсортировать массив, и передаем ей в качестве последнего аргумента функцию (в данном случае — cmp):

static int

cmp(const void *str1, const void *str2)

{

return strcoll(*(char **)str1, *(char **)str2);

}

Функция сравнения cmp преобразует свои параметры-указатели к типу char ** (qsort требует, чтобы параметры имели тип void *; предполагается, что программист сам произведет все необходимые приведения типов). Затем вызывается стандартная библиотечная функция C с именем strcoll, которая сравнивает две строки с учетом локального контекста. Эта функция возвращает отрицательное, равное нулю или положительное число, если первая строка соответственно меньше, равна или больше второй в локальном языковом контексте упорядочения строк. То же самое функция qsort ожидает от своей функции сравнения, так что значение, возвращаемое strcoll, может возвращаться и самой функцией cmp.

После выполнения qsort, строки оказываются отсортированными  в соответствии с функцией strcoll. Все, что остается — построить новый массив объектов String и заполнить его результатами. Далее, строится массив с помощью рассмотренной выше функции alloc_class_array и затем в цикле вызывается makeJavaString для задания каждого

объекта String. Наконец, мы освобождаем массив strings, созданный функцией malloc, и

возвращаем результат.

Упражнение А.6

Модифицируйте класс LocalString, чтобы он мог работать с объектами, каждый из которых обладает собственным локальным контекстом, вместо того, чтобы полагаться на один общий контекст. Методы перестанут быть статическими, и понадобится новое строковое поле, определяющее контекст. Помните, что функция POSIX с именем setlocale устанавливает локальный контекст лишь до следующего вызова setlocale.

Источник: Арнольд К., Гослинг Д. – Язык программирования Java (1997)

По теме:

  • Комментарии