У нас есть протокол приложения, определенный как классы C ++, которые передаются по сети. Я хочу подключиться к серверу, который отправляет данные в этом формате. Я хочу написать клиент на lisp (предпочтительно sbcl) для связи с этим сервером. Я бы предпочел, чтобы он был написан на чистом lisp вместо использования CFFI, чтобы обернуть вокруг C ++ dll. Пример структуры будет выглядеть примерно так:
class Header
{
public:
int MsgType;
uint64_t Length;
}
class SampleMsg
{
public:
Header MsgHeader;
char Field1[256];
bool Field2;
double Field3;
SomeOtherClass Field4;
}
Я хочу знать, как отобразить эти структуры в lisp, чтобы они были двоично-совместимыми, и как читать / записывать такие структуры. Есть ли более простой способ, чем упаковка / распаковка каждого поля в структуре?
Например, в C # вы можете отобразить двоичную структуру следующим образом и прочитать ее непосредственно из байтового массива:
[StructLayout(LayoutKind.Sequential)]
public struct Header
{
public int MsgType;
public ulong Length;
}
[StructLayout(LayoutKind.Sequential)]
public struct SampleMsg
{
public:
public Header MsgHeader;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string Field1;
public bool Field2;
public double Field3;
public SomeOtherClass Field4;
}
Если подобный метод возможен в lisp, он будет идеальным. Если нет, я хочу сделать некоторые сантехники, пока это можно сделать.
РЕДАКТИРОВАТЬ:
Попробовал предложение Сванте:
(ql:quickload "userial")
(in-package :sb-bsd-sockets)
(defun read-buffer (host port)
(let ((socket (make-instance 'inet-socket :type :stream :protocol :tcp)))
(socket-connect socket host port)
(let ((buf (socket-receive socket nil 1024 :element-type '(unsigned-byte 8))))
(socket-close socket)
buf)))(defstruct header
msg-type
length)(userial:make-slot-serializer (:header header (make-header))
:int64 msg-type
:uint64 length)
(defvar *buffer*)
(defvar *b*)
(setq *buffer* (read-buffer #(10 1 2 75) 5003))
(setq *b* (make-array 2048 :element-type '(unsigned-byte 8) :fill-pointer 0 :adjustable t))
(map 'vector #'(lambda (x) (vector-push x *b*)) *buffer*)
(setf (fill-pointer *b*) 0)
С этой точки зрения, *b*
содержит что-то вроде этого: #(7 0 0 0 0 0 0 0 176 2 0 0 0 0 0 0 45 71 253 83 0 0 0 0 165 30 11 11 0 0 0 ...)
, Первые 7 соответствуют типу сообщения, который должен быть 7. Длина должна быть 688 (176 + 2 * 256).
Сейчас делаю
(userial:with-buffer *b* (userial:unserialize :header))
, Это дает мне
#S(HEADER :MSG-TYPE 504403158265495552 :LENGTH 12682699500628738048)
#(7 0 0 0 0 0 0 0 176 2 0 0 0 0 0 0)
Похоже, проблема порядка байтов. Как это исправить? Я не могу найти какой-либо способ справиться с порядком байтов в userial lib.
EDIT2:
Наконец, в конечном итоге отказался от использования и написания этих (следующий практический общий Lisp книга):
(defun read-64 (buf)
(let ((u 0))
(setf (ldb (byte 8 56) u) (aref buf 7))
(setf (ldb (byte 8 48) u) (aref buf 6))
(setf (ldb (byte 8 40) u) (aref buf 5))
(setf (ldb (byte 8 32) u) (aref buf 4))
(setf (ldb (byte 8 24) u) (aref buf 3))
(setf (ldb (byte 8 16) u) (aref buf 2))
(setf (ldb (byte 8 8) u) (aref buf 1))
(setf (ldb (byte 8 0) u) (aref buf 0))
u))
(defun read-32 (buf)
(let ((u 0))
(setf (ldb (byte 8 24) u) (aref buf 3))
(setf (ldb (byte 8 16) u) (aref buf 2))
(setf (ldb (byte 8 8) u) (aref buf 1))
(setf (ldb (byte 8 0) u) (aref buf 0))
u))
(defun read-16 (buf)
(let ((u 0))
(setf (ldb (byte 8 8) u) (aref buf 1))
(setf (ldb (byte 8 0) u) (aref buf 0))
u))
Теперь я могу написать (read-uint64 (subseq *buffer* 8 16))
чтобы получить длину сообщения. Спасибо за помощь.
Вы могли бы использовать userial
, доступный от Quicklisp.
Тем не менее, я бы очень искал способ устранить необходимость синхронизации двух мест определения (одно на C ++, одно на стороне Lisp).
Редактировать: Вот что я имел в виду. Я только сделал несколько очень мелких испытаний, поэтому никаких гарантий. В частности, я не тестировал с выходом C ++, и вам, скорее всего, придется многое отрегулировать для выравнивания.
(defstruct header
msg-type
length)
;; Msg-type might be best handled with an enum unserializer:
;; (make-enum-unserializer :msg-type (:foo :bar)), but I don't know
;; what your values are.
(defstruct sample-msg
msg-header
field-1
field-2
field-3
field-4)
;; You might need to use a different serializer for msg-type for
;; alignment.
(make-slot-serializer (:header header (make-header))
:int msg-type
:uint64 length)
(make-vector-serializer :vector-256-char :uint8 256)
;; I have no idea how a boolean is serialized and aligned on the C++
;; side, so I'll just use :boolean for field-3 here as a first
;; attempt.
(make-slot-serializer (:sample-msg sample-msg (make-sample-msg))
:header msg-header
:vector-256-char field-1
:boolean field-2
:float64 field-3
:some-other field-4)
;; You can serialize and unserialize now:
(serialize :sample-msg some-sample-msg)
(rewind-buffer)
(unserialize :sample-msg)
;; Userial operates on an adjustable vector with fill-pointer in the
;; special variable *buffer*, so you'll need to fill that with content
;; from wherever you read that from.
(with-buffer (read-my-content)
(unserialize :sample-msg))