1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
附录A 使用非JAVA代码.md 150 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

Приложение A Использование неприсоединённого кода от Java

Язык Java со своими стандартными API (прикладными программными интерфейсами) вполне достаточен для написания приложений. Однако в некоторых случаях всё же требуется использование кода, который не является частью Java. Например, иногда нам нужно обращаться к специальным характеристикам операционной системы, работать с особым оборудованием, переиспользовать существующие не-Java интерфейсы или использовать "временные" фрагменты кода и т. д. Общение с не-Java кодом требует специальной поддержки компилятора и "виртуальной машины", а также дополнительных средств для отображения Java кода как не-Java кода (есть простой метод: пример объясняется в разделе "Один веб-приложение" главы 15, показывающий, как использовать стандартные входные и выходные данные для связи с не-Java кодом). В настоящее время различные производители предлагают различные решения: Java 1.1 предлагает "Java Native Interface" (JNI), Netscape предлагает свой план "Java Runtime Interface", Microsoft предлагает J/Direct, "Raw Native Interface" (RNI) и интеграцию Java/COM.Различные подходы различных производителей могут усложнять жизнь программистам. Если Java-приложение должно вызывать native методы, программист может быть вынужден реализовать различные версии этих методов — в зависимости от платформы, на которой работает приложение. Программист может фактически нуждаться в различных версиях Java-кода и различных Java-виртуальных машинах.Другим решением является CORBA (Common Object Request Broker Architecture), это технология интеграции, разработанная организацией OMG (Object Management Group, некоммерческой организацией). CORBA не является частью какого-либо языка программирования, а представляет собой стандарт для реализации универсальной шины коммуникаций и услуг. Она позволяет объектам, реализованным на различных языках, взаимодействовать друг с другом. Эта шина коммуникаций называется ORB (Object Request Broker) и реализуется различными производителями, но не является частью стандарта Java.

Это приложение представляет собой общую характеристику JNI, J/Direct, RNI, интеграции Java/COM и CORBA. Оно не углубляется в детали и иногда предполагает, что читатель уже знаком с концепциями и технологиями. Однако в конце концов читатель должен иметь возможность сравнивать различные подходы и выбирать наиболее подходящий для решения своих задач.## A.1 Java Native Interface (JNI)

Java Native Interface (JNI) — это очень широкий программный интерфейс, который позволяет вызывать native методы из Java-приложений. Он был введен с Java 1.1 и поддерживает некоторую совместимость с соответствующими возможностями Java 1.0 — "Native Method Interface" (NMI). Некоторые особенности дизайна NMI делают его неподдерживаемым всеми виртуальными машинами. В связи с этим будущие версии языка Java могут прекратить поддержку NMI, поэтому здесь мы не будем рассматривать этот аспект.

На данный момент JNI может взаимодействовать только с встроенными методами, написанными на C или C++. При помощи JNI наши встроенные методы могут:

  • Получать доступ к системным ресурсам.
  • Выполнять быстрый доступ к данным.
  • Обмениваться данными между Java-приложением и native кодом.+ Создавать, проверять и обновлять объекты Java (включая массивы и строки)
  • Вызывать методы Java

  • Ловить и выбрасывать "исключения"

  • Загружать классы и получать информацию о классах

  • Выполнять проверку типов во время выполнения

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

А.1.1 Вызов встроенных методов

Давайте начнем с простого примера: программа на Java вызывает встроенный метод, который затем вызывает Win32 API функцию MessageBox(), чтобы отобразить графическое окно сообщений. Этот пример будет использоваться вместе с J/Direct позже. Если ваша платформа не является Win32, просто замените заголовочный файл C следующего содержания:

#include <windows.h>

на

#include <stdio.h>

и замените вызов MessageBox() на вызов printf() соответственно.

Первым шагом является создание объявления встроенного метода и его параметров на Java:

public class ShowMsgBox {
  public static void main(String[] args) {
    ShowMsgBox app = new ShowMsgBox();
    app.showMessage("Сгенерировано с помощью JNI");
  }

  private native void showMessage(String msg);

  static {
    System.loadLibrary("MsgImpl");
  }
}
```После объявления встроенного метода следует статический блок кода, который вызывает `System.loadLibrary()`. Это можно делать в любое время, но так будет более подходящим. `System.loadLibrary()` загружает библиотеку DLL в память и устанавливает связь с ней. Библиотека DLL должна находиться в системной директории или в том же каталоге, где находятся Java классы. В зависимости от конкретной платформы JVM автоматически добавляет соответствующее расширение файла.### Примечание к директиве препроцессора `#ifdef __cplusplus`

Из директивы препроцессора `#ifdef __cplusplus` можно сделать вывод, что этот файл может компилироваться как C-компилятором, так и C++-компилятором. В начале файла используется команда `#include`, которая включает заголовочный файл `jni.h`, определяющий типы данных, используемые в остальной части файла. Макросы `JNIEXPORT` и `JNICALL` расшифровываются таким образом, чтобы работать с платформозависимыми вызовами, а также используются JNI-определённые данные типа `JNIEnv`, `jobject` и `jstring`.

Также стоит отметить, что конструкция `extern "C"` позволяет использовать этот заголовочный файл в C++-коде, обеспечивая корректность внешних объявлений при взаимодействии с C-библиотеками.#### 2. Управление именами и сигнатуры функций

JNI стандартизирует названия методов, реализованных на языках программирования; это важно, поскольку является частью механизма, связывающего вызовы Java с внутренними методами. В общем, все внутренние методы должны начинаться со слова «Java», за которым следует имя Java-метода; разделитель используется в виде символа подчеркивания. Если Java-методы имеют несколько реализаций (перегрузка), то к имени добавляется сигнатура функции. В комментариях перед прототипом можно найти описание внутренней сигнатуры. Для получения более подробной информации о правилах названий и сигнатур внутренних методов обратитесь к соответствующей документации JNI.

#### 3. Реализация собственного DLL

На данном этапе требуется написать C- или C++-файл, содержащий заголовочные файлы, созданные с помощью `javah`; затем реализовать внутренние методы; и скомпилировать его, чтобы получить динамически связываемую библиотеку (DLL). Эта часть работы зависит от конкретной платформы, поэтому предполагается, что читатель уже знает, как создать DLL. Ниже приведён пример кода, реализующий внутренний метод через вызов Win32-API:

```c
#include <windows.h>
#include "ShowMsgBox.h"

BOOL APIENTRY DllMain(HANDLE hModule,
                       DWORD dwReason, void** lpReserved) {
    return TRUE;
}
``````java
JNIEXPORT void JNICALL
Java_ShowMsgBox_ShowMessage(JNIEnv * jEnv,
  jobject this, jstring jMsg) {
  const char * msg;
  msg = (*jEnv)->GetStringUTFChars(jEnv, jMsg, 0);
  MessageBox(HWND_DESKTOP, msg,
    "Thinking in Java: JNI",
    MB_OK | MB_ICONEXCLAMATION);
  (*jEnv)->ReleaseStringUTFChars(jEnv, jMsg, msg);
}

Если вас не интересует Win32, просто пропустите вызов MessageBox(). Самым интересным является код вокруг него. Аргументы, передаваемые внутрь примитивных методов, являются ключом для возврата в Java. Первый аргумент имеет тип JNIEnv, который содержит все необходимые хуки для обратного вызова JVM (подробнее об этом будет рассказано в следующей секции). В зависимости от типа метода второй аргумент имеет свое уникальное значение. Для непростых методов (также известных как экземплярные методы) второй аргумент эквивалентен указателю this в C++, аналогичен this в Java и ссылается на объект, на котором был вызван примитивный метод. Для простых методов он представляет собой ссылку на конкретный объект класса, где этот метод реализован. Оставшиеся параметры представляют Java объекты, передаваемые в вызов внутреннего метода. Базовые типы также передаются таким образом, но они передаются "по значению".


### А.1.2 Доступ к JNI функциям: параметр `JNIEnv`

С помощью JNI функций программист может взаимодействовать с JVM изнутри внутреннего метода. Как вы видели в примерах выше, каждый JNI внутренний метод принимает специальный параметр — первый параметр: параметр `JNIEnv`, который является указателем на специальную структуру данных JNI типа `JNIEnv_`. Элемент структуры JNI представляет собой указатель на массив, созданный JVM; каждый элемент этого массива является указателем на JNI функцию. Вызов JNI функций изнутри внутреннего метода осуществляется путём отмены ссылок на эти указатели (что на самом деле очень просто). Каждый JVM реализует JNI функции своим способом, но их адреса точно находятся в заранёженных смещениях.

Параметр `JNIEnv` позволяет программисту получать доступ к ряду функций. Эти функции можно разделить на следующие категории:

+ Получение информации о версии
+ Операции с классами и объектами
+ Управление глобальными и локальными ссылками на Java объекты
+ Доступ к экземплярным полям и статическим полям
+ Вызов экземплярных методов и статических методов
+ Выполнение операций со строками и массивами
+ Создание и управление Java исключениямиКоличество JNI функций довольно велико, поэтому здесь мы не будем углубляться в детали. Вместо этого мы раскроем некоторые основные принципы использования этих функций. Для получения более подробной информации обратитесь к документации JNI вашего компилятора.

Если внимательно рассмотреть заголовочный файл `jni.h`, то можно заметить, что при компиляции C++ компилятором структура `JNIEnv_` определяется как класс внутри условия препроцессора `#ifdef _cplusplus`. Этот класс содержит множество встроенных функций. С помощью простого и знакомого синтаксиса эти функции позволяют легко обращаться к JNI функциям. Например, в предыдущем примере содержится следующий код:

