c++ - 从 LISP 中的套接字读取 C++ 结构

标签 c++ serialization lisp common-lisp sbcl

我们有一个应用程序协议(protocol)定义为通过网络传输的 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 中可以使用类似的方法,那就太理想了。如果没有,我愿意做一些管道工程,只要它是可管理的。

编辑:

尝试了 Svante 的建议:

(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对应msg类型,应该是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)

似乎是字节序问题。如何解决这个问题?我找不到任何方法来处理用户库中的字节顺序。

编辑2:

最终放弃了 userial 并编写了这些(遵循 Practical Common 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))

关于c++ - 从 LISP 中的套接字读取 C++ 结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25417353/

相关文章:

java - @ApplicationScoped 必须是可序列化的?

lisp - 在循环中使用 Setf 语法

lisp - 如何修复 Common Lisp REPL 中 <TAB> 触发的自动文档查找的 URL?

scheme - 导入模块的方案语法是什么(尤其是诡计)?

java - 使用 Xstream 是可能的序列化方法吗?

asp.net-core - 使用当前版本的 System.Text.Json.JsonSerializer 序列化 DataSet

c++ - 必须调用对非静态成员函数的引用

c++ - 使用基类指针查找派生类对象的大小

c++ - 如何使用 type_traits 检测字符串文字?

c++ - boost::lexical_cast 从字符串到字符异常