Code Aquarium

minazoko's blog -*- 水底のブログ -*-

Clojure-PyでWin32API

Clojureからネイティブライブラリ

Pythonにはctypesというネイティブライブラリをダイレクトに扱うモジュールがあります。ということはClojure-Pyを使うとClojureからネイティブが呼べるということになりますね。

;;-*- coding:utf-8 -*-
(ns sample-ctypes
  (:require ctypes)
  (:require [clojure.string :as string]))

;; convert to unicode string
(defn u$
  ([s codec] (py/unicode s codec))
  ([s] (u$ s "utf8")))

;; get attribute from python-object
(defmacro pyattr [obj a]
  `(py/getattr ~obj ~(name a)))

;; get attribute thread macro
(defmacro pyattr.. [obj & attrs]
  `(-> ~obj ~@(map (fn [a] `(pyattr ~a)) attrs)))

;; Sample user32 MessageBoxW
(let [title (u$ "【サンプル CLojure-Py】")
      message (u$ (string/join "\n\n"
                               ["clojure-py からの〜"
                                "ctypes.windll からの〜"
                                "MessageBoxW を表示"]))
      message-box (pyattr.. ctypes/windll user32 MessageBoxW)]
  (message-box 0 message title 0x40))

Python2.7で試しました。

Pythonオブジェクトのメンバへのアクセス

Clojure-PyのドキュメントではJVMと同様に

(.attrname obj)

でオブジェクトのメンバにアクセスできると書いてありますが何故かうまくいきませんでした 書き方が違いました、最後に追記しておきます。

(py/getattr obj "attrname")

でメンバにアクセスできることが分かりました。*1
したがってMessageBoxを使うには。

(let [user32 (py/getattr ctypes/windll "user32")
      message-box (py/getattr user32 "MessageBoxW")]
  (message-box ...))

のようにする必要がありますが、面倒なのでスレッドマクロにしています。

Clojureの新しいメンバ参照方式(追記)

TLで教えてもらいましたが、Clojure族はこれまでメンバ変数もメソッドも . で参照していましたが、メンバ変数については .- という新しい記法が導入されているそうです。というかReadMe.mdに書いてありました。*2

(.user32 ctypes/windll)

はダメでしたが、

(.-user32 ctypes/windll)

なら行けました。これならユーティリティマクロなど書かなくても。

(let [message-box (-> ctypes/windll
                      .-user32
                      .-MessageBoxW)]
  (message-box 0 (u$ "本文") (u$ "タイトル") 0x40))

あるいは少し強引に

(-> ctypes/windll
    .-user32
    .-MessageBoxW
    (apply [0 (u$ "本文") (u$ "タイトル") 0x40]))

と書けますね。スッキリした。

*1:Clojure-PyからPythonの組み込み関数を呼ぶ場合デフォルトで参照されているpy名前空間を使います。

*2:.-__name__ なんてアンダーバーだらけで見逃しちゃったじゃないですかぁ〜(言い訳)