Как я к яве подключался
После знакомства с Java сразу возникает мысль - а нельзя ли воспользоваться всей этой кучей полезных возможностей без необходимости программировать на данном шаманском языке. Оказывается можно. Ява разделяется на три составляющие - 1) Java Virtual Machine; 2) стандартные библиотеки; 3) ну и собственно язык ява. JVM это обычная dll'ка и ее можно запустить из обычной программы. После этого ей можно скормить скомпилированную ява-программу. Или - получить доступ к возможностям стандартных библиотек. Правда дело это муторное и без меры запутанное.
Подготовка к приступу
Что надо для работы? Во-первых spf4 . Во-вторых ява. Ява бывает разной. Микрософтовская и сановская явы используют разные механизмы для взаимодействия с нативными программами. Я пока разобрался только с сановской явой. Значит нужно взять Java 2 (я эксперементировал с версией 1.3.1). В нее встроен механизм Java Native Interface версии 1.2. С ним и будем работать. Да яву еще надо установить - переменная PATH должна указывать на \jre\bin и на \jre\bin\classic (здесь собственно jvm и обитает).
Первым делом надо перевести jni.h в форто-понятный вид. В этом заголовочном файле содержатся описания двух структур: JNIEnv и JavaVM (ну и еше по мелочи). Эти структуры набиты членами-функциями. Раньше мне такие функции не попадались, пришлось нарисовать слово для их описания:
: f: ( offset "new-name" -- offset+cell ) CREATE DUP , CELL+ DOES> @ OVER @ + @ API-CALL ;Здесь можно взять готовый jni.h.f и набор прочих библиотек (пригодятся).
Загрузка и запуск .class файла
Перенесем на форт пример из jni-туториала, запускающий на выполнение файл Prog.java
public class Prog {
public static void main(String[] args) {
System.out.println("Hello World " + args[0]);
}
}
Прежде всего надо подумать об обработке ошибок. После вызова каждой функции
из JNIEnv, невредно проверить - нормально ли она отработала. Все функции,
которые нам понадобятся, возвращают ноль в случае возникновения ошибки.
Это и будем проверять:
: CheckRes ( n -- n ) DUP 0= IF DROP DestroyJVM THEN ;Что делать с ошибками? Так как программа экспериментальная, то просто выведем на печать информацию об ошибке, и закончим работу
: DestroyJVM ( -- )
env @ JNIEnv::ExceptionOccurred IF
env @ JNIEnv::ExceptionDescribe DROP
THEN
jvm @ JavaVM::DestroyJavaVM DROP
BYE
;
Теперь можно запускать ява-машинку
\ указатель на структуру JNIEnv. Через него идет все взаимодействие с jvm VARIABLE env \ указатель на экземпляр явы VARIABLE jvm CREATE vm_args JavaVMInitArgs::/SIZE ALLOT CREATE options JavaVMOption::/SIZE ALLOT \ сообщаем яве где искать Prog.class (в текущем каталоге) S" -Djava.class.path=." DROP options JavaVMOption::optionString ! 0x00010002 vm_args JavaVMInitArgs::version ! options vm_args JavaVMInitArgs::options ! 1 vm_args JavaVMInitArgs::nOptions ! JNI_TRUE vm_args JavaVMInitArgs::ignoreUnrecognized ! \ получаем jvm vm_args env jvm JNI_CreateJavaVM 0< IF S" Can't create Java VM" H-STDERR ?DUP IF WRITE-FILE THROW ELSE 2DROP THEN 1 HALT THENТеперь начинаются шаманские пляски. Интерфейс взаимодействия с jvm очень развесистый (более 200 функций) и запутанный. Дело в том что создатели явы очень гордятся ее строгой типизацией и объектностью, и поэтому старательно охраняют ее от всяческих ошибок и проблем внешнего мира. Для того чтобы добраться до чего-нибудь надо точно знать - что из себя представляет это что-нибудь. Есть свои функции для работы с классами, объектами, методами классов, полями классов; эти методы могут быть статическими или нет; могут возвращать значения разных типов. И все это надо учитывать. В общем открываем доку и начинаем меедленно двигаться
\ ищем класс, с которым будем работать S" Prog" DROP env @ JNIEnv::FindClass CheckRes TO cls \ получаем id нужного метода S" ([Ljava/lang/String;)V" DROP S" main" DROP cls env @ JNIEnv::GetStaticMethodID CheckRes TO midВот пример - если бы main был не static - то надо было бы использовать GetMethodID. А если бы main был полем - то GetStaticFieldID. Причем мало знать имя искомого метода - нужна еще его сигнатура. В данном случае это ([Ljava/lang/String;)V. Что означает - метод возвращает значение типа void, а в качестве параметров принимает массив объектов типа строка.
\ создаем новую строку S" from FORTH!" DROP env @ JNIEnv::NewStringUTF CheckRes TO jstr \ main требует массив, значит делаем из строки массив S" java/lang/String" DROP env @ JNIEnv::FindClass CheckRes TO stringClass jstr stringClass 1 env @ JNIEnv::NewObjectArray CheckRes TO args \ и наконец вызываем метод main (mid) в классе Prog (cls) \ с входным параметром args. args mid cls env @ JNIEnv::CallStaticVoidMethod DROP \ уничтожаем нашу ява-машинку DestroyJVMРезультатом работы будет печать строки "
Hello World from FORTH!"
в консоли. Итоговый файл proba1.f.
Доступ к возможностям ява-библиотек
Теперь добьемся того же результата без использованя ява-кода:
REQUIRE JNIEnv ~af\jni\jni.h.f
REQUIRE SLITERAL2 ~af\lib\sliteral2.f
REQUIRE STR@ ~ac\lib\str2.f
REQUIRE USES ~af\lib\api-func.f
USES jvm.dll
0 VALUE env
0 VALUE jvm
: CreateJVM ( -- )
{ \ *env *jvm [ JavaVMInitArgs::/SIZE ] vm_args -- }
\ никаких опций передовать jvm не будем
0x00010002 vm_args JavaVMInitArgs::version !
0 vm_args JavaVMInitArgs::nOptions !
JNI_TRUE vm_args JavaVMInitArgs::ignoreUnrecognized !
vm_args AT *env AT *jvm JNI_CreateJavaVM 0< IF
S" Can't create Java VM" H-STDERR
?DUP IF WRITE-FILE THROW ELSE 2DROP THEN
1 HALT
THEN
\ упростим последующий код - уберем @ после каждого env
*env TO env
*jvm TO jvm
;
: DestroyJVM ( -- )
env JNIEnv::ExceptionOccurred IF
env JNIEnv::ExceptionDescribe DROP
THEN
jvm JavaVM::DestroyJavaVM DROP
BYE
;
: CheckRes ( n -- n )
DUP 0= IF DROP DestroyJVM THEN
;
: Z" POSTPONE S" DROP ; IMMEDIATE
CreateJVM
\ Мы хотим напечатать строку. На яве это звучит так -
\ System.out.println("Hello World");
\ Смотрим в доке - что такое System. Оказывается это клас java.lang.System
\ Получим ссылку на него. Только вместо . надо почему-то писать /
Z" java/lang/System"
env JNIEnv::FindClass CheckRes VALUE cls
\ Ищем out. Это статическое поле типа PrintStream. Ищем в доке PrintStream
Z" Ljava/io/PrintStream;" Z" out" cls
env JNIEnv::GetStaticFieldID CheckRes VALUE fid
\ Получили id out-та. Теперь надо получить сам out
fid cls
env JNIEnv::GetStaticObjectField CheckRes VALUE joout
\ out - это объект. А объект это вам не класс. Надо из объекта
\ получить его класс (PrintStream)
joout
env JNIEnv::GetObjectClass CheckRes VALUE clsout
\ создаем строку, которую надо напечатать
Z" FORTH + JAVA = IT'S WORK!"
env JNIEnv::NewStringUTF CheckRes
\ ищем в классе PrintStream метод println, умеющий печатать строки
Z" (Ljava/lang/String;)V" Z" println" clsout
env JNIEnv::GetMethodID CheckRes
\ наконец печатаем (только надо не забыть, что печатает объект out, а
\ не класс в котором мы только что нашли метод для печати)
joout
env JNIEnv::CallVoidMethod CheckRes DROP
DestroyJVM