miniupnpc for JavaとJNAeratorのお話
miniupnpcというCなUPnPのライブラリがあって、これは結構多くのルータをサポートしてて色々なオープンソースアプリで(もしかしたら商用アプリでも)用いられているんだけれど、Javaバインディングがない。
どーせ小さなコードなので、JNIするのもそんな面倒じゃないだろ、と思って書き始めてみた。
が、途中で、JNAという別のネイティブサポートの実装があって、これ用のバインディングをJNAeratorで全自動生成出来るという事に気がついた。
これは中々優秀で、たったこれだけのシェルスクリプトでバインディングが生成出来た:
#!/bin/sh UNAME=`uname` if [ $UNAME = "Darwin" ]; then LIBRARY=libminiupnpc.dylib elif [ $UNAME = "Linux" ]; then LIBRARY=libminiupnpc.so fi java -jar jnaerator.jar -library miniupnpc miniupnpc.h declspec.h upnpcommands.h upnpreplyparse.h igd_desc_parse.h miniwget.h upnperrors.h $LIBRARY \ -package fr.free.miniupnp -o . -jar miniupnpc_$UNAME.jar -v
で、これを使うとCで書いていたminiupnpcのテストコードが簡単にJavaで書き直せた:
Cのコード
/* $Id: upnpc.c,v 1.67 2009/08/03 22:58:37 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard * Copyright (c) 2005-2009 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ #include <stdio.h> #include <stdlib.h> #include <string.h> #ifdef WIN32 #include <winsock2.h> #define snprintf _snprintf #endif #include "miniupnpc.h" #include "upnpcommands.h" #include "upnperrors.h" int main(int argc, char **argv) { struct UPNPDev * devlist = 0; char lanaddr[16]; /* my ip address on the LAN */ int i; if (devlist = upnpDiscover(2000, NULL, NULL, 0)) { struct UPNPDev * device; struct UPNPUrls urls; struct IGDdatas data; if(devlist) { printf("List of UPNP devices found on the network :\n"); for(device = devlist; device; device = device->pNext) { printf(" desc: %s\n st: %s\n\n", device->descURL, device->st); } } i = 1; if(i = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr))) { switch(i) { case 1: printf("Found valid IGD : %s\n", urls.controlURL); break; case 2: printf("Found a (not connected?) IGD : %s\n", urls.controlURL); printf("Trying to continue anyway\n"); break; case 3: printf("UPnP device found. Is it an IGD ? : %s\n", urls.controlURL); printf("Trying to continue anyway\n"); break; default: printf("Found device (igd ?) : %s\n", urls.controlURL); printf("Trying to continue anyway\n"); } printf("Local LAN ip address : %s\n", lanaddr); { char externalIPAddress[16]; char intClient[16]; char intPort[6]; int r; UPNP_GetExternalIPAddress(urls.controlURL, data.servicetype, externalIPAddress); if(externalIPAddress[0]) printf("ExternalIPAddress = %s\n", externalIPAddress); else printf("GetExternalIPAddress failed.\n"); r = UPNP_AddPortMapping(urls.controlURL, data.servicetype, argv[1], argv[1], lanaddr, 0, argv[2], 0); if(r!=UPNPCOMMAND_SUCCESS) printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", argv[1], argv[1], lanaddr, r, strupnperror(r)); r = UPNP_GetSpecificPortMappingEntry(urls.controlURL, data.servicetype, argv[1], argv[2], intClient, intPort); if(r!=UPNPCOMMAND_SUCCESS) printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n", r, strupnperror(r)); if(intClient[0]) { printf("InternalIP:Port = %s:%s\n", intClient, intPort); printf("external %s:%s %s is redirected to internal %s:%s\n", externalIPAddress, argv[1], argv[2], intClient, intPort); } } FreeUPNPUrls(&urls); } else { fprintf(stderr, "No valid UPNP Internet Gateway Device found.\n"); } freeUPNPDevlist(devlist); devlist = 0; } else { fprintf(stderr, "No IGD UPnP Device found on the network !\n"); } }
Javaのコード
import java.nio.ByteBuffer; import fr.free.miniupnp.*; /** * * @author syuu */ public class JavaBridgeTest { public static void main(String[] args) { int UPNP_DELAY = 2000; MiniupnpcLibrary miniupnpc = MiniupnpcLibrary.INSTANCE; UPNPDev devlist = null; UPNPUrls urls = new UPNPUrls(); IGDdatas data = new IGDdatas(); ByteBuffer lanaddr = ByteBuffer.allocate(16); ByteBuffer intClient = ByteBuffer.allocate(16); ByteBuffer intPort = ByteBuffer.allocate(6); int ret; int i; devlist = miniupnpc.upnpDiscover(UPNP_DELAY, (String) null, (String) null, 0); if (devlist != null) { System.out.println("List of UPNP devices found on the network :"); for (UPNPDev device = devlist; device != null; device = device.pNext) { System.out.println("desc: " + device.descURL.getString(0) + " st: " + device.st.getString(0)); } if ((i = miniupnpc.UPNP_GetValidIGD(devlist, urls, data, lanaddr, 16)) != 0) { switch (i) { case 1: System.out.println("Found valid IGD : " + urls.controlURL.getString(0)); break; case 2: System.out.println("Found a (not connected?) IGD : " + urls.controlURL.getString(0)); System.out.println("Trying to continue anyway"); break; case 3: System.out.println("UPnP device found. Is it an IGD ? : " + urls.controlURL.getString(0)); System.out.println("Trying to continue anyway"); break; default: System.out.println("Found device (igd ?) : " + urls.controlURL.getString(0)); System.out.println("Trying to continue anyway"); } System.out.println("Local LAN ip address : " + new String(lanaddr.array())); ByteBuffer externalAddress = ByteBuffer.allocate(16); miniupnpc.UPNP_GetExternalIPAddress(urls.controlURL.getString(0), new String(data.servicetype), externalAddress); System.out.println("ExternalIPAddress = " + new String(externalAddress.array())); ret = miniupnpc.UPNP_AddPortMapping( urls.controlURL.getString(0), new String(data.servicetype), args[0], args[0], new String(lanaddr.array()), null, args[1], null); if (ret != MiniupnpcLibrary.UPNPCOMMAND_SUCCESS) System.out.println("AddPortMapping() failed with code " + ret); ret = miniupnpc.UPNP_GetSpecificPortMappingEntry( urls.controlURL.getString(0), new String(data.servicetype), args[0], args[1], intClient, intPort); if (ret != MiniupnpcLibrary.UPNPCOMMAND_SUCCESS) System.out.println("GetSpecificPortMappingEntry() failed with code " + ret); System.out.println("InternalIP:Port = " + new String(intClient.array()) + ":" + new String(intPort.array())); ret = miniupnpc.UPNP_DeletePortMapping( urls.controlURL.getString(0), new String(data.servicetype), args[0], args[1], null); if (ret != MiniupnpcLibrary.UPNPCOMMAND_SUCCESS) System.out.println("DelPortMapping() failed with code " + ret); miniupnpc.FreeUPNPUrls(urls); } else { System.out.println("No valid UPNP Internet Gateway Device found."); } miniupnpc.freeUPNPDevlist(devlist); } else { System.out.println("No IGD UPnP Device found on the network !\n"); } } }
まぁ、全然JavanizedされてないAPIだけど、Binding書かなくても良いのは大きい。
但し、ぬるぽするとVMごと落ちたりするので、そういう注意は必要。
何がGCされて何がGCされないのか?とか、どれがポインタで実体で…とか、色々面倒くさい事はある。
でも、少なくとも取り敢えず動く。
ちなみに、JRubyはJNAを使ってPOSIXサポートを実装したらしい。
で、出来上がったのでminiupnpcの作者にマージさせろボケがってメール送った。
このままじゃ拒否されるかもしれんが、まぁ、何か議論するのはいいんじゃないカナ。