Windows上でeclを使ってCommon Lispの関数をC++から実行する
はじめに
Windows上でeclを使ってCommon Lispの関数をC++から実行する方法を紹介します。 eclを使えばC++からCommon Lispを実行することができるため、 エントリポイントはプラットフォームが提供するC++のDSLで実装するしかなさそうなんだけど、 メインの処理はスムーズに開発できるCommon Lispで書きたい、ということが可能になります。 このeclの使い方はlinux限定かなと勝手に思い込んでいたのですが、実はWindowsでも使えるようでした。
実行環境
eclを使うにあたってまずはeclをビルドして環境を構築する必要があります。 今回はCドライブの直下にlispフォルダを作って以下のような構成でeclを実行します。
C:/lisp/ + ecl-build/ // eclのソースコード、ツール + ecl-21.2.1/ // eclのソースコード + yasm-1.3.0-win64.exe + ecl/ // eclの実行ファイル、ランタイム + print-factorial/ // C++から実行するCommon Lispのシステム + print-factorial.lisp + print-factorial.asd + print-factorial-lib/ // Common Lispから生成したlibファイルを置く場所 + print-factorial-entry/ // MSVCのプロジェクト (C++のエントリポイント)
eclのビルドとインストール
参考: https://ecl.common-lisp.dev/static/manual/Building-ECL.html
Visual Studio 2022をインストールする
- 2022でなくてもいいかもしれないですが、少なくともx64 Native Tools Command Promptは必要です
yasm-1.3.0-win64.exeをダウンロードする
- 上述したように
C:/lisp/ecl-build
下にダウンロードします
x64 Native Tools Command Prompt for VS 2022を起動する
- 「スタートメニュー > Visual Studio 2022 > x64 Native Tools Command Prompt for VS 2022」にあります
msvcディレクトリに移動する
cd C:/lisp/ecl-build/ecl-21.2.1/msvc
yasmへのパスを通す
set path=C:\lisp\ecl-build;%path%
ビルドする
nmake ECL_CMP=1 ECL_ASDF=1 ECL_WIN64=1 GMP_TYPE=AMD64
インストールする
nmake install prefix=C:\lisp\ecl
quicklispをダウンロードしてインストールする
- どこにダウンロードしてもいいです
サンプルシステムの作成
C++から実行するシステムを作ります。
;; print-factorial.asd (asdf:defsystem :print-factorial :components ((:file "print-factorial")) :depends-on (:alexandria))
;; print-factorial.lisp (defpackage :print-factorial (:use :cl) (:export :print-factorial)) (in-package :print-factorial) (defun print-factorial (n) (print (alexandria:factorial n)) (force-output))
Common Lispのコードからlibファイルの作成
eclを起動する
- x64 Native Tools Command Prompt for VS 2022を起動します
- このコマンドプロンプトでないと
asdf:make-build
時に「 指定されたファイルが見つかりません。」エラーが出ます。 - おそらく、64bitビルドしたlibファイルを読み込めることができないのだと思われます。
- このコマンドプロンプトでないと
cd c:\lisp\
しますasdf:make-build
をすると一時ファイルが現在のフォルダに作られるため、一時ファイルを問題なく作れるフォルダに移動します
- コマンドプロンプトから
c:\lisp\ecl\ecl
を実行します
libファイルを生成する
- eclのREPLから以下を実行します
;; print-factorialの読み込み (push "C:/lisp/print-factorial/" asdf:*central-registry*) (ql:quickload :print-factorial) ;; asdf:make-buildのための事前準備 (require 'cmp) ;; libファイルの生成 (asdf:make-build :print-factorial :type :static-library :monolithic t :move-here "C:\\lisp\\print-factorial-lib" :init-name "init_print_factorial")
eclのドキュメントにはasdf:make-build
をすればlibファイルが生成されるとありますが、実際はその前に(require 'cmp)
しないとファイルが生成されません。
参考: https://stackoverflow.com/questions/55086558/what-is-the-correct-way-to-compile-a-file-using-embeddable-common-lisp
asdf:make-build
を実行すると以下のようなログが出力され、ファイルが生成されます。
> (asdf:make-build :print-factorial :type :static-library :monolithic t :move-here "C:\\lisp\\print-factorial-lib" :init-name "init_print_factorial") ;;; ;;; Compiling C:/lisp/print-factorial/print-factorial.lisp. ;;; OPTIMIZE levels: Safety=2, Space=0, Speed=3, Debug=0 ;;; ;;; End of Pass 1. ;;; Finished compiling C:/lisp/print-factorial/print-factorial.lisp. ;;; (#P"C:/lisp/print-factorial-lib/print-factorial--all-systems.lib")
MSVCでプロジェクトを作成し、eclで生成したlibファイルと一緒にビルド
MSVCプロジェクトを作成する。
ソリューションエクスプローラー > プロパティで「構成: すべての構成」「プラットフォーム: x64」を選択し、以下のように設定する。
C:\lisp\print-factorial-lib C:\lisp\ecl
- 構成プロパティ > リンカー > 入力 > 追加の依存ファイルに以下を追加
print-factorial--all-systems.lib
以外のファイルはeclフォルダ下にあるlibファイルで、dir /b C:\lisp\ecl\*.lib
で得られます
print-factorial--all-systems.lib asdf.lib cmp.lib deflate.lib defsystem.lib ecl-cdb.lib ecl-curl.lib ecl-help.lib ecl-quicklisp.lib ecl.lib package-locks.lib profile.lib ql-minitar.lib rt.lib sb-bsd-sockets.lib sockets.lib
ソースコードを書く。
#include <ecl/ecl.h> extern "C" { void init_print_factorial(cl_object); } int main() { _wputenv_s(L"ECLDIR", L"C:\\lisp\\ecl\\"); const char* ECL_STRING = "ecl"; char ecl[sizeof(ECL_STRING)]; strncpy_s(ecl, ECL_STRING, sizeof(ecl)); char* argv[] = { ecl }; cl_boot(1, argv); ecl_init_module(NULL, init_print_factorial); cl_funcall(2, cl_eval(c_string_to_object("'print-factorial:print-factorial")), MAKE_FIXNUM(10)); cl_shutdown(); return 0; }
このようにMSVCのプロジェクトを作成してビルドすると、Windowsの実行ファイルが生成されるはずです。
C:\lisp\ecl
フォルダに移動して (ecl.dllを読み込ませるため) 実行ファイルを実行すると、無事結果が表示されます。
c:\lisp\ecl> C:\lisp\print-factorial-entry\x64\Debug\print-factorial-entry.exe 3628800
おわりに
Windows上でeclを使ってCommon Lispの関数をC++から実行する方法を紹介しました。 Common LispのコードとWindowsの関数とを組み合わせることができるので、いろいろできることが広がりそうです。