Статьи

Как получить доступ к C ++ из Java

JNIEasy — это библиотека Java для разработки, ориентированная на доступ JNI к собственным методам C / C ++. Здесь Хосе, его создатель, рассказывает вам об этом через пример сценария.
И он спрашивает: «Какие из известных вам нативных библиотек могут быть интересными для доступа из Java?» — Гертьян Виленга, лидер зоны JavaLobby

JNIEasy делает все возможное, используя прозрачную синхронизацию с Java. Любой Java POJO может представлять объект C ++, включая методы и поля экземпляра. Поле, измененное в Java, также изменяет нативное поле и наоборот. Необязательно, методы Java могут вызываться из собственного кода C / C ++ с использованием указателей на функции без кода JNI. Это «прозрачное нативное программирование» является переводом в нативный мир Java технологий, используемых в прозрачном постоянстве, таких как JPA или JDO.

Ниже следует краткое руководство о том, как вы можете смешивать свой код C / C ++ с Java. Два мира могут быть смешаны таким прозрачным образом, что трудно различить, когда наша задача выполняется в C ++ и когда в Java, хотя различие, конечно, возможно, если мы захотим это сделать.

Следующий класс Java:

    public class MyCPPClassOnDLL

{

protected int virtualTable; // the C++ class has a virtual method

protected double value;

public MyCPPClassOnDLL() { // mandatory (is not native)

}

public MyCPPClassOnDLL(int a, int b) {

throw new RuntimeException("Not enhanced");

}

public native static void destroy(MyCPPClassOnDLL obj);

public native static long addStatic(int a,int b);

public double getValue() {

return value;

}

public native double sub(int a,int b);

public native static void varargsEx(byte[] buffer,Object[] args);

}

… разработан как оболочка для этого класса C ++:

#include "JNIEasy.h"

// MyCPPClassOnDLL.h
class DLLEXPORT MyCPPClassOnDLL
{
protected:
double m_value;

public:
MyCPPClassOnDLL(int a,int b);
virtual ~MyCPPClassOnDLL();

static MyCPPClassOnDLL* __stdcall create(int a,int b);

static void __stdcall destroy(MyCPPClassOnDLL* obj);

static __int64 __stdcall addStatic(int a,int b);

double __stdcall getValue();

virtual double __stdcall sub(int a,int b);

static void __cdecl varargsEx(char* buffer,...);

};


#include "MyCPPClassOnDLL.h"
#include <stdio.h>
#include <stdarg.h>

MyCPPClassOnDLL::MyCPPClassOnDLL(int a,int b)
{
m_value = a + b;
}

MyCPPClassOnDLL::~MyCPPClassOnDLL()
{
}

MyCPPClassOnDLL* __stdcall MyCPPClassOnDLL::create(int a,int b)
{
return new MyCPPClassOnDLL(a , b); // may be an inherited class
}

void __stdcall MyCPPClassOnDLL::destroy(MyCPPClassOnDLL* obj)
{
delete obj;
}

__int64 __stdcall MyCPPClassOnDLL::addStatic(int a,int b)
{
return (__int64)a + (__int64)b;
}

double __stdcall MyCPPClassOnDLL::getValue()
{
return m_value;
}

double __stdcall MyCPPClassOnDLL::sub(int a,int b)
{
m_value = m_value - (a + b);
return m_value;
}

void __cdecl MyCPPClassOnDLL::varargsEx(char* buffer,...)
{
va_list marker;
va_start( marker, buffer );
const char* name = va_arg( marker, const char*);
int age = va_arg( marker, int);
int brothers = va_arg( marker, int);
va_end( marker );

sprintf(buffer,"%s is %d years old and has %d brothers",name,age,brothers);
}

… где DLLEXPORT — это __declspec (dllexport) в Windows (unixes по умолчанию экспортируют все методы), а __int64 — это макрос, определенный как long long в unix. Эти макросы определяются JNIEasy.h.

Учтите, что класс C ++ связан в динамической библиотеке MyLibrary.dll в Windows, libMyLibrary.so в Linux / Solaris и libMyLibrary.dylib в Mac OS X. Эти имена являются типичными соглашениями этих операционные системы и JNIEasy пытается следовать этим соглашениям по умолчанию. Однако они не являются обязательными. В JNIEasy можно использовать любое имя динамической библиотеки.

