JNIと教科書選び
新しい技術を習得したいとき、Webは欠かせない情報検索手段になっています。
初めての人は、まず、動くものを手っ取り早く作りたいと考えます。私も例外ではありません。
技術を紹介する側にあっては、最初に読んでもらいたいと考えるそれなりの理由や、核を下支えする技術があったりで、
情報の提供者と求めるものの間には、乖離があります。
ピンポイントでほしい情報を得られるような方法がないものかと、悩みの多い今日この頃です。
さて、今回は、Windowsで作った、DLLファイルをJavaから呼ぶ方法を考えます。
先般、Felica Lite-S を扱ったDLLファイルを改造してビルドし直します。
僕なりに、痒い所に手が届くような情報提供を行っていきますので、ご期待ください。
GitHub
- 手順解説
- 呼び出し元(Java)コーディング
- 呼び出し元(Java)コーディング解説
- 呼ばれる側(Win32-C/C++)コーディング
- 呼ばれる側(Win32-C/C++)コーディング解説
0.手順解説
- 呼び出し元のJavaソースコードを書いて、コンパイルを通します。
- javah -jni コマンドの発行。
- 呼ばれる側のWin32DLLの作成
となります。
1.呼び出し元(Java)コーディング
import java.net.*;
import java.io.*;
import java.util.*;
public class Koneko {
static {System.loadLibrary("MyFelicaLiteS");}
public static native int felica(int rw, String wData, int block);
public static native void set(String[] ret, int num);
public static void main(String[] args) {
int num = 5;
String[] ret = new String[num];
felica(1, "", 0);
set(ret, num);
try
{
byte[] array = ret[4].getBytes("MS932");
String name = new String(array);
System.out.println(name);
}
catch(Exception e)
{
e.printStackTrace();
}
String ss = "http://konekophoto.azurewebsites.net/FelicaMail?user_id=" + ret[2] + "&password=" + ret[3] + "&mail_address=" + ret[0] + "@" + ret[1];
System.out.println(ss);
client(ss);
}
public static void client(String ss)
{
try {
URL url = new URL(ss);
Object content = url.getContent();
}
catch (ArrayIndexOutOfBoundsException e) {
System.err.println("引数にURLを指定してください");
System.exit(-1);
}
catch (IOException e) {
System.err.println(e);
System.exit(-1);
}
}
}
2.呼び出し元(Java)コーディング解説
3.JavahによるC言語ヘッダファイルの生成
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Koneko */
#ifndef _Included_Koneko
#define _Included_Koneko
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Koneko
* Method: felica
* Signature: (ILjava/lang/String;I)I
*/
JNIEXPORT jint JNICALL Java_Koneko_felica
(JNIEnv *, jclass, jint, jstring, jint);
/*
* Class: Koneko
* Method: set
* Signature: ([Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_Koneko_set
(JNIEnv *, jclass, jobjectArray, jint);
#ifdef __cplusplus
}
#endif
#endif
4.呼ばれる側(Win32-C/C++)コーディング
// FelicaRW.cpp : 実装ファイル
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "FelicaRW.h"
#pragma warning(disable : 4996)
unsigned char account[16];
unsigned char domain[16];
unsigned char user_id[16];
unsigned char password[16];
unsigned char name[16];
void JNICALL Java_Koneko_set(JNIEnv *env, jclass hoge, jobjectArray retObj, int num)
{
jstring jstr = NULL;
int len = 0;
int uniLen = 0;
WCHAR *retBuff = NULL;
const char *pBuffA = (const char*)account;
len = strlen(pBuffA);
// Unicode文字列の長さを取得
uniLen = MultiByteToWideChar(CP_ACP, 0, pBuffA, len, NULL, 0);
retBuff = new WCHAR[uniLen];
// Unicode文字列に変換
MultiByteToWideChar(CP_ACP, 0, pBuffA, len, retBuff, uniLen);
// Javaの文字列生成
jstr = env->NewString((jchar *)retBuff, uniLen);
delete retBuff;
// 配列の指定インデックスに文字列をセット
env->SetObjectArrayElement(retObj, 0, jstr);
const char *pBuffD = (const char*)domain;
len = strlen(pBuffD);
// Unicode文字列の長さを取得
uniLen = MultiByteToWideChar(CP_ACP, 0, pBuffD, len, NULL, 0);
retBuff = new WCHAR[uniLen];
// Unicode文字列に変換
MultiByteToWideChar(CP_ACP, 0, pBuffD, len, retBuff, uniLen);
// Javaの文字列生成
jstr = env->NewString((jchar *)retBuff, uniLen);
delete retBuff;
// 配列の指定インデックスに文字列をセット
env->SetObjectArrayElement(retObj, 1, jstr);
const char *pBuffU = (const char*)user_id;
len = strlen(pBuffU);
// Unicode文字列の長さを取得
uniLen = MultiByteToWideChar(CP_ACP, 0, pBuffU, len, NULL, 0);
retBuff = new WCHAR[uniLen];
// Unicode文字列に変換
MultiByteToWideChar(CP_ACP, 0, pBuffU, len, retBuff, uniLen);
// Javaの文字列生成
jstr = env->NewString((jchar *)retBuff, uniLen);
delete retBuff;
// 配列の指定インデックスに文字列をセット
env->SetObjectArrayElement(retObj, 2, jstr);
const char *pBuffP = (const char*)password;
len = strlen(pBuffP);
// Unicode文字列の長さを取得
uniLen = MultiByteToWideChar(CP_ACP, 0, pBuffP, len, NULL, 0);
retBuff = new WCHAR[uniLen];
// Unicode文字列に変換
MultiByteToWideChar(CP_ACP, 0, pBuffP, len, retBuff, uniLen);
// Javaの文字列生成
jstr = env->NewString((jchar *)retBuff, uniLen);
delete retBuff;
// 配列の指定インデックスに文字列をセット
env->SetObjectArrayElement(retObj, 3, jstr);
const char *pBuffN = (const char*)name;
jstr = env->NewStringUTF(pBuffN);
// 配列の指定インデックスに文字列をセット
env->SetObjectArrayElement(retObj, 4, jstr);
}
bool PrintNfcLastErr(char* pFuncName)
{
unsigned long pErrInfo[2];
bool bSuccess;
bSuccess = felicalib_nfc_get_last_error(pErrInfo);
if (bSuccess)
{
printf("%s でエラー発生 [%x, %x].\n",
pFuncName,
pErrInfo[0], // NFCアクセスライブラリエラーコード
pErrInfo[1]); // ドライバエラーコード
}
else
{
printf("%s でエラー発生. エラー情報取得失敗.\n",
pFuncName);
}
return true;
}
bool MyPrintHex(unsigned char *pBuf, int iLen) {
char buf[16];
for (int i = 0; i < iLen; i++)
{
sprintf(buf, "%02x ", pBuf[i]);
//strcat(po, buf);
}
sprintf(buf, "%s", "\r\n");
//strcat(po, buf);
return true;
}
bool MyPolling(unsigned short usSystemCode,
unsigned char pIDm[IDM_SIZE]) {
bool bSuccess;
// Polling コマンドの組み立て
unsigned char pCom[] = {
0x00, // データ長
0x00, // コマンドコード
0x00, 0x00, // システムコード
0x00, // リクエストコード
0x00 // タイムスロット
};
unsigned char pRes[512];
unsigned short usComLen = sizeof(pCom);
unsigned short usResLen = sizeof(pRes);
pCom[0] = sizeof(pCom);
pCom[2] = usSystemCode >> 8;
pCom[3] = usSystemCode & 0xFF;
// コマンドの発行
bSuccess = felicalib_nfc_thru(pCom, usComLen, pRes, &usResLen);
if (!bSuccess)
{
PrintNfcLastErr("felicalib_nfc_thru(polling)");
return false;
}
// レスポンスのチェック
if (usResLen != 1 + 1 + 8 + 8)
{
printf("polling のレスポンス長 %d が不正 #1\n", usResLen);
return false;
}
if (pRes[0] != 1 + 1 + 8 + 8)
{
printf("polling のレスポンス長 %d が不正 #2\n", pRes[0]);
return false;
}
if (pRes[1] != 0x01)
{
printf("polling のレスポンスコード %d が不正\n", pRes[1]);
return false;
}
// IDm のコピー
memcpy_s(pIDm, IDM_SIZE, &pRes[2], IDM_SIZE);
return true;
}
/*
bSuccess = MyWriteWithoutEncryption(
pIDm, // Polling コマンドで取得した IDm IN
SERVICE_CODE_RW, // サービスコード IN
pBlockNum[iBlockCount], // ブロック番号 IN
pDataR); // 読み出したデータの格納場所 OUT
*/
bool MyWriteWithoutEncryption(unsigned char *pIDm,
unsigned short usServiceCode,
unsigned char ucBlockOffset,
unsigned char *pDataW) {
bool bSuccess;
// Write Without Encryption コマンドの組み立て
unsigned char pCom[] = {
0x00, // データ長
0x08, // コマンドコード
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IDm
0x01, // サービス数
0x00, 0x00, // サービスコード
0x01, // ブロック数
0x00, 0x00, 0x00, // ブロック
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 任意のデータ
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 任意のデータ
};
unsigned char pRes[512];
unsigned short usComLen = sizeof(pCom);
unsigned short usResLen = sizeof(pRes);
pCom[0] = sizeof(pCom);
memcpy_s(&pCom[2], sizeof(pCom) - 2, pIDm, IDM_SIZE);
pCom[11] = usServiceCode & 0xFF;
pCom[12] = usServiceCode >> 8;
pCom[15] = ucBlockOffset;
memcpy_s(&pCom[17], sizeof(pCom) - 17, pDataW, BLOCK_SIZE);
// コマンドの発行
bSuccess = felicalib_nfc_thru(pCom, usComLen, pRes, &usResLen);
if (!bSuccess)
{
PrintNfcLastErr("felicalib_nfc_thru(write without encryption)");
return false;
}
// レスポンスのチェック
if (usResLen != 1 + 1 + 8 + 1 + 1)
{
printf("write w/o enc のレスポンス長 %d が不正 #1\n", usResLen);
return false;
}
if (pRes[0] != 1 + 1 + 8 + 1 + 1)
{
printf("write w/o enc のレスポンス長 %d が不正 #2\n", pRes[0]);
return false;
}
if (pRes[1] != 0x09)
{
printf("write w/o enc のレスポンスコード %d が不正\n", pRes[1]);
return false;
}
if (pRes[10] != 0x00)
{
printf("write w/o enc のステータスフラグが [%02x, %02x] のエラー\n", pRes[10], pRes[11]);
return false;
}
return true;
}
bool MyReadWithoutEncryption(unsigned char *pIDm,
unsigned short usServiceCode,
unsigned char ucBlockOffset,
unsigned char *pDataR) {
bool bSuccess;
// Read Without Encryption コマンドの組み立て
unsigned char pCom[] = {
0x00, // データ長
0x06, // コマンドコード
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IDm
0x01, // サービス数
0x00, 0x00, // サービスコード
0x01, // ブロック数
0x00, 0x00, 0x00 // ブロック
};
unsigned char pRes[512];
unsigned short usComLen = sizeof(pCom);
unsigned short usResLen = sizeof(pRes);
pCom[0] = sizeof(pCom);
memcpy_s(&pCom[2], sizeof(pCom) - 2, pIDm, IDM_SIZE);
pCom[11] = usServiceCode & 0xFF;
pCom[12] = usServiceCode >> 8;
pCom[15] = ucBlockOffset;
// コマンドの発行
bSuccess = felicalib_nfc_thru(pCom, usComLen, pRes, &usResLen);
if (!bSuccess)
{
PrintNfcLastErr("felicalib_nfc_thru(read without encryption)");
return false;
}
// レスポンスのチェック
if (usResLen < 1 + 1 + 8 + 1 + 1)
{
printf("read w/o enc のレスポンス長 %d が不正 #1\n", usResLen);
return false;
}
if (pRes[0] < 1 + 1 + 8 + 1 + 1)
{
printf("read w/o enc のレスポンス長 %d が不正 #2\n", pRes[0]);
return false;
}
if (pRes[1] != 0x07)
{
printf("read w/o enc のレスポンスコード %d が不正\n", pRes[1]);
return false;
}
if (pRes[10] != 0x00)
{
printf("read w/o enc のステータスフラグが [%02x, %02x] のエラー\n", pRes[10], pRes[11]);
return false;
}
// 読み出したデータのコピー
memcpy_s(pDataR, BLOCK_SIZE, &pRes[13], BLOCK_SIZE);
return true;
}
int JNICALL Java_Koneko_felica(JNIEnv *env, jobject hoge, int rw, jstring wData, int block)
{
//strcpy(po, "IDm = ");
bool bSuccess;
unsigned char pIDm[IDM_SIZE];
// ライブラリの初期化
bSuccess = felicalib_nfc_initialize();
if (!bSuccess)
{
PrintNfcLastErr("felicalib_nfc_initialize");
goto L_End;
}
// ポートのオープン
char szPort[16] = "";
bSuccess = felicalib_nfc_open(szPort);
if (!bSuccess)
{
PrintNfcLastErr("felicalib_nfc_open");
goto L_Uninitialize;
}
// デバイスの補足とアクセス権の取得
DEVICE_INFO *pInfo;
bSuccess = felicalib_nfc_poll_and_start_dev_access(
DEVICE_TYPE_NFC_18092_212K // FeliCa 212kbps
| DEVICE_TYPE_NFC_18092_424K, // FeliCa 424kbps
3000, // タイムアウト ms
&pInfo);
if (!bSuccess)
{
PrintNfcLastErr("felicalib_nfc_poll_and_start_dev_access");
goto L_Close;
}
// IDm の表示
DEVICE_DATA_NFC_18092_212_424K *pf;
pf = (DEVICE_DATA_NFC_18092_212_424K *)pInfo->dev_info;
MyPrintHex(pf->NFCID2, IDM_SIZE);
// Polling コマンド発行
bSuccess = MyPolling(SYSTEM_CODE, pIDm);
if (!bSuccess)
{
goto L_StopDevAccess;
}
int pBlockNum[] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88,
};
char *pBlockName[] =
{
"S_PAD0", "S_PAD1", "S_PAD2", "S_PAD3", "S_PAD4", "S_PAD5", "S_PAD6", "S_PAD7",
"S_PAD8", "S_PAD9", "S_PAD10", "S_PAD11", "S_PAD12", "S_PAD13", "REG",
"ID", "D_ID", "SER_C", "SYS_C", "CKV", "CK",
"MC",
};
if (1 == rw)
{
bSuccess = MyReadWithoutEncryption(
pIDm, // Polling コマンドで取得した IDm
SERVICE_CODE_RO, // サービスコード
pBlockNum[0], // ブロック番号
account); // 読み出したデータの格納場所
bSuccess = MyReadWithoutEncryption(
pIDm, // Polling コマンドで取得した IDm
SERVICE_CODE_RO, // サービスコード
pBlockNum[1], // ブロック番号
domain); // 読み出したデータの格納場所
bSuccess = MyReadWithoutEncryption(
pIDm, // Polling コマンドで取得した IDm
SERVICE_CODE_RO, // サービスコード
pBlockNum[2], // ブロック番号
user_id); // 読み出したデータの格納場所
bSuccess = MyReadWithoutEncryption(
pIDm, // Polling コマンドで取得した IDm
SERVICE_CODE_RO, // サービスコード
pBlockNum[3], // ブロック番号
password); // 読み出したデータの格納場所
bSuccess = MyReadWithoutEncryption(
pIDm, // Polling コマンドで取得した IDm
SERVICE_CODE_RO, // サービスコード
pBlockNum[4], // ブロック番号
name); // 読み出したデータの格納場所
}
else
{
unsigned char pDataW[16] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
char* koneko = "koneko";
MyWriteWithoutEncryption(
pIDm,
SERVICE_CODE_RW, //usServiceCode,
block, //ucBlockOffset,
(unsigned char*)koneko);
}
L_StopDevAccess:
// デバイス使用権の解放
bSuccess = felicalib_nfc_stop_dev_access(
RE_NOTIFICATION_SAME_DEVICE);
if (!bSuccess)
{
PrintNfcLastErr("felicalib_nfc_stop_dev_access");
}
// デバイス捕捉処理の停止
bSuccess = felicalib_nfc_stop_poll_mode();
if (!bSuccess)
{
PrintNfcLastErr("felicalib_nfc_stop_poll_mode");
}
L_Close:
// ポートのクローズ
bSuccess = felicalib_nfc_close();
if (!bSuccess)
{
PrintNfcLastErr("felicalib_nfc_close");
}
L_Uninitialize:
// ライブラリの終了処理
bSuccess = felicalib_nfc_uninitialize();
if (!bSuccess)
{
PrintNfcLastErr("felicalib_nfc_uninitialize");
}
L_End:
return 0;
}
#pragma once
#include <Windows.h>
#include "felica_nfc_library.h"
#include <jni.h>
#pragma comment(lib ,"felica_nfc_library.lib")
#pragma warning(disable : 4996)
#define IDM_SIZE (8) // IDm のサイズ
#define BLOCK_SIZE (16) // ブロック(読み書きの最小単位)のサイズ
#define SYSTEM_CODE (0x88b4) // 0x88b4 = FeliCa Lite-S のシステムコード
#define SERVICE_CODE_RW (0x0009) // 0x0009 = Random Read/Write のサービスコード
#define SERVICE_CODE_RO (0x000B) // 0x000B = Random Read Only のサービスコード
void convJString(JNIEnv *env, const char *pBuff, int num);
bool MyPolling(unsigned short usSystemCode, unsigned char pIDm[IDM_SIZE]);
bool MyWriteWithoutEncryption(unsigned char *pIDm, unsigned short usServiceCode, unsigned char ucBlockOffset, unsigned char *pDataW);
bool MyReadWithoutEncryption(unsigned char *pIDm, unsigned short usServiceCode, unsigned char ucBlockOffset, unsigned char *pDataR);
extern "C"
{
JNIEXPORT void JNICALL Java_Koneko_set(JNIEnv *env, jclass hoge, jobjectArray retObj, int num);
int JNICALL Java_Koneko_felica(JNIEnv *env, jobject hoge, int rw, jstring wData, int block);
}
LIBRARY
EXPORTS
Java_Koneko_set @1
Java_Koneko_felica @2
5.呼ばれる側(Win32-C/C++)コーディング解説
<jni.h>のクラスパスを通します
C:\Program Files (x86)\Java\jdk1.8.0_111\include
<jni_md.h>のクラスパスを通します
C:\Program Files (x86)\Java\jdk1.8.0_111\include\win32
6.実行