```cpp
(*jEnv)->ReleaseStringUTFChars(jEnv, jMsg, msg);

Это может быть преобразовано в C++ следующим образом:

jEnv->ReleaseStringUTFChars(jMsg, msg);

Вы можете заметить, что больше нет необходимости отменять две ссылки на jEnv, одинаковый указатель уже не передается как первый параметр в вызовах JNI функций. В остальных примерах я буду использовать стиль кода на C++.##### 1. Доступ к Java-строкеКак пример доступа к JNI-функциям, рассмотрим приведённый выше код. Здесь мы используем параметр jEnv, представляющий собой JNIEnv, чтобы получить доступ к Java-строке. Java-строка представлена в Unicode-формате, поэтому если нам передаётся такая строка и требуется передать её в нон-Unicode функцию (например, printf()), сначала её следует преобразовать в ASCII-символы с помощью JNI-функции GetStringUTFChars(). Эта функция принимает Java-строку и преобразует её в UTF-8 символы (включая ASCII значения шириной в 8 бит или Unicode значения шириной в 16 бит; если исходная строка состоит полностью из ASCII символов, то результат также будет содержать только ASCII).GetStringUTFChars является полем структуры, к которой JNIEnv указывает косвенной ссылкой, а это поле представляет собой указатель на функцию. Для вызова JNI-функций используется традиционный C-синтаксис для вызова функции через указатель. Такой подход позволяет получать доступ ко всем JNI-функциям.

А.1.3 Передача и использование объектов Java

В предыдущем примере мы передали строку в примитивный метод. Однако можно также передать созданный Java-объект в примитивный метод.

Внутри нашего примитивного метода мы можем получить доступ к полям и методам объектов, которые были переданы.

Для передачи объектов при объявлении примитивного метода используются оригинальные Java-синтаксис. В следующем примере класс MyJavaClass имеет публичное поле и публичный метод. Класс UseObjects объявляет примитивный метод, который принимает объект класса MyJavaClass. Чтобы проверить возможность управления параметрами примитивного метода, мы установили значение публичного поля переданных параметров, вызвали примитивный метод и затем вывели значение публичного поля.

class MyJavaClass {
  public void divByTwo() { aValue /= 2; }
  public int aValue;
}

public class UseObjects {
  public static void main(String[] args) {
    UseObjects app = new UseObjects();
    MyJavaClass anObj = new MyJavaClass();
    anObj.aValue = 2;
    app.changeObject(anObj);
    System.out.println("Java: " + anObj.aValue);
  }

  private native void changeObject(MyJavaClass obj);
}
```  static {
    System.loadLibrary("UseObjImpl");
  }
}

После компиляции кода и передачи .class-файлов в javah, можно реализовать простой метод. В данном примере после получения ID полей и методов они могут быть использованы через JNI-функции.JNIEXPORT void JNICALL Java_UseObjects_изменитьОбъект( JNIEnv *env, jobject jThis, jobject obj) { jclass cls; jfieldID fid; jmethodID mid; int значение; cls = env->GetObjectClass(obj); fid = env->GetFieldID(cls, "значение", "I"); mid = env->GetMethodID(cls, "разделитьНаДва", "()V"); значение = env->GetIntField(obj, fid); printf("Native: %d\n", значение); env->SetIntField(obj, fid, 6); env->CallVoidMethod(obj, mid); значение = env->GetIntField(obj, fid); printf("Native: %d\n", значение); }Кроме первого параметра, C++-функция принимает один jobject, который представляет собой Java объектное ссылающееся значение "интрузии" — ту сторону, которую мы передаем из Java-кода. Мы просто читаем aValue, выводим его, меняем это значение, вызываем метод divByTwo() объекта и снова выводим значение.Для доступа к полю или методу сначала необходимо получить его идентификатор. Используя соответствующие функции JNI, можно легко получить объект класса, имя элемента и сигнатур. Эти функции возвращают идентификатор, которым затем можно воспользоваться для доступа к соответствующему элементу. Несмотря на то, что этот подход может показаться сложным, наша интрузия действительно ничего не знает о внутреннем расположении Java объекта. Поэтому она должна использовать индексы, возвращённые JVM, чтобы получить доступ к полям и методам. Таким образом, различные реализации JVM могут иметь различное внутреннее расположение объектов, не влияя при этом на работу интрузий.Если запустить Java программу, вы заметите, что объекты, передаваемые из Java стороны, обрабатываются нашей интрузией. Но что именно передается? Это указатель или Java ссылка? А что происходит с сборщиком мусора во время выполнения интрузии?

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

Поскольку эти ссылки создаются и уничтожаются каждый раз при вызове функции, локальная копия интрузии (локальный экземпляр) не может храниться в static переменной. Чтобы ссылка была действительна в течение всего времени существования функции, требуется глобальная ссылка. Глобальная ссылка не создается JVM, но программист может расширить локальную ссылку до глобальной ссылки, используя специфические функции JNI. При создании глобальной ссылки программист отвечает за "время жизни" ссылочного объекта. Глобальная ссылка (и объект, на который она ссылается) остаётся в памяти до тех пор, пока не будет явно освобождена с помощью специальных функций JNI. Она аналогична C-функциям malloc() и free().### А.1.4 JNI и Java исключения

Используя JNI, можно выбрасывать, перехватывать, печатать и заново выбрасывать Java исключения так же, как это делается в Java-программе. Однако программист должен самостоятельно вызывать специализированные функции JNI для обработки исключений. Ниже приведены некоторые функции JNI для обработки исключений:

  • Throw(): бросает существующий объект исключения; используется в интроспективных методах для повторной броски исключения.
  • ThrowNew(): создает новый объект исключения и бросает его.
  • ExceptionOccurred(): определяет, было ли уже выброшено исключение, но ещё не очищено.
  • ExceptionDescribe(): выводит информацию об исключении и трассировку стека.
  • ExceptionClear(): очищает ожидающее исключение.
  • FatalError(): вызывает смертельную ошибку, которая не возвращает управление.Среди всех этих функций наиболее важными являются ExceptionOccurred() и ExceptionClear(). Большинство функций JNI могут генерировать исключения, и нет таких языковых конструкций, как блоки try-catch в Java. Поэтому после каждого вызова функции JNI следует проверять наличие исключений с помощью ExceptionOccurred(), чтобы узнать, было ли исключение выброшено. При обнаружении исключения можно выбрать, как с ним работать (например, повторно выбросить его). Однако важно гарантировать, что исключение будет очищено в конечном итоге. Это можно сделать с помощью ExceptionClear() внутри своей функции; если исключение было повторно выброшено, это может произойти и в других функциях. Но в любом случае, выполнение этой работы обязательно.Нужно обеспечить полное удаление исключения. В противном случае, при вызове функции JNI при наличии ожидающего исключения результат может быть непредсказуемым. Несколько функций JNI безопасны для использования при наличии исключения; конечно, они специализированы для управления исключениями.

А.1.5 JNI и многопоточность

Поскольку Java является многопоточным языком, несколько потоков могут одновременно обращаться к интроспективному методу (если другой поток делает вызов, интроспективный метод может быть прерван во время выполнения). В этом случае полностью зависит от программиста обеспечить безопасность вызова интроспективных методов в многопоточной среде. Например, нужно защититься от модификации общих данных с использованием неконтролируемого подхода. Здесь у нас есть два основных варианта: объявить интроспективный метод "синхронным", или применить другие стратегии внутри интроспективного метода, чтобы гарантировать правильное параллельное выполнение операций над данными.

Кроме того, никогда не передавайте JNIEnv между потоками, так как внутренняя структура, на которую он указывает, распределена на основе "каждый поток" и содержит информацию, значимую только для конкретных потоков.### А.1.6 Использование готового кода

Для реализации JNI-внешних методов самым простым способом является создание прототипа этих методов в Java-классе, компиляция этого класса и последующее выполнение javah с .class файлами. Однако, если у нас уже есть большая существующая кодовая база, которую мы хотим вызывать из Java, как нам действовать в этом случае? Недопустимо переименовывать все функции в DLL, чтобы они соответствовали правилам названий JNI, так как это недействительно. Лучшим подходом будет написание обёртки вокруг DLL за пределами существующей кодовой базы. Java-код будет вызывать функции из новой DLL, которая затем будет вызывать функции из оригинальной DLL. Этот метод не просто решение; чаще всего мы должны делать именно так, поскольку нам необходимо обращаться к объектам при использовании JNI-функций, иначе использовать их невозможно.

А.2 Решение MicrosoftНа момент окончательной сдачи рукописи книги Microsoft ещё не предоставлял поддержки JNI, а предлагал свои патентованные методы для вызова неприсоединённого к Java кода. Эта поддержка встроена в компилятор Microsoft JVM и внешние инструменты. Только при использовании компилятора Microsoft Java и запуске программы на Microsoft Java виртуальной машине (JVM) будут работать описываемые здесь возможности. Это может стать серьёзной проблемой, если вы планируете распространять своё приложение через Интернет или внутреннюю сеть предприятия, основанную на различных платформах.

Microsoft предлагает три способа взаимодействия с Win32 кодом:

(1) J/Direct: удобный способ вызова функций DLL Win32 с некоторыми ограничениями.

(2) Оригинальный RNI (Raw Native Interface): возможность вызова функций DLL Win32, но требует самостоятельного решения проблемы сборки мусора.

(3) Интеграция Java/COM: прямое раскрытие или вызов COM служб из Java.

Дальнейшие разделы подробно рассматривают эти три технологии.

При написании этой книги все эти возможности были поддержаны Microsoft SDK for Java версии 2.0 бета 2. Этот разработочный набор можно скачать со страниц сайта компании Microsoft (процесс выбора называется "Active Setup"). Java SDK представляет собой набор командных строковых инструментов, однако компилятор легко интегрируется в среду Developer Studio, чтобы мы могли использовать Visual J++ 1.1 для компиляции кода Java 1.1.## А.3 J/DirectJ/Direct является самым простым способом вызова функций DLL Win32. Его основная цель — работа с Win32 API, но он также позволяет обращаться ко всем другим API. Однако, хотя эта функциональность очень удобна, она также создает некоторые ограничения и снижает производительность (по сравнению с RNI). Тем не менее, J/Direct имеет несколько очевидных преимуществ. Во-первых, нет необходимости писать дополнительный неприсоединённый к Java код помимо того, что уже содержится в нужной DLL, то есть нам не требуется обёртка или агент/шаблон DLL. Во-вторых, параметры функций автоматически преобразуются в стандартные типы данных. Если вам нужно передать пользовательский тип данных, J/Direct может не работать так, как вы ожидали. В-третьих, как показывает следующий пример, это очень просто и прямо. С помощью нескольких строк этот пример демонстрирует вызов функции Win32 API MessageBox(), которая выводит маленькое модальное окно с заголовком, сообщением, опциональным значком и несколькими кнопками.

public class ShowMsgBox {
    public static void main(String[] args) throws UnsatisfiedLinkError {
        MessageBox(0,
            "Создано с помощью MessageBox() Win32 функции",
            "Thinking in Java", 0);
    }

