Lisp/基本からさらに一歩進んで/ファイルとディレクトリ/C struct
Lisp でのC言語構造体の操作
編集概要
編集C言語や C++ などで作成された 構造体
を処理するには、バッファーに構造体をロードして、それぞれのフィールドを抽出することが必要になります。同様に、そのような構造体を送信する場合も、バッファーの作成を伴うことになります。
Lisp では文字列や文字を扱うときにいくつかのタイプの変換が必要となります。
C構造体とデータをやり取りするための、ここでの好ましいやり方は '(unsigned-byte 8)
を使用することです。
一方で、文字の配列は C言語 のような他の言語や、あるいは事実上の初期設定である ASCII文字コード の利用を許可することでより簡単になるでしょうが、これは ANSI Common Lisp では間違ったやり方です。
ANSI の特徴は文字が ASCII や ISO-8859-1 や似たような文字コードを含むことを保証しないことです。実際、 ANSI では、たとえ最新の実装が Unicode をサポートしようが、文字コード 96
[1] しか保証しません。
同様に、ファイルとディレクトリについての助言ですが、読み書きは単純に符号なしバイトのベクトルで行うのが良いでしょう。
Lisp プログラムでは、実際の文字列を用いるか、読み込んだ後に必要なときにだけ変換する文字を用いる必要があるでしょう。
読み込み
編集(defun read-c-file (&optional (file-path "data.struct") (max-length 48))
(with-open-file (stream (merge-pathnames file-path)
:element-type '(unsigned-byte 8)
:direction :input)
(let ((buffer (make-array max-length
:element-type '(unsigned-byte 8)
:fill-pointer t)))
(let ((actual-length (read-sequence buffer stream
:end max-length)))
(setf (fill-pointer buffer) actual-length)
(format t "received=~a max=~a buffer=~s~%" actual-length max-length buffer))
buffer)))
配列に fill-pointer
を使用することは任意ですが、受け取った実際の長さは読み込もうとしたものとは違うでしょうから、それまでの手順を役立てるためには推奨します。
書き込み
編集(defun write-C-file (buffer &optional length (file-path "data.struct"))
(unless length
(setf length (length buffer)))
(with-open-file (stream (merge-pathnames file-path)
:element-type '(unsigned-byte 8)
:direction :output
:if-exists :rename)
(let ((written (length (write-sequence buffer stream))))
(format t "wrote=~a bytes buffer=~s~%" written buffer)))
buffer)
処理
編集'(unsigned-bytte 8)
の要素のベクトルを処理するとき、 C構造体の同様のそれぞれのフィールドを、必要に応じてバイトオフセットに基づいたものに変換します。(make-array
で作成されたものですが、実際に配列ではなくベクトルだということに注意してください。この区別は、1次元だけの配列かどうかによります。)
文字列 を生のバイトから抜き出す。
(map 'string #'code-char
(subseq buffer *start-index* *end-index*))
一つのバイトだけを取り出す。
(subseq buffer *state-index* (1+ *state-index*))
当然、上の二つの例でも、結果の値を代入したくなることがあるでしょう。
話は変わりますが、生のバイトのバッファーに代入するには以下のようにします。
(setf (elt buffer *magic-number-index*) (logand #xFF *preamble-value*))
代入するものを保護することは重要なことです。logand
のようなビットマスクがこのような場合に役立ちます。
1バイト以上のもののためには、 sequence2 を挿入することで subsequence1 の部分集合となります。以下のように使うことができます。
(replace sequence1 (map 'vector #'char-code sequence2)
:start1 a :end1 b)
あるいは以下の例も参考になるでしょう。
(replace buffer (map '(vector '(unsigned-byte 8)) #'char-code string-text)
:start1 *start-index* :end1 *end-index*)
下で作成する補助関数も作成されたものから中間ベクトルを回避します。
補助関数
編集(defun map-replace (fn sequence1 sequence2 &key (start1 0) end1 (start2 0) end2)
"最初のシーケンスに関数を適用した後に、二番目のシーケンスと最初のシーケンスを変更します。
それぞれの要素を順番どおり処理します。
結果は下の例と等しいですが、中間ベクトルを作るものではありません。
:(replace sequence1 (map 'vector #'char-code sequence2) :start1 start1 :end1 end1)
詳しくは次のURLも参照してください。
:http://common-lisp.net/project/trivial-utf-8
副作用として、シーケンス1はシーケンス2が有効な nil でなければ変更されます。
全ての変更が終わった後でシーケンス1が返り値となります。
"
(loop
for i upfrom start1 below (or end1 (length sequence1))
and j upfrom start2 below (or end2 (length sequence2))
do (setf (elt sequence1 i) (funcall fn (elt sequence2 j))))
sequence1)
(defun network-bytes-to-number (buffer start-index total-bits)
"符号なしバイトの、ネットワークバイトオーダのシーケンスを数値に変換する。"
(unless (= (mod total-bits 8) 0)
(error "Please specify total-bits as total for multiples of eight bit bytes"))
(let ((value 0))
(loop for i downfrom (- total-bits 8) downto 0 by 8
for cursor upfrom start-index
do (setf value (dpb (elt buffer cursor)
(byte 8 i) value))
(format t "buffer[~d]==#x~2X; shift<< ~d bits; value=~d~%"
cursor (elt buffer cursor) i value))
value))
(defun number-to-network-bytes (number total-bits &optional buffer (start-index 0))
"数値を、符号なしバイト文字のネットワークバイトのシーケンスに変換する。"
(unless (= (mod total-bits 8) 0)
(error "Please specify total-bits as total for multiples of eight bit bytes"))
(unless buffer
(setf buffer (make-array (/ total-bits 8) :element-type '(unsigned-byte 8))))
(loop for i downfrom (- total-bits 8) downto 0 by 8
for cursor upfrom start-index
do (setf (elt buffer cursor) (ldb (byte 8 i) number))
(let ((value (ldb (byte 8 i) number)))
(format t "number=~d: shift>> ~d bits; value=~d #x~2X; buffer[~d]==#x~2X~%"
number i value value cursor (elt buffer cursor))))
buffer)
時間とエポック
編集他の言語から時間の値を変換するときは、一つのオペレーティングシステムを使用していても、エポックタイム(意味的には 0
の値)が異なるかもしれないことに気をつけましょう。 ANSI Common Lisp ではUTCの1900年1月1日の真夜中午前0時が 値0 になるのに対して、 Unix や他の多くの C言語 ライブラリでは1970年の1月1日を 値0 としています。この二つを変換するのは単純な計算になります。