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++のエントリポイント)

lispフォルダ

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/C++ > 全般 > 追加のインクルード ディレクトリにC:\lisp\eclを追加

  • 構成プロパティ > リンカー > 全般 > 追加のライブラリ ディレクトリに以下を追加

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の関数とを組み合わせることができるので、いろいろできることが広がりそうです。