    /** @dll.import("USER32") */
    private static native int MessageBox(int hwndOwner, String text,
                                          String title, int fuStyle);
}
``````Шокирующим является то, что здесь представлено все необходимое кодовое содержимое для вызова функций Win32 DLL с помощью J/Direct. Ключевой особенностью являются `@dll.import` указательные команды перед объявлением `MessageBox()`, расположенные в нижней части демонстрационного кода. На первый взгляд они выглядят как комментарии, но это не так. Эти команды предназначены для того, чтобы сообщить компилятору, что функции ниже этой строки реализованы в библиотеке USER32 DLL и должны вызываться соответственно. Все, что нам нужно сделать,  это предоставить прототип функции, который соответствует функциям внутри DLL, и затем вызвать эти функции. Однако нет необходимости вручную вводить каждый Win32 API функционал в версии Java; Microsoft Java пакет сделает это за нас (мы подробнее рассмотрим это позже). Для того чтобы пример работал правильно, функции должны быть "по имени" экспортированы из DLL. Однако можно также использовать `@dll.import` указательные команды для "последовательной" связи. Например, мы можем указать положение входа функции в DLL. Мы более подробно рассмотрим возможности `@dll.import` указательных команд.
```Одним из важных вопросов при использовании неприсоединённого кода является автоматическое конфигурирование аргументов функций. Как видно, объявление `MessageBox()` в Java использует два строковых параметра, тогда как оригинальная C-версия использует два указателя типа char. Компилятор поможет нам автоматически преобразовать стандартные типы данных, следуя правилам, которые будут описаны в последней части главы.

Кроме того, возможно, вы заметили исключение `UnsatisfiedLinkError` в объявлении `main()`. Это исключение активируется во время выполнения, если связывающая программа не может распознать символы из неприсоединённой функции. Причины этого могут быть различными: отсутствие .dll файла, недействительность DLL или отсутствие поддержки J/Direct вашей используемой виртуальной машиной Java. Чтобы найти DLL, она должна находиться либо в каталогах Windows или Windows\System, либо в одном из каталогов, перечисленных в переменной окружения PATH, либо в том же каталоге, где находится .class файл. J/Direct поддерживается Microsoft Java компиляторами версий 1.02.4213 и выше, а также Microsoft JVM версий 4.79.2164 и выше. Чтобы узнать номер версии вашего компилятора, запустите JVC из командной строки без использования параметров. Чтобы узнать номер версии JVM, найдите значок msjava.dll и просмотрите его свойства через контекстное меню правого клика.### А.3.1 `@dll.import` Указательные Команды

Как единственный способ использования J/Direct, командный указатель `@dll.import` довольно гибок. Он предоставляет множество модификаторов, с помощью которых можно настраивать способ установления связи с неприспособленным для Java кодом. Его также можно применять как к отдельным методам внутри класса, так и ко всему классу в целом. Это значит, что все методы, объявленные в этом классе, реализуются в одной и той же DLL. Давайте более подробно рассмотреть эти возможности.

##### 1. Обработка псевдонимов и последовательная связывание

Чтобы команда `@dll.import` работала так, как показано выше, функции внутри DLL должны быть экспортированы по имени. Однако иногда мы хотим использовать имя, отличное от оригинального имени в DLL (обработка псевдонимов), а не экспортировать функцию по порядковому номеру (например, последовательно). В следующем примере объявлен метод `FinestraDiMessaggio()` (итальянское название для `MessageBox`). Как видно, синтаксис очень прост.

```java
public class Aliasing {
  public static void main(String[] args) 
  throws UnsatisfiedLinkError {
    FinestraDiMessaggio(0, 
      "Создано командой MessageBox() Win32", 
      "Размышления над Java", 0);
  }

  /** @dll.import("USER32", 
  entrypoint="MessageBox") */
  private static native int 
  FinestraDiMessaggio(int hwndOwner, String text, 
    String title, int fuStyle);
}
```В этом примере показано, как можно связывать функцию, которая не экспортирована по имени в DLL, но фактически экспортирована по её положению в DLL. Предположим, что есть DLL с именем `MYMATH`, содержащая функцию на позиции 3, которая принимает два целых числа в качестве аргументов и возвращает их сумму.```java
public class ByOrdinal {
  public static void main(String[] args)
  throws UnsatisfiedLinkError {
    int j = 3, k = 9;
    System.out.println("Результат работы функции DLL: "
      + Add(j, k));
  }

  /** @dll.import("MYMATH", entrypoint = "#3") */
  private static native int Add(int op1, int op2);
}

Как видно, единственным различием является форма параметра entrypoint.

2. Применение @dll.import ко всему классу

Команда @dll.import может применяться ко всему классу. Это значит, что все методы этого класса реализованы в одной и той же DLL и имеют одинаковые свойства связи. Эта команда не будет наследоваться дочерними классами; поэтому лучшим шаблоном проектирования является использование независимого класса для обёртки API-функций, как показано ниже:

/** @dll.import("USER32") */
class MyUser32Access {
  public static native int
  MessageBox(int hwndOwner, String text,
    String title, int fuStyle);

  public native static boolean
  MessageBeep(int uType);
}

public class WholeClass {
  public static void main(String[] args)
  throws UnsatisfiedLinkError {
    MyUser32Access.MessageBeep(4);
    MyUser32Access.MessageBox(0,
      "Создано командой MessageBox() Win32",
      "Размышления над Java", 0);
  }
}

Из-за того что функции MessageBeep() и MessageBox() объявлены как статические методы в разных классах, при вызове этих функций обязательно указывать область видимости. Возможно, вы полагаете, что все функции Win32 API (функции, константы и типы данных) должны быть отображены в виде Java-классов. Однако это не всегда требуется.

А.3.2 Пакет com.ms.win32

Объем Win32 API действительно велик — он включает тысячи функций, констант и типов данных. Конечно же, мы не хотели бы привязываться к тому, чтобы каждый метод Win32 API был представлен в виде Java-класса. Microsoft учёл этот вопрос и выпустил Java-пакет, который позволяет с помощью J/Direct отображать Win32 API в виде Java-классов. Этот пакет называется com.ms.win32. При установке Java SDK 2.0 этот пакет может быть установлен в вашей пути классов, если соответствующие настройки были сделаны во время установки. Пакет состоит из множества Java-классов, которые полностью воспроизводят константы, типы данных и функции Win32 API. Три наиболее важных класса — это User32.class, Kernel.class и Gdi32.class. Они содержат основной контент Win32 API. Для использования этих классов просто импортируйте их в свой Java-код. Пример ShowMsgBox можно переписать следующим образом с использованием пакета com.ms.win32 (также учитывается более правильное использование UnsatisfiedLinkError):

import com.ms.win32.*;

public class UseWin32Package {
  public static void main(String[] args) {
    try {
      User32.MessageBeep(winm.MB_ICONEXCLAMATION);
      User32.MessageBox(0, "Создано с помощью функции MessageBox() Win32", "Thinking in Java", winm.MB_OKCANCEL | winm.MB_ICONEXCLAMATION);
    } catch (UnsatisfiedLinkError e) {
      System.out.println("Не удалось связать Win32 API");
      System.out.println(e);
    }
  }
}
```Пакет Java импортирован первой строчкой. Теперь вы можете вызывать функции `MessageBeep()` и `MessageBox()` без необходимости делать дополнительные объявления. В функции `MessageBeep()` видны константы Win32, которые также объявлены при импорте пакета. Эти константы определены во многих Java-интерфейсах, названных `winx` (`x` представляет первую букву константы).На момент написания этой книги разработка пакета `com.ms.win32` ещё не завершена, но он уже вполне работоспособен.

### А.3.3 Объединение "Сериализация" (marshalling)  это процесс преобразования аргумента функции из его исходной двоичной формы в независимую от языка программирования форму, а затем конвертации этой универсальной формы в двоичный формат, подходящий для вызова функции. В предыдущем примере мы вызвали функцию `MessageBox()`, передав ей две строки. Функция `MessageBox()` является C-функцией, и бинарная структура Java-строк отличается от структуры C-строк. Однако параметры всё равно были правильно переданы благодаря тому, что J/Direct уже выполнил необходимое преобразование Java-строк в C-строки до вызова C-кода. Это применимо ко всем стандартным типам данных Java. Ниже приведена таблица, которая сводит кратко простые данные с их соответствующими типами:

```markdown
| Java Type | Corresponding C Type |
|-----------|----------------------|
| boolean   | jboolean             |
| byte      | jbyte                |
| short     | jshort               |
| int       | jint                 |
| long      | jlong                |
| float     | jfloat               |
| double    | jdouble              |
| char      | jchar                |
Java C
byte BYTE или CHAR
short SHORT или WORD
int INT, UINT, LONG, ULONG или DWORD
char TCHAR
long __int64
float FLOAT
double DOUBLE
boolean BOOL
String LPCTSTR (только как возвращаемое значение в OLE-режиме)
byte[] BYTE *
short[] WORD *
char[] TCHAR *
int[] DWORD *

А.3.4 Написание обратных вызовов

Некоторые функции Win32 API требуют передачи указателя на функцию в качестве своего аргумента. Windows API функции затем могут вызывать эту функцию (обычно при возникновении определенного события). Эта техника называется "обратный вызов". Примеры обратных вызовов включают процесс окон и обратные вызовы, которые мы задаем во время печати (адрес обратного вызова предоставляется фоновой программой печати для обновления состояния и прекращения печати при необходимости).Еще один пример — это функция API EnumWindows(), которая перечисляет все верхние уровни окон в текущей системе. EnumWindows() требует получения указателя на функцию в качестве своего аргумента; затем она начинает поиск через внутренний список, который поддерживается Windows. Для каждого окна в списке она вызывает обратный вызов, передавая ссылку на окно в качестве аргумента обратному вызову.Чтобы достичь того же эффекта в Java, следует использовать класс Callback из пакета com.ms.dll. Мы наследуемся от Callback и переопределяем метод callback(). Этот метод принимает только один аргумент типа int и возвращает либо int, либо void. Подпись метода и его конкретная реализация зависят от используемого обратного вызова Windows API.

Теперь наша задача состоит в создании экземпляра производного класса Callback и передачи его как указателя на функцию API-функции. После этого J/Direct поможет автоматически завершить остальную работу. В этом примере вызывается функция Win32 API EnumWindows(); метод callback() класса EnumWindowsProc получает ссылку на каждый верхнеуровневый оконный объект, получает заголовочную информацию и выводит её в консоль.