Класс Java должен быть объявлен как «нативный» с JNIEasy. Это может быть «улучшено», чтобы связать этот класс с C ++. Для этого нам понадобится XML-дескриптор. XML объявляет наш класс Java как JNIEasy-native и объясняет, как связать нужные методы с нативными методами:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Archive MyCPPClassOnDLL.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">

<package name="examples.manual">
<imports/>
<class name="MyCPPClassOnDLL" type="class" libraryPath="MyLibrary" >

<constructor params="int, int" onLibrary="true" nativeName="MSC:?create@MyCPPClassOnDLL@@SGPAV1@HH@Z;gcc:_ZN15MyCPPClassOnDLL6createEii"> </constructor>

<method name="destroy" params="MyCPPClassOnDLL" onLibrary="true" nativeName="MSC:?destroy@MyCPPClassOnDLL@@SGXPAV1@@Z;gcc:_ZN15MyCPPClassOnDLL7destroyEPS_"> </method>

<method name="addStatic" params="int,int" onLibrary="true" nativeName="MSC:?addStatic@MyCPPClassOnDLL@@SG_JHH@Z;gcc:_ZN15MyCPPClassOnDLL9addStaticEii">
</method>

<method name="sub" params="int, int" onLibrary="true" nativeName="MSC:?sub@MyCPPClassOnDLL@@UAGNHH@Z;gcc:_ZN15MyCPPClassOnDLL3subEii">
</method>

<method name="varargsEx" onLibrary="true" nativeName="MSC:?varargsEx@MyCPPClassOnDLL@@SAXPADZZ;gcc:_ZN15MyCPPClassOnDLL9varargsExEPcz" callConv="c_call">
<return />
<params>
<param class="byte[]" />
<param class="Object[]" varargs="true" />
</params>
</method>
</class>
</package>
</jniEasyEnhancer>

Следующий атрибут:

nativeName = «MSC: создать @ MyCPPClassOnDLL @@ SGPAV1 @ HH @ Z; НКУ: _ZN15MyCPPClassOnDLL6createEii»

… объясняет JNIEasy использовать «? create @ MyCPPClassOnDLL @@ SGPAV1 @ HH @ Z» в качестве имени экспортируемого метода, если собственная библиотека была скомпилирована с помощью компилятора Microsoft C / C ++. В противном случае он использует «_ZN15MyCPPClassOnDLL6createEii», если он скомпилирован с помощью gcc.

Оба «макроса» определяются пользователем, вызывая NativeTypeManager.defineMacro (String, Object). Использование этой макрос-системы, основанной на C, позволяет JNIEasy во время выполнения разрешать подходящее имя библиотеки, имя метода, объем памяти и т. Д. Таким образом, «нативный» код Java может переноситься между платформами, компиляторами и так далее.

Обратите внимание, что метод getValue () не объявлен в файле XML Enhancer. JNIEasy держит этот метод «нетронутым». По умолчанию любое поле Java в классе объявляется собственным и связывается с полем C ++ энхансером. Соответствие между Java и C ++ соответствует исходной схеме класса. Это причина для поля Java «virtualTable», потому что класс C ++ имеет скрытый указатель на член для разрешения адресации виртуального метода. И наш класс C ++ имеет виртуальный метод, это подразумевает таблицу виртуальных методов.

Этот код Java показывает пример того, как мы можем получить доступ к нашему коду C ++ и создать объект C ++ из Java:

long res = MyCPPClassOnDLL.addStatic(1,2);
System.out.println("Must be 3: " + res);

MyCPPClassOnDLL obj = new MyCPPClassOnDLL(1,2); // Calls create() C++ method.
// New object is already native and maps the C++ object
System.out.println("Must be 3: " + obj.getValue());
// Java method getValue() is not native (but field "value" is native)

double res2 = obj.sub(1,2);
System.out.println("Must be 0: " + res2);

MyCPPClassOnDLL.destroy(obj); // Calls the C++ method destroy(), the memory is freed.

