Узгодження з C і C + +

Узгодження Java c мовою С відбувається досить прямолінійно Для стикування рідних методів з викликами C використовується згенерований заголовний файл, що містить всі необхідні оголошення типів і сигнатури функцій, а також програмні заглушки на C, які допомагають runtime-системі Java викликати ці методи

Ми розглянемо тільки основні моменти узгодження Програма, яка

використовується тут як приклад, являє собою текст класу LockableClass з пакету local: / На жаль, ми не змогли скористатися угодою про імена пакетів, оскільки це призвело б до подовження ідентифікаторів і ускладнило б роботу з книгою /

package local

import javaio*

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 { Systemload(&quotLockableFile&quot)

}

}

Після того, як ця програма буде оброблена компілятором Java, слід згенерувати заголовний файл за допомогою утиліти javah, Передавши їй імя класу, для якого створюється даний файл Утиліта згенерує файл, що містить всі оголошення і визначення на мові C, необхідні для стикування У нашому прикладі команда буде виглядати наступним чином:

javah localLockableFile

Імя згенерованого файлу визначається по імені класу, відповідно до описаної нижче схемою узгодження імен У нашому випадку заголовний файл буде називатися local_LockableFileh Вміст цього заголовного файлу і реалізація його рідних методів будуть приведені нижче в даній главі

Процес узгодження з C + + виглядає так само Фактично, узгодження з C + + зводиться до включення згенерованого заголовного файлу в оголошення extern &quot C&quot:

&quotC&quot:

extern &quotC&quot {

# include local_LockableFileh

}

Символи, які використовуються під час виконання програми для виклику оболонок рідних методів, створюються в просторі імен С, а не так званих перетворених (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-контекст

А21 Імена

Для перекладу імен методів і полів на мову C використовуються повні імена, що включають назви пакетів, в яких всі крапки () Замінюються підкресленнями (_) У тих мовах, де не підтримується можливість перевантаження методів (до них відноситься C) ви не зможете реалізувати в класі кілька методів з однаковими іменами, оскільки їм буде відповідати одне і те ж імя функції

Аналогічно переводяться та імена типів – за тим винятком, що перед імям типу ставиться префікс Class У програмі на C тип для LockableFile буде називатися Classlocal_LockableFile Для кожного класу також необхідний дескриптор (handle), оскільки він використовується для внутрішнього подання посилань Імя дескриптора збігається з імям класу, але замість префікса Class використовується префікс H Таким чином, тип дескриптора для LockableFile буде називатися Hlocal_LockableFile

Символи імен, що відносяться до набору ASCII (символи, менші або рівні \u007f) Переходять в C і в імена пакетів без змін Всі інші символи переводяться в вид _0dddd, Де d – Цифри, які використовуються в символьному поданні Java Наприклад, символ г (код \u00e3) Буде представлений ідентифікатором_000e3 Коса риса (/) В імені пакету переходить в символ підкреслення (_)

А22 Методи

Кожен рідний метод представляється у вигляді функції Наприклад, метод lock буде називатися local_LockableFile_lock і мати відповідні параметри Перший параметр функції – це дескриптор обєкта, для якого викликається метод (посилання this) Для статичних методів такої дескриптор завжди дорівнює null Нижче дескриптори розглядаються більш докладно

Для виклику методів потрібен додатковий шар у вигляді файлів-заглушок (stub files)

Останні також генеруються утилітою javah, Але з параметром -stubs:

javah -stubs localLockableFile

Така команда генерує вихідний файл на мові C, який повинен бути скомпільований і завантажений в динамічну бібліотеку разом в реалізаціями рідних методів Імя цього файлу збігається з імям заголовного файлу, однак розширення h змінюється на c – В нашому випадку це буде файл local_LockableFilec

А23 Типи

У наведеній нижче таблиці показано відповідність між примітивними типами 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 Нижче ви побачите кілька прикладів того, як це робиться

А25 Засоби безпеки

Засоби безпеки мови Java не мають аналогів в C Ви повністю відповідаєте за роботу програми на C (як це зазвичай буває) без будь-якої автоматичної допомоги з боку Java

А26 Робота з памяттю

Рідні методи можуть створювати нові обєкти Java за допомогою функцій, описаних нижче

А3 Приклад

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

#include &quotlocal_LockableFileh&quot

#include &lt&ltjavaStringh&gt&gt

#include &lt&ltfcntlh&gt&gt

#include &lt&lterrnoh&gt&gt

void local_LockableFile_lock(

struct Hlocal_LockableFile *this_h, long mode)

{

Classlocal_LockableFile *this = unhand(this_h)

struct flock lock

if (this-&gt&gtfd == -1 &amp&amp open_fd(this))

return / * Сталася помилка * /

if (setup_lock(&amplock, mode))

return

if(fcntl(this-&gt&gtfd, F_SETLKW, &amplock) == -1) SignalError(EE(),

&quotjava/io/IOException&quot, strerror(errno))

}

Спочатку ми включаємо потрібні заголовні файли – згенерований файл для

LockableFile, Допоміжний заголовний файл для роботи з рядками

&lt&ltjavastringh&gt&gt, Системний заголовний файл &lt&ltfcntlh&gt&gt, Що визначає виклики для здійснення блокування в POSIX, і системний заголовний файл &lt&lterrnoh&gt&gt для обробки помилок, отриманих в ході викликів системних функцій

Перший рядок у реалізації методу 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

lockl_whence = lockl_start = lockl_len = 0

lockl_type = F_UNLCK

if (fcntl(this-&gt&gtfd, F_SETLKW, &amplock) == -1) SignalError(EE(),

&quotjava/io/IOException&quot, strerror(errno))

}

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