import com.ms.dll.*;
import com.ms.win32.*;

class EnumWindowsProc extends Callback {
  public boolean callback(int hwnd, int lParam) {
    StringBuffer text = new StringBuffer(50);
    User32.GetWindowText(hwnd, text, text.capacity() + 1);
    if (text.length() != 0)
      System.out.println(text);
    return true;  // чтобы продолжить перечисление.
  }
}

public class ShowCallback {
  public static void main(String[] args)
      throws InterruptedException {
    boolean ok = User32.EnumWindows(new EnumWindowsProc(), 0);
    if (!ok)
      System.err.println("EnumWindows failed.");
    Thread.currentThread().sleep(3000);
  }
}

Вызов sleep() позволяет завершиться процессу создания окон до выхода из метода main().

А.3.5 Другие возможности J/DirectЧерез модификаторы (маркеры) в команде импорта @dll.import также доступны две дополнительные возможности J/Direct. Первая возможность — это упрощённый доступ к OLE функциям, а вторая — выбор между ANSI и Unicode версиями API функций.Согласно соглашению, все OLE функции возвращают значение типа HRESULT, которое представляет собой структурированное целое число COM. Если вы пишете программу на уровне COM и хотите получить что-то другое от одной из OLE функций, вам нужно передать специальный указатель на область памяти, куда будет записана информация. Однако в Java нет указателей, поэтому этот подход неудобен. Используя J/Direct, можно использовать модификатор ole в команде импорта @dll.import, чтобы удобнее работать с OLE функциями. Внутренний метод, помеченный как ole, автоматически преобразует сигнатуру Java метода (через которую определяется тип возврата) в сигнатуру функции COM.Вторая возможность заключается в выборе между ANSI и Unicode версиями строковых функций управления. Большинство функций Win32 API, контролирующих строки, предоставляют два варианта. Например, если мы рассмотрим символы, экспортированные из USER32.DLL, то мы не найдём функцию MessageBox(). Вместо этого мы увидим функции MessageBoxA() и MessageBoxW() — ANSI и Unicode версии этой функции соответственно. Если в команде импорта @dll.import не указан конкретный вариант, JVM попробует сам выбрать. Но эта операция может занять значительное время во время выполнения программы. Поэтому обычно используются модификаторы ansi, unicode или auto для жёсткого указания. Для получения более подробной информации о этих характеристиках обратитесь к техническому руководству компании Microsoft.## А.4 Оригинальный нативный интерфейс (RNI)

По сравнению с J/Direct, RNI представляет собой значительно более сложный интерфейс для ненавязчивого кода; но его возможности также очень мощны. RNI ближе к JVM, чем J/Direct, что позволяет нам писать более эффективный код, который может работать с объектами Java в нативных методах и обеспечивает более тесную интеграцию с внутренними механизмами работы JVM.

С точки зрения концепции, RNI аналогичен JNI от Sun Microsystems. В связи с этим и тем фактом, что продукт ещё не завершён, здесь приведены основные различия между ними. Для получения более подробной информации обратитесь к документации компании Microsoft.

Между JNI и RNI существует несколько заметных различий. Ниже представлен C-заголовочный файл, созданный msjavah, применительно к Java-классу ShowMsgBox, использованному ранее в примере JNI:

/* НЕ РЕДАКТИРОВАТЬ - автоматически создано msjavah */
#include <native.h>
#pragma warning(disable:4510)
#pragma warning(disable:4512)
#pragma warning(disable:4610)

struct Classjava_lang_String;
#define Hjava_lang_String Classjava_lang_String

/* Заголовок для класса ShowMsgBox */

#ifndef _Included_ShowMsgBox
#define _Included_ShowMsgBox

#define HShowMsgBox ClassShowMsgBox
typedef struct ClassShowMsgBox {
#include <pshpack4.h>
  long MSReserved;
#include <poppack.h>
} ClassShowMsgBox;

#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void __cdecl
ShowMsgBox_ShowMessage (struct HShowMsgBox *,
  struct Hjava_lang_String *);
#ifdef __cplusplus
}
#endif

#endif  /* _Included_ShowMsgBox */

