Portál AbcLinuxu, 30. dubna 2025 21:23
V druhém díle seriálu se podíváme na ostatní primitivní datové typy a naučíme se pracovat s objekty typu String.
V ukázce v minulém díle jste si mohli všimnout, že se v nativním kódu místo typu int
používalo jint
. Situace je taková, že se velikost datových typů může mezi Javou a C/C++ lišit, a proto byla zavedena tato značení typů. V případě jint
půjde typicky ve výsledku jen o #define na int
, protože většina kompilátorů má int
32bitový, ale s javovským longem už je to o trochu složitější.
Typ v Javě | Typ v JNI | Typ v C/C++ | Identifikátor | Poznámka |
---|---|---|---|---|
boolean | jboolean | unsigned char (bool) | Z | Pro kompatibilitu s C není použit bool z C++ |
byte | jbyte | unsigned char | B | |
char | jchar | unsigned short (wchar_t) | C | Java pro znaky používá kódování UTF-16 (dříve UCS-2) |
short | jshort | short (int16_t) | S | |
int | jint | int (int32_t) | I | Staré C kompilátory měly int 16bitový, definice typů je v C dosti vágní |
long | jlong | long long (int64_t) | J | Pozor, velikost long se v C typicky liší podle velikosti ukazatele na platformě, proto long long |
float | jfloat | float | F | 32bitové desetinné číslo |
double | jdouble | double | D | 64bitové desetinné číslo |
Protože Java podporuje přetěžování metod, mají všechny datové typy v Javě svůj identifikátor, který se používá při "dekoraci" názvů metod o signaturu pro nalezení té správné přetížené varianty. Sami si s identifikátory budeme hrát až v dalších dílech. Do té doby můžeme tyto identifikátory zahlédnout v hlavičkových souborech generovaných programem javah
.
Všechny javovské objekty jsou v JNI reprezentovány jako jobject
nebo jako potomek této třídy. Samotný typ jobject
je ve skutečnosti jen #definovaný ukazatel (_jobject*
), proto se při kopírování jobjectu nevolají žádné konstruktory nebo něco podobného. Každý jobject
přitom představuje z hlediska Javy pouze referenci na objekt, to znamená, že dva jobjecty s odlišnou adresou mohou ve skutečnosti zastupovat ten samý javovský objekt, který bude předmětem garbage collection, jakmile budou všechny reference na něj zrušeny.
Pro odchycení některých základních programátorských chyb obsahuje JNI potomky jobjectu. Především proto, aby bylo jasné, že daná funkce JNI požaduje objekt právě takového typu. Veškerá pole (i primitivních hodnot) jsou také objekt – castování pole na Object v Javě by sice člověka možná praštilo do očí, ale i zde se ukazuje, že je to skutečnost.
Typ v JNI | Typ v Javě |
---|---|
jobject | java.lang.Object (instance libovolné třídy) |
jstring | java.lang.String |
jclass | java.lang.Class |
jthrowable | java.lang.Throwable |
jarray (a příbuzní jako jintArray) | type[] (pole primitivních hodnot nebo objektů) |
jweak | speciální typ pro slabé globální reference, odpovídá java.lang.ref.WeakReference |
Konečně se dostáváme k vytvoření našeho prvního řetězce. Řetězce mají v Javě speciální postavení a není tomu naštěstí jinak ani v JNI. Díky tomu nemusíme vytvářet instance java.lang.String a ručně volat konstrukor, což by bylo poněkud pracné. Upravíme si tedy příklad z přechozího dílu a naprogramujeme si dvě nativní metody: jedna bude číslo převádět na řetězec a druhá zase naopak. Javovská třída:
package test; public class TestNative { public static native String intToString(int number); public static native int stringToInt(String str); public static void main(String[] args) { System.loadLibrary("mynative"); int number = 567; String str = "321"; System.out.println("intToString(): " + intToString(number)); System.out.println("stringToInt(): " + stringToInt(str)); } }
Na straně nativního kódu se už konečně dostane ke slovu pomyslná brána k JNI, tedy JNIEnv*
. Vygenerujeme si nový hlavičkový soubor pomocí javah
a začneme implementovat. Nejprve se podíváme na odlišnost volání metod JNI mezi C a C++ – díky podpoře OOP v C++ je zde práce snazší. V mých příkladech budu i z tohoto důvodu používat styl C++.
/* Máme nějaký objekt env: extern JNIEnv* env; */ /* Takto voláme funkce JNI v jazyce C: */ (*env)->funkce(env, arg1, arg2); /* Takto voláme funkce JNI v jazyce C++: */ env->funkce(arg1, arg2);
Přejděme tedy k práci a podívejme se na implementaci intToString a stringToInt:
jstring Java_test_TestNative_intToString(JNIEnv* env, jclass myClass, jint num) { char buf[50]; snprintf(buf, 50, "%d", num); jstring str = env->NewStringUTF(buf); return str; } jint Java_test_TestNative_stringToInt(JNIEnv* env, jclass myClass, jstring str) { const char* cstr; int num; cstr = env->GetStringUTFChars(str, NULL); num = atoi(cstr); env->ReleaseStringUTFChars(str, cstr); return num; }
Použili jsme tři funkce JNI, které si popíšeme:
jstring NewStringUTF(const char* str)
Vrátí novou lokální referenci na instanci java.lang.String s obsahem UTF-8 řetězce, na který odkazuje str
. Existuje také varianta NewString(const jchar*, jsize length)
, která pracuje s řetězci v UTF-16. Tato lokální reference bude v tomto příkladu zrušena při návratu funkce, respektive bude nahrazena jinou referencí ve volající metodě, pokud si vrácenou hodnotu uchovává.
const jbyte* GetStringUTFChars(jstring str, jboolean* isCopy)
Vrátí ukazatel na UTF-8 řetězec s obsahem předaného Stringu. Pokud isCopy není NULL, je nastaveno na JNI_TRUE
, nebo JNI_FALSE
podle toho, zda bylo nutné vytvořit kopii dat retězce. Dvě pravidla: do vráceného řetězce nesmíte zapisovat a musíte jej uvolnit pomocí ReleaseStringUTFChars
. Existuje také varianta GetStringChars
, která vrací řetězec v UTF-16.
void ReleaseStringUTFChars(jstring str, const jbyte* cstr)
Uvolní paměť daného C řetězce. Existuje také varianta ReleaseStringChars
, která se používá spolu s GetStringChars
.
Na Linuxu budeme chtít používat hlavně funkce spojené s UTF-8; na Windows může být výhodnější používat funkce vracející UTF-16, protože Unicode varianta WinAPI používá právě toto kódování. Na rozdíl od C/C++ jsou řetězce v Javě vždy konstantní, to znamená, že se jejich obsah nemůže od založení do zrušení objektu měnit. Tím pádem jen marně můžeme hledat takové funkce.
V dokumentaci najdeme ještě několik dalších funkcí pro práci s řetězci:
GetStringLength
– vrátí délku Stringu v počtu Unicode znakůGetStringUTFLength
– vrátí bajtovou délku UTF-8 reprezentace řetězceGetStringRegion
a GetStringUTFRegion
– zapíší určitou část řetězce do námi dodaného bufferuGetStringCritical
a ReleaseStringCritical
– slouží pro přímý přístup k UTF-16 bufferu daného Stringu. Vyplatí se při práci s dlouhými řetězci (obsah se typicky nemusí kopírovat), ale má přísnější pravidla: mezi Get a Release se nesmí volat funkce JNI a náš kód nesmí blokovat, neboť by mohl blokovat i další části JVM.Nyní zkompilujeme naší nativní knihovnu a pak spustíme javovský program:
$ java -Djava.library.path=/tmp -cp build/classes test.TestNative intToString(): 567 stringToInt(): 321
V příštím díle se naučíme pracovat s lokálními a globálními referencemi na javovské objekty.
Na pristup k nejakym hroznym zdrojom ktore maju nepredvidatelne spravanie takze aj ten C/C++ bude mat nepredvidatelne (chybne) spravanie.Tak to ano, ale v takové situaci by se dělalo oddělení stranou i u nativních aplikací. Já využití vidím v přístupu k nativním knihovnám jako SQLite (bylo by hloupé to reimplementovat v Javě, ano, znám JDBC, je to jen příklad), Qt, GTK+, ffmpeg nebo různým platformně specifickým věcem (XVideo).
Take zamyslenie. Na co by som potreboval spustat nativny kod?Odpověď je naprosto triviální. Volitelná integrace s určitýma nativníma knihovnama/frameworkama. Konkrétní příklad z praxe? AddressBook.framework - volání jeho API z Javy je jednoduché a efektivní. Dvě otázky, kterými byste mohl oponovat: 1) Proč volat framework dostupný pouze na jednom OS? 2) Proč si nenapsat obdobnou funkcionalitu v Javě? Moje zdůvodnění bylo prosté. Proč ne, když je to k dispozici. Lépe tam ta aplikace zapadá a nebudu znovu vynalézat kolo. Při pečlivém a správném napsání nativního kódu (bez použití JNA) nebyl jediný problém. Uznávám, daný kód byl vcelku jednoduchý a moc šancí na chybu v něm nebylo. Nicméně z části to bylo dáno také volbou nativního jazyka Objective-C.
Ja nemam nic proti spustaniu Javy z C/C++Pokud jsem to pochopil zprávně, ta JNI pracuje přesně opačně - umožňuje použít C/C++ kod v Javě.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.