V takzvaných vyšších jazycích založených na bajtkódu a plném GC je často nutné volat nativní kód, ať už kvůli neexistenci knihovny v onom “vyšším” jazyce nebo pro vyšší výkon (z hlediska rychlosti a/nebo zacházením s pamětí). Zatímco .NET má své C++ Interop (dříve IJW; P/Invoke nikdy, ale opravdu nikdy nepoužívejte), Java si dlouho musela vystačit s primitivním JNI. Situaci poněkud zlepšila knihovna JNA, nicméně vzhledem k návrhu celé JVM i zde je hranice mezi oběma světy, narozdíl od .NET, velmi ostrá. Na dvou typických případech ukážu, jak se vyhnout nejčastějším chybám.
Při předání parametru z Javy do C převede JNA automaticky hodnotové typy. Problém může nastat u tříd, jež se předávají jako ukazatel na strukturu. Nativní funkce totiž může používat asynchronní zpracování s následným voláním callbacku. V takovém případě se může stát, že Java objekt zruší, než jej použijeme. A protože není k dispozici obdoba pin_ptr (“přišpendlení objektu na haldě JVM”), nezbývá nic jiného než strukturu explicitně zkopírovat. Celkem se tedy v takovém scénáři zkopíruje dvakrát (kontrolní otázka: proč?). Ono i pin_ptr má v tomto ohledu nevýhodu, přišpendlením objektu (jemuž se v .NET nelze vyhnout, neboť pokud by GC objekt přesunul, zneplatnil by tak ukazatel) totiž celkem efektivně zabráníme GC v práci, protože nebude schopen provádět kompaktizaci haldy.
Druhým případem je předání návratové hodnoty referencí, až už jde o řetězec, pole nebo jakoukoliv třídu. Z céčka můžeme sice přímo vrátit ukazatel, jenže pokud příslušnou paměť později neuvolníme, máme memory leak jak vyšitý. Pochopitelně bychom mohli JNA zavolat znovu a paměť uvolnit, jenže JNA má svou režii, tj. zbytečných volání nativní knihovny se chceme vyhnout. Řešení je naštěstí jednoduché. Místo Neco* funkce(…) použijeme void funkce(…, CallbackFunkce cb), tj. místo vrácení ukazatele jej předáme callbacku a až po něm paměť uvolníme (místo return x; máme cb(x); free(x);). V Javě 8 se jako callback dá využít lambda. Nevýhodou takovéhoto řešení je menší přehlednost kódu v Javě (mohou vznikat vnořené callbacky). Alternativou je použití autopoolu v céčku (pak můžeme použít přímo return x; a dál se o uvolnění pamětí nestarat, aniž by vznikl memory leak), čímž přesuneme odpovědnost za paměť z JVM na nativní knihovnu.
[1] Ten nativní kód je často volán knihovnami, které ty nasazené programy používají. Nicméně já preferuji JNI, a to právě kvůli tomu, že se tam předávají pouze reference na Java objekty, se kterými jde pracovat přímo (mohu např. přímo přistupovat k obsahu byte[]), což výborně řeší zde uvedený problém se správou paměti.
Mne se naopak osvedcilo, ze kdykoliv je potreba komunikovat mezi takhle oddelenymi svety, je dobre vse predavat hodnotou (resp. kopirovat data), stejne jako se to resi pri komunikaci kernel-userspace. V momente, kdy se zacnou predavat reference, callbacky a prepisovat data, zacinaji se objevovat spatne objevitelne chyby.
@1,2 Ja bych se pridal na Frantovu stranu, zatim jsem za spoustu let, co delam s Javou nebyl nucen pouzit JNI/JNA. Nekolikrat jsem byl ve velkem pokuseni, ale pak se ukazalo, ze to, co potrebuju, se da najit ve standardni knihovne, jen je to obcas nekde zastrcene.
Resil jsem kdysi stejny problem jak predavat data z merici karty do javy k naslednemu zpracovani. Nakonec se to deje pres soubor, ale jako dalsi alternativu jsem chtel pouzit ByteBuffer. Pokud se alokuje jako direct, je mozne ziskat v C pointer na ten buffer. Prislo mi to jako nejelegantnejsi reseni.
>> P/Invoke nikdy, ale opravdu nikdy nepoužívejte
> Proč?
To by mě taky zajímalo. Je pravda, že je to celkem opruz, ale na jednodušší věci se celkem hodí. Použití Managed C++ je v něčem příjemnější, v něčem zase ne. Explicitní PInvoke bych nezatracoval.
Jinak kromě případů, kdy jsem chtěl zjistit "jak to vlastně funguje", jsem použil volání nativního kódu jen 1x, u .Netu, kdy jsem potřeboval přímo hrabat na ovladače hardwaru.
Autor se zabývá vývojem kompilátorů a knihoven pro objektově-orientované programovací jazyky.
Přečteno 36 341×
Přečteno 25 469×
Přečteno 23 873×
Přečteno 20 263×
Přečteno 17 977×