#pragma warning(default:4510)
#pragma warning(default:4512)
#pragma warning(default:4610)
```Помимо ухудшения удобочитаемости, этот код скрывает некоторые технические проблемы, которые будут рассмотрены ниже.

В RNI программисты нативных методов знают двоичную структуру объектов. Это позволяет нам напрямую обращаться к нужной информации; мы не должны получать идентификаторы полей или методов, как это происходит в JNI. Однако, поскольку не все виртуальные машины используют одну и ту же двоичную структуру для своих объектов, указанный выше нативный метод работает только в Microsoft JVM.В RNI через параметр JNIEnv доступна большая часть функций для взаимодействия с JVM. В RNI функции управления работой JVM становятся вызываемыми напрямую. Некоторые из них (например, управление исключениями) похожи на свои "братья" из JNI. Но большинство функций RNI имеют другие имена и назначение по сравнению с JNI. Наиболее значительной разницей между JNI и RNI является модель "мусорной сборки". В JNI мусорная сборка во время выполнения встроенных методов следует тем же правилам, что и при выполнении кода на Java. В то время как в RNI управление мусорной сборкой во время работы встроенных методов возложено на программиста. По умолчанию мусорный сборщик находится в режиме выключенного состояния до тех пор, пока программа не войдет в область выполнения встроенных методов; таким образом, программист может предположить, что объекты, используемые в этот период времени, не будут являться целью мусорной сборки. Однако если встроенный метод планируется длительное время выполнять, программист должен рассмотреть возможность активации мусорного сборщика — путём вызова функции `GCEnable()` (где GC сокращение от "Garbage Collector", то есть "мусорный сборщик").Существует также механизм, аналогичный глобальным ссылкам — программист может использовать его для обеспечения того, чтобы определённые объекты не были удалены сборщиком мусора во время его работы. Концепция здесь такая же, но названия отличаются — в RNI это называется `GCFrames`.

### А.4.1 Обзор RNI

Факт тесной интеграции RNI с Microsoft JVM одновременно является и преимуществом, и недостатком. RNI значительно сложнее JNI, однако он предоставляет высокий уровень контроля над внутренней работой JVM, включая управление сборкой мусора. Кроме того, он явно оптимизирован для скорости и использует некоторые компромиссы и техники, знакомые C-программистам. Однако этот подход не применим к другим реализациям JVM.## А. 5 Интеграция Java/COM (ранее известное как OLE) представляет собой «компонентную объектную модель» (Component Object Model) компании Microsoft. Это основа всех технологий ActiveX (включая ActiveX-контролы, Automation и ActiveX-документы). Однако COM включает в себя гораздо больше. Это спецификация, согласно которой созданные по ней компонентные объекты могут взаимодействовать друг с другом через специальные возможности операционной системы. В реальных приложениях все новые программы для Win32-систем связаны с COM — операционная система раскрывает свои возможности через COM-объекты. Компоненты, созданные другими производителями, также могут базироваться на COM, позволяя нам создавать и регистрировать свои собственные COM-компоненты. Независимо от формы, если вы хотите писать код для Win32, вам придется иметь дело с COM. В этом разделе мы просто повторим основные концепции программирования COM, предполагая, что читатель уже знаком с понятиями COM-сервера (любой COM-объект, который может предоставлять услуги COM-клиентам) и COM-клиента (любого COM-объекта, который может запрашивать услуги у COM-сервера). Мы постараемся сделать объяснение максимально простым. На самом деле, эти инструменты гораздо мощнее, и мы можем использовать их более продвинутыми способами. Но это требует более глубокого понимания COM, выходящего за рамки данного приложения.Если вас интересуют мощные, но платформозависимые возможности, рекомендуется изучить документацию по COM и материалы Microsoft, особенно разделы, связанные с интеграцией Java/COM. Для получения дополнительной информации я советую книгу Дэйла Рогерсона "Inside COM", выпущенную издательством Microsoft Press в 1997 году. Использование COM (Component Object Model) является ключевой частью всех новых приложений Win32, поэтому способность использовать (или раскрывать) службы COM с помощью Java-кода становится особенно важной. Интеграция Java/COM бесспорно является одним из самых интересных аспектов компилятора Microsoft Java и виртуальной машины. Так как модели Java и COM очень похожи, эта интеграция кажется интуитивной и легко реализуется технически — для доступа к COM практически не требуется писать специальный код. Большинство технических деталей контролируется компилятором и/или виртуальной машиной. В результате Java-программист может обращаться к объектам COM так же, как к обычным Java-объектам. Также клиенты COM могут использовать серверы COM, реализованные на Java, точно так же, как они используют другие серверы COM.Здесь стоит отметить, что хотя я использую общие термины "COM", можно полностью реализовать ActiveX Automation Server на Java, а также использовать ActiveX Control в Java-приложении.

Наиболее примечательное сходство между Java и COM заключается в том, как COM-интерфейсы соотносятся с ключевым словом `interface` в Java. Это почти идеальное соответствие, поскольку:

+   Объекты COM раскрывают только интерфейсы;
+   Сам по себе COM-интерфейс не имеет реализации; его реализация осуществляется объектом COM, который раскрывает этот интерфейс;
+   COM-интерфейс представляет собой описание семантически связанных набора функций; он не раскрывает никаких данных;
+   COM-класс объединяет несколько COM-интерфейсов. Java-класс может реализовать любое количество Java-интерфейсов;
+   COM имеет модель ссылочных объектов; программист никогда не "владеет" объектом, а лишь получает ссылку на один или более интерфейсов этого объекта. У Java тоже есть модель ссылочных объектов — ссылка на объект может быть преобразована в ссылку на один из его интерфейсов;
+   "Жизненный цикл" объекта COM в памяти зависит от количества клиентов, использующих этот объект; если это число становится нулевым, объект сам удаляет себя из памяти. В Java жизненный цикл объекта также определяется количеством клиентов, использующих его; если больше нет ссылок на объект, он ждет сборки мусора.Эта близкая корреляция между Java и COM позволяет Java-программистам удобно использовать возможности COM, а также делает Java эффективным языком для написания кода COM. Хотя COM является языково-нейтральным, фактический язык разработки COM обычно C++ и Visual Basic. По сравнению с Java, C++ предоставляет более мощные средства для разработки COM и может генерировать более эффективный код, но он сложнее в использовании. Visual Basic намного проще Java, но находится слишком далеко от базовой операционной системы, и его модель объектов не обеспечивает такой же степени совместимости. Хорошее соответствие (соответствие) между ними. Java является хорошей компромиссной схемой для обоих.

Далее мы рассмотрим некоторые ключевые вопросы, связанные с разработкой COM. Эти вопросы следует выяснить первыми при создании клиентских и серверных приложений на Java/COM.

### 5.1 Основы COMCOM — это бинарный стандарт, предназначенный для обеспечения взаимодействия объектов. Например, COM считает, что двоичное представление одного объекта должно позволять вызывать службы другого COM-объекта. Поскольку это описание двоичного представления, любой язык, который может генерировать такое представление, может использоваться для создания COM-объектов. Обычно программисты не должны беспокоиться о таких деталях уровня, поскольку компиляторы могут автоматически создавать правильное представление. Например, если ваша программа написана на C++, большинство компиляторов будут создавать виртуальную таблицу функций, соответствующую стандарту COM. Для языков, которые не генерируют исполняемый код, таких как VB и Java, соединение с COM происходит автоматически во время выполнения.Библиотека COM также предоставляет несколько базовых функций, таких как функции для создания объектов или поиска уже зарегистрированных COM-классов в системе.

Основные цели модели объектов COM включают:

+   Возможность вызова служб одним объектом от другого объекта

+   Возможность бесшовного внедрения новых типов объектов (или обновленных объектов)Первое из этих целей решается задачами объектно-ориентированного программирования: у нас есть клиентский объект, который может обращаться к серверному объекту. В этом контексте термины "клиент" и "сервер" используются в обычном смысле, а не относятся к конкретным аппаратным конфигурациям. Для любого объектно-ориентированного языка первая цель легко достижима — просто ваш код представляет собой полный блок кода, который реализует как код клиента, так и код сервера. При изменении формы взаимодействия между клиентским и серверным объектами достаточно просто перекомпилировать и перезапустить приложение; при следующем запуске оно будет использовать самые новые версии компонентов. Однако если приложение состоит из компонентных объектов, которые находятся вне нашего контроля, ситуация может значительно отличаться — мы не контролируем их исходный код, а их обновление может происходить независимо от нашего приложения. Например, когда мы используем ActiveX-компоненты, разработанные другими производителями, в своём приложении, это становится актуальным. Компоненты устанавливаются в нашу систему, и наше приложение может (в процессе выполнения) найти серверный код, активировать объект, установить с ним связь и использовать его.Впоследствии мы можем установить новые версии этих компонентов, и наше приложение должно продолжать работать; даже в худшем случае оно должно вежливо сообщить об ошибке, например "Компонент не найден" и так далее; обычно оно не должно внезапно зависнуть или выйти из строя. В этих случаях наши компоненты реализуются в независимых исполняемых файлах кода: DLL или EXE. Если серверные объекты реализованы в независимых исполняемых файлах кода, требуется стандартный метод, предоставляемый операционной системой, чтобы активировать эти объекты. Конечно же, мы не хотим использовать физические имена и расположение DLL или EXE в нашем коде, так как эти параметры могут часто меняться. В этом случае нам нужны идентификаторы, поддерживаемые самой операционной системой. Кроме того, наша программа должна иметь описание услуг, представленных сервером. Два следующих раздела будут рассматривать эти две проблемы.##### 1. GUID и реестр

COM использует структурированные целочисленные значения (длиной 128 бит) для уникальной идентификации зарегистрированных в системе COM объектов. Эти числа имеют официальное название — GUID (Globally Unique Identifier, глобально уникальный идентификатор), который может быть сгенерирован специальными инструментами. Кроме того, эти числа гарантируют уникальность "в любом пространстве и времени", то есть они никогда не повторятся. Пространственно это обеспечивается за счет чтения номера сетевой карты генератором чисел; временно — благодаря использованию системы дат и времени. GUID можно использовать для идентификации COM-классов (тогда он называется CLSID) или COM-интерфейсов (IID). Несмотря на различия в названии, базовая концепция и двоичная структура одинаковы. GUID также могут применяться в других контекстах, что здесь не будет подробно рассматриваться.GUID и связанные с ними данные хранятся в Windows-реестре, или, другими словами, в "регистрационной базе данных" (registration database). Это иерархическая база данных, которая встроена в операционную систему и содержит большое количество информации, связанной с конфигурацией программного и аппаратного обеспечения системы. Для COM реестр отслеживает установленные в системе компоненты, такие как их CLSID, имя и местоположение исполняющих их файлов, а также множество других деталей. Одним из важнейших таких деталей является ProgID компонента; ProgID аналогичен по концепции GUID, поскольку оба служат для идентификации COM-компонента. Отличие заключается в том, что GUID представляет собой двоичное значение, сгенерированное алгоритмически, тогда как ProgID — это строковое значение, определяемое программистом. ProgID присваивается вместе с одним CLSID.Мы говорим о регистрации COM-компонента в системе, если его CLSID и исполняющий его файл уже существуют в реестре (ProgID обычно также присутствует). В последующих примерах основной задачей будет регистрация и использование COM-компонентов. Одним из важных свойств реестра является его использование в качестве деконнектированного слоя между клиентами и серверными объектами. Используя информацию, хранящуюся в реестре, клиент активирует сервер; одной из этих сведений является физическое расположение модуля выполнения сервера. В случае изменения этого положения информация в реестре будет обновлена соответственно. Однако этот процесс обновления остаётся "невидимым" для клиента. Для последнего достаточно использовать ProgID или CLSID напрямую. Другими словами, реестр делает возможной прозрачность местоположения кода сервера. С появлением DCOM (Distributed COM) сервер, работающий локально на машине, может быть перемещён на удалённую машину в сети, при этом весь процесс остаётся незамеченным для клиента (в большинстве случаев).Примечание: Исходный текст был на китайском, но для правильного понимания задачи было указано, что язык исходного текста необходимо определить. Учитывая контекст и содержание текста, можно сделать вывод, что он был написан на английском и затем переведён на китайский. Поэтому перевод выполнен на русский язык, как было указано в условии задачи.

##### 2. Типовые библиотекиИз-за возможности динамической связи в COM, а также потому что клиентский и серверный код могут развиваться независимо друг от друга, клиент всегда должен динамически обнаруживать услуги, предоставляемые сервером. Эти услуги описываются в виде двоичной формы, независимой от языка программирования, с использованием "типовой библиотеки" (Type Library) (как это делается для интерфейсов и сигнатур методов). Она может быть как самостоятельным файлом (обычно с расширением .TLB), так и Win32-ресурсом, связанным с исполняемым файлом. В процессе выполнения клиент использует информацию из типовой библиотеки для вызова функций на сервере.

Можно написать исходный файл на языке определения интерфейса Microsoft (Microsoft Interface Definition Language, MIDL), скомпилировать его с помощью компилятора MIDL и получить файл .TLB. Язык MIDL используется для описания COM-классов, интерфейсов и методов. Он аналогичен OMB/CORBA IDL по имени, синтаксису и назначению. Однако Java-программистам не обязательно использовать MIDL. В дальнейшем будет рассмотрено другое средство Microsoft, которое способно читать Java-классы и генерировать типовую библиотеку.

##### 3. COM:HRESULT функции возврата кодаФункции COM, представленные сервером, возвращают значение типа HRESULT, которое заранее определено. HRESULT представляет собой целое число, содержащее три поля. Это позволяет использовать множество кодов ошибок и успешных операций, а также другие данные. Поскольку COM-функции возвращают HRESULT, они не могут вернуть первоначальные данные через значения возврата. Если требуется вернуть данные, можно передать указатель на область памяти, где функция заполнит эти данные. Такие параметры называются "внешними". Как Java/COM-разработчики, нам не нужно сильно беспокоиться об этом вопросе, поскольку виртуальная машина автоматически управляет всем этим. Этот вопрос будет более подробно рассмотрен в последующих разделах.### А.5.2 Интеграция MS Java/COM

Данный текст уже на русском языке и требует минимальных изменений. Вот исправленный вариант:

### А.5.2 Интеграция MS Java/COMПо сравнению с C++/COM-разработчиками, Microsoft Java-компилятор, виртуальная машина и различные средства существенно упрощают работу Java/COM-разработчиков. Компилятор имеет специальные команды и пакеты, позволяющие рассматривать Java-классы как COM-классы. Однако в большинстве случаев мы можем полагаться на поддержку COM, предоставляемую Microsoft JVM, а также использовать два мощных внешних средства. Microsoft Java Virtual Machine (JVM) играет роль моста между объектами COM и Java. Если создать Java объект как COM сервер, то наш объект всё ещё будет выполняться внутри JVM. Microsoft JVM реализован в виде DLL, которая представляет COM интерфейсы операционной системе. Внутри JVM вызовы функций COM интерфейсов преобразуются в вызовы методов Java объектов. Конечно, JVM должна знать, какой Java class file соответствует модулю выполнения сервера; эта информация доступна благодаря тому, что мы заранее зарегистрировали class files с помощью `Javareg` в реестре Windows. `Javareg` — это утилита, предоставляемая вместе с Microsoft Java SDK, которая может прочитать Java class file, сгенерировать соответствующую типовую библиотеку и GUID, а также зарегистрировать класс в системе. Также можно использовать `Javareg` для регистрации удалённых серверов. Например, его можно использовать для регистрации сервера, работающего на другой машине. Если вы хотите создать Java/COM-клиента, вам потребуется пройти через ряд различных шагов.Java/COM "клиент" — это специальное Java-кодирование, которое хочет активировать и использовать COM-сервер, зарегистрированный в системе. Также виртуальная машина будет взаимодействовать с COM-сервером и представит его услуги как различные методы внутри Java-класса. Другой инструмент от Microsoft называется `jactivex`, который может прочитать библиотеку типов и сгенерировать соответствующие Java-источники, содержащие специальные команды компилятора. Генерируемые источники являются частью пакета, названного нами после указания типа библиотеки. Следующий шаг — импортирование этого пакета в собственные Java-источники клиента.Давайте рассмотрим два примера.

### А.5.3 Создание COM-сервера на Java

Этот раздел представляет процесс создания ActiveX-компонентов, серверов Automation или любого другого сервера, соответствующих стандартам COM. В этом примере реализован простой сервер Automation, который выполняет сложение целых чисел. Мы используем метод `setAddend()` для установки значения `addend`. Каждый раз при вызове метода `sum()`, значение `addend` добавляется к текущему значению `result`. Мы используем метод `getResult()` для получения значения `result` и метод `clear()` для сброса значений. Реализация поведения данного Java-класса очень проста:

```java
public class Adder {
  private int addend;
  private int result;

