J/Direct — это новое средство Microsoft Visual J+ + , облегчающее доступ к динамически
подключаемым библиотекам (DLL) Microsoft Windows. Применение J/Direct позволяет
вызывать функции из стандартных системных библиотек (KERNEL32 и USER32) и библиотек
других поставщиков напрямую. J/Direct гораздо проще, чем прежние интерфейсы
Raw Native Interface (RNI) и Java Native Interface (JNI). Они требуют написания
специализированной библиотеки-оболочки и выполнения нетривиального преобразования
типов. Благодаря J/Direct можно обращаться к большинству существующих в DLL
функций, просто объявив и вызвав их. J/Direct применяет директиву @dll.import,
которая похожа на оператор DECLARE из Visual Basic.
Для использования
J/Direct нужно установить Microsoft Visual J++ и Microsoft Internet Explorer
версии 4.0 или более новой. Для быстрой сборки приложений, учитывающих преимущества
J/Direct, применяйте J/Direct Call Builder, входящий в состав среды разработки
Visual J+ + . В этой главе объясняется, как использовать директиву @dll.import,
чтобы из Java-кода вызвать функцию, помещенную в DLL. Здесь также подробно описано,
как передаются и принимаются данные каждого типа и как применять директиву @dll.struct
для обмена структурами из DLL-методов.
В этом разделе
показано приложение для информационного окна, в котором используется директива
@dll.import.
Применять
J/Direct для вызова библиотек Win32 DLL из Java-кода очень просто. Вот короткое
Java-приложение, которое выводит информационное окно:
class ShowMsgBox
{
public static
void main(String args[]) {
MessageBox(0,
"It worked!",
"This messagebox
brought to you using J/Direct", 0); }
/** @dll.import("USER32") */
private static
native int MessageBox(int hwndOwner, String text,
String title,
int fuStyle); }
Директива
@dll.import сообщает компилятору, что метод MessageBox свяжется с USER32.DLL
посредством протокола J/Direct, а не RNI, который поддерживался в предыдущих
версиях. Кроме того, Microsoft Virtual Machine (VM) for Java (Виртуальная машина
(ВМ) Java от Microsoft) поддерживает автоматическое размещение типов из объектов
String в строки, оканчивающиеся нулем, что требуется в С. Нет необходимости
указывать, должна ли вызываться функция с кодировкой ANSI (MessageBoxA) или
с кодировкой Unicode (MessageBoxW). В предыдущем примере всегда будет вызываться
ANSI-версия функции. В разделе «Как ВМ выбирает между ANSI и Unicode»
далее в этой главе объясняется, как использовать модификатор auto для
вызова подходящей версии функции в соответствии с версией Microsoft Windows,
на которой выполняется приложение.
Используя
J/Direct Call Builder, Вы быстро создадите вызовы функций Win32 API в стандарте
J/Direct. Это средство автоматически вставляет в Ваш Java-код определения для
элементов Win32 API вместе с соответствующими тэгами @dll.import.
Чтобы
получить доступ к Win32 API при помощи J/Direct Call Builder:
Примечание
Чтобы найти элемент по начальным символам его имени, введите эти
символы в поле ввода Find. Автоматически будет выбран первый элемент,
имя которого начинается с этих символов.
При выборе
элемента Win32 API в J/Direct Call Builder в нижней панели предварительного
просмотра выводится соответствующее Java-определение. Этот текст копируется
и вставляется в любой файл. Чтобы быстро получить интерактивную справочную информацию
по элементу Win32 API, щелкните его правой кнопкой мыши и в появившемся контекстном
меню выберите Display API Help.
Информацию
о параметрах J/Direct Call Builder, установленных по умолчанию, см. в следующем
разделе «Установка параметров J/Direct Call Builder».
Установка
параметров J/Direct Call Builder
Visual J++
позволяет установить два параметра J/Direct Call Builder, влияющие на его функционирование.
Чтобы
установить параметры J/Direct Call Builder:
Параметр |
Описание |
||
Show Target
window when copy to target Disable stack crawl security check |
Выбран но умолчанию.
При вводе вызова J/Direct в свой класс, компоновщик автоматически
открывает файл .Java в текстовом редакторе (если он еще не открыт).
По умолчанию сброшен. Для каждого вызова J/Direct BM будет инициировать
проверку безопасности стека вызовов. Если хотя бы один из вызовов
на стеке не вполне надежен, генерируется исключение по безопасности
и обращения к вызову J/Direct не происходит. Это относится главным
образом к Java-коду на Web-страницах. Если этот параметр выбирается
для блокировки проверки безопасности, то в Ваш класс будет добавлен
тэг @security(checkDHCalls = off) при следующей вставке вызова
J/Direct. (Если Вы в дальнейшем очистите этот параметр, то имеющиеся
тэги ©security останутся в файлах, но новые не добавятся.) |
||
В этом разделе
представлен краткий обзор директив @dll.import, @dll.struct и @dll.structmap.
Для каждой директивы показан и пояснен требуемый синтаксис.
Директива
@dll.import должна размещаться непосредственно над объявлением метода. Синтаксис
директивы таков:
/**@dll.import("LibName",<Modifier>)*/
...объявление
метода...;
LibName
— имя DLL, в которой содержится вызываемая функция. Modifier необязателен,
а возможные значения зависят от Ваших потребностей. При объявлении метода используется
то же имя функции, что и в DLL, либо методу присваивается иное имя как псевдоним.
Подробнее о псевдонимах см. в разделе «Псевдонимы, (переименование методов)»
далее в этой главе. Типы данных Java, выбранные Вами для параметров метода и
его возвращаемого значения, должны соответствовать типам данных для параметров
и возвращаемого значения функции в DLL. Дополнительную информацию о соответствии
типов данных Java и системы см. в разделе «Как преобразуются типы данных»
далее в этой главе.
В таблице
показан синтаксис (a dll.import для нескольких ситуаций, обсуждаемых в этой
главе.
Ситуация |
Требуемый
синтаксис |
Разъяснение |
||
Вызов Win32
DLL |
/**@dll. import
("Libname")*/ |
|
||
Вызов OLE API
' |
/**@dll. import
("Libname", ole)*/ |
|
||
Псевдоним |
/**@dll. import
("Libname", entrypoint="DLLFunction Name")*/ |
В объявлении
метода используйте выбранное имя Java. |
||
Связывание по
порядковому номеру |
/**@dll. import("Libname",
en trypoint=" #ordinal")*/ |
«ordinal»
— это 16-разрядное целое число, заданное в десятичном виде, которое
определяет импортируемую из DLL функцию. |
||
Директива
@dll.struct должна размещаться непосредственно над объявлением класса. Синтаксис
ее таков:
/**@dll.struct(<LinkTypeModifier>,<pack=n>)*/
...объявление класса...;
LinkTypeModifier
сообщает компилятору, представлены типы String и char символами ANSI или
Unicode. Его значениями могут быть ansi, Unicode или auto. Если
LinkTypeModifier не задан, то по умолчанию используется ansi. Чтобы
лучше понять, какие значения следует применять для LinkTypeModifier, см.
раздел «Как ВМ делает выбор между ANSI и Unicode» далее в этой главе.
Можно также
задать параметр pack=n, сообщающий компилятору размер выравнивания структуры
на 1, 2, 4 или 8 в зависимости от значения п. При отсутствии модификатораpack=n
размер выравнивания равен 8 по умолчанию. Подробнее об установке размера
выравнивания см. в разделе «Выравнивание структур» далее в этой
главе. В объявлении класса необходимо использовать поля, типы которых соответствуют
типам полей в исходной структуре, и в том порядке, как они расположены в исходной
структуре. Подробнее о выборе типов данных для полей см. в разделе «Соответствие
между типами внутри структур» далее в этой главе.
В таблице
описан синтаксис для ситуаций, в которых используется директива @dll. struct.
Ситуация |
Требуемый синтаксис
Разъяснение |
||
Объявление |
/**@dll.struct()*/
Если не задан |
||
структуры |
LinkTypeModifier,
то предполагается, что поля типа char или String представлены
символами ANSI. |
||
Установка размера
выравнивания структуры |
/**@dll.struct(pack=n)*/
n может быть 1, 2, 4 или 8. |
||
Объявление структуры,
имеющей поле типа char |
/**@<Ш. struct(ansi)*/
Поле представлено символами ANSI. |
||
Объявление структуры,
имеющей поле типа String, и установка размера выравнивания |
/**@dll. struct(unicode,
Поля типа String pack=n)*/ представлены в формате Unicode, а размер
выравнивания равен 1, 2, 4 или 8 — в зависимости от значения п. |
||
Директива
@dll.structmap используется для объявления строк и массивов фиксированной длины,
входящих в состав структур. Директива должна быть помещена внутри структуры,
объявленной с @dll.struct, и размещаться непосредственно над объявлением поля
строки или массива фиксированной длины. Подробнее об использовании @dll.structmap
см. в разделах «Строки фиксированной длины внутри структур» и «Скалярные
массивы фиксированной длины внутри структур» далее в этой главе. В следующей
таблице описан синтаксис директивы @dll.structmap.
Ситуация |
Требуемый
синтаксис |
Разъяснение |
||
Структура, содержащая строку фиксиро ванной длины | /**@dll. struct map ([type =TCHAR[size]])*/ | Size — десятичное целое, обозначающее число символов в строке вместе с завершающим нулем. | ||
Структура, содержащая массив фиксированной длины |
/**@dll. structmap([type
=FIXEDARRAY,size= n])*/ |
п — десятичное целое, которое обозначает размер массива. |
||
Если объяснять
просто, то Microsoft VM проверяет аргументы на Java и затем преобразует их в
типы данных, присущие языку C++. Microsoft VM определяет принадлежность типа
каждого параметра и возвращаемого значения на основе объявленного (во время
компиляции) типа Java-данных. Например, параметр, объявленный в Java как целое,
передается как 32-разрядное целое, параметр, объявленный в Java как объект String,
передается как строка, оканчивающаяся нулем, и т. д. Нет никаких невидимых атрибутов,
предоставляющих информацию о «родных» типах данных. В Java реализуется
принцип полного соответствия: что видишь, то и получишь.
В следующих
двух таблицах перечислены «родные» (собственные) типы данных, соответствующие
каждому типу Java-данных. В первой описаны соответствия для параметров и возвращаемых
значений, а во второй — для директивы @dll.struct.
Соответствия
параметров и возвращаемых значений
Тип Java |
«Родной»
тип |
Примечания/Ограничения |
||
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. В
режиме ole String отображается на LPWSTR. Microsoft VM очищает
память под строку, используя CoTaskMemFree. |
||
StringBuffer |
LPTSTR |
Недопустимо
в качестве возвращаемого значения. Установите вместимостьStringBuffer
достаточной для хранения строки максимального размера, которую может
сгенерировать функция из DLL. |
||
byte[] |
BYTE* |
|
||
shortf] |
WORD* |
|
||
char[] |
TCHAR* |
|
||
int[] |
DWORD* |
|
||
float[] |
float* |
|
||
doublef] |
double* |
|
||
long[] |
__int64* |
|
||
Boolean[] |
BOOL[] |
|
||
Объект |
Указатель на
структуру |
В режиме ole
передается IUnknown*. |
||
Интерфейс |
Интерфейс COM |
Для генерации
файла интерфейса используйте jactivex или подобное ему средство. |
||
com.ms.com.
SafeArray |
SAFEARRAY* |
Недопустимо
в качестве возвращаемого значения. |
||
com.ms.com.
Guid |
GUID, IID, CLSID |
|
||
com.ms.com.Variant |
VARIANT* |
|
||
Классы @dll.
struct |
Указатель на
структуру |
|
||
Классы |
Указатель на
структуру |
|
||
@com. struct |
|
|
||
void com.ms.dll.
Callback |
VOID Указатель на
функцию |
Только как возвращаемое
значение. Только как параметр. |
||
Соответствия,
используемые с директивой @dll.struct
Тип Java |
«Родной»
тип |
||
byte |
BYTE или CHAR |
||
char |
TCHAR |
||
short |
SHORT или WORD |
||
int |
INT, UINT, LONG,
ULONG или DWORD |
||
float |
__int64 |
||
long |
float |
||
double |
double |
||
Boolean |
ВООL[] |
||
String |
Указатель на
строку или встроенная строка фиксированного размера |
||
Класс с |
Вложенная
структура |
||
директивой |
|
||
@dll.struct |
|
||
charf] |
Вложенный массив
TCHAR |
||
byte[] |
Вложенный массив
BYTE |
||
short[] |
Вложенный массив
SHORT |
||
int[] |
Вложенный массив
LONG |
||
loagfl |
Вложенный массив
int64 |
||
float[] |
Вложенный массив
float |
||
double[] |
Вложенный массив
double |
||
Базовые скалярные типы отображаются следующим образом.
Тип Java | Тип в DLL |
int | 32-разрядное целое со знаком |
byte | 8-разрядное целое со знаком |
short | 16-разрядное целое со знаком |
long | 64-разрядное целое со знаком |
float | 32-разрядное число с плавающей точкой |
double | 64-разрядное число с плавающей точкой |
Обратите
внимание: в Java нет прямого соответствия для целых типов без знака. Поскольку
в Java нет беззнаковых типов, то используйте тип, имеющий тот же размер, что
и нужный Вам целый тип. Например, тип int в Java можно использовать для представления
распространенного типа DWORD (32-разрядное целое без знака).
Тип char в Java превращается в CHAR (8-разрядный ANSI-символ) за исключением случаев использования в @dll.struct модификаторов unicode или ole, когда он превращается в WCHAR (16-разрядный Unicode-символ).
Тип Boolean в Java соответствует 32-разрядному типу BOOL в Win32. В качестве
параметра true отображается в 1, a false — в 0. В качестве возвращаемого значения
любая ненулевая величина отображается в true. Обратите внимание, что BOOL и
VARIANT_BOOL (внутренний Булев тип в Microsoft Visual Basic) не являются взаимозаменяемыми.
Для передачи типа VARIANT_BOOL в DLL на Visual Basic Вы должны использовать
тип short из Java и значения -1 для VARIANT_TRUE и О для VARIANT_FALSE.
Здесь рассказано, как передавать строки в форматах ANSI и Unicode в функцию,
находящуюся в DLL, а также обсуждаются два способа возврата строк из таких функций.
Передача строки в функцию
Для передачи в функцию стандартной оканчивающейся нулем строки просто передайте
строку типа String.
Например, для изменения текущего каталога можно использовать функцию CopyFile
из КегпеШ:
class ShowCopyFile {
public static void main(String args[])
{
CopyFile("old.txt", "new.txt", true);
}
/** @dll.import("KERNEL32") */
private native static boolean CopyFile(String existingFile,
String newFile, boolean (); }
В Java строки доступны только для чтения, поэтому Microsoft VM конвертирует
строку только как входной параметр. Чтобы виртуальная машина могла преобразовать
строки без копирования символов, параметры типа. String не должны использоваться
с функциями, которые могут изменить строку. Если функция способна изменить строку,
передайте объект типа StringBuffer.
Строки преобразуются в формат ANSI за исключением случаев применения модификаторов
Unicode или ole, когда строки передаются в формате Unicode.
Строки не могут быть указаны в качестве возвращаемых значений, за исключением
режима ole, когда в качестве возвращаемого типа предполагается строка типа LPWSTR,
память под которую выделена функцией CoTaskMemAlloc.
Получение
строки из функции DLL
Есть два
распространенных способа получения строки из функции: вызывающий выделяет буфер,
который заполняется функцией, либо функция выделяет память под строку и возвращает
строку вызывающему. Большинство функций Win32 используют первый способ, а функции
OLE — второй. (В разделе «Обращение к функциям OLE API» далее в
этой главе см. подробнее о специальной поддержке, которую J/Direct предоставляет
для вызова функций OLE.) Одна из функций, использующих первый способ, — GetTempPath
из Кегпе132, она имеет следующий прототип;
DWORD GetTempPath(DWORD sizeofbuffer, LPTSTR buffer); Эта функция возвращает путь к системному каталогу временных файлов (например, «c:\tmp\»). Аргумент buffer указывает на буфер, выделенный вызывающим, — в него будет помещен путь, a sizeofbuffer указывает количество символов, которое можно поместить в буфер. (Что не равно числу байт в случае Unicode-версии функции.) В Java строки доступны только для чтения, поэтому нельзя передать в качестве буфера объект String. Нужно использовать класс StringBuffer для создания объекта, в который можно записать строку. Вот пример обращения к функции
GetTempPath: class
ShowGetTempPath
{
static final
int MAX_PATH = 260; public static void main(String args[])
{
StringBuffer
temppath = new StringBuffer(MAX_PATH);
GetTempPath(temppath.capacity(),
temppath);
System.out.println("Temppath
= " + temppath); }
/** @dll.import("KERNEL32")
*/
private static
native int GetTempPath(int sizeofbuffer,
StringBuffer
buffer); }
Для понимания
этого примера важно различать длину StringBuffer и его вместимость. Длина —
это количество символов в строке, хранящейся в StringBuffer. Вместимость — объем
памяти, выделенный для StringBuffer. После выполнения оператора: StringBuffer
sb = new StringBuffer(259); длина (sb.length) будет равна 0, а вместимость
(sb.capacity) — 259. При вызове функции из DLL с передачей в нее StringBuffer
виртуальная машина проверяет вместимость StringBuffer, добавляет единицу для
завершающего нуля, умножает на 2, если по умолчанию используется
Unicode,
и выделяет полученное количество байт в памяти для буфера, передаваемого в функцию.
Иными словами, для установки размера буфера используется не длина, а вместимость.
Будьте внимательны и не сделайте следующую ошибку:
StringBuffer sb = new StringBuffer();
//Неверно!
GetTempPath(MAX_PATH,
sb);
Вызов StringBuffer-конструктора
без аргументов создает объект String-Buffer со значением capacity, равным 16,
что, видимо, слишком мало. В функцию GetTempPath передается МАХ_РАТН, сообщая,
что в буфере достаточно места для хранения 260 символов. Следовательно, GetTempPath,
скорее всего, выйдет за пределы буфера. Если планируется интенсивно использовать
GetTempPath, сделайте оболочку на Java следующим образом:
public static
String GetTempPath() {
StringBuffer
temppath = new StringBuffer(MAX_PATH-1);
int res = GetTempPath(MAX_PATH,
temppath);
if (res == 0
|| res > MAX_PATH) {
throw new RuntimeException("GetTempPath
error!");
}
return temppath.toString();
// нельзя вернуть StringBuffer }
В этом методе
сочетается удобство и безопасность, а также производится перевод ошибочного
возвращаемого значения функции в Java-исключение. Обратите внимание, что нельзя
возвращать объекты StringBuffer.
J/Direct
автоматически обрабатывает массивы данных скалярных типов. Вот таблица трансляции
типов массивов Java в «родные» типы указателей.
Тип Java |
«Родной»
тип |
Количество
байт на элемент |
||
byte[] |
BYTE* |
1 |
||
shortf] |
SHORT* |
2 |
||
int[] |
DWORD* |
4 |
||
floatt] |
FLOAT* |
4 |
||
double[] |
DOUBLE* |
8 |
||
long[] |
__int64* |
8 |
||
Booleanf] |
BOOL* |
4 |
||
Тип массива
charf] отображается в CHAR*, за исключением случая использования модификатора
Unicode, когда он отображается в WCHAR*. Все параметры типа скалярный
массив могут быть изменены (такие, как параметры [in, out]).
Массивы не
могут быть возвращаемыми значениями. Не бывает массивов объектов или массивов
строк.
Обычно указатели
используются функциями OLE для возврата значений. (Функции OLE используют возвращаемое
функцией значение для возврата кода ошибки типа HRESULT.) В разделе «Обращение
к функциям OLE API» далее в этой главе см. о получении возвращаемого значения
функции OLE.
В языке Java
отсутствует непосредственная поддержка концепции структур. Хотя классы Java
содержат поля и используются для эмуляции концепции структур внутри языка Java,
обычные Java-объекты не могут применяться для эмуляции структур в вызовах функций
из DLL. Это вызвано тем, что язык Java не гарантирует выравнивания полей, а
также что сборщик мусора может свободно перемещать объект по памяти. Следовательно,
чтобы обмениваться структурами с функциями в DLL, нужно использовать директиву
компилятора @dll.struct. Использование ее с определением класса приводит к тому,
что все экземпляры этого класса попадают в блок памяти, который не будет перемещаться
во время сборки мусора. Кроме того, выравнивание полей контролируется и при
помощи модификатора pack (см. раздел «Выравнивание структур» далее
в этой главе). Например, структура SYSTEMTIME из Win32 описывается на языке
С следующим образом:
typedef struct
{
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME;
Корректное объявление
этой структуры на языке Java выглядит так:
/** @dll.struct()
*/ class SYSTEMTIME {
public short
wYear;
public short
wMonth;
public short
wDayOfWeek;
public short
wDay;
public short
wHour;
public short
wMinute;
public short
wSecond;
public short
wMilliseconds; }
Вот пример использования
структуры SYSTEMTIME при вызове функции из DLL:
class ShowStruct
{
/** tdll.import("KERNEL32")
*/
static native
void GetSystemTime(SYSTEMTIME pst);
public static
void main(String args[])
{
SYSTEMTIME systemtime
= new SYSTEMTIMEO;
GetSystemTime(systemtime);
System.out.println("Year
is " + systemtime.wYear);
System.out.println("Month
is " + systemtime.wMonth);
// и т.д. }
>
Примечание
Классы, объявленные с директивой @dll.struct, рассматриваются как
незащищенные и, следовательно, не могут использоваться ненадежными (untrusted)
апплетами.
Соответствие
между типами внутри структур
В таблице
показано соответствие между скалярными типами внутри структур.
Тип Java |
«Родной»
тип |
||
byte char short |
BYTE TCHAR (CHAR
или WCHAR в зависимости от определения @dll. struct) SHORT |
||
int |
LONG |
||
long float |
_int64 float |
||
double | double | ||
Boolean |
BOOL (32-разрядный
Булев) |
||
Ссылочные
типы (объекты и классы Java) обычно отображаются на встроенные структуры и массивы.
В следующей таблице описано каждое поддерживаемое отображение.
Тип Java |
«Родной»
тип |
||
String Класс с директивой
@dll.struct char[] |
Указатель на
строку или встроенная строка фиксированного размера Вложенная структура Вложенный массив
TCHAR (CHAR/WCHAR) |
||
Тип Java |
«Родной»
тип |
||
byte[] |
Вложенный массив
BYTE |
||
short[] |
Вложенный массив
SHORT |
||
int[] |
Вложенный массив
LONG |
||
long[] |
Вложенный массив
__ int64 |
||
float[] |
Вложенный массив
float |
||
double[] |
Вложенный массив
double |
||
Указатели
внутри структур не поддерживаются напрямую из-за огромного числа способов создания
и удаления объекта, на который ссылается указатель. Для представления структур
со встроенными указателями объявите поле указателя как тип int. Вам придется
сделать явные вызовы функций выделения памяти из DLL и самостоятельно инициализировать
блоки памяти. (Для отображения блоков на классы, объявленные с директивой @dll.struct,
можно использовать DllLib.ptrToStruct.)
Вложенные
структуры
Структура
способна вмещать другую структуру благодаря простому объявлению последней в
качестве типа поля. Например, MSG-структу-ра в Windows вмещает структуру POINT
следующим образом:
typedef struct
{
LONG x;
LONG у; } POINT;
typedef struct
{
int hwnd;
int message;
int wParam;
int IParam;
int time;
POINT pt; }
MSG; Это напрямую переводится на язык Java:
/** @dll.struct()
*/ class POINT {
int x;
int y; }
/** @dll.struct()
*/ class MSG {
public int hwnd;
public int message;
public int wParam;
public int IParam;
public int time;
public POINT
pt; }
Совет
Хотя вложение структур удобно, но факт остается фактом — Java поддерживает
не вложение объектов, а лишь ссылки на объекты. Microsoft VM вынуждена переводить
данные между этими двумя форматами при каждой передаче вложенной структуры.
Следовательно, в особо важном коде можно улучшить производительность путем вложения
структур вручную (копируя поля вложенной структуры в контейнерную структуру).
Например, поле pt в структуре MSG можно объявить как два целых поля pt_x и pt_y.
Строки
фиксированного размера, встроенные в структуры
В некоторые
структуры встроены строки фиксированного размера. Например, структура LOGFONT
определена следующим образом:
typedef struct
{
LONG IfHeight;
LONG IfWidth;
/* <другие
поля для краткости не показаны> */ TCHAR lfFaceName[32]; } LOGFONT;
Она может быть
объявлена на Java с использованием расширенного синтаксиса, задающего размер:
/** edll.struct()
*/ class LOGFONT {
int IfHeight;
int IfWidth;
/* <другие
поля для краткости не показаны> */
/** <s>dll.structmap([type=TCHAR[32]])
*/
String IfFaceName;
}
Директива
@dll.structmap описывает размер фиксированной строки в символах (включая место
для нуля в конце строки). Подробнее см. раздел «Скалярные массивь( фиксированного
размера, встроенные в структуры».
Скалярные
массивы фиксированного размера, встроенные в структуры
Скалярные массивы фиксированного размера, встроенные в структуры, можно задать с использованием директивы @dll.structmap. Вот объявление на языке С структуры, содержащей скалярные массивы фиксированного размера:
struct EmbeddedArrays
{
BYTE b[4];
CHAR c[4];
SHORT s[4];
INT i[4];
__int64 1[4];
float f[4];
double d[4];
};
Ha Java эту
структуру можно описать с использованием директивы @dll.structmap:
/** edll.struct()
*/ class EmbeddedArrays
{
/** @dll.structmap([type=FIXEDARRAY,
size=4]) */
byte b[];
/** @dll.structmap([type=FIXEDARRAY,
slze=4]) */ char c[];
/** @dll.structmap([type=FIXEDARRAY,
size=4]) */ short s[];
/** @dll.structmap([type=FIXEDARRAY,
size=4]) */ int i[];
/** @dll.structmap([type=FIXEDARRAY,
size=4]) */ long l[];
/** @dll\structmap([type=FIXEDARRAY,
size=4]) */ float f[];
/** @dll.structmap([type=FIXEDARRAY,
size=4]) */ double d[];
}
Выравнивание
структур
Поля структур выравниваются в соответствии с проектом (draft) ANSI С 3.5.2.1. Размер выравнивания может быть задан при помощи модификатора pack:
/** @dll.struct(pack=n)
*/
где п
равен 1, 2, 4 или 8. По умолчанию используется 8. Для тех, кто знаком с
компиляторами Microsoft Visual C++: «pack=n» эквивалентно «#pragma
раск(л)».
Различие
между @dll.struct и @com.struct
Директива
@dll.struct очень похожа на директиву @com.struct, вставляемую посредством jactivex
и неявно при помощи javatlb (javatlb, документированное в предыдущих версиях
Microsoft Visual J++, заменено на jactivex). Главное различие между директивами
в том, что для первой отображение типов по умолчанию соответствует вызову функций
Microsoft Windows, а для второй — вызову объекта СОМ. Из этого следует, что
можно сгенерировать классы @dll.struct, описав структуры в библиотеке типов
и применив jactivex для генерации класса Java. Обычно, однако, быстрее создать
класс вручную.
В Java не
поддерживается тип указателя данных. Однако вместо передачи указателя допустимо
передавать одноэлементный массив. Можно хранить указатели в целых числах Java,
а также читать и писать данные из нетипизированных (raw) указателей.
Возврат
значений указателей
Функции Win32,
которые возвращают несколько значений одновременно, как правило, используют
для этого указатели на переменные, обновляемые внутри функции. Например, функция
GetDiskFreeSpace имеет следующий прототип:
BOOL GetDiskFreeSpace(LPCTSTR
szRootPathName,
DWORD *IpSectorsPerCluster,
DWORD *IpBytesPerCluster,
DWORD *IpFreeClusters,
DWORD *lpClusters);
Вызывается эта функция обычно так:
DWORD sectorsPerCluster,
bytesPerCluster, freeClusters, clusters; GetDiskFreeSpace(rootname, §o
rsPe rCluste r,
&bytesPerCluster,
&freeClusters, &clusters);
В Java это
просто особый случай передачи скалярного массива, когда размерность массива
равна одному элементу. Вот пример вызова функции GetDiskFreeSpace:
class ShowGetDiskFreeSpaoe
{
public static
void main(String args[]) {
int sectorsPerCluster[]
= {0}; int bytesPerCluster[] = {0};
int freeClusters[]
= {0};
int clusters[]
= {0};
GetDiskFreeSpace("c:\\",
sectorsPerCluster, bytesPerCluster,
freeClusters,
clusters);
System.out.println("sectors/cluster = " + sectorsPerCluster[0]);
System.out.priptln("bytes/cluster = " + bytesPerCluster[0]);
System.out.println("free clusters = " + freeClusters[0]);
System.out.println("clusters
= " + clusters[0]); }
/** @dll.import("KERNEL32")
*/
private native
static boolean GetDiskFreeSpace(String rootname, int pSectorsPerCluster[], int
pBytesPerCluster[], int pFreeClusters[], int pClusters[]); }
Нетипизированные
указатели
Указатель
на неизвестную или очень сложную структуру может храниться в Java как простое
целое. Если приложению нужно только сохранить указатель и не нужно переименовывать
(dereference) его, то это самый простой и наиболее эффективный подход. Он используется
для хранения указателя, который возвращен DLL-функцией выделения памяти. Нечего
и говорить, что применение нетипизированных указателей исключает многие преимущества
Java в области безопасности. Везде, где можно, используйте другие способы. Однако
бывают ситуации, когда приходится применять нетипизированные указатели. Есть
два варианта чтения/записи данных с использованием этих указателей.
Приведение
к ссылке на класс, объявленный с директивой @dll.struct
Один из способов
чтения/записи данных через нетипизированный указатель состоит в приведении этого
указателя к ссылке на класс, объявленный с директивой @dll.struct. После этого
можно читать/записывать данные, используя нормальный синтаксис обращения к полям
объекта. Предположим, к примеру, что Вы хотите обращаться к нетипизированному
указателю как к структуре RECT. Для этого используется системный метод DllLib.ptrToStruct
следующим образом:
/** @dll.struct()
*/ class RECT { int left; int top; int right; int bottom; }
import com.ms.dll.*;
int rawptr =
...;
RECT reet =
(RECT)DllLib.ptrToStruct(RECT.class, rawptr);
rect.left =
0;
rect.top = 0;
rect.right =
10;
rect.bottom
= 10;
Метод ptrToStruct
преобразует нетипизированный указатель в ссылку на экземпляр RECT. В отличие
от экземпляров, созданных оператором new, этот RECT-экземпляр не будет
пытаться освободить нетипизированный указатель по требованию сборщика мусора,
так как объект RECT не может узнать, каким образом получен указатель:, Кроме
того, поскольку системная память уже выделена к моменту вызова ptrToStruct,
конструктор класса RECT не вызывается.
Использование
методов копирования из DIILib
Другой способ
чтения/записи данных посредством нетипизированного указателя состоит в использовании
замещаемых (overloaded) методов копирования из DIILib. Они копируют данные между
массивами разных типов и нетипизированными указателями. Если нужно трактовать
нетипизированный указатель как указатель на строку (LPTSTR), то можно использовать
один из методов ptrToStringAnsi, ptrToStringUni или ptrToString из DIILib для
разбора строки и преобразования ее в объект Java. lang. String:
import com.ms.dll.*;
int rawptr =
String s = DIILib.ptrToStringAnsi
(rawptr);
Внимание
Все Java-объекты могут быть передвинуты в памяти по решению сборщика
мусора. Следовательно, Вы не должны пытаться получить указатель на массив Java
путем вызова функции из DLL, которая выполняет простое приведение типов. Вот
пример неправильного способа получения указателя:
// Не делайте
этого!
/** edll.import("MYDLL")
*/
private native
static int Cast(int javaarray[ ]);
// Внутри MYDLL.DLL
LPVOID Cast(LPVOID ptr) {
// He делайте
этого!
return ptr;
// входит как массив Java; выходит как тип int Java }
Неизменность
значения ptr гарантируется только на время вызова функции Cast. Это обусловлено
тем, что виртуальные машины передают
массивы в функции путем копирования, и тем, что сборка мусора может привести
к изменению физического положения массива после возврата из функции Cast.
В некоторых
функциях Win32 есть параметры, чей тип зависит от значения другого параметра.
Например, функция WinHelp объявлена следующим образом:
BOOL WinHelp(int
hwnd, LPCTSTR szHelpFile, UINT cmd, DWORD.dwData);
В зависимости
от значения параметра cmd внешне невинный параметр dwData может
быть указателем на строку, указателем на структуру MULTIKEYHELP, указателем
на HELPWININFO или просто целым. J/Direct предлагает два способа для объявления
такого параметра:
Их сравнение
— в разделе «Сравнение двух способов» далее в этой главе.
Объявление
параметра типа Object
Вот пример
объявления WinHelp с параметром dwData типа Object:
/** edll.import("USER32")
*/
static native
boolean WinHelp(int hwnd, String szHelpFile,
int cmd, Object
dwData);
При вызове
WinHelp J/Direct использует тип времени выполнения для определения того, во
что преобразовать dwData. В таблице описано это преобразование таких
типов.
Тип |
Преобразуется
в |
||
Java.lang.Integer |
4-байтное целое |
||
Java. lang.
Boolean |
4-байтное BOOL |
||
Java. lang.
Char |
CHAR (или WCHAR,
если действуют модификаторы Unicode или ole) |
||
Java.lang.Short |
2-байтное SHORT |
||
Java. lang.
Float |
4-байтное FLOAT |
||
Java. lang.
String |
LPCSTR (или
LPCWSTR, если действуют модификаторы Unicode или ole) |
||
java.lang.String |
LPSTR (или LPWSTR,
если действуют |
||
Buffer |
модификаторы
Unicode или ole) |
||
byte[] |
BYTE* |
||
char[] |
CHAR* (или WCHAR*,
если действуют модификаторы Unicode или ole) |
||
short[] |
SHORT* |
||
int[] |
INT* |
longf] |
_int64* |
float[] |
float* |
doublef] |
double* |
@dll.struct |
Указатель на
структуру |
Замещение
функции
Другой способ объявления функции WinHelp состоит в замещении функции для каждого возможного типа данных:
/** @dll.import("USER32")
*/
static native
boolean WinHelp(int hwnd, String szHelpFile,
int cmd, int
dwData);
/** @dll.import("USER32")
*/
static native
boolean WinHelp(int hwnd, String szHelpFile,
int cmd, String
dwData); /** tdll.import("USER32") */
static native
boolean WinHelp(int hwnd, String szHelpFile,
int cmd, MULTIKEYHELP
dwData);
/** @dll.import("USER32")
*/
static native
boolean WinHelp(int hwnd, String szHelpFile,
int cmd, HELPWININFO
dwData);
При помощи
замещения нельзя обработать полиморфные возвращаемые значения потому, что методы
в Java не могут отличаться только возвращаемым значением. Следовательно, нужно
дать каждому варианту функции отдельное имя и использовать модификатор entrypoint
для привязки всех вариантов к одной функции в DLL. Подробнее о переименовании
методов из DLL см. в разделе «Псевдонимы (переименование методов)»
далее в этой главе.
Сравнение
двух способов
В большинстве
случаев замещение предпочтительнее потому, что обеспечивает как более высокую
производительность во время выполнения, так и лучшую проверку типов. Кроме того,
замещение делает ненужным сохранение целых аргументов внутри объекта Integer.
Однако объявление параметра как тип Object стоит применять в случаях, когда
в наличии более одного полиморфного параметра. Этот способ также годится,
когда нужен доступ к сервису, работающему с широким спектром различных типов,
например функция, которая может получать в качестве параметра любой объект,
объявленный с директивой @dll. struct.
Некоторые
функции Win32 API требуют предоставить в качестве параметра адрес функции обратного
вызова (callback). Эти функции можно написать на Java, расширив системный класс
com.ms.dll.Callback.
Объявление
метода, получающего функцию обратного вызова
Чтобы представить
на Java параметр, являющийся функцией обратного вызова, объявите его как тип
com.ms.dll.Callback или как. производный от него класс. Например, функция EnumWindows
в Win32 имеет следующий прототип:
BOOL EnumWinduws(WNDENUMPROC wndenumproc, LPARAM Iparam);
Соответствующий
прототип на Java выглядит так:
import com.ms.dll.Callback;
/** @dll.import("USER32")
*/
static native
boolean EnumWindows(Callback wndenumproc, int Iparam);
Вызов
функции, получающей функцию обратного вызова
Для вызова
функции, получающей в качестве параметра функцию обратного вызова, нужно определить
класс, являющийся расширением класса Callback. Производный класс должен иметь
нестатический (non-static) метод с именем callback (все символы строчные). На
языке С определение WNDENUMPROC выглядит так:
BOOL CALLBACK
EnumWindowsProc(HWND hwnd, LPARAM Iparam);
Для перевода
на Java нужно объявить класс, который расширяет Callback следующим образом:
class EnumWindowsProc
extends Callback {
public boolean
callback(int hwnd, int Iparam) {
StringBliffer text = new StringBuffer(50);
GetWindowText(hwnd,
text, text.capacity()+1);
if (text.length()
i= 0) {
System.out.println("hwnd
= " + Integer.toHexString(hwnd) +
"h: Text
= " + text); }
return true;
// возвращает TRUE для продолжения перебора. }
/** @dll.import("USER32")
*/
private static
native int GetWindowText(int hwnd, StringBuffer text, int cch);
}
Вызов EnumWindows
делается так: boolean result = EnumWindows(new EnumWindowsProc0, 0);
Ограничения
на типы, используемые с методом callback
Тип возвращаемого
значения метода callback должен быть void, int. boolean, char или short. Единственный
тип параметра, допустимый в настоящее время, — int. К счастью, это не так страшно,
как кажется. Можно использовать методы ptrToStringAasi, ptrToStringUni и ptrTo-String
из DllLib для преобразования параметра в LPTSTR. Допустимо применять метод ptrToStruct
для преобразования параметра к указателю на класс, объявленный с директивой
@dll.struct.
Передача
данных с функцией обратного вызова
Часто требуется
передать некоторые данные из вызывающей функции в функцию обратного вызова.
Этим объясняется наличие в Enum Windows дополнительного параметра Iparam.
Большинство функций Win32, работающих с функциями обратного вызова, имеют
также один дополнительный 32-разрядный параметр, который без изменения передается
в функцию обратного вызова. При использовании механизма на основе класса Callback
нет необходимости передавать данные через аргумент Iparam. Поскольку
метод callback является нестатическим, данные можно хранить как поля объекта
EnumWindowsProc.
Время
жизни объекта Callback
Внимательно
следите за тем, чтобы объект, представляющий функцию обратного вызова, не был
уничтожен сборщиком мусора до того, как системная функция закончит свою работу
с ним. Если время жизни объекта мало (он вызывается только во время выполнения
какой-то функции), особых действий не требуется, ибо есть уверенность в том.
что объект, передаваемый в функцию из DLL, не будет уничтожен сборщиком мусора
во время выполнения функции.
Если время
жизни объекта велико (он используется в нескольких функциях), необходимо защитить
его от уничтожения, например сохранив ссылку на него в структуре Java-данных.
Ссылки сохраняются также в «родной» структуре данных, если применить
класс com.ms.dll.Root для сохранения объекта внутри корневого описателя (root
handle) — 32-разрядного описателя, который предотвращает уничтожение объекта
до тех пор, пока явно не будет освобожден сам описатель. Например, корневой
описатель для WndProc может быть сохранен в области данных приложения структуры
HWND и затем явно освобожден при обработке сообщения WM_NCDESTROY.
Ссылка
на функцию обратного вызова из структуры
Чтобы сослаться
на функцию обратного вызова из структуры, сначала вызовите метод com.ms.dll.Root.alloc
для сохранения функции внутри корневого описателя. Затем передайте корневой
описатель в метод DllLib.addrOf для получения реального адреса функции обратного
вызова. Затем сохраните адрес в структуре как целое. Например, структура WNDCLASS
может быть объявлена на языке Java следующим образом:
/** edll.struct() */
class WNDCLASS
{ int style;
int IpfnWndProc;
// функция обратного вызова .../* <остальные поля для краткости не показаны>
*/ }
Предположим,
что класс Callback расширен так: class WNDPROC extends Callback {
public int callback(int
hwnd, int msg, int wparam, int lparam) {
} }
Для сохранения
указателя на функцию обратного вызова в классе WNDCLASS используйте следующую
последовательность операторов:
import com.ms.dll.
*;
WNDCLASS we
= new WNDCLASS();
int callbackroot
= Root.alloc(new WNDPROC());
we.IpfnWndProc
= DllLib.addrOf(callbackroot);
В следующих,
разделах подробнее описаны функции OLE API.
Директива @dll.import имеет особый режим для импорта функций OLE API. Чтобы воспользоваться этим режимом, просто включите в директиву модификатор ole, как показано в следующем примере:
/** @dll.import("OLE32", ole) */
public class
OLE32 {
}
Теоретически,
функции, экспортируемые из OLE32.DLL и OLE-AUT32.DLL, ничем не отличаются от
функций из других DLL. Но в действительности функции OLE имеют свой стиль вызова.
Функции OLE отличаются от функций Win32 тем, что:
Сравнение
кода Win32 и кода OLE
Код для вызова
простой функции Add в стиле" Win32 выглядит примерно так:
Int sum;
sum = Add(10,
20);
В стиле OLE
функция Add должна вызываться так:
HRESULT hr;
int sum;
hr = Add(10, 20, &sum);
if (FAILED(hr))
{
...обработка
ошибки.. }
Достоинство
режима ole — использование стиля кодирования, поддерживающего способ
вызова функций OLE, привычный для Java-программистов. Обращение к функции Add
в стиле OLE из Java весьма похоже на обращение к традиционным функциям Win32:
/** dll.import("OLELIKEMATHDLL",
ole) */
private native
static int Add(int x, int y);
int sum = Add(10,
20);
// Если мы оказались
здесь, то вызов Add прошел успешно.
Из-за наличия
модификатора ole Microsoft VM автоматически считает, что исходная функция
Add возвращает HRESULT. ВМ замечает также, что функция Add возвращает
целое. При вызове Add BM автоматически создает временную переменную типа int
и вставляет указатель на нее в качестве третьего параметра. После возврата из
.исходной функции Add BM автоматически проверяет HRESULT, и если его значение
говорит об ошибке (старший бит установлен), то генерируется исключение com.ms.com.ComFailException.
Если значение HRESULT не соответствует ошибке, то ВМ берет настоящее возвращаемое
значение из временной переменной и возвращает его.
При такой
интеграции Java и СОМ возврат значения S_FALSE не вызывает генерации ComSuecessException.
Если нужно отличить один успешный результат от другого, используйте нормальный
режим вызова функций из DLL и рассматривайте HRESULT как целое. Говоря вкратце,
режим ole изменяет семантику вызова из DLL следующим образом.
Передача
и получение строк из функций OLE
Объявление
параметра как тип String на функции режима ole приводит к передаче LPCOLESTR.
Кроме того, Microsoft VM включает в строку префикс, содержащий ее длину, чтобы
строка могла трактоваться как BSTR*.
Объявление
в режиме ole возвращаемого значения типа String заставляет Microsoft VM передавать
указатель на неинициализированное зна
чение LPCOLESTR.
После возврата из собственной функции Microsoft VM преобразует возвращенное
LPCOLESTR в Java String и вызывает CoTaskMemFree для очистки памяти, выделенной
под строку.
Передача
значений GUID (IID, CLSID)
Для представления
GUID используется системный класс com.ms.com._Guid. Передача _Goid-объекта как
параметра вызывает передачу в исходную функцию указателя на GUID. Объявление
возвращаемого значения типа _Guid заставляет Microsoft VM передать указатель
на неинициализированное значение GUID, которое будет заполнено вызывающей функцией
(только в режиме ole). Например, OLE32 экспортирует функции CLSIDFromProgID
и ProgIDFromCLSID для преобразования CLSID в удобочитаемое имя (и обратно),
используемое функцией CreateObject из Visual Basic. Эти методы имеют следующие
прототипы: HRESULT CLSIDFromProgID(LPCOLESTR szProgID, LPCLSID pclsid); HRESULT
ProgIDFromCLSID(REFCLSID clsid, LPOLESTR *lpszProgId);
В Java эти
методы объявляются следующим образом:
import com.ms.com._Guid;
class OLE {
/** @dll.import("OLE32",
ole) */
public static
native _Guid CLSIDFromProgID(String szProgID);
/** @dll.import("OLE32",
ole) */
public static
native String ProgIDFromCLSID(_Guid clsid); }
Примечание
Обратите внимание, что com.ms.com._Guid замещает com. ms.com.Guid (без подчеркивания).
Передача
значений типа VARIANT
Объявление
параметра типа com.ms.com.Variant приводит к передаче в исходную функцию указателя
на VARIANT, Объявление возвращаемого значения типа com.ms.com.Variant (только
в режиме ole) приводит к передаче в исходную функцию указателя на неинициализированное
значение Variant, которое заполняется этой функцией.
Передача
указателей на интерфейсы СОМ
Для передачи
указателя на интерфейс СОМ нужно сгенерировать интерфейсный класс Java/COM посредством
jactivex.exe или другого подобного средства. Затем можно передавать/принимать
интерфейсы СОМ путем объявления параметра, имеющего тип этого класса. Например,
системный класс com.ms.com.IStream является интерфейсом Java/COM, который представляет
интерфейс структурированного хранилища
IStream*. Функция CreateStreamOnHGlobal из OLE32 может быть объявлена следующим
образом:
import com.ms.com.*;
/** @dll.import("OLE32",
ole) */
public static
native IStream CreateStreamOnHGlobal(int hGlobal,
boolean fDeleteOnRelease);
Псевдонимы
(переименование методов)
Иногда для
Java-метода требуется имя, отличное от применяемого для экспорта функции из
DLL. Например, желательно, чтобы оно начиналось со строчной буквы и удовлетворяло
соглашению об именовании, принятом в языке Java. Для этого просто используйте
модификатор entrypoint в-директиве @dll.import, как показано в следующем
примере:
/** @dll.import("USER32", entrypoint="GetSysColor") */
static native
int getSysColor(int nlndex);
Псевдонимы
не нужны при работе с ANSI/Unicode версиями функций Win32 API. Microsoft VM
автоматически делает это за Вас (см. раздел «Как ВМ делает выбор между
ANSI и Unicode» далее в этой главе). Не требуются псевдонимы и при доступе
к функциям, экспортируемым при помощи файла .def. Экспорт их имен обычно сопровождается
так называемой «стандартной подгонкой вызова» («stdcall mangling»).
В следующем примере метод sample будет переименован в _sample@8 (где 8 — количество
байт, занимаемых параметрами функции):
extern "С"
__declspec(dllexport)
VOID sample(int
x, int y){
}
J/Direct
автоматически связывается с именам, подвергнутым подгонке, без явного указания
точки входа (entrypoint).
Microsoft
VM автоматически присваивает псевдонимы для следующих функций KERNEL32 API.
Функция Кеrnе1
32 |
Псевдоним |
||
CopyMemory MoveMemory FillMemory ZeroMemory |
RtlMoveMemory RtlMoveMemory RtlFillMemory RtlZeroMemory |
||
Связывание
по порядковому номеру
Некоторые
библиотеки экспортируют функции по порядковому номеру (16-разрядное целое),
а не по имени. Для обращения к такой функ-
ции-нужно
использовать директиву @dll.import на уровне метода для указания порядкового
номера. Синтаксис связывания по порядковому номеру таков:
/** @dll.import("Libname", entrypoint="#ordinal") */
Обратите внимание, что ordinal задается в десятичном виде. Например, для связывания с функцией под номером 82 из DLL «MyDll.DLL» нужно написать следующий код:
/** @dll.import("MyDll", entrypoint="#82") */
public static
native void MySample();
Применение
@dll.import ко всему классу
Директиву
@dll.import иногда указывают до определения класса, чтобы задать библиотечные
имена для всех собственных (native) методов, объявленных в этом классе. Следующее
объявление использует @dll.import для класса в целом:
/** edll.import("KERNEL32") */
class EnvironmentStrings
{
public static
native int GetEnvironmentStrings();
public static
native int GetEnvironmentVariable(String name,
StringBuffer value, int ccbValue);
public static
native boolean SetEnvironmentVariable(String name,
String value); }
Это эквивалентно
объявлению @dll.import для каждого метода:
class EnvironmentStrings
{
/** @dll.import("KERNEL32")
*/
public static
native int GetEnvironmentStrings();
/** §dll.import("KERNEL32")
*/
public static
native int GetEnvironmentVariable(String name,
StringBuffer
value, int ccbValue);
/** @dll.import("KERNEL32")
*/
public static
native boolean SetEnvironmentVariable(String name,
String value);
}
Использование
директивы @dll.import на уровне класса экономит место в файле .class и исключает
лишнюю информацию. Но такая директива не наследуется подклассами.
Как
ВМ делает выбор между ANSI и Unicode
Вернемся
к примеру информационного окна в начале этой главы. Отметим важный факт: USER32
не экспортирует функцию под именем MessageBox. Поскольку функция MessageBox
получает строку в качестве параметра, она '(подобно другим функциям Win32, работающих
со строками) существует в двух версиях: ANSI и Unicode (MessageBoxA и Mes-sageBoxW
соответственно). Когда в коде на С или C++ стоит вызов «функции»
MessageBox, в действительности вызывается макрос, который замещается на MessageBoxA
или MessageBoxW в зависимости от того, определен макрос UNICODE или нет.
Вызов
ANSI-версии функции из DLL
По умдлчанию
Microsoft VM предполагает, что нужна ANSI-версия функции MessageBox. Если Вы
импортируете функцию MessageBox, используя @dll.import (без модификатора):
/** @dll.import("USER32")
*/
static native
int MessageBox(int hwnd, String text,
String title, int style);
то Microsoft
VM выполняет следующие операции.
Вызов
Unicode-версии функции из DLL
Предположим,
что вместо вызова ANSI-версии функции MessageBox Вы хотите вызвать ее Unicode-версию.
Это можно сделать, используя директиву @dll.import с модификатором unicode:
/** @dll.import("USER32",Unicode)
*/
static native
int MessageBox(int hwnd, String text, String title, int style);
Поскольку
указан модификатор Unicode, то Microsoft VM выполняет следующие операции.
Вызов
оптимальной версии функции из DLL
К сожалению,
ни вызов ANSI-версии, ни вызов Unicode-версии функции из DLL не идеален при
обращении к функциям Win32. Использование режима ANSI, установленного по умолчанию,
позволяет выполнять код на любой Win32-платформе, но снижает производительность
на системах, полностью основанных на Unicode, например на Microsoft Windows
NT. При применении модификатора Unicode производительность остается на
том же уровне, но возникает ограничение: код выполняется только системами, реализующими
Unicode API. К счастью, с директивой @dll.import можно использовать модификатор
auto, чтобы вызывать оптимальную версию функции из DLL, исходя из применяемой
операционной системы.
Использование
модификатора auto позволяет взять лучшее из обоих вариантов. В следующем
примере показано, как вызвать оптимальную версию функции MessageBox:
/** edll.import("USER32",auto)
*/
static native
int MessageBox(int hwnd, String text, String title, int style);
Когда присутствует
модификатор auto, Microsoft VM во время выполнения определяет, поддерживает
ли используемая платформа Unicode API. Если да, то ВМ действует, будто задан
модификатор Unicode. В противном случае ВМ действует так, будто задан
модификатор ansi. Поэтому модификатор auto позволяет сгенерировать
единый двоичный код, который работает на ANSI- и Unicode-системах с использованием
оптимального набора API на каждой платформе.
Как правило,
модификатор auto требуется при всяком вызове функций Windows API. При
вызове функций из Ваших библиотек выберите ansi (по умолчанию) или Unicode
в зависимости от того, что Вам нужно. Далее детально описано, как ВМ решает,
использовать ANSI или Unicode при наличии модификатора auto.
Вам не придется
самостоятельно задавать значение параметра Dlllm-portDefaultType. Он существует
в основном для того, чтобы программа установки могла задать соответствующее
значение для будущих платформ Windows., Вы можете программно узнать основной
режим для Вашей платформы, прочитав поле com.ms.dll.DllLib.systemDefaultChar-Size.
Его значение равно 1 для систем ANSI и 2 для систем Unicode. Модификаторы ansi,
Unicode и auto используются также с директивой @dll. struct.
Получение
кода ошибки, установленного функцией из DLL
Не обращайтесь
к функции GetLastError из Win32 для получения кода ошибки, установленной при
вызове другой функции из DLL. Поскольку Microsoft VM способна выполнять собственные
вызовы функций при исполнении Java-кода, код ошибки может быть изменен к моменту,
когда станет Вам доступен.
Для получения
достоверного кода ошибки, установленного функцией из DLL, нужно использовать
модификатор setLastError, чтобы ВМ сохранила код ошибки сразу после обращения
к соответствующему методу. (Это не делается по умолчанию ради производительности.)
Далее для получения кода ошибки можно вызвать метод com.ms.dll.DllLib.getLastWin32Error.
В каждом потоке Java имеется свое место для хранения этого значения.
Например,
функция FindNextFile возвращает информацию о состоянии при помощи кода ошибки
и может быть объявлена следующим образом: /** @dll.import("KERNEL32",setLastError)
*/
static native
boolean FindNextFile(int hFindFile,
WIN32_FIND_DATA
wfd);
Типичный вызов
выглядит так: import com.ms.dll.DHLib;
boolean f = FindNextFile(hFindFile, wfd);
if (If) {
int errorcode
= DllLib.getLastWin32Error();
}
Загрузка
и обращение к DLL во время выполнения
Есть случаи,
когда нужен больший контроль над процессом загрузки и связывания DLL, чем тот,
что обычно предоставляется директивой @dll.import, например:
Win32 API
всегда предоставляет возможность контролировать загрузку и связывание. Функции
LoadLibrary, LoadLibraryEx и FreeLibrary позволяют явно управлять загрузкой
и выгрузкой DLL. Функция Get-ProcAddress — связаться с конкретной экспортируемой
функцией. Она возвращает указатель на функцию (function pointer), и потому Вы
без труда реализуете динамический вызов на любом языке программирования, допускающем
обращение к функции через указатель на нее. Средствами J/Direct программисты
на Java могут объявить нужные функции следующим образом.
Примечание
Если Вы используете пакет com.ms.Win32, то эти объявления попадут
также в класс Кегпе132.
/** @dll.import("KERNEL32",auto)
*/
public native
static int LoadLibrary(String IpLibFileName);
/** dll. import("KERNEL32",
auto) */
public native
static int LoadLibraryEx(String IpLibFileName, int hFile, int dwFlags);
/** @dll.import("KERNEL32",auto)
*/
public native
static boolean FreeLibrary(int hLibModule);
/** @dll.import("KERNEL32",ansi)
*/
public native
static int GetProcAddress(int hModule, String lpProcName);
Обратите
внимание, что GetProcAddress объявлена с модификатором ansi, а не auto.
Это сделано потому, что GetProcAddress является одной из немногих функций
Windows, не имеющих Unicode-эквивалента. При модификаторе auto функция
не могла бы использоваться в системах Microsoft Windows NT.
Осталось
решить проблему вызова функции, полученной через GetProcAddress. Для удобства
msjava.dll (DLL, реализующая Microsoft VM) экспортирует специальную функцию
по имени call. Первый ее аргумент —указатель
на другую функцию. Все, что делает call, — это вызов другой функции с передачей
ей остальных своих аргументов. Представленный далее пример показывает, как приложение
загружает DLL и вызывает экспортируемую функцию AFunction.
BOOL AFunction(LPCSTR
argument);
/** @dll.import("msjava")
*/
static native
boolean call(int funcptr,String argument);
int hmod = LoadLibrary("...");
int funcaddr
= GetProcAddress(hmod, "AFunction");
boolean result
= call(funcaddr, "Hello");
FreeLibrary(hmod);
J/Direct
и RNI (Raw Native Interface) являются дополняющими друг друга технологиями.
Использование RNI требует, чтобы функции DLL придерживались строгих правил именования
и работали в согласии со сборщиком мусора Java. Иными словами, функции RNI должны
надежно обращаться к GCEnable и GCDisable в коде, отнимающем много времени,
и способном отдать управление системе, или в коде, который может заблокироваться
другим потоком или ожиданием ввода пользователя. Функции RNI надо разрабатывать
специально для работы в среде Java, но зато они получают быстрый доступ к внутренним
данным объектов Java и к загрузчику классов Java.
J/Direct
связывает Java с существующим кодом (например, с функциями Win32 API), который
не предназначен для работы со сборщиком мусора Java и не учитывает других тонкостей
библиотеки Java времени выполнения. Однако J/Direct автоматически вызывает GCEnable
на стороне клиента, так что Вы можете вызывать функции, которые блокируют выполнение
или реализуют пользовательский интерфейс, не создавая трудностей^со сборщиком
мусора. Кроме того, J/Direct автоматически переводит обычные типы данных (строки
и структуры) в форму, требуемую для функций на С. Поэтому Вам не нужно писать
длинный «склеивающий» код и библиотеки-оболочки. Цена, которую приходится
платить за это: функции из DLL не могут получить доступ к полям и методам любого
объекта Java. В данной реализации им доступны только поля и методы объектов,
объявленных с директивой @dll.struct. Другое ограничение в том, что функции
RNI нельзя вызывать из функций в DLL, вызванных посредством J/Direct. Это происходит,
потому что сборщик мусора способен работать параллельно с функциями из DLL.
Следовательно, любым описателям объектов, возвращенным функциями RNI или контролируемым
функциями из DLL свойственна нестабильность.
К счастью,
можно использовать или RNI, или J/Direct (или и то, и другое). Компилятор и
Microsoft VM позволяют смешивать J/Direct и RNI внутри одного класса в соответствии
с Вашими потребностями.
Хотя J/Direct
— очень мощное средство для отдельных Java-приложений и надежных Web-приложений
для внутренней сети, ясно, что оно слишком сильно для Web в нормальных Java-апплетах.
В этом разделе описывается, как J/Direct совместно с системой безопасности Microsoft
VM предотвращает воздействие ненадежного кода на J/Direct.
J/Direct
делит все загруженные классы Java на две категории:
Только полностью
надежным классам позволено использовать J/Direct. В Java класс считается полностью
надежным, если он удовлетворяет одному из следующих критериев:
С другой
стороны, апплет в Web без цифровой подписи представляет собой ненадежный Java-код.
Проверка
безопасности для вызовов методов J/Direct
Microsoft
VM осуществляет три проверки безопасности вызова методов J/Direct:
Попытка вызова
удовлетворяется, только если пройдены все три проверки или они явно заблокированы.
Проверка
безопасности во время связывания
Связывание
происходит, когда один класс Java вызывает компонент из другого класса или обращается
к нему (используя Reflection API). Во время связывания Microsoft VM проверяет,
доступен ли вызываемый класс и соответствуют ли переданные аргументы по типу
и количеству. Класс считается доступным, если он находится в том же самом пакете
или если он объявлен открытым (public).
В стандартном
языке Java есть два варианта доступности класса: он объявлен открытым (так что
любой может связываться с ним) или закрытым, т. е. без модификатора public (и
тогда с ним могут связываться только, классы из одного пакета). Однако в Microsoft
Internet Explorer 4.0 появился третий вариант — класс объявляется как «public
for fully trusted callers only» («открытый только для полностью
надежного кода»). Этот вариант доступности возможен для любого класса,
даже если тот не использует J/Direct. Чтобы объявить такой класс, поместите
в начало его следующую директиву:
/** @security(checkClassLinking=on)
*/
Важно понять,
что эта проверка безопасности предотвращает только прямой вызов «защищенного»
класса со стороны ненадежного кода. Но она не предотвращает косвенные вызовы.
Третий (полностью надежный) класс способен перенаправлять вызов от ненадежного
кода к «защищенному» классу. Но не стоит забывать и о мерах предосторожности.
Промежуточный класс надо либо установить в каталог CLASSPATH целевого компьютера,
либо добавить к нему цифровую подпись и установить с помощью обозревателя.
Проверка
безопасности при первом обращении
Первое обращение
к методу — это первый вызов метода из какого-либо вызывающего кода. В этот момент
для любого метода, помеченного ключевым словом native, Microsoft VM проверяет,
является ли тот членом полностью надежного класса. Если это не так, то генерируется
ис-ключение SeсulуЕхсерtion с сообщением «Only fully trusted classes can
have native methods as members» («Только полностью надежные классы
могут иметь native-методы»). Поскольку проверка не зависит от контекста
вызова, она может быть сделана один раз. Если метод прошел проверку, то она
не производится при последующих обращениях. Запретить эту проверку невозможно.
Проверка
безопасности при каждом обращении
Это самый
строгий тест. При каждом вызове проверяется весь стек вызовов. Если обнаруживается
хотя бы один вызывающий код, не являющийся полностью надежным, генерируется
исключение SecurityExcep-tion. По умолчанию все методы J Direct проходят эту
проверку. Методы RNI не проходят этот тест по причинам, вызванным обратной со-
вместимостью.
RNI разработан с учетом легкости переноса кода из собственного интерфейса JDK
1.0, который не рассчитан на такую проверку. Хотя эта проверка обеспечивает
максимальную безопасность, Microsoft предлагает способ ее блокировки, потому
что возникают два важных побочных эффекта.
Возможное снижение
производительности
Негибкость |
Все стеки вызовов просматриваются при каждом вызове метода J/Direct. Снижение производительности наиболее заметно на надежных апплетах, которые обычно выполняются в присутствии диспетчера безопасности (security manager). С другой стороны, уменьшение производительности приложений обычно незначительно. Это происходит потому, что J/Direct пропускает просмотр стека вызовов для приложений, выполняющихся без диспетчера безопасности. Данный механизм
требует использования максимальных привилегий, даже когда нужна только
одна конкретная. Рассмотрим, например, надежную библиотеку, которая
использует J/Direct для предоставления ненадежным апплетам одной привилегии
абсолютно надежным способом. Для нее стоило бы отключить проверку
безопасности во время вызова и выполнять свою собственную проверку
на наличие конкретной привилегии. |
||
Директива
@security позволяет запретить проверку безопасности при каждом вызове. Синтаксис
ее следующий:
/** @security(checkDllCalls=off)
*/
Директива @security применяется к классу в целом. Отдельные методы внутри класса не могут быть помечены таким образом. Следующий пример показывает размещение директивы @security:
/** @security(checkDllCalls=off) */
class FastJDirectMethods{ /** @dll.import(...) */
static native
void func(); }
Учтите, что запрет этой проверки безопасности переносит ответственность с Microsoft VM на Вас. Помните, что даже при запрете такой проверки нужно иметь классы с цифровой подписью для обеспечения максимальной надежности. Если Вы решили использовать эту директиву, примите следующие меры предосторожности:
Примечание
Вызовы из методов апплета ink, start, stop и destroy могут породить
исключение SecurityExceptionEx, даже если апп-лет является надежным. Чтобы избежать
этого, подтвердите свои права, выполнив следующий код:
import coin.ms.security.
*; PolicyEngine.assertPermission(PermissionID.SYSTEM);
Проверка
безопасности для структур J/Direct
J/Direct
также налагает на классы ограничения по безопасности, если классы объявлены
директивой @dll.struct. Поскольку структуры становятся незащищенными только
при их реализации, эти проверки безопасности более эффективны, чем используемые
для методов J/Direct. Для классов с директивой @dll.struct выполняются две проверки
безопасности.
Во время загрузки
Во время связывания |
Классы, объявленные директивой @dll.struct, загружаются, лишь если контекст указывает на полную надежность. Не полностью
надежный код не может связываться с классами, объявленными директивой
@dll. struct. Если такая попытка все же делается, Microsoft VM генерирует
исключение NoClassDefFoundError. |
||
Безопасность
и классы com.ms.win32
Для максимальной
безопасности методы J/Direct, определенные в пакете com.ms.win32, проверяют
стек вызовов при каждом обращении к ним. Если использовать эти классы в Java-приложениях
(выполняющихся под JVIEW или WJVIEW), снижение производительности будет незначительным.
Если использовать классы com.ms.win32 из надежного класса при требовании максимальной
производительности, необходимо скопировать нужные объявления J/Direct в свои
классы и запретить проверку безопасности при каждом вызове. Подробнее о запрете
проверки безопасности при каждом вызове см. в разделе «Проверка безопасности
при каждом обращении» ранее в этой главе.
При использовании
J/Direct Microsoft VM может генерировать несколько исключений. Далее описаны
возможные причины и решения для каждой из следующих ошибок времени выполнения:
java.lang.SecurityException
[класс.метод]
Класс исключения
Сообщение Возможные причины
Возможные решения |
java.lang.SecurityException Класс.метод: Only fully trusted classes can have native methods as members. Ключевое слово native используется с методом, являющимся членом класса, который загружен не со всеми привилегиями (например, апплет без подписи). Это исключение генерируется, только при попытке обращения к native-методу. Создайте апплету
цифровую подпись с запросом всех привилегий. |
||
java.lang.lllegalAccessError
Класс исключения |
Java. lang.
Illegal AccessFrror |
||
Сообщение |
Class has been
marked as nonpublic to untrusted code. |
||
Возможные решения | Создайте
апплету цифровую подпись с запросом всех привилегий. |
||
Возможные причины |
Ненадежный класс
пытается обратиться к полю или методу класса, который предназначен
для использования только надежными классами. К ним относятся многие
системные классы из пакетов com.ms.com и com. rns.dll. Класс можно
объявить закрытым для ненадежного кода, используя директиву @security
следующим образом: /** @security(checkClassLlnWng=on)
*/ public class ForTrustedUseOnly{ } |
||
java.lang.SecurityException
Класс исключения |
java.lang.SecurityException |
||
Сообщение |
J/Direct method
has not been authorized for use on behalf of an untrusted caller. |
||
Возможные причины |
Ненадежный класс
обращается к надежному методу, который вызывает J/Direct. Даже если
класс, вызывающий J/Direct, является надежным, диспетчер безопасности
генерирует исключение SecurityException, если какой-нибудь метод в
стеке вызовов принадлежит к ненадежному классу. |
||
Возможные решения |
Создайте апплету
цифровую подпись с запросом всех привилегий. Или запретите проверку
безопасности, объявив класс, делающий вызов J/Direct, с директивой
@security, например: /** @security(checkDllCalls=off)
*/ public class SafeDllCalls{ ... } |
||
Примечание
Учтите, что запрет этой проверки безопасности переносит ответственность
с Microsoft VM на Вас. Помните, что даже при запрете этой проверки нужно иметь
классы с цифровой подписью для достижения максимальной надежности. Если Вы решили
использовать директиву @security, то убедитесь, что:
java.lang.SecurityException
Класс исключения
Сообщение Возможные причины
Возможные решения |
java.lang.SecurityException J/Direct method has not been authorized for use on behalf of an untrusted caller. Ненадежный класс обращается к надежному методу, который вызывает J/Direct. Даже если класс, вызывающий J/Direct, является надежным, диспетчер безопасности генерирует исключение SecurityException, если какой-нибудь метод в стеке вызовов принадлежит к ненадежному классу. Создайте апплету
цифровую подпись с запросом всех привилегий. Или запретите проверку
безопасности, объявив класс, делающий вызов J/Direct, с директивой
©security, например: /** @security(checkDHCalls=off)
*/ public class SafeDllCalls{ } Примечание
Учтите, что запрет этой проверки безопасности переносит ответственность
с Microsoft VM на Вас. Помните, что даже при запрете этой проверки
нужно иметь классы с цифровой подписью для достижения максимальной
надежности. Если Вы решили использовать директиву @security, то убедитесь,
что:
|
||
java.lang.NoClassDefFoundError
Класс исключения Сообщение Возможные причины
Возможные решения |
Java. long. NoClassDefFoundError Отсутствует. Ненадежный класс пытается загрузить класс, объявленный с директивой @dll. struct или созданный с использованием jactivex. Хотя это нарушение защиты (а не ошибка загрузчика классов), исключение NoClassDefFoundError генерируется по причине обратной совместимости. Создайте апплету
цифровую подпись с запросом всех привилегий. |
||
com.ms.security.SecurityExceptionEx
Класс исключения Сообщение Возможные причины
Возможные решения |
com. ms. security. SecurityExceptionEx [host] J/Direct method has not been authorized for use on behalf of an untrusted caller. Сделана попытка вызвать J/Direct из методов апплета mil, start, stop или destroy. Чтобы вызывать J/Direct из этих методов, апплет должен получить привилегии, даже если он имеет цифровую подпись. Выполните следующий
код: import com. ms. security. *; .... PolicyEngine.assertPermissiom(PermissionID.
SYSTEM); |
||
Советы
по обнаружению и устранению трудностей
В следующих
разделах описаны трудности, вероятные при использовании J/Direct, и приведены
возможные решения.
UnsatisifiedLinkError
при вызове метода
// Этот метод экспортируется по номеру 34.
/** @dll.import("MyDll",entrypoint="#34") */
public static native
void MySample();
SecurityException
при вызове метода из DLL или при использовании класса с директивой @dll.struct
Вызов DLL
и использование классов с директивой @dll.struct ограничены Java-приложениями
и Java-апплетами с цифровой подписью.
При возврате
из функции из DLL обрезаются строки в StringBuffer
Перед вызовом
функции убедитесь, что вместимость (capacity) String-Buffer достаточна для нужной
строки. Вместимость указана в конструкторе StringBuffer. Чтобы гарантировать
минимальную вместимость перед вызовом функции из DLL, используют метод StringBuffer.ensureCapacity.
Синтаксические
ошибки внутри директив @dll
Пробелы внутри
директив @dll.import и @dll.struct могут вызвать синтаксические ошибки. Не используйте
пробелы внутри конструкций @dll.
Компилятор
не может найти пакет com.ms.dll
Используется
старая версия Classes.zip. Попробуйте переименовать все старые версии Classes.zip
на Вашем жестком диске и переустановите Visual J+ + .
Директивы
@dll не работают с апплетами (или работают только внутри среды Microsoft Visual
J++)
Поскольку
использование J/Direct может нарушить систему защиты, то оно ограничено Java-приложениями
и Java-апплетами с цифровой подписью.
Использование
J/Direct делает класс ненадежным
Любое использование
J/Direct внутри класса делает этот класс небезопасным, поэтому его нельзя употреблять
для ненадежного кода, даже если методы J/Direct в реальности не вызываются.
J/Direct
генерирует ParameterCountMismatchError после вызова исходной функции
Исключение
ParameterCountMismatchError предупреждает, что вызванная функция использовала
(вернула на стек) больше или меньше параметров, чем было передано ей от J/Direct.
Эта обычно свидетельствует, что параметры в объявлении Java-метода не соответствуют
параметрам, которые ожидает получить функция из DLL.
Если функция
не вернула на стек ни одного аргумента (не освободила стек), предполагается,
что она использует соглашения о вызовах cdecl, и исключение не генерируется,
даже если в Java-методе объявлено ненулевое число аргументов.
Внимание
Не пытайтесь перехватывать и обрабатывать исключение ParameterCountMismatchError.
Оно предназначено для разработчиков — находить ошибки на стадии конструирования.
Чтобы не снижать производительность, количество параметров проверяется, только
когда приложение выполняется под отладчиком. Важно также помнить, что J/Direct
осуществляет ее после завершения вызова функции. Поскольку это исключение говорит
о том, что в функцию могли быть переданы неправильные параметры, нет гарантии,
что процесс можно восстановить и продолжить выполнение.
J/Direct
выгружает DLL, когда Microsoft VM перестает использовать класс Java, который
импортировал эту DLL. Для Java-приложений, выполняющихся под JVIEW, это не происходит
до завершения процесса. Для надежных Java-апплетов это происходит спустя неопределенное
время после удаления страницы с апплетом из обозревателя. Microsoft VM пытается
сохранить классы Java загруженными для нескольких последовательных страниц,
чтобы оптимизировать перезагрузку апплета в случае возврата к странице.
Если нужен явный контроль над загрузкой и выгрузкой DLL, вызывайте загрузчик Windows и обращайтесь к функциям динамически. Подробнее об этом см. в разделе «Загрузка и обращение к DLL во время выполнения» ранее в этой главе.