CORBAでJavaとCommon Lispの連携!
はじめに
相変わらずCommon LispのベースシステムにJavaの遺産を数多く使いたい年頃でして,ABCL(Armed Bear Common Lisp)というJVM上で動作するCommon Lisp処理系を使って開発しています.
ある程度基本的な部分はこれで実現できているのですが,やはりABCLはまだ発展途上であり,現代の有効なライブラリを完全に使えるわけではないようです.Quicklispから読んで使用することができないライブラリが多数あります.(2017年10月26日現在)
これを解決するために,ABCLだけでなく,CCLやSBCLなどのJVM上に実装されているわけではないCL処理系からもJavaの昨日を呼び出せる仕組みを作ろうと思いました.
解決に向けて
この目的のために選択肢はいくつかあって,まさにこれを目指したライブラリもあるにはあるようです.(http://www.cliki.net/Java)
はじめに試してみたのが,CL+Jです.
これは一番ありえる選択肢でした.他言語との連携のためにJavaが提供しているネイティブインターフェース(JNI:Java Native Interface)を介して連携するものです.Common LispのFFIライブラリであるCFFIを中で用いて実現しており,実際にCLでJavaのコードを書けるということでとても直感的だと感じました.
しかも,最も最近のバージョンは0.4で,5年ぶりではありますが,半年ほど前(2017年の1月)に更新されています.
しかし,MKCL(ManKai CL)をメインのテスト環境においているようで,CCLやSBCLでは正しく動作しません.
一度ほかを試すことにしました.
JACOLもCL+Jと同様のアプローチを取っているようですが,こちらの最終更新は2002年と,少し心配が残ります.実際につかてみると案の定使えません.ほかも見ます.
Jathaは,ComomnLisp.netにはあるのですが,実物がどこにあるかわかりません.
Foilにも可能性を感じましたが,CCLでもSBCLでも正しく動作しませんでした.
LispworksもAllegro CLも完全に独自実装に走っているようです.
ということで調べた限りでは全滅です.なぜでしょう.JavaとCLの連携をする人はいないのでしょうか?使う人は処理系を固定する使い方をしている? しかも,現存するライブラリはすべてSourceForgeにアップされています.
QuicklispにないとRoswellに頼っている現代の若者な自分はめんどくささを感じてしまうのです. なにかJavaのライセンスとかと関係しているんでしょうか?Javaの不確定な仕様にCLをあわせていくつもりがないだけでしょうか?
なにはともあれ,自分は今何か動くものが必要なので,解決策を探しました.
おそらくCL+Jを修正するのが最良の手かもしれませんが,何をするにしてもそれなりに時間が書かてしまうことは明らかです.
そんなとき,ふとCORBAを見つけました.これでさえも実装が簡単になったりはしませんが,なんとなく分散オブジェクト指向という響きに惹かれてしまい,勉強がてら,これを試してみることにしました.
CORBAとは
ベテランのエンジニア方にとっては常識であり,今更使用するのは非常識であると思われる方もいるのかもしれませんが,CORBAというものの紹介を簡単にしてみます.
CORBAとは,Common Object Request Broker Architectureの略で,要は,共通のインターフェースを介することで様々なプログラミング言語のコンポーネント同士の相互のやり取りを実現する技術です.
Common Object Request Broker Architecture - Wikipedia
CORBAは,OMG(Object Management Group)という,UMLなどの標準化をしたりしている,オブジェクト指向に関する何かの団体によって定義されています.
CORBAの仕様の最も新しいバージョンである3.1が確立されたのが2010年の1月のようで,2017年現在で,すでに7年が経過した,とても枯れた(枯れ果てた?)技術みたいです.
About the Common Object Request Broker Architecture Specification Version 3.3
ドキュメントはまるでありませんが,強いてあげるなら以下のページあたりが参考になります.
絶版のようですが,研究室にあった
この本は分かりやすかったです.
短く言うと,「起動しているオブジェクトサーバー」に対して「クライアント」から「同一のインターフェースを介して」,「オブジェクトを求めるRequestを発信」するような仕組みになっています.
とても手探りながら,牛の歩みではありますが,進めてみました.
CORBAとCommon Lispの連携
IDLの準備
今回は,CORBAで,JavaのシンプルなHelloWorldメソッドを,Common Lispから呼び出し操作する,というのをやってみたいと思います.
まずは,JavaでもCLでも共通するインターフェースをCORBAが提供するインターフェース定義言語(IDL:Interface Definition Language)で定義します.
Hello.idlという名前で以下のようなファイルを定義します.
module HelloApp { interface Hello { string sayHello(); oneway void shutdown(); }; };
インターフェースとしては,HelloAppというプロジェクトの中に,Helloというインターフェースがあり,String型のsayHelloとVoid型のshutdownメソッドがあるという形です.
今回はこれをサーバープログラムとして,sayHelloを呼び出すと,Java側でHelloWorldが実行されるような形式にします.
Java用のプログラムの生成
上記のIDLのコード例と,本章のJavaのコードは,Oracleのページからそのまま用いています.
JavaはCORBA用のツールを用意しています.以下のコマンドでIDLを元にしたJavaのソースファイルが生成されます.
? idlj -fall Hello.idl
見てみると,同じディレクトリにHelloAppというプロジェクトができていることがわかります. Java用の雛形ができました.
Javaのサーバープログラムの記述
そしてサーバーとなるJavaのプログラムを書きます.
HelloServer.javaという名前で以下のファイルを作ります.
import HelloApp.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; import org.omg.CORBA.*; import org.omg.PortableServer.*; import org.omg.PortableServer.POA; import java.util.Properties; class HelloImpl extends HelloPOA { private ORB orb; public void setORB(ORB orb_val) { orb = orb_val; } // implement sayHello() method public String sayHello() { return "\nHello world !!\n"; } // implement shutdown() method public void shutdown() { orb.shutdown(false); } } public class HelloServer { public static void main(String args[]) { try{ // create and initialize the ORB ORB orb = ORB.init(args, null); // get reference to rootpoa & activate the POAManager POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA")); rootpoa.the_POAManager().activate(); // create servant and register it with the ORB HelloImpl helloImpl = new HelloImpl(); helloImpl.setORB(orb); // get object reference from the servant org.omg.CORBA.Object ref = rootpoa.servant_to_reference(helloImpl); Hello href = HelloHelper.narrow(ref); // get the root naming context org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); // Use NamingContextExt which is part of the Interoperable // Naming Service (INS) specification. NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef); // bind the Object Reference in Naming String name = "Hello"; NameComponent path[] = ncRef.to_name( name ); ncRef.rebind(path, href); System.out.println("HelloServer ready and waiting ..."); // wait for invocations from clients orb.run(); } catch (Exception e) { System.err.println("ERROR: " + e); e.printStackTrace(System.out); } System.out.println("HelloServer Exiting ..."); } }
後はコンパイルするだけです.
? javac --add-modules java.corba HelloServer.java HelloApp/*.java -Xlint:deprecation -Xlint:unchecked
※Java9からCORBAのライブラリがJavaの標準から消えているので,パスを明示しないと上手くコンパイルできません.CORBAは現時点ではまだ--add-modulesオプションで指定すれば入りますが,コンパイルするのに警告が出るので,それに従ってオプションを付けておきます.
※Java8までは,後半のオプションだけでいけました.
これでJava側のプログラムの準備ができました.
CLORBの準備
CommonLispのためのCORBAライブラリとしては,CLORBというものがありました.
最終更新日は2013年の4月です.まぁ,他に比べれば新しい方ではないですかね.SourceForgeで公開されているのでダウンロードして使います.
中身を見るときちんとASDFに対応しています.
QuicklispからPATHが見えるところに落とします.
僕の場合は,ソースコードはDropbox内において,~/.roswell/local-projects/下にASDファイルのあるディレクトへのシンボリックリンクを貼るようにして使っています.
? ln -nfs /path/to/clorb ~/.roswell/local-projects/clorb
こんな感じですね.念のため.
これでQuickloadできます.
残念ながらCCLでは十分には動かないみたいです...結局ダメですね.
まぁ,SBCLでは動きますし,せっかくなので使ってみます.場合によっては自分で書き換えようと思います.
CL-USER> (ql:quickload :clorb) To load "clorb": Load 1 ASDF system: clorb ; Loading "clorb"
準備が整いました.
では,IDLJと同様に同じインターフェースに基づいた,Lispコードを生成します.
CL-USER> (corba:idl "Hello.idl" :output "Hello.lisp")
2つ目の引数には,先程Javaで使用したものと同じIDLファイルを指定し,:outputキーワド引数に対しては,出力するファイル名を指定します. 実行すると同様の名前のファイルができているはずです.
Lispからの呼び出しにはこのとき生成されたプログラムを用います.
サーバーを起動し,実際に連携
CORBAでは,サーバーとクライアントの間でオブジェクトをやり取りするためのNameServiceを提供しています.
JavaのサーバープログラムはこのNameServiceとしてORB(Object Request Broker)を通して,クライアントからの要求に応答します.
よって,アプリケーションの起動手順としては,ORBを起動し,Javaを起動した後,Common LispのクライアントアプリケーションからJavaオブジェクトにアクセス・操作する,ということになります.
ORBサーバーの実行にはORBコマンドを用いて,ポート番号,ホスト名を伴って起動します. 起動が成功すると特に表示なく待機状態になります.今回はバックグラウンドで動かしましょう.
? orb -PRBInitialPort 1050 -ORBInitialHost localhost &
これで待機状態になります.
完了すれば,Javaで書いたサーバープログラムを起動します. このプログラムに,sayHelloメソッドと,さらにshutdownメソッドという,サーバープロセスを終了させるメソッドがありますので,これらを呼び出すことにします. ターミナルから以下のコマンドを入力すればHelloServerクラスが動き出します.
? java --add-modules java.corba HelloServer -ORBInitialPort 1050 -ORBInitialHost localhost -Xlint:deprecation :Xlint:unchecked
コンパイル時と同様の問題があることを忘れるとハマります.
実行完了すると,今回のサーバープログラムでは
HelloServer ready and waiting ...
という表示が出て,こちらもクライアントからの要求待ちで待機状態になります.
この間にCommon Lispから,以下の手順でサーバーにリクエストを送ります. QuicklispでCLORBを読み込んでいる前提とします.
CL-USER> (load "/path/to/Hello.lisp") ;; IDLから生成したLispプログラムを読み込む T CL-USER> (corba:orb_init '("-ORBPort" "1050" "-ORBInitRef" "NameService=corbaloc::localhost:1050/NameService")) ;; ORBを初期化します #<NET.CDDR.CLORB::CLORB-ORB {1002902B53}> NIL CL-USRE> (defvar *hello-interface* (corba:resolve "Hello")) ;; インターフェース参照を取得 ;;; connect to 192.168.0.34:52848 = #<FD-STREAM for "socket 192.168.0.34:52874, peer: 192.168.0.34:52848" {1004C1B623}> CL-USER> (op:sayhello *hello-interface*) ;; 参照先のsayhelloメソッドを実行する "Hello world !!"
というわけで,JavaのHelloインターフェースに含まれているsayHelloメソッドの実行結果をCommon Lispで取得することができました.
こんな感じで,別言語間でオブジェクトの通信ができることは確認できました.
おわりに
CORBAを介してJavaとCommon Lispのオブジェクトを共有し,連携する手法の基本中の基本的な部分を紹介しましたが,日本語の,いや英語でさえも,これをやっている文献は全くないので,(これをする人はだれもいないかもしれませんが)もし万が一にもやろうと思う人がいたら,少しは参考になるのではないでしょうか?
今回は,Webサーバーとして動作し,リクエストに応じてプログラムを発火させる手法で事足りるものでしたが,CORBAではオブジェクト自体を受け渡すことができますので,CL側で受け取った元Javaのオブジェクトをこねこねしてなんとかするといった処理もしたいと思います.
CORBA自体が,正直使うのが結構大変ですし,普通にハマりまくりました.
まぁ,また気が向いたら,自分が今作っているアプリケーションと合わせて,簡単なアプリケーション構築について書きたいと思.......いますかねぇ?笑
もしJava+Common Lispに詳しい方,CORBAに精通されている方,そうでもないけどおかしいことがわかる方などがいらっしゃいましたら,ぜひまさかり投げてください.
長くなってしまいました,すみません.では.