  public void setAddend(int a) { this.addend = a; }

  public int getAddend() { return this.addend; }

  public int getResult() { return this.result; }

  public void sum() { this.result += this.addend; }

  public void clear() {
    this.result = 0;
    this.addend = 0;
  }
}

Чтобы использовать этот Java-класс как объект COM, мы применяем инструмент Javareg к скомпилированному файлу Adder.class. Этот инструмент предоставляет множество опций; в данном случае мы указываем имя файла Java-класса ("Adder"), ProgID, который мы хотим внести в реестр для этого сервера ("JavaAdder.Adder.1"), а также имя библиотеки типов, которую мы хотим сгенерировать ("JavaAdder.tlb"). Поскольку CLSID ещё не указан, Javareg автоматически генерирует его. При повторном использовании Javareg для того же сервера он будет использовать уже существующий CLSID.``` javareg /register /class:Adder /progid:JavaAdder.Adder.1 /typelib:JavaAdder.tlb


Инструмент `Javareg` также регистрирует новый сервер в реестре Windows. На данном этапе важно помнить, что файл `Adder.class` должен быть скопирован в каталог `Windows\Java\trustlib`. Из соображений безопасности (особенно при вызовах COM-служб программными фрагментами), эти серверы будут активированы только после того, как они будут установлены в каталог `trustlib`. Теперь мы успешно установили новый сервер автоматизации в нашей системе. Для тестирования нам необходим контроллер автоматизации, который представляет собой Visual Basic (VB). Ниже приведены несколько строк кода VB. В соответствии с форматом VB я создал текстовое поле для получения значений от пользователя, которые требуется сложить, и метку для отображения результата. Два кнопочных элемента используются для вызова методов `sum()` и `clear()`. В начале мы объявляем объектную переменную `Adder`. В процедуре `Form_Load` (при первой загрузке формы) создаётся новый экземпляр сервера автоматизации `Adder`, и поля формы инициализируются значениями. Как только пользователь нажимает кнопки "Сумма" или "Очистка", соответствующие методы вызываются на стороне сервера.```vb
	Dim Adder As Object
	
	Private Sub Form_Load()
	    Set Adder = CreateObject("JavaAdder.Adder.1")
	    Addend.Text = Adder.GetAddend
	    Result.Caption = Adder.GetResult
	End Sub
	
	Private Sub SumBtn_Click()
	    Adder.SetAddend (Addend.Text)
	    Adder.Sum
	    Result.Caption = Adder.GetResult
	End Sub
	
	Private Sub ClearBtn_Click()
	    Adder.Clear
	    Addend.Text = Adder.GetAddend
	    Result.Caption = Adder.GetResult
	End Sub
```Обратите внимание, что этот код совершенно не знает, что сервер реализован на Java.

При запуске этого приложения и выполнении функции `CreateObject()`, Windows реестр будет проверять указанный ProgID. Самое важное в информации, связанной с этим ProgID, — это имя Java-класса. В ответ на это запускается Java Virtual Machine (JVM), которая затем вызывает экземпляр Java-объекта внутри JVM. С этого момента JVM автоматически управляет взаимодействием между клиентским и серверным кодами.

### А.5.4 Разработка COM-клиента на JavaТеперь давайте перейдем к другой стороне и разработаем COM-клиента на Java. Этот программный модуль будет использовать службы, установленные в системе COM-сервера. В данном примере мы используем клиента, который был создан для сервера в предыдущем примере. Несмотря на то, что код может показаться знакомым для Java-разработчика, происходящее за кулисами является необычным. В этом примере используется сервер, написанный на Java, но он применим ко всем ActiveX-компонентам, ActiveX Automation-серверам или ActiveX-компонентам, установленным в системе, если у нас есть типовая библиотека.
Сначала мы применяем инструмент `Jactivex` к типовой библиотеке сервера. У `Jactivex` есть ряд опций и ключей для выбора. Однако в его самом базовом виде он читает типовую библиотеку и генерирует Java-файлы источника. Этот источник сохраняется в нашем каталоге `windows/java/trustlib`. В следующей команде он применяется к типовой библиотеке, сгенерированной для внешнего COM Automation сервера:```bash
/path/to/Jactivex /path/to/typeLibrary.tlb -o windows/java/trustlib

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

Jactivex C:\external\comAutomation\types.tlb -o windows/java/trustlib
jactivex /javatlb JavaAdder.tlb

После выполнения Jactivex мы можем обратиться к своему каталогу windows/java/trustlib. В этом каталоге теперь можно найти новый подкаталог с названием JavaAdder. Этот каталог включает исходные файлы для нового пакета. Это библиотека, аналогичная функциям типовой библиотеки в Java. Эти файлы требуют специальных команд запуска Microsoft компилятора: @com. Jactivex создаёт несколько файлов, поскольку COM использует множество объектов для описания одного сервера COM (ещё одна причина заключается в том, что я не выполнил детальной настройки MIDL файла и инструментов Java/COM).

