Beim verwenden meines Wrappers bekomme ich
immer :java.lang.UnsatisfiedLinkError:
card_terminalapi_omnikey.CT_Init(SS)C
Mein bisheriger Wrapper:
public class card_terminalapi_omnikey
{
// maps to CT_Init
public native char CT_Init (short ctn, short pn);
// maps to CT_Close
public native int CT_Close (short ctn);
// maps to CT_Data
public native int CT_Data (short ctn, byte dad, byte sad, int
lenc, byte[] command, char lenr, byte[] response);
// get the native library
static
{
System.out.print("Lade native library...");
System.loadLibrary("ctdeutin");
System.out.println("erfolgreich!");
}
public card_terminalapi_omnikey(String readername)
{
super();
}
}
Und hier die Header DAtei:
/
*****************************************************************************
@doc INT EXT
******************************************************************************
* $ProjectName: $
* $ProjectRevision: $
*-----------------------------------------------------------------------------
* $Source: z:/pr/ctapi/sw/ct/rcs/ct.h $
* $Revision: 3 $
*-----------------------------------------------------------------------------
* $Author: tbruendl $
*-----------------------------------------------------------------------------
* History: see EOF
*-----------------------------------------------------------------------------
*
* Copyright © 2000 - 2006 OMNIKEY
******************************************************************************/
#ifndef _INC_CT
#define _INC_CT
/
*****************************************************************************/
/** CT-API return codes according CT-API
1.1 **/
/
*****************************************************************************/
/*
** @consts CT API Error Codes | The CT API functions return following
error codes.
*/
#define OK 0 /* @cnst Function call was
successful */
#define ERR_INVALID -1 /* @cnst Invalid parameter or
value */
#define ERR_CT -8 /* @cnst CT error (CT not in
operation) */
#define ERR_TRANS -10 /* @cnst Non-eliminable
transmission error */
#define ERR_MEMORY -11 /* @cnst Memory assignment
error in HTSI */
#define ERR_HTSI -128 /* @cnst HTSI
error */
#define SAD_HOST 0x02
#define SAD_REMOTE_HOST 0x05
#define SAD_ICC1 0x00
#define SAD_CT 0x01
#define DAD_HOST 0x02
#define DAD_REMOTE_HOST 0x05
#define DAD_ICC1 0x00
#define DAD_CT 0x01
#ifdef __cplusplus
extern "C" {
#endif
char _stdcall CTDEUTICM_close (
unsigned short ctn
);
char _stdcall CTDEUTICM_init (
unsigned short ctn,
unsigned short pn
);
char _stdcall CTDEUTICM_data (
unsigned short ctn,
unsigned char * dad,
unsigned char * sad,
unsigned short lenc,
unsigned char * command,
unsigned short * lenr,
unsigned char * response
);
char _stdcall CT_close (
unsigned short ctn
);
char _stdcall CT_init (
unsigned short ctn,
unsigned short pn
);
char _stdcall CT_data (
unsigned short ctn,
unsigned char * dad,
unsigned char * sad,
unsigned short lenc,
unsigned char * command,
unsigned short * lenr,
unsigned char * response
);
#ifdef __cplusplus
}
#endif
#endif /* _INC_CT */
Danke für jeden Hinweis,
Susanne
> // maps to CT_Init
> public native char CT_Init (short ctn, short pn);
...
> char _stdcall CT_init (
> unsigned short ctn,
> unsigned short pn
So einfach ist es mit JNI leider lange nicht. Zu Deiner "native"
Signatur erwartet Java eine ganz bestimmte C Signatur, die Du am
besten per "javah" erzeugen kannst. Die musst Du dann in C auf Dein
eigentliches C-API abbilden. Das stinkt und ist sehr fehlertraechtig.
Kannst Du Java6 und javax.smartcardio nutzen? Ich hab das schon
erfolgreich unter Windows am Laufen gehabt.
http://java.sun.com/javase/6/docs/jre/api/security/smartcardio/spec/javax/smartcardio/package-tree.html
Matthias
> Kannst Du Java6 und javax.smartcardio nutzen?
Ich würde das natürlich nutzen, nur ist mein Hauptproblem, dass ich
das PinPad meines Kartenlesers benutzen möchte (unterstützt kein PC/SC
2), und da bin ich mir nciht sicher, wie das mit javax.smartcardio
ausschaut. Ich hatte damit anfangs mal ein wenig getestet, bin aber
leider bzgl. des PinPÜads zu keinem Erfolg gekommen.
Hast Du da eine Idee?
Grüße und schon mal Danke für die Bemühungen,
Susanne
> Mein bisheriger Wrapper:
>
> public class card_terminalapi_omnikey
> {
> // maps to CT_Init
> public native char CT_Init (short ctn, short pn);
> // maps to CT_Close
> public native int CT_Close (short ctn);
> // maps to CT_Data
> public native int CT_Data (short ctn, byte dad, byte sad, int
> lenc, byte[] command, char lenr, byte[] response);
>
> // get the native library
> static
> {
> System.out.print("Lade native library...");
> System.loadLibrary("ctdeutin");
> System.out.println("erfolgreich!");
> }
>
> public card_terminalapi_omnikey(String readername)
> {
> super();
>
> }
>
> }
Jag' die kompilierte Klasse anschliessend mal durch javah.
Die dabei entstehende Headerdatei nutzt Du dann fuer die
Implementierung.
> Und hier die Header DAtei:
>
> char _stdcall CTDEUTICM_close (
> unsigned short ctn
> );
Die entsprechenden Signaturen sehen dann etwas anders aus,
vor allem sind die Typen allesamt Wrapper, hier also z.B.
jshort als Parametertyp und jchar als Typ des Rueckgabewerts.
Ansonsten kannst Du dann wie gewohnt weitermachen. Strings
und Arrays verwendest Du ja keine, da gaebe es noch ein
paar Sachen zu beachten, die in diesem Schnellkurs nicht
mehr beachtet werden koennen ;-)
Gruesse, Lothar
--
Lothar Kimmeringer E-Mail: spam...@kimmeringer.de
PGP-encrypted mails preferred (Key-ID: 0x8BC3CD81)
Always remember: The answer is forty-two, there can only be wrong
questions!
> Ich habe vor kurzem von einem Kobil Kaan auf einen Omnikey Cardman
> 3621 Reader gewechselt und habe Probleme diesen unter Java
> anzusteuern. Leider bietet Omnikey kein eigenes Java-API an, daher
> dachte ich mir, dass es doch eigentlich nicht so schwer sein kann
> einen eigenen Wrapper für die CT-API zu schreiben, zumal es eine
> Dokumentierte Header-Datei gibt.
>
> Beim verwenden meines Wrappers bekomme ich
> immer :java.lang.UnsatisfiedLinkError:
> card_terminalapi_omnikey.CT_Init(SS)C
In HBCI4Java (http://hbci4java.kapott.org) ist u.a.
ein JNI-Wrapper fuer die CT-API enthalten. Vielleicht
kannst du den Code ja von dort uebernehmen.
Gruss
Olaf
Ok, das habe ich jetzt gemacht. Nur, was hat es mit der
Implementierung auf sich? Also mir ist schon klar, dass ich meine
Methode in C implementieren muss, aber müsste ich dazu nicht wissen,
was der Hersteller in seiner dll macht, und das dann in meiner
Implementierung auch machen?
Weil ich würde doch jetzt folgendes programmieren:
#include <jni.h>
#include "meinHeader.h"
JNIEXPORT jint JNICALL JNICALL
Java_card_1terminalapi_1omnikey_CT_1Init (JNIEnv *, jobject, jshort,
jshort)
{
was müsste hier hin ?
}
Und würde dann daraus eine eigene dll erzeugen.
Grüße und nochmals danke für die Hilfe,
Susanne
> JNIEXPORT jint JNICALL JNICALL
> Java_card_1terminalapi_1omnikey_CT_1Init (JNIEnv *, jobject, jshort,
> jshort)
> {
> was müsste hier hin ?
> }
Du wolltest doch einen Wrapper schreiben?
Also sicherlich der Aufruf in die "echte" DLL, nachdem du die Argumente
angepasst hast, also sowas wie
CT_init(a,b);
(wobei du a und b dann als Name hinter die "jshort" in die Deklaration
schreiben müsstest - die haben bisher keinen Namen und sind daher
schlecht verwendbar). Ob man jetzt jshort als unsigned short verwenden
kann, oder wie man das casten müsste, weiß ich nicht.
Das CT_Init kannst du entweder statisch oder dynamisch dem
C++-Compiler/Linker bekannt machen. Statisch ist einfacher, aber dazu
brauchst du IIRC die .lib zu der .dll, in die du rufen willst. Dynamisch
ist flexibler (LoadLibrary und GetProcAddress wären die Funktionen, die
du da brauchst), aber das is Zeigergefuzzel mit viel Tipparbeit ;)
hth
Ok, also die .lib hätte ich ja, aber irgendwie komme ich ein wenig
durcheinander:
ich habe vom Hersteller eine Header-Datei (siehe oben), eine .lib und
auch eine ctapi.dll.
Desweiteren haben ich meine Java-Klasse card_terminalapi_omnikey
(s.o.). Dort lade ich mit loadLibrary(ctdeutin) die dll des
Herstellers, ist das korrekt, oder muss da dann meine hin? Außerdem
habe ich eine terminal.h (javah von card_terminalapi_omnikey) und eine
terminal.c
#include <jni.h>
#include "terminal.h"
JNIEXPORT jchar JNICALL
Java_card_1terminalapi_1omnikey_CT_1Init(JNIEnv *env, jobject obj,
jshort a, jshort b)
{
CT_init(a,b);
}
diese versuche ich so zu kompilieren:
<project default="cc" basedir=".">
<target name="cc">
<exec dir="c:/Programme/cygwin/bin/" executable="c:/Programme/
cygwin/bin/gcc">
<arg value="-mno-cygwin" />
<arg value="-I" />
<arg value="C:/Programme/Java/jdk1.6.0_03/include" />
<arg value="-I" />
<arg value="C:/Programme/Java/jdk1.6.0_03/include/win32" />
<arg value="-shared" />
<arg value="-Wl,--add-stdcall-alias" />
<arg value="-o" />
<arg value="${basedir}/terminal.dll" />
<arg value="${basedir}/terminal.c" />
</exec>
</target>
</project>
Da bekomme ich ein "build successful", allerdings mit einer Warnung
"undefined reference to `_CT_init'". Die terminal.dll kann ich
allerdings nirgendwo finden. Und wie müsste ich die einbinden? muss
ich die dann in meiner card_terminalapi_omnikey mit loadLibrary laden,
anstatt der originalen ctdeutin.dll vom Hersteller ?
Grüße,
Susanne
>> Jag' die kompilierte Klasse anschliessend mal durch javah.
>> Die dabei entstehende Headerdatei nutzt Du dann fuer die
>> Implementierung.
>
> Ok, das habe ich jetzt gemacht. Nur, was hat es mit der
> Implementierung auf sich?
Da Du einen Wrapper um die bestehende DLL machen wolltest, dachte
ich, dass der Tipp von mir schon reicht, um Dich auf den richtigen
Weg zu bringen ;-)
Anstatt hier Codewuesten zu posten, verweise ich einfach mal
auf einen Wrapper, an dem ich mitgewirkt habe (also in erster
Linie fuer 64 Bit angepasst, die Ursprungsdatei stammt nicht
von mir):
http://jcapi.cvs.sourceforge.net/jcapi/win32/
Aus JCapi.h sei hier mal exemplarisch eine Wrapper-Funktion
herausgenommen:
/*
* Class: Jcapi
* Method: nRegister
* Signature: (IIII[I)I
*/
JNIEXPORT jint JNICALL Java_net_sourceforge_jcapi_Jcapi_nRegister
(JNIEnv *, jclass, jint, jint, jint, jint, jintArray);
Also die Funktion, die beim Aufruf von
private static native int nRegister
(int bufsize, int maxcon, int maxblocks, int maxlen, int[] p_appid);
aufgerufen wird.
Die Implementierung in
http://jcapi.cvs.sourceforge.net/jcapi/win32/JCAPI.C?view=markup
sieht dann folgendermassen aus:
JNIEXPORT jint JNICALL Java_net_sourceforge_jcapi_Jcapi_nRegister(JNIEnv *env, jclass obj, jint bufsize, jint maxcon, jint maxblocks, jint maxlen, jintArray ja){
jint rc, *jap;
DWORD appid=0; /* initial value for some capi2032.dll, thanks to Leos Urban */
if ((env)->GetArrayLength(ja) < (jsize)1)
{
return ERR_NO_APPID;
}
else if (CapiRegister)
{
rc = (jint) CapiRegister ((DWORD)bufsize,(DWORD)maxcon,(DWORD)maxblocks,(DWORD)maxlen,&appid);
jap = (env)->GetIntArrayElements(ja, 0);
*jap = (jint)appid;
(env)->ReleaseIntArrayElements(ja, jap, 0); /* under control of JVM */
return rc;
}
else
{
return ERR_METHOD_NOT_AVAILABLE;
}
}
CapiRegister ist ein Funktionspointer, der auf die Funktion
der gewrappten DLL verweist. Dieser Pointer wird in der
init-Funktion ermittelt, die in JCapi.java im static-Contructor
aufgerufen wird:
JNIEXPORT void JNICALL Java_net_sourceforge_jcapi_Jcapi_init(JNIEnv *env, jclass obj){
#ifdef WIN64
hCAPI=LoadLibrary("CAPI2064.DLL");
#else
hCAPI=LoadLibrary("CAPI2032.DLL");
#endif
[...]
CapiRegister = (CAPI_REGISTER) GetProcAddress(hCAPI,"CAPI_REGISTER");
[...]
}
Hier siehst Du gleich, wie Du Deinen 64-Bit Cardreadertreiber
einlesen kann ;-)
> Weil ich würde doch jetzt folgendes programmieren:
>
> #include <jni.h>
> #include "meinHeader.h"
>
> JNIEXPORT jint JNICALL JNICALL
> Java_card_1terminalapi_1omnikey_CT_1Init (JNIEnv *, jobject, jshort,
> jshort)
> {
> was müsste hier hin ?
> }
Entweder laedst Du hier wie oben gesehen die DLL mit LoadLibrary
und ueberpruefst, ob Du gueltige Funktionszeiger zurueckbekommst
oder Du tust das vorher (je nachdem, ob das init jetzt einmalig
pro Prozessaufruf oder pro Cardreadersession zu verstehen ist).
Die Vorgehensweise hier laedt die DLL des Cardreaders zur Lauf-
zeit nach, d.h. liefert der Hersteller ein Update der DLL, muss
Dein Wrapper nicht neu gebaut werden (sofern sich nichts an den
Schnittstellen aendert, ist klar). Bei CAPI ist diese Vorgehens-
weise prinzipiell notwendig, da die CAPI2032.DLL ja abhaengig
von der im Einsatz befindlichen ISDN-Hardware ist und man daher
nichts statisch linken kann.
Du kannst natuerlich das ganze auch so bauen, dass Du das
statisch linkst, dafuer brauchst Du dann die Header- und
LIB-Datei, die dann von dem von Dir verwendeten Linker
verwurschtelt wird.
Letzteres hat auch seine Vorteile, z.B. habe ich auf diese Weise
die rcapi-Bibliothek mit einem eigenen JNI-Wrapper fuer oben
verheiratet, um Bintec-Geraete unter Linux mit jCapi zum Laufen
zu bringen (die "CAPI-2.0"-Treiber von denen sind in ihren
Schnittstellen leider anders als die capi20.h, die man sonst so
fuer alles andere unter Linux verwendet). Da haette man im anderen
Fall eine zweite Shared Library bauen und mitliefern koennen.
Da man aber sowieso beides selbst bauen muss und das ganze ohne
den beiden gar nicht funktioniert, kann man das auch gleich zu
einer Datei zusammenbacken, womit man eine Abhaengigkeit weniger
im System hat.
> Und würde dann daraus eine eigene dll erzeugen.
Das machst Du dann ueber das Projekt bei Visual Studio oder ueber
entsprechende make-Tools. Dafuer muesste man jetzt aber Deine
Entwicklungsumgebung kennen, um das naeher zu beantworten. Wenn
Du aber tatsaechlich 64-Bit-DLLs fuer Windows produzieren
moechtest, wirst Du um Visual-Studio >= 2005 nicht herumkommen.
Das entsprechende Linux-Pendant kannst Du Dir bei Sourceforge
direkt anschauen, das kaue ich hier nicht mehr durch. Der
einzige prinzipielle Unterschied ist aber eigentlich der, das
beim Fehlschlagen des Ladens einer Shared Library ein Unsatified-
LinkError geschmissen wird, was es bei Windows AFAIR nicht tut.
Deswegen kann man sich das Ueberpruefen der Funktionspointer sparen.
> ich habe vom Hersteller eine Header-Datei (siehe oben), eine .lib und
> auch eine ctapi.dll.
das is schonmal gut (und ziemlich offtopic *g*)
> Desweiteren haben ich meine Java-Klasse card_terminalapi_omnikey
> (s.o.). Dort lade ich mit loadLibrary(ctdeutin) die dll des
> Herstellers, ist das korrekt, oder muss da dann meine hin?
Mit Java-Klasse meinst du jetzt den Wrapper, der in "deine" DLL soll?
Darin musst du mit LoadLibrary die "fremde" DLL laden.
> Außerdem
> habe ich eine terminal.h (javah von card_terminalapi_omnikey) und eine
> terminal.c
ok
> Da bekomme ich ein "build successful", allerdings mit einer Warnung
> "undefined reference to `_CT_init'". Die terminal.dll kann ich
> allerdings nirgendwo finden.
Das ist doch dann sicher ein Fehler, und keine Warnung?
Ich hab noch keine DLL mit dem gcc gebacken, keine Ahnung ob der
statisches Linken gegen DLLs kann. Du müsstest ihm eigentlich mindestens
irgendwie die .lib mitteilen, die finde ich in deinem Aufruf nicht.
Dazu solltest du aber vielleicht doch in einer passenderen Gruppe
fragen, wo das sicher schneller geklärt werden kann.
> Und wie müsste ich die einbinden? muss
> ich die dann in meiner card_terminalapi_omnikey mit loadLibrary laden,
> anstatt der originalen ctdeutin.dll vom Hersteller ?
eigentlich is mir grad unklar, wieso du auch wieder ne DLL bauen willst.
wird sowas eigentlich von java selbst unterstützt? du bist ja auf der
"anderen" seite von deinem wrapper schon im "java-bereich", also willst
du doch eher eine .jar bauen, die man in Java-Projekten benutzen kann.
Gruß
Matthias
So, ich war ein paar Tage im Urlaub, daher antworte ich erst jetzt:
das stimmt natürlich, ich möchte ja den Kartenleser über seine CT-API
in meinen Java Applikationen verwenden, daher brauche ich als
Endprodukt natürlich keine dll. Ich werde dann jetzt erst einmal
versuchen dem Fehler "undefined reference to `_CT_init' " von oben auf
die Spur zu kommen, und dann der Sache, wie ich das Ganze dann in
meine Java Anwendung implementiere. Vielleicht kann da jemand auch
nochmal einen Tipp zu abgeben.
Vielen Dank auf jeden Fall für euer Bemühen,
Susanne
Ok, my new idea looks like this. Now I do not get any errors, but when
I try to use a function of the dll I get back "FF", where I should get
an ok, that the reader is successfully initiated.
#include <jni.h>
#include "terminal.h"
#include <stdio.h>
#include <windows.h>
// DLL function signature
// type definitions for function addresses returned by loading
ctdeutin.dll
typedef jchar (JNICALL CT_init_t)(unsigned short, unsigned short);
JNIEXPORT jchar JNICALL
Java_card_1terminalapi_1omnikey_CT_1Init(JNIEnv *env, jobject obj,
jshort a, jshort b)
{
CT_init_t *ctinit;
jchar result;
// Load DLL file
HINSTANCE hinstLib = LoadLibrary("ctdeutin.dll");
if (hinstLib == NULL) {
printf("ERROR: unable to load DLL\n");
return 1;
}
ctinit = (CT_init_t*)GetProcAddress(hinstLib, "CT_init");
if (ctinit == NULL) {
printf("ERROR: unable to find DLL function\n");
return 1;
}
result = ctinit(a,b);
return result;
}
Das FF bekomme ich bei rc bei:
card_terminalapi_omnikey reader = new card_terminalapi_omnikey();
short cardterminalnumber = (short)1;
char rc = reader.CT_Init(cardterminalnumber, (short)0);
#include <jni.h>
#include "terminal.h"
#include <stdio.h>
#include <windows.h>
// DLL function signature
// type definitions for function addresses returned by loading
ctdeutin.dll
typedef jchar (JNICALL CT_init_t)(unsigned short, unsigned short);
JNIEXPORT jchar JNICALL
Java_card_1terminalapi_1omnikey_CT_1Init(JNIEnv *env, jobject obj,
jshort a, jshort b)
{
> So, ich glaube ich bin ein Stück weiter gekommen. Nur bekomme ich
> jetzt immer als Antwort "FF" zurück, wenn ich die CT_init Funktion
> aufrufe...
Was ist "FF". Der Rueckgabewert der Funktion wird es nicht sein,
denn die beiden Fs duerften sich in dem einen char ziemlich
beengt fuehlen.
> JNIEXPORT jchar JNICALL
> Java_card_1terminalapi_1omnikey_CT_1Init(JNIEnv *env, jobject obj,
> jshort a, jshort b)
Ist das ein Vertipper oder ist das in Deinen C-Sourcen
auch ein CT_1Init statt CT_Init?
> char rc = reader.CT_Init(cardterminalnumber, (short)0);
Hier rufst Du naemlich CT_Init auf.
Grüße,
Susanne