JavaからDLLを呼び出してみる3
タイトルあんまり名前が繋がってない
まあ、意味は伝わるはず
C++のクラス用DLLを作成
MathFuncsDll.h
// MathFuncsDll.h #ifdef MATHFUNCSDLL_EXPORTS #define MATHFUNCSDLL_API __declspec(dllexport) #else #define MATHFUNCSDLL_API __declspec(dllimport) #endif namespace MathFuncs { // This class is exported from the MathFuncsDll.dll class MyMathFuncs { public: // Returns a + b static MATHFUNCSDLL_API double Add(double a, double b); // Returns a - b static MATHFUNCSDLL_API double Subtract(double a, double b); // Returns a * b static MATHFUNCSDLL_API double Multiply(double a, double b); // Returns a / b // Throws const std::invalid_argument& if b is 0 static MATHFUNCSDLL_API double Divide(double a, double b); }; }
MathFuncsDll.cpp
// MathFuncsDll.cpp : Defines the exported functions for the DLL application. // #include "stdafx.h" #include "MathFuncsDll.h" #include <stdexcept> using namespace std; namespace MathFuncs { double MyMathFuncs::Add(double a, double b) { return a + b; } double MyMathFuncs::Subtract(double a, double b) { return a - b; } double MyMathFuncs::Multiply(double a, double b) { return a * b; } double MyMathFuncs::Divide(double a, double b) { if (b == 0) { throw invalid_argument("b cannot be zero!"); } return a / b; } }
ラッパー用
SampleDllWrapper.cpp
// SampleDllWrapper.cpp : DLL アプリケーション用にエクスポートされる関数を定義します。 // #include "stdafx.h" #include "./MathFuncsDll.h" double MyMathFuncs_Add(double a, double b) { return MathFuncs::MyMathFuncs::Add(a, b); }
SampleDllWrapper.def
LIBRARY SampleDllWrapper EXPORTS MyMathFuncs_Add @1
※DLLを参照して、ヘッダーファイルを指定する
Java
Sample02.java
package sample02; import com.sun.jna.Library; import com.sun.jna.Native; interface HelloLib extends Library { HelloLib INSTANCE = (HelloLib) Native.loadLibrary("SampleDllWrapper.dll", HelloLib.class); // Cの関数名と一致させる double MyMathFuncs_Add(double s1, double s2); } public class Sample02 { public static void main(String[] args) { HelloLib hello = HelloLib.INSTANCE; double a = 100; double b = 1200; System.out.println(hello.MyMathFuncs_Add(a, b)); } }
↓
1300.0
※データ型がうまくあわないとエラーになりました(´・ω・`)
わかったこと
DLLを静的に読み込んでいるのでヘッダーファイルなどが必要。
最終的にはDLLだけでやりたいのでヘッダーファイルなしのバージョンも知りたい
難しいなあ(´・ω・`)
Windowsで作成したDLLをJavaで動かす2
関数を1つしか定義していなかったので
複数定義した場合のパターンをやってみる
SampleDll.cpp
// SampleDll.cpp : DLL アプリケーション用にエクスポートされる関数を定義します。 // #include "stdafx.h" #include "stdio.h" int GetHello(int data) { printf("%d", data); return 0; } int GetHelloString(char *data) { printf("%s", data); return 0; } int GetHelloAdd(int data1, int data2) { return data1 + data2; }
SampleDll.def
LIBRARY SampleDll EXPORTS GetHello @1 GetHelloString @2 GetHelloAdd @3
※この@1とかって引数のやつと思ったけど違うようです。
DEF ファイルを使った DLL からのエクスポート
エクスポート関数を確認
Dump of file SampleDll.dll File Type: DLL Section contains the following exports for SampleDll.dll 00000000 characteristics 5857D09C time date stamp Mon Dec 19 21:20:44 2016 0.00 version 1 ordinal base 3 number of functions 3 number of names ordinal hint RVA name 1 0 0001102D GetHello = @ILT+40(?GetHello@@YAHH@Z) 3 1 00011190 GetHelloAdd = @ILT+395(?GetHelloAdd@@YAHHH@Z) 2 2 0001121C GetHelloString = @ILT+535(?GetHelloString@@YAHPEAD@Z) Summary 1000 .00cfg 1000 .data 1000 .gfids 1000 .idata 3000 .pdata 3000 .rdata 1000 .reloc 1000 .rsrc 8000 .text 10000 .textbss
Javaで実装
Sample01.java
package sample01; import com.sun.jna.Library; import com.sun.jna.Native; interface HelloLib extends Library { HelloLib INSTANCE = (HelloLib) Native.loadLibrary("SampleDll.dll", HelloLib.class); // Cの関数名と一致させる int GetHello(int s); int GetHelloString(String s); int GetHelloAdd(int s1, int s2); } public class Sample01 { public static void main(String[] args) { HelloLib hello = HelloLib.INSTANCE; hello.GetHello(100); hello.GetHelloString("hoge"); System.out.println(hello.GetHelloAdd(600, 1200)); } }
↓
1800 100hoge
※system.out.printlnのほうが先に出力していますね、なんで?
わかったこと
・char *で定義したら Stringで一致する。
・byte[]のやつは一部文字化けらしき問題を起こすらしい
・結果の出力は前後する場合がある
Spring Bootでアップロードファイルを受け取る
FormとかBeanとかいうクラスを使用することで受け取ることが可能になりました。
ファイルってどうするのかな?
MultipartFileとかいうクラス
があれば受け取れます(^^)
フォーム系
SendDataForm.java
package com.example.form; import java.io.Serializable; import org.springframework.web.multipart.MultipartFile; public class SendDataForm implements Serializable{ /** * */ private static final long serialVersionUID = 1L; private String id; private String text; private String item_id; private MultipartFile upload_file; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getItem_id() { return item_id; } public void setItem_id(String item_id) { this.item_id = item_id; } public MultipartFile getUpload_file() { return upload_file; } public void setUpload_file(MultipartFile upload_file) { this.upload_file = upload_file; } public String toString(){ String str = ""; str += "id = " + this.id + "\n"; str += "text = " + this.text + "\n"; str += "item_id = " + this.item_id + "\n"; if(null != this.upload_file){ str += "upload_file = " + this.upload_file.getOriginalFilename() + " size = " + this.upload_file.getSize() + "\n"; } return str; } }
Item.java
package com.example.form; import java.io.Serializable; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Item implements Serializable{ /** * */ private static final long serialVersionUID = 1L; private String id; private String text; private String item_id; private String upload_file; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getItem_id() { return item_id; } public void setItem_id(String item_id) { this.item_id = item_id; } public String getUpload_file() { return upload_file; } public void setUpload_file(String upload_file) { this.upload_file = upload_file; } }
受信用コントローラー
SampleController.java
package com.example.web; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.example.form.Item; import com.example.form.SendDataForm; @RestController @RequestMapping("/sample") public class SampleController { @RequestMapping(value = "/upload", method = {RequestMethod.POST}) public Item upload(SendDataForm form){ Item item = new Item(); item.setId(form.getId()); item.setText(form.getText()); item.setItem_id(form.getItem_id()); item.setUpload_file(form.getUpload_file().getOriginalFilename()); return item; } }
実験
$ curl -X POST http://localhost:8080/sample/upload \ > -F "id=100" \ > -F "text=hogehoge" \ > -F "item_id=12345678" \ > -F "upload_file=@work/file/image.jpg" {"id":"100","text":"hogehoge","item_id":"12345678","upload_file":"image.jpg"}
※curlってこういうとき簡単に試せるから便利(^^)
MultipartFileの中でファイルの名前やサイズ、データも管理できるみたいなので結構便利な感じ
フォームで送信する形式になるのでヘッダーにjsonとかは設定できないんですね…なんかその辺まだ良くわかっていない(´・ω・`)
まあ、json形式で返却できるみたいなので大丈夫かな?
Spring Bootでjson文字列を受信してクラスに割り当てる
BeanとかFormとか呼ばれているもので画面とかの項目が設定されるやつ
前の時代などではパラメータを指定して取り込んでいたけどjsonで送った場合なども対応したクラスに割り当てたい場合等に対応
json形式で送った際きちんとクラスに設定されるか検証する
やること
受付用のクラスを作成
@RequestBodyアノテーションを使用
@RequestMappingで受け付けるデータを指定
受付用クラス
ItemForm.java
package com.example.form; import java.io.Serializable; public class ItemForm implements Serializable{ /** * */ private static final long serialVersionUID = 1L; private String name; private String date; private String price; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getPrice() { return price; } public void setPrice(String price) { this.price = price; } public String toString(){ return "name = " + this.name + " date = " + this.date + " price = " + this.price; } }
ItemsForm.java
package com.example.form; import java.io.Serializable; import java.util.List; public class ItemsForm implements Serializable{ /** * */ private static final long serialVersionUID = 1L; private String count; private List<ItemForm> items; public String getCount() { return count; } public void setCount(String count) { this.count = count; } public List<ItemForm> getItems() { return items; } public void setItems(List<ItemForm> items) { this.items = items; } public String toString(){ String str = ""; str = "count = " + this.count + "\n"; str += ""; for(ItemForm item : this.items) { str += item.toString() + "\n"; } return str; } }
コントローラー設定
SampleController.java
package com.example.web; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.example.form.ItemForm; import com.example.form.ItemsForm; @RestController @RequestMapping("/sample") public class SampleController { @RequestMapping(value = "/test1", method = {RequestMethod.POST}, consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public String item(@RequestBody ItemForm form){ return form.toString(); } @RequestMapping(value = "/test2", method = {RequestMethod.POST}, consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public String items(@RequestBody ItemsForm form){ return form.toString(); } }
実行結果
・/sample/test1
$ curl -X POST \ > -H 'Content-Type:application/json' \ > -d '{ > "name": "hogehoge", > "date": "20151028", > "price": "12000" > }' http://localhost:8080/sample/test1 name = hogehoge date = 20151028 price = 12000
・/sample/test2
$ curl -X POST \ > -H 'Content-Type:application/json' \ > -d '{ > "count": "10", > "items": [ > { > "name": "foo1", > "date": "20160112", > "price": "1200" > }, > { > "name": "foo2", > "date": "20160212", > "price": "4300" > }, > { > "name": "foo3", > "date": "20160217", > "price": "2500" > }, > { > "name": "foo4", > "date": "20160519", > "price": "2000" > }, > { > "name": "foo5", > "date": "20160630", > "price": "1500" > } > ] > }' http://localhost:8080/sample/test2 count = 10 name = foo1 date = 20160112 price = 1200 name = foo2 date = 20160212 price = 4300 name = foo3 date = 20160217 price = 2500 name = foo4 date = 20160519 price = 2000 name = foo5 date = 20160630 price = 1500 $
クラスを階層構造を内部に持つことでネスト構造の場合も対応できる感じ
ファイルとかできるかはわからない
送り込むjsonのキーが存在しなくてもクラスの方はnullになるだけでエラーを起こすわけではない
Spring BootでXMLやJSONを返す
忙しいです…
Spring BootでRest APIを作成する場合には
・コントローラー用のクラスに"@RestController"アノテーションを付ける
・クラスを任意で用意して返却することで基本、json形式で返却できる
・XMLを返したい場合は返却用のクラスに"@XmlRootElement"を付与する必要があります
返却用のクラス
Item.java
package com.example.entity; import java.io.Serializable; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Item implements Serializable{ /** * */ private static final long serialVersionUID = 1L; private String name; private String date; public Item(){ } public Item(String name, String date){ this.name = name; this.date = date; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } }
コントローラー
Sample1Controller.java
package com.example.web; import java.util.List; import java.util.ArrayList; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.example.entity.Item; @RestController @RequestMapping("/sample1") public class Sample1Controller { private List<Item> items = new ArrayList<Item>(); Sample1Controller(){ items.add(new Item("hoeg1", "20131122")); items.add(new Item("hoeg2", "20131130")); items.add(new Item("hoeg3", "20131205")); items.add(new Item("hoeg4", "20131210")); items.add(new Item("hoeg5", "20131225")); items.add(new Item("hoeg6", "20141012")); items.add(new Item("hoeg7", "20141014")); items.add(new Item("hoeg8", "20141024")); } @RequestMapping(value = "/", method = { RequestMethod.GET }) public String index(){ return "/sample/index"; } @RequestMapping(value = "/test1", method = { RequestMethod.GET }) public String test1(){ return "/sample/test1"; } @RequestMapping(value = "/items", method = { RequestMethod.GET }) public List<Item> items(){ return this.items; } @RequestMapping(value = "/item/{id}", method = { RequestMethod.GET }) public Item item(@PathVariable int id){ return this.items.get(id); } }
内容
・/sample1
→ "/sample/index"を返す
$ curl -i -H "Accept: application/json" \ > http://localhost:8080/sample1/ HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Content-Length: 13 Date: Sat, 17 Dec 2016 04:10:47 GMT /sample/index
・/sample1/test1
→ "/sample/test1"を返す
$ curl -i -H "Accept: application/json" \ > http://localhost:8080/sample1/test1 HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Content-Length: 13 Date: Sat, 17 Dec 2016 04:11:29 GMT /sample/test1
・/sample1/items
→ itemクラスのjson一覧を返す
$ curl -i -H "Accept: application/json" \ > http://localhost:8080/sample1/items HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Sat, 17 Dec 2016 04:11:56 GMT [{"name":"hoeg1","date":"20131122"},{"name":"hoeg2","date":"20131130"},{"name":"hoeg3","date":"20131205"},{"name":"hoeg4","date":"20131210"},{"name":"hoeg5","date":"20131225"},{"name":"hoeg6","date":"20141012"},{"name":"hoeg7","date":"20141014"},{"name":"hoeg8","date":"20141024"}]
・/sample1/item{id}
→ itemクラスのjsonを返す
$ curl -i -H "Accept: application/json" \ > http://localhost:8080/sample1/item/5 HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Sat, 17 Dec 2016 04:12:21 GMT {"name":"hoeg6","date":"20141012"}
Windowsで作成したDLLをJavaより呼び出して使用する
C言語で作成すると
超速いというのは知っていますが、そもそも他の言語で作成できるのかな?
と思ったので簡単なプログラムを作成して試してみる。
DLL作成
ウィザードが立ち上がるのでそのまま次へを選択
アプリケーションの種類を「DLL」を選択
こうなります
SampleDll.cpp
// SampleDll.cpp : DLL アプリケーション用にエクスポートされる関数を定義します。 // #include "stdafx.h" #include "stdio.h" int GetHello(int data) { printf("%d", data); return 0; }
※簡単な処理を作成する
SampleDll.def
LIBRARY SampleDll EXPORTS GetHello @1
※公開設定としてdefファイルを追加して以下を記載する
64bitでビルドし、エクスポート関数を確認する
>"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\dumpbin.exe" /exports SampleDll.dll Microsoft (R) COFF/PE Dumper Version 14.00.24215.1 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file SampleDll.dll File Type: DLL Section contains the following exports for SampleDll.dll 00000000 characteristics 584B9C0D time date stamp Sat Dec 10 15:09:17 2016 0.00 version 1 ordinal base 1 number of functions 1 number of names ordinal hint RVA name 1 0 0001102D GetHello = @ILT+40(?GetHello@@YAHH@Z) Summary 1000 .00cfg 1000 .data 1000 .gfids 1000 .idata 3000 .pdata 3000 .rdata 1000 .reloc 1000 .rsrc 8000 .text 10000 .textbss >
Javaで実装
JNA用のパッケージが必要
GitHub - java-native-access/jna: Java Native Access
上のサイトよりjna.jarをダウンロードしてライブラリとして登録する。
Sample01.java
package sample01; import com.sun.jna.Library; import com.sun.jna.Native; interface HelloLib extends Library { // loadLibrary HelloLib INSTANCE = (HelloLib) Native.loadLibrary("C:/hogehoge/SampleDll.dll", HelloLib.class); // Cの関数名と一致させる int GetHello(int s); } public class Sample01 { public static void main(String[] args) { HelloLib hello = HelloLib.INSTANCE; hello.GetHello(100); } }
↓
100
詰みかけたところ
Windows64bitでJNAでJavaからC++コード呼び出そうとしてハマった話 - きしだのはてな
→ 32bitで作成したのを64bitで実行しようとしてエラーになった(´・ω・`)
java - Invalid memory access - Stack Overflow
→ データ型が良くないので合わせる
参考
http://blue-red.ddo.jp/~ao/wiki/wiki.cgi?page=JNI%A1%A2JNA%A4%CE%BB%C8%A4%A4%CA%FD
JavaからCの処理を呼ぶ方法(JNI/JNA/SWIG) - Qiita
GitHub - java-native-access/jna: Java Native Access
javaでwindowsのdllを利用する | Tech-Sketch
Visual Studio 2015でDLLを作成して動的読み込みしてみる。 - 小さい頃はエラ呼吸
所感
C++はわかりませんけどとりあえずは動かすことができました。
関数を実行することはできたけどクラス作ったやつのアクセスとかできるかもやってみたい。
同じような感じでいいのかな?
JavaでクラスをシリアライズしてDBへ保存
まあ、なんかの役に立つかもしれない
クラスをデータ化して保存する
環境
MySQL
Java8
テーブル定義
mysql> create table obj_data( -> id integer not null auto_increment, -> data blob, -> create_at datetime, -> update_at datetime, -> primary key(id) -> ); Query OK, 0 rows affected (0.02 sec)
対象オブジェクト
package sample_mysql03; import java.io.Serializable; public class Item implements Serializable{ private static final long serialVersionUID = 1L; private String name; private String price; private String remarks; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPrice() { return price; } public void setPrice(String price) { this.price = price; } public String getRemarks() { return remarks; } public void setRemarks(String remarks) { this.remarks = remarks; } }
※Serializableが必要です。
追加処理
package sample_mysql03; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; public class Sample01 { // 接続情報 private static final String URL = "jdbc:mysql://192.168.33.10/myapp?useSSL=false"; private static final String USER = "app"; private static final String PASS = "Password123@"; public static void main(String[] args) throws IOException { System.out.println("start"); try ( Connection con = DriverManager.getConnection(URL, USER, PASS); Statement st = con.createStatement(); ){ // オブジェクト生成 Item item = new Item(); item.setName("hoge"); item.setPrice("hoge"); item.setName("bikoaaaaaaaa"); String sql = null; PreparedStatement pstmt = null; int result; // プリペアードステートメント sql = "insert into obj_data(data, create_at) values(?, ?);"; pstmt = con.prepareStatement(sql); // オブジェクトを出力するストリームをくみ上げる ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(item); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); // ステートメントにパラメータを入れる pstmt.setBinaryStream(1, bais, baos.toByteArray().length); pstmt.setTimestamp(2, new Timestamp(System.currentTimeMillis())); result = pstmt.executeUpdate(); System.out.println("処理結果=" + result); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("end"); } }
↓
mysql> select * from obj_data; +----+-------------------------------------------------------------------------------------------------------------------------+---------------------+-----------+ | id | data | create_at | update_at | +----+-------------------------------------------------------------------------------------------------------------------------+---------------------+-----------+ | 1 | �� sr sample_mysql03.Item L namet Ljava/lang/String;L priceq ~ L remarksq ~ xpt bikoaaaaaaaat hogep | 2016-12-04 16:27:06 | NULL | +----+-------------------------------------------------------------------------------------------------------------------------+---------------------+-----------+ 1 row in set (0.00 sec) mysql>
なんかそれっぽいデータが入っている。
読み込み
package sample_mysql04; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import sample_mysql03.Item; public class Sample02 { // 接続情報 private static final String URL = "jdbc:mysql://192.168.33.10/myapp?useSSL=false"; private static final String USER = "app"; private static final String PASS = "Password123@"; public static void main(String[] args) throws IOException, ClassNotFoundException { System.out.println("start"); try ( Connection con = DriverManager.getConnection(URL, USER, PASS); Statement st = con.createStatement(); ){ String sql = null; PreparedStatement pstmt = null; // プリペアードステートメント sql = "select * from obj_data where id = ?"; pstmt = con.prepareStatement(sql); // パラメータ指定 pstmt.setInt(1 , 2); Item item; // 実行 ResultSet rs = pstmt.executeQuery(); while(rs.next()){ InputStream is = rs.getBinaryStream("data"); ObjectInputStream obis = new ObjectInputStream(is); item = (Item) obis.readObject(); System.out.println(item.getName() + "" + item.getPrice() + "" + item.getRemarks()); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("end"); } }
↓
start name1price222bikobikobiko end
できました(^^)
参考
(・ω・)Serendipity ::: Light in Moment - 14
4. ラージオブジェクト | TECHSCORE(テックスコア)
備忘録的なblog: OracleのBLOBにJavaオブジェクトを保存
わかったこと
- 対象のクラス(オブジェクト)にはSerializableを指定する必要がある。
- パッケージ名も保存されるので他のパッケージの同じ名前のクラスはエラーとなる
- 結構めんどくさい(´・ω・`)
です