Файл Adder.java эквивалентен команде coclass в MIDL файле: это объявление COM класса. Остальные файлы представляют собой Java эквиваленты COM интерфейсов, раскрываемых сервером. Эти интерфейсы (например, Adder_DispatchDefault.java) относятся к "распределённому" (Dispatch) интерфейсу, который является частью механизма взаимодействия между контроллерами и серверами Automation. Возможность реализации и использования двойных интерфейсов также поддерживается Java/COM интеграционными возможностями. Однако проблемы с IDispatch и двойными интерфейсами выходят за рамки данного приложения. ```Ниже представлен пример клиентского кода. Первая строка просто импортирует пакет, созданный jactivex. Затем создаётся и используется экземпляр COM Automation сервера, как если бы он был обычным Java классом. Обратите внимание на модель типа внутри строки, где "инстанцирован" COM объект (то есть создан и вызван его экземпляр). Это согласуется с моделями COM объектов. В COM программист никогда не получает прямую ссылку на весь объект. Напротив, они имеют доступ к одному или нескольким интерфейсам, реализованным внутри класса.

После "инстанцирования" Java объекта Adder, это эквивалентно указанию COM активировать сервер и создать экземпляр этого COM объекта. Но затем нам нужно указать, какой интерфейс мы хотим использовать, выбирая один из интерфейсов, реализованных сервером. Именно эта работа выполняется моделью типа. Здесь используется "по умолчанию" распределённый интерфейс, который является стандартным интерфейсом для связи между одним контроллером Automation и сервером Automation. Подробнее об этом см. книгу «Inside COM» авторства Ibid. Обратите внимание на простоту активации сервера и выбора COM интерфейса!

import javaadder.*;

public class JavaClient {
  public static void main(String [] args) {
    Adder_DispatchDefault iAdder = 
         (Adder_DispatchDefault) new Adder();
    iAdder.setAddend(3);
    iAdder.sum();
    iAdder.sum();
    iAdder.sum();
    System.out.println(iAdder.getResult());
  }
}

Теперь мы можем скомпилировать этот код и начать запуск программы.##### 1. Пакет com.ms.com

Пакет com.ms.com определяет множество классов для работы с COM. Он поддерживает использование GUID, типы Variant (переменной) и SafeArray Automation (безопасного массива автоматизации), взаимодействие с ActiveX-компонентами на более глубоком уровне и управление COM-исключениями.Из-за ограничений по объёму здесь невозможно рассмотреть все эти темы. Однако мне хотелось бы акцентировать внимание на проблеме COM-исключений. По спецификациям практически все COM-функции возвращают значение HRESULT, которое сообщает нам, был ли вызов функции успешным и причины его неудачи при провале. Но если вы посмотрите на сигнатуры методов Java в серверном и клиентском кодах, то заметите отсутствие HRESULT. Вместо этого мы используем значения, возвращаемые некоторыми функциями, чтобы получить данные. "Виртуальная машина" (VM) преобразует вызовы функций в стиле Java в вызовы функций в стиле COM, включая параметры выхода. Однако если функция, вызванная нами на сервере, проваливается на уровне COM, что происходит в виртуальной машине? В этом случае JVM считает HRESULT как ошибку и генерирует внутреннее исключение Java типа com.ms.com.ComFailException. Таким образом, мы можем использовать механизм управления исключениями Java для управления ошибками COM, а не проверять значения, возвращаемые функциями.

Для получения более подробной информации о классах, содержащихся в этом пакете, обратитесь к документации продуктов Microsoft.### А.5.5 Интеграция ActiveX/Beans

Одним интересным результатом интеграции Java/COM является возможность интеграции ActiveX/Beans. Это означает, что Java Bean может быть помещён в контейнер ActiveX, такой как Visual Basic или любой продукт семейства Microsoft Office. Активный компонент ActiveX также может быть помещён в контейнер Beans, такой как Sun BeanBox. Microsoft JVM помогает учесть все необходимые детали. Активный компонент ActiveX представляет собой COM-сервер, который предоставляет заранее определённые, запрошенные интерфейсы. Bean представляет собой специальный Java-класс, следующий определённому стилю программирования. Однако на момент написания данной книги эта интеграция ещё не была совершенной. Например, виртуальная машина не может преобразовать события JavaBeans в модель событий COM. Если вам требуется контролировать события внутри Bean из контейнера ActiveX, Bean должен перехватывать системные события, такие как действия мыши, используя низкоуровневую технику, а не стандартную модель событий JavaBeans.

Пренебрегая этим вопросом, интеграция ActiveX/Beans всё равно остаётся очень интересной. Поскольку концепции и инструменты, затронутые здесь, полностью совпадают с теми, которые были обсуждены выше, рекомендую обратиться к вашей документации Microsoft для получения дальнейших деталей.

А.5.6 Внимание к встроенным методам и апплетамИспользование встроенных методов может привести к некоторым проблемам безопасности. Когда ваш код на Java вызывает встроенный метод, это эквивалентно передаче управления за пределы "системы" виртуальной машины. Встроенные методы имеют полный доступ к операционной системе! Конечно, если вы сами пишете эти встроенные методы, то именно такую возможность вы и хотите получить. Однако это недопустимо для апплетов — по крайней мере, без явного согласия. Мы не хотели бы видеть, как апплет, скачанный с удалённого сервера через Интернет, свободно работает с файловой системой и другими чувствительными областями системы, если это не специально разрешено. Чтобы предотвратить такие ситуации при использовании J/Direct, RNI и COM, только доверенные (делегированные) части кода на Java могут вызывать встроенные методы. В зависимости от конкретного использования апплета должны выполняться различные условия. Например, апплет, использующий J/Direct, должен иметь цифровую подпись, указывающую на его полное доверие. На момент написания этой книги некоторые из этих механизмов безопасности ещё не были реализованы (для Microsoft SDK for Java версии beta 2). Поэтому обязательно проверьте документацию новых версий.

А.6 CORBAВ больших распределённых приложениях некоторые наши требования могут не удовлетворяться ранее рассмотренными методами. Например, мы можем хотеть взаимодействовать с существующими данными, хранящимися в базе данных, или получать услуги от объекта-сервера независимо от его местонахождения. В таких случаях требуется форма "удалённого вызова процедур" (RPC), которая может быть также языково независимой. В этом случае CORBA может значительно помочь нам.CORBA не является языковой особенностью, а представляет собой технологию интеграции. Это определённый стандарт, который производители используют для создания продуктов, совместимых со стандартами CORBA. Стандарт CORBA был разработан организацией OMG (Object Management Group). Эта некоммерческая организация стремится определить стандартный набор средств для обеспечения взаимодействия между распределёнными, языково независимыми объектами.

С помощью CORBA можно осуществлять удалённый вызов как Java-объектов, так и объектов, написанных на других языках программирования, а также взаимодействовать с традиционными системами — используя "локально-независимый" подход. Java предоставляет сетевые возможности и является отличным "объектно-ориентированным" языком программирования для создания графических и нетекстовых приложений. Языки Java и CORBA хорошо сочетаются; например, они оба поддерживают концепцию "интерфейса" и имеют модель ссылочных объектов.### А.6.1 Основы CORBAОбъектно-ориентированные стандарты взаимодействия, разработанные организацией OMG, обычно называются «объектной архитектурой управления» (Object Management Architecture, OMA). OMA определяет два компонента: «основной объектный модель» (Core Object Model) и «референсная модель OMA» (OMA Reference Model). Референсная модель OMA определяет набор базовых служебных структур и механизмов, обеспечивающих возможность взаимодействия между объектами. Референсная модель OMA включает «объектный запросный брокер» (Object Request Broker, ORB), «объектные службы» (Object Services, также известные как CORBA services) и некоторые общие механизмы.ORB представляет собой канал связи для запросов между объектами. При выполнении запроса нет необходимости знать физическое расположение другого объекта. Это означает, что процесс, который выглядит как вызов логики программы с точки зрения клиентского кода, на самом деле является сложной операцией. В первую очередь должна существовать связь с серверным объектом. Для создания этой связи ORB должен знать местоположение реализованного серверного кода. После установления соединения параметры метода должны быть «собраны». Например, они должны быть преобразованы в двоичный поток данных для передачи через сеть. Другая информация, которую необходимо передать, включает имя машины сервера, процесс сервера и информацию для идентификации объекта внутри этого процесса. Наконец, эта информация передается через низкоуровневый протокол связи, декодируется на стороне сервера и затем выполняется вызов. ORB скрывает все эти сложные операции от программиста и делает работу почти такой же простой, как вызов метода локального объекта.

Не существует жесткого стандарта для реализации основного ORB, но чтобы обеспечить базовое совместимое взаимодействие между ORB различных производителей, организация OMG определила ряд услуг, доступных через стандартные интерфейсы.#### 1. Язык определения интерфейсов CORBA (IDL)

CORBA был спроектирован для прозрачного использования несколькими языками: клиентский объект может вызвать методы серверного объекта, независимо от того, какой язык используется для его реализации. Конечно, клиентский объект должен заранее знать название и сигнатуру метода, представленного серверным объектом. Здесь применяется IDL. CORBA IDL — это языково-независимый способ проектирования, который можно использовать для указания типов данных, свойств, операций, интерфейсов и многого другого. Синтаксис IDL похож на синтаксис C++ или Java. Ниже представлена таблица, которая сводит вместе некоторые общие концепции трёх языков и демонстрирует их соответствие.| CORBA IDL | Java | C++ | | --- | --- | --- | | Модуль (Module) | Пакет (Package) | Пространство имён (Namespace) | | Интерфейс (Interface) | Интерфейс (Interface) | Абстрактный класс (Abstract class) | | Метод (Method) | Метод (Method) | Член-функция (Member function) |

Концепция наследования также поддерживается — как в C++, использует оператор двоеточия. Для свойств, методов и интерфейсов, необходимых для реализации серверами и клиентами, программист пишет описание на IDL. Затем IDL компилируется с помощью компилятора IDL/Java, предоставленного производителем, который читает исходный код IDL и генерирует соответствующий код Java.Компилятор IDL является полезным инструментом: он не только создаёт эквивалентный код Java для IDL, но и генерирует код для сборки аргументов методов и отправки удалённых вызовов. Этот код называется "stub" и "skeleton", он организован в нескольких файлах Java и обычно относится к одной группе пакетов Java.

2. Услуга именования

Услуга именования является одной из базовых услуг CORBA. Объекты CORBA доступны через ссылку. Хотя информация о ссылках мало что говорит нашему глазу, её можно привязать к строковым именам, определённым программистом. Эта операция называется "строка ссылки". Компонент OMA, известный как "Услуга именования" (Naming Service), предназначен для выполнения преобразований и отображений между строками и объектами. Поскольку служба именования выполняет роль телефонной книги, которую могут запрашивать и использовать серверы и клиенты, она работает как самостоятельный процесс. Процесс создания отображения "объект-строка" называется "биндингом объекта"; удаление этого отображения называется "отвязыванием"; а передача строки через ссылку на объект называется "расшифровкой имени".

Например, при запуске серверное приложение может создать объект сервера, связать его со службой именования и затем ждать запросов от клиента. Клиент сначала получает ссылку на сервер, расшифровывает строковое имя и затем делает запрос к серверу через эту ссылку.Аналогично, спецификация Services Naming также является частью CORBA, но приложения, реализующие её, предоставляются производителями ORB. Из-за различий между производителями способы обращения к службе именования могут отличаться.

А.6.2 Пример

Показанный ниже код может не содержать всех подробностей, так как различные ORB предоставляют разные методы доступа к CORBA-сервисам. Поэтому любой пример будет зависеть от конкретного производителя (в данном случае используется JavaIDL — бесплатный продукт компании Sun, который включает упрощённую версию ORB, службу именования и компилятор "IDL→Java"). Кроме того, поскольку Java находится ещё в начальной стадии развития, то не все возможности CORBA могут быть реализованы во всех продуктах Java/CORBA. Мы хотим реализовать сервер, который будет работать на некоторых машинах, а другие машины смогут обращаться к нему за правильным временем. Также мы хотим создать клиента, который будет запрашивать правильное время. В данном случае, мы решили реализовать оба этих программных обеспечения на Java. Однако в реальных приложениях обычно используются различные языки.

1. Написание исходного кода IDL

Первым шагом является написание описания IDL для предоставляемого сервиса. Обычно это делает программист сервера. После этого программист может реализовать сервер на любом языке, если этот язык имеет компилятор IDL CORBA.Файл IDL распространяется среди программистов клиента и служит мостом между двумя языками.

Пример ниже демонстрирует описание IDL для сервера времени:

module RemoteTime {
   interface ExactTime {
      string getTime();
   };
};

Это объявление интерфейса ExactTime в пространстве имён RemoteTime. Интерфейс состоит из одного метода, который возвращает текущее время в виде строки.

2. Создание корневых и сухих классов

Второй шаг — это компиляция IDL, чтобы создать корневые Java-классы. Мы будем использовать эти классы для реализации клиента и сервера. Инструмент, который поставляется вместе с продуктом JavaIDL, называется idltojava:

idltojava -fserver -fclient RemoteTime.idl

Два этих маркера сообщают idltojava, что он должен генерировать код как для корней, так и для сухих классов. idltojava создаёт Java-пакет, названный в соответствии с модулем IDL, RemoteTime, и помещает сгенерированные Java-файлы в подкаталог RemoteTime. _ExactTimeImplBase.java представляет собой "сухую" часть, которую мы используем для реализации объекта сервера;

а _ExactTimeStub.java используется для клиента. В ExactTime.java представлено представление IDL-интерфейса на Java; также включены другие поддерживающие файлы, такие как файлы, упрощающие доступ к службе имен.

3. Реализация сервера и клиентаКод, который вы видите ниже, используется для серверной части. Объект сервера реализован в классе ExactTimeServer. Приложение RemoteTimeServer выполняет следующие действия: создает объект сервера, регистрирует его через ORB, указывает имя, которое должно использоваться для ссылки на объект, затем "тихо" ожидает запросов от клиентов.```

import RemoteTime.*;

import org.omg.CosNaming.; import org.omg.CosNaming.NamingContextPackage.; import org.omg.CORBA.*;

import java.util.; import java.text.;

// Реализация объекта сервера class ExactTimeServer extends _ExactTimeImplBase { public String getTime() { return DateFormat.getTimeInstance(DateFormat.FULL).format(new Date(System.currentTimeMillis())); } }


```md
## Реализация удаленного приложения
```java
public class RemoteTimeServer {
  public static void main(String args[]) {
    try {
      // Создание и инициализация ORB:
      ORB orb = ORB.init(args, null);
      // Создаем объект сервера и регистрируем его:
      ExactTimeServer timeServerObjRef = new ExactTimeServer();
      orb.connect(timeServerObjRef);
      // Получаем корневой контекст именования:
      org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
      NamingContext ncRef = NamingContextHelper.narrow(objRef);
      // Привязываем строковое имя к объекту:
      NameComponent nc = new NameComponent("ExactTime", "");
      NameComponent path[] = {nc};
      ncRef.rebind(path, timeServerObjRef);
      // Ожидаем запросов от клиентов:
      java.lang.Object sync = new java.lang.Object();
      synchronized(sync) {
        sync.wait();
      }
    } catch (Exception e) {
      System.out.println("Ошибка сервера времени: " + e);
      e.printStackTrace(System.out);
    }
  }
}

Как можно заметить, реализация серверного объекта очень проста; это обычный Java-класс, который наследует "сухую" кодовую базу, сгенерированную IDL-компилятором. Однако взаимодействие с ORB и другими службами CORBA несколько усложняется.