Этот код в основном одинаков, когда мы делаем то же самое в C ++.

Вызов obj.getValue () интересен, несмотря на то, что getValue () не является «нативным» методом (это обычный Java-метод), он возвращает значение поля «значение» в C ++. Поле Java «value» является прокси поля C ++, если поле Java модифицируется из Java (внутри класса Java), то также автоматически изменяется собственное поле (класс Java расширяется для обеспечения этой прозрачности).

В этом примере показан класс C ++, готовый для использования с JNIEasy (методы экспортируются со стандартным соглашением или соглашением о вызовах C, это не C / C ++ по умолчанию). Методы устаревших классов C ++ можно экспортировать и «оборачивать», используя ложные методы C ++. В главе руководства («КАРТИРОВАНИЕ КОРРЕКТОВ НАРОДНОГО УРОВНЯ») показано, как это сделать.

Кроме того, из C ++ мы можем связывать и вызывать методы Java, используя обычный указатель на функции:

double (__stdcall * MyCPPClassOnJava::_sub)(void*,int,int) = 0;
_sub = (double (__stdcall *)(void*,int,int)) JNIEasyHelper::findExportedMethodAddress("examples.manual.MyCPPClassOnJava.sub(int,int)");

Таким образом, мы можем определить классы C ++ как оболочки Java POJO.

Следующий звонок:

MyCPPClassOnJava* obj = …
_sub(obj, a, b);

Где «obj» указывает на объект C ++ MyCPPClassOnJava (оболочка C ++ класса Java) вызывает метод Java экземпляра sub (int, int) класса Java MyCPPClassOnJava (этот класс должен быть предварительно объявлен как собственный с JNIEasy и загружен из Java ).

Пример Java-класса MyCPPClassOnJava:

public class MyCPPClassOnJava
{
protected double value;

public MyCPPClassOnJava() // mandatory
{
}

public MyCPPClassOnJava(int a,int b)
{
this.value = a + b;

JNIEasy.get().getLockedRegistry().lock(this); // To avoid GC if called from native
}

public static void destroy(MyCPPClassOnJava obj)
{
JNIEasy.get().getLockedRegistry().unlock(obj); // To enable GC again
JNIEasy.get().getNativeManager().free(obj);
}

public static long addStatic(int a,int b)
{
return (long)a + (long)b;
}

public double sub(int a,int b)
{
this.value = this.value - (a + b);
return this.value;
}
}

Методы MyCPPClassOnJava можно экспортировать как обратные вызовы в собственный мир с помощью следующего дескриптора XML Enhancer:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Archive MyCPPClassOnJava.jnieasy.enh.xml -->
<jniEasyEnhancer version="1.1">

<package name="examples.manual">
<imports />
<class name="MyCPPClassOnJava" type="class">
<constructor exportMethod="true" params="int,int">
</constructor>
<method name="destroy" exportMethod="true"
params="MyCPPClassOnJava">
</method>
<method name="addStatic" exportMethod="true" params="int,int">
</method>
<method name="sub" exportMethod="true" params="int,int">
</method>
<fieldMethod name="value" exportMethod="true" />
</class>
</package>
</jniEasyEnhancer>

Эти примеры показывают «прозрачный» уровень. JNIEasy также предоставляет множество служебных классов для достижения детального контроля в мире Java.

Текущая версия JNIEasy — 1.2.1. Эта версия работает на следующих платформах x86: Windows, Linux, MacOSX (включая Leopard) и Solaris. JNIEasy является коммерческим программным обеспечением, но оно бесплатное для некоммерческого использования через временные лицензии, которые могут быть обновлены без пользовательских данных. Также обратите внимание, что LAMEOnJ , оболочка с открытым исходным кодом на основе Java API LAME , использующая JNIEasy, была обновлена ​​до JNIEasy 1.2.1, добавив поддержку Solaris x86.

Вы можете загрузить библиотеку, также для временной лицензии, а также посетить соответствующую домашнюю страницу . Итак … может быть, пришло время привести ваши почтенные нативные программы / библиотеки в Java на пути в будущее? И вопрос к читателям: из каких родных библиотек вы знаете, что может быть интересно получить доступ из Java?