В Java существует возможность использования программного кода, реализованного на других языках программирования, так называемый JNI. Можно написать динамически линкуемую библиотеку, затем загрузить ее в Java-коде и использовать функции оттуда, объявив их как native методы загрузившего ее класса. JNI создавался в первую очередь для того, чтобы выполнять машинно-зависимые действия (а также, возможно, улучшить производительность критических по скорости частей приложения) на C/C++, но никто не мешает нам написать библиотеку и на ассемблере.
Инструментарий
Для создания сего извращения применялись Windows, Oracle JDK, Yasm (без макросов) и линковщик из Microsoft Visual Studio 2010. При желании, заменить любой из этих компонентов на ваш любимый не составит большого труда, я старался не использовать всякие нестандартные фичи.
Использование JNI
Для начала, создадим на Java класс, в котором будет вызываться функция из будущей .dll'ки:
public class TestJNI {
native static int sum(int x, int y); // импортируемая функция sum
public static void main(String[] args) {
System.loadLibrary("mydll"); // загружаем библиотеку mydll.dll
System.out.println(sum(2, 3)); // вызываем функцию
}
}
Name Mangling в Java
Теперь нужно узнать, какое имя функции рассчитывает найти в нашей библиотеке Java-машина. Воспользуемся для этого программой javah из JDK:
javac TestJNI.java
javah TestJNI
При этом будет сгенерирован C++ header:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJNI */
#ifndef _Included_TestJNI
#define _Included_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: TestJNI
* Method: sum
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_TestJNI_sum
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
Информация, содержащаяся здесь, нужна главным образом для дальнейшего написания библиотеки на C++, нас же интересует только сигнатура функции:
JNIEXPORT jint JNICALL Java_TestJNI_sum
(JNIEnv *, jclass, jint, jint);
Мы видим, что в dll функция должна иметь имя JNICALL Java_TestJNI_sum и принимать 4 параметра. Для простейших функций первые два из них нам не понадобятся. Всякие странные типы данных вроде JNIEXPORT, как несложно догадаться, объявлены в файле jni.h, который входит в состав JDK.
Ассемблер
Напишем теперь библиотеку:
;mydll.asm
section .text
global Java_TestJNI_sum
Java_TestJNI_sum:
mov eax, [esp + 12] ; игнорируем первые 2 параметра
add eax, [esp + 16]
ret 16
end
;mydll.def
LIBRARY mydll
EXPORTS Java_TestJNI_sum
Скомпилируем и слинкуем:
yasm -f win32 mydll.asm
link /SUBSYSTEM:windows /DLL /NOENTRY /DEF:mydll.def mydll.obj
На выходе получим .dll-файл, который и нужно передавать коду на Java.
Результат
Положим файлы mydll.dll и TestJNI.class в одну папку и посмотрим, что получилось:
>java TestJNI
5
Это победа, мы научились складывать два числа!
Если вдруг кому-то стало интересно, то ему пригодятся следующие ссылки:
- Invoking Assembly Language Programs from Java — очень хорошая и гораздо более подробная статья на английском на эту тему;
- Writing dll in assembler for external calling in Maple — создание библиотеки на ассемблере для Maple;
- Java Native Interface: Programmer's Guide and Specification — толстенная книга по JNI.
Автор: sementry