Некоторые службы CORBA

```Далее следует краткое описание того, что делает связанный с JavaIDL код (не будем пока обращаться к различиям между CORBA-кодом различных производителей). Первая строка в методе main() используется для запуска ORB. И это логично, так как именно этот объект позволяет серверному объекту общаться с внешним миром. После инициализации ORB создается временный объект сервера. В действительности, он является "краткосрочным объектом сервера": принимает запросы от клиентов и существует до тех пор, пока процесс, создавший его, остаётся активным. После регистрации краткосрочного объекта сервера через ORB последний становится известен ORB, который может направлять к нему запросы.

До настоящего момента у нас есть всего лишь объектное ссылочное значение timeServerObjRef, которое является действительным только в текущем серверном процессе. Следующий шаг — это присвоение этому сервисному объекту имени в виде строки. Клиенты будут использовать это имя для поиска сервисного объекта. Мы используем службу именования (Naming Service) для выполнения этой задачи. В первую очередь нам требуется ссылочное значение объекта службы именования. Это можно сделать с помощью вызова метода resolve_initial_references(), который предоставляет строковое ссылочное значение объекта службы именования (в JavaIDL это NameService) и возвращает его.Это модель использования метода narrow() для получения ссылки на конкретный контекст именования (NamingContext). Теперь мы можем начать использование службы именования. Для связи служебного объекта со строкой, представляющей объектное имя, мы сначала создаем объект NameComponent, используя ExactTime для его инициализации. ExactTime — это строка имени, которую мы хотим использовать для привязки служебного объекта. Затем мы используем метод rebind(), который ограничивается строковым представлением объектного имени. Мы используем rebind() для назначения ссылки, даже если она уже существует. Если ссылка уже существует, то вызов bind() приведёт к возникновению исключения. В CORBA имена состоят из последовательности NameComponent — вот почему нам нужно использовать массив для привязки имен к объектным ссылкам.Служебные объекты лучше всего подготовить для использования клиентами. В этот момент серверный процесс переходит в состояние ожидания. Также, поскольку это «short-term service» (краткосрочное обслуживание), время жизни ограничивается жизненным циклом серверного процесса. JavaIDL в настоящее время не предоставляет поддержку для «persistent objects» (постоянных объектов), которые существуют до тех пор, пока продолжает работать процесс, создающий их.

Теперь, когда мы имеем некоторое понимание работы серверного кода, давайте рассмотрим клиентский код:

import RemoteTime.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;

public class RemoteTimeClient {
  public static void main(String args[]) {
    try {
      // Создание и инициализация ORB:
      ORB orb = ORB.init(args, null);
      // Получение корневого контекста именования:
      org.omg.CORBA.Object objRef =
        orb.resolve_initial_references(
          "NameService");
      NamingContext ncRef =
        NamingContextHelper.narrow(objRef);
      // Получение (разрешение) строкового представления ссылки на объект времени:
      NameComponent nc =
        new NameComponent("ExactTime", "");
      NameComponent path[] = {nc};
      ExactTime timeObjRef =
        ExactTimeHelper.narrow(
          ncRef.resolve(path));
      // Вызов запросов к служебному объекту:
      String exactTime = timeObjRef.getTime();
      System.out.println(exactTime);
    } catch (Exception e) {
      System.out.println(
         "Ошибка сервера удаленного времени: " + e);
      e.printStackTrace(System.out);
    }
  }
}
```Первые строки выполняют ту же работу, что и они выполняли в серверном процессе: ORB получает инициализацию, а затем разрешает ссылку на службу именования.
Затем нам требуется ссылка на служебный объект, поэтому мы передаем строковое представление ссылки непосредственно методу `resolve()`. Затем используем метод `narrow()` для преобразования результата в ссылку на интерфейс `ExactTime`. Наконец, вызываем метод `getTime()`.

##### 5. Активация процесса службы именТеперь, когда мы имеем отдельный сервер и клиентское приложение, они готовы к взаимной коммуникации. Оба они требуют использования службы имен для привязки и анализа строковых объектных ссылок. Перед запуском сервиса или клиента нам необходимо активировать процесс службы имен. В JavaIDL служба имен представляет собой Java-приложение, которое входит в состав продукта. Однако она может отличаться от аналогичных служб других продуктов. Процесс службы имен в JavaIDL работает в экземпляре JVM и (по умолчанию) мониторит сетевой порт  Yöntem 900.

##### 6. Активация сервера и клиента

Теперь мы можем запустить серверное и клиентское приложение (в указанном порядке, так как существование сервера является временным). При правильной настройке все должно работать корректно, что будет видно по одной строке вывода в окне консоли клиента, которая покажет текущее время. Конечно, сам этот результат не особенно впечатляет. Но стоит обратить внимание на то, что даже находясь на одном компьютере, клиентское и серверное приложения выполняются в разных виртуальных машинах. Между ними происходит коммуникация через базовый слой интеграции  ORB и службу имен.

---

Исправленный текст:

Теперь, когда мы имеем отдельный сервер и клиентское приложение, они готовы к взаимной коммуникации. Оба они требуют использования службы имен для привязки и анализа строковых объектных ссылок. Перед запуском сервиса или клиента нам необходимо активировать процесс службы имен. В JavaIDL служба имен представляет собой Java-приложение, которое входит в состав продукта. Однако она может отличаться от аналогичных служб других продуктов. Процесс службы имен в JavaIDL работает в экземпляре JVM и (по умолчанию) мониторит сетевой порт 900.

##### 6. Активация сервера и клиента

Теперь мы можем запустить серверное и клиентское приложение (в указанном порядке, так как существование сервера является временным). При правильной настройке все должно работать корректно, что будет видно по одной строке вывода в окне консоли клиента, которая покажет текущее время. Конечно, сам этот результат не особенно впечатляет. Но стоит обратить внимание на то, что даже находясь на одном компьютере, клиентское и серверное приложения выполняются в разных виртуальных машинах. Между ними происходит коммуникация через базовый слой интеграции  ORB и службу имен.Это всего лишь простой пример, предназначенный для работы в локальной сети. Однако обычно ORB настраивается таким образом, чтобы обеспечивать "независимость от местоположения". Если сервер и клиент расположены на разных машинах, то ORB использует компонент, называемый "репозиторием реализации", для анализа удалённых строковых ссылок. Хотя "репозиторий реализации" является частью CORBA, его конкретные спецификации не установлены, поэтому реализация этого компонента различается между производителями.Как вы можете заметить, есть много аспектов CORBA, которые не были подробно рассмотрены здесь. Тем не менее, надеюсь, что данное описание дало вам общее представление о CORBA. Для получения более подробной информации о CORBA лучшим источником являются официальные сайты OMG, доступные по адресу `http://www.omg.org`. Этот сайт предоставляет множество документов, белых страниц, программ и ссылок на другие ресурсы и продукты CORBA.

### A.6.3 Java апплеты и CORBA

Java апплеты могут выполнять роль клиента CORBA. Таким образом, апплеты могут получать доступ к удаленным данным и услугам, предоставленным объектами CORBA. Однако апплеты могут связываться только с тем сервером, который первоначально загрузил их, поэтому все объекты CORBA, с которыми апплеты взаимодействуют, должны располагаться на этом сервере. Это противоречит основной идеологии CORBA, которая обещает "независимость от местоположения" или "независимость от расположения". Использование Java программы как клиента CORBA также может вызвать некоторые проблемы безопасности. Если вы работаете в локальной сети, одним из решений может быть снижение уровня безопасности браузера. В качестве альтернативы можно использовать прокси-сервер для обеспечения безопасной связи с внешними серверами.По этому вопросу некоторые продукты Java ORB предлагают свои решения. Например, некоторые реализуют технологию, известную как "HTTP-туннелирование" (HTTP Tunneling), другие предоставляют специальные возможности для работы с прокси-серверами.Как часть приложения, все эти темы кажутся слишком сложными для простого понимания. Однако они действительно являются важными моментами для рассмотрения.

### А.6.4 Сравнение CORBA и RMI

Мы уже знаем, что одной из ключевых особенностей CORBA является поддержка удалённых вызовов процедур (RPC). Эта технология позволяет нашим локальным объектам вызывать методы удалённых объектов. Конечно, существует встроенная возможность Java  RMI (см. главу 15)  которая делает то же самое. Хотя RMI позволяет Java объектам вызывать RPC друг у друга, CORBA способна выполнять RPC между объектами, написанными на любом языке. Это явно большая разница.

Однако через RMI можно вызывать службы, использующие удалённый, не-Java код. Для этого нам требуется некоторый вид упакованного Java объекта на стороне сервера, который "обёртывает" этот не-Java код. Упакованный объект связывается с Java клиентом через RMI и внутри него создаёт соединение с не-Java кодом  с помощью технологии, такой как JNI или J/Direct.

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

## А.7 ЗаключениеВ этом приложении мы рассматриваем самые базовые техники для вызова не-Java кода из Java приложений. Каждая из этих техник имеет свои преимущества и недостатки. Однако основная проблема заключается в том, что не все эти возможности доступны во всех JVM. Поэтому даже если Java программа может вызывать встроенные методы на конкретной платформе, она может не работать на другой платформе с другой JVM. Компания Sun предоставляет JNI, которая характеризуется гибкостью, простотой (хотя она требует значительного контроля над ядром JVM) и мощностью, а также универсальностью для большинства JVM. На момент окончания работы над этой книгой Microsoft всё ещё не предоставлял поддержки JNI, вместо этого предлагая свой J/Direct (удобный способ вызова Win32 DLL функций) и RNI (особенно подходящий для написания эффективного кода, но требующий глубокого понимания ядра JVM). Microsoft также предлагает своё патентованное решение для интеграции Java с COM. Это решение обладает большой мощностью и делает Java эффективным языком для написания COM серверов и клиентов. Только компиляторы и JVM от компании Microsoft поддерживают J/Direct, RNI и Java/COM. Последним, что мы рассмотрели, является CORBA, который позволяет нашим объектам на Java взаимодействовать с другими объектами  независимо от их местоположения и языка реализации.CORBA отличается от всех ранее упомянутых технологий тем, что он не интегрирован в язык Java, а использует технологии других производителей (третьих сторон) и требует покупки ORB от этих производителей. CORBA представляет собой интересное и универсальное решение, но если вам требуется просто вызывать операции системы, это может не считаться оптимальным решением.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master