You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用SBCL sb-alien创建Gtk4窗口并实现有效回调?

问题
  • 无法理解SBCL的Foreign Function Interface(FFI)文档,不知道如何在代码中实现GObject的Callback类型
  • 无法将现有Gtk3示例适配到Gtk4,自行编写的代码出现编译错误,急需可运行的Gtk4示例

我的尝试代码

;;; test1

(cl:in-package "CL-USER") ; 已导入SB-ALIEN包

(load-shared-object
 "/usr/lib/x86_64-linux-gnu/libgtk-4.so.1.600.6")

(define-alien-routine gtk_application_new (* t) (app (* t)) (flags (* T)))

(define-alien-routine g_object_unref void  (win (* t)))

(define-alien-routine gtk_application_new (* t) (txt c-string) (flags int))

(define-alien-routine g_application_run int
  (app (* t)) (argc int) (argv (* t)))

(define-alien-routine g_signal_connect long
  (instance (* t))
  (sig c-string)
  ;(cback (function void (* t) (* t))) ; 不知道正确的回调类型
  (alien-funcall  (cback (function void)))
  (data (* t)))

(define-alien-callable app_activate void
    (with-alien ((void))
      (format t "application is activated")))

(with-alien ((app (* t)) (status int))
  (setf app (gtk_application_new "test1.app.gtk" 0))
  (g_signal_connect app "activate"
                    (alien-callable-function 'app_activate)
                    nil)
  (setf status (g_application_run  app 0 nil))
  (g_object_unref app)
  ;; 返回状态
  status)

报错信息

> ;   在计算#P"/home/jacek/Programming/Lisp/lispy-experiments/aliengtk4/test1.lisp"
> 第24行第0列开始的表单时捕获到1个错误:
> 
> 调试器在主线程#<THREAD "main thread" RUNNING {10010A0003}>中触发SB-INT:COMPILED-PROGRAM-ERROR:
>   执行存在编译错误的表单。表单:
>   (SB-ALIEN::ALIEN-LAMBDA VOID
>     (WITH-ALIEN ((VOID))
>       (FORMAT T "application is activated")))
> 编译期错误:
>   在宏展开(SB-ALIEN::ALIEN-LAMBDA VOID
>     (WITH-ALIEN #
>       #))期间出错。可使用*BREAK-ON-SIGNALS*拦截。
> 
>  值 WITH-ALIEN 不是 LIST 类型

Gtk3工作示例

;;; test2

(load-shared-object "/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.2404.29")

;; 排除GTK库可能触发的:divide-by-zero陷阱,默认陷阱为(:OVERFLOW :INVALID :DIVIDE-BY-ZERO)
;; 参考: (sb-int:get-floating-point-modes)
(sb-int:set-floating-point-modes :traps '(:overflow :invalid))


(define-alien-routine gtk_application_window_new (* t) (app (* t)))

(define-alien-routine gtk_application_new (* t) (txt c-string) (flags int))

(define-alien-routine g_application_run int
  (app (* t)) (argc int) (argv (* t)))

(define-alien-routine g_signal_connect_data long
  (instance (* t)) (sig c-string)
  (cback (function void (* t) (* t)))
  (data (* t)) (unusedptr (* t)) (unusedint int))

(define-alien-routine gtk_window_set_title void (win (* t)) (ttl (c-string)))

(define-alien-routine gtk_window_set_default_size void
  (win (* t)) (x int) (y int))

(define-alien-routine gtk_widget_show_all void (win (* t)))

(sb-alien::define-alien-callable mycallback void ((app (* t)) (u (* t)))
                                 (with-alien ((win (* t)))
                                   (setf win (gtk_application_window_new app))
                                   (gtk_window_set_title win "This is window")
                                   (gtk_window_set_default_size win 100 100)
                                   (gtk_widget_show_all win)))


(with-alien ((app (* t)) (status int))
  (setf app (gtk_application_new "org.gtk.example" 0))
  (g_signal_connect_data app "activate" (alien-callable-function  'mycallback) nil nil 0)
  (g_application_run app 0 nil))

(format t "App finished successfully~%")

SBCL版本

sbcl --version
SBCL 2.2.11.160-65fe8bb77

解决方案与可运行Gtk4示例

错误原因分析

  1. define-alien-callable语法错误:错误地在回调函数定义中使用with-alien ((void)),该宏用于声明alien变量,回调的参数列表需直接指定类型,无需包裹with-alien。
  2. g_signal_connect定义错误:GObject的activate信号回调签名应为void (*)(GApplication*, gpointer),对应SBCL FFI的(function void (* t) (* t)),之前的写法完全不符合规范。
  3. 重复定义函数:重复定义了gtk_application_new,第一个定义参数类型错误,应删除。
  4. Gtk4 API变更:Gtk4不再使用gtk_widget_show_all,需用gtk_window_present显示窗口。

可运行的Gtk4示例代码

(cl:in-package "CL-USER")

;; 加载Gtk4库,路径根据你的系统调整
(load-shared-object "/usr/lib/x86_64-linux-gnu/libgtk-4.so.1")

;; 关闭可能由GTK触发的浮点除零陷阱
(sb-int:set-floating-point-modes :traps '(:overflow :invalid))

;; 定义Gtk/GObject核心函数
(define-alien-routine gtk_application_new (* t)
  (application-id c-string)
  (flags int))

(define-alien-routine gtk_application_window_new (* t)
  (application (* t)))

(define-alien-routine g_application_run int
  (application (* t))
  (argc int)
  (argv (* t)))

(define-alien-routine g_object_unref void
  (object (* t)))

(define-alien-routine g_signal_connect long
  (instance (* t))
  (signal c-string)
  (callback (function void (* t) (* t)))
  (data (* t)))

(define-alien-routine gtk_window_set_title void
  (window (* t))
  (title c-string))

(define-alien-routine gtk_window_set_default_size void
  (window (* t))
  (width int)
  (height int))

(define-alien-routine gtk_window_present void
  (window (* t)))

;; 定义activate信号的回调函数
(define-alien-callable app-activate void ((app (* t)) (user-data (* t)))
  (declare (ignore user-data))
  (with-alien ((window (* t)))
    (setf window (gtk_application_window_new app))
    (gtk_window_set_title window "Gtk4 + SBCL FFI")
    (gtk_window_set_default_size window 400 300)
    (gtk_window_present window)))

;; 主程序逻辑
(with-alien ((app (* t)) (status int))
  (setf app (gtk_application_new "org.example.gtk4" 0))
  (g_signal_connect app "activate" (alien-callable-function 'app-activate) nil)
  (setf status (g_application_run app 0 nil))
  (g_object_unref app)
  status)

(format t "应用程序正常退出~%")

代码说明

  • 回调函数app-activate严格遵循GObject信号的参数要求:第一个参数是信号发射实例(GApplication),第二个是用户数据,用declare (ignore user-data)忽略未使用的参数。
  • 使用gtk_window_present替代Gtk3的gtk_widget_show_all,符合Gtk4的显示逻辑。
  • 所有函数定义的参数类型严格对应C API的签名,避免类型不匹配导致的错误。

内容的提问来源于stack exchange,提问作者ruby_object

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 23:50:43