Вправа А1

Якщо у вас є доступ до системи, відмінної від POSIX і підтримуючої блокування файлів, реалізуйте клас LockableFile з використанням її механізмів

Вправа А2

Якщо ви працюєте тільки з POSIX-сумісними системами або виконали вправу

А1, включіть в клас описані вище можливості

А31 Внутрішня будова LockableFile

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

static int open_fd(Classlocal_LockableFile *this)

{

char *path = allocCString(this-&gt&gtpath)

if ((this-&gt&gtfd = open(path, O_RDWR)) == -1) SignalError(EE(),

&quotjava/io/IOException&quot, strerror(errno))

free (path) / * Більше не потрібно * /

return (this-&gt&gtfd = -1)

}

static int setup_lock(

struct flock *lock, long mode)

{

lock-&gt&gtl_whence = lock-&gt&gtl_start = lock-&gt&gtl_len = 0

switch (mode) {

case local_LockableFile_READ: lock-&gt&gtl_type = F_RDLCK break

case local_LockableFile_WRITE: lock-&gt&gtl_type = F_WRLCK break

default:

SignalError(EE(), &quotjava/lang/IllegalArgumentException&quot,  NULL)

return 0

}

return 1

}

А4 Рядки

Рідні методи часто повинні використовувати строкові обєкти String Заголовний файл &lt&ltjavaStringh&gt&gt  визначає кілька функцій, що допомагають у вирішенні цього завдання Всі функції, що перетворюють обєкти 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

визначається при включенні файлу &lt&ltnativeh&gt&gt і являє собою

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 { SystemloadLibrary(&quotLocalString&quot)

}

}

Метод 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(),

&quotjava/lang/OutOfMemorytException&quot,  NULL)

return NULL

}

strxfrm(xfrm, str, xfrm_len)

retval = makeJavaString(xfrm, xfrm_len)

free(xfrm) free(str) return retval

}

Перше, що ми повинні зробити – це налаштувати локальний контекст функцією set_locale:

#include &lt&ltlocalel&gt&gt

void set_locale()

{

static int done_set = 0

if (done_set) { setlocale(LC_COLLATE, &quot&quot) done_set++

}

}

Функція setlocale встановлює алгоритм порівняння рядків для локального контексту, заданого змінними середовища, на що вказує параметр &quot&quot. Після виходу з 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(),

&quotjava/lang/OutOfMemorytException&quot,  NULL)

return NULL

}

unhand(retval)-&gt&gtbody[cnt] =

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

return retval

}

Спочатку ми намагаємося створити масив типу T_CLASS і перевіряємо, чи вдалося нам це Потім – отримуємо обєкт Class для заданого типу Функція FindClass отримує в якості параметрів середовище виконання, імя типу у вигляді рядка мови C і логічне значення, яке визначає необхідність завантаження класу в тому випадку, якщо він не був раніше завантажений Функція EE повертає поточний стан середовища виконання

Обєкт, що повертається функцією FindClass, Вставляється після кінця масиву і використовується runtime-системою для перевірки того, що кожен заносимий в масив елемент відноситься до правильного типу Щоб створити масив, який може містити будь-які обєкти, слід скористатися класом &quotjava/lang/Object&quot.

Реалізація LocalStringsort показує, як працює вся ця інфраструктура Спочатку давайте подивимося, як реалізована сама функція local_LocalString_sort:

#include &quotlocal_LocalStringh&quot

#include &lt&ltstdlibh&gt&gt

#include ,javaStringh&gt&gt

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 (),

&quotjava/lang/NullPointerException&quot, &quotnull array&quot)

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(),

&quotjava/lang/OutOfMemorytException&quot,  NULL)

return NULL

}

for (i = 0 i &lt&lt str_cnt i++) {

if (in_strings-&gt&gtbody[i] == NULL) { SignalError(EE(), &quotjava/lang/NullPointerException&quot, &quotNull string in array&quot)

goto cleanup

}

strings[i] = makeCString(in_strings-&gt&gtbody[i])

if (strings[i] == NULL)

goto cleanup / * Функція SignalError () уже викликана * /

}

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

retval = (HArrayOfString *)

alloc_class_array(&quotjava/lang/String&quot,  str_cnt)

retstrs = unhand(retval)

for (i = 0 i &lt&lt str_cnt i++) {

retstrs-&gt&gtbody[i] =

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

}

cleanup: free(strings) return retval

}

Спочатку ми перевіряємо, чи дійсно був переданий масив Потім встановлюється локальний контекст, як це робилося в LocalStringxfrm

Потім – створюємо строковий масив, в якому буде зберігатися вміст сортируемих обєктів 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)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*