Clozure CL中文版012:外部函数接口

  • 0

Clozure CL中文版012:外部函数接口

Category:帮助手册 Tags : 

外部函数接口

Clozure CL可以调用使用平台C ABI的外部代码。无法直接调用C ++函数。用于执行此操作的所有机制最终都基于函数%ff-call,但 #_读取器宏和external-call宏通常更方便且更易于使用。

还有一种机制可以让外部代码调用lisp。当您想要将lisp函数作为回调函数传递时,这非常有用。(见 defcallback。)

简要例子

在详细讨论FFI设施之前,可以使用一些示例。

系统C库中的大多数函数都包含在Clozure CL附带的接口数据库中。读者宏#_ 查询该数据库以获取有关foriegn代码的信息,并使外部函数易于使用。

? (#_getpid)2845? (#_log2 2048d0)11.0D0? (with-encoded-cstrs :ascii ((s “Yow!”))    (#_write 1 s 4))

这些相同的调用可以使用稍低级别的调用 external-call

? (external-call “getpid” :pid_t)2845? (external-call “log2” :double 2048d0 :double)11.0D0? (with-encoded-cstrs :ascii ((s “Yow!”))    (external-call “write” :int 1 :address s :size_t 4 :ssize_t))

请注意,有external-call必要指定参数的外部类型和返回值。所述#_ 读取器宏使用接口数据库,以确定该函数getpid(例如)返回一个pid_t值,因此,它是没有必要明确指定。

最后,为了吓唬宠物和小孩,这是一个相当复杂的例子(改编自手册页getpwuid_r):

? (let ((bufsize (#_sysconf #$_SC_GETPW_R_SIZE_MAX)))    (when (= bufsize -1)      (error “sysconf failed”))    (%stack-block ((buf bufsize))      (rlet ((pw (:struct :passwd))            (result :address))        (let ((err (#_getpwuid_r (#_getuid) pw buf bufsize result)))         (when (or (not (zerop err))                  (%null-ptr-p (%get-ptr result)))           (error “getpwuid_r failed”))         (let ((cname (pref pw :passwd.pw_name))               (gid (pref pw :passwd.pw_gid)))           (format t “~&user ~s gid is ~d” (get-encoded-string :ascii                                                        cname                                                       (#_strlen cname))                gid))))))

指定和使用外来类型

Clozure CL为定义和指定外来数据类型提供了相当丰富的语言(该语言源自CMUCL的“外来类型”系统。)

实际上,大多数外来类型定义通过其接口数据库引入Clozure CL,但也可以交互地和/或以编程方式定义外部类型。

Clozure CL的外来类型系统正在“发展”(一个不太完整的礼貌用语):例如,存在一些涉及包使用的不一致。外部类型说明符中使用的符号应该 是关键字,但并不总是强制执行此约定。

外部类型,记录和字段名称区分大小写; Clozure CL使用一些转义约定来允许使用关键字来表示这些名称。

输入注释

从版本1.2开始,Clozure CL支持在Mac OS X上注释外部指针的类型。创建指向外部内存的指针的表单 – 即,MACPTR在MACPTR对象中存储一个类型注释,用于标识指向的对象的外部类型。调用PRINT-OBJECT在MACPTR 尝试打印有关鉴定国外类型的信息,包括是否有人在堆或堆栈中分配,而无论是预定由垃圾收集器自动回收。

对类型注释的支持尚未完成。特别是,一些应用PREF 和SLOT-VALUE做OT尚未采取型注释考虑,也不做DESCRIBE 和INSPECT。

外来类型作为类

某些类型的外部指针利用对类型注释的支持,并且这些类型的指针可以被视为已知类的实例。具体来说,指向a的指针:<NSR>ect被识别为内置类的实例,指向NS:NS-RECTa的指针<NSS>ize被视为实例NS:NS-SIZE,指向a的指针<NSP>oint被识别为实例NS:NS-POINT,指向a的指针<NSR>ange被识别为实例的NS:NS-RANGE。

一些更模糊的结构类型也支持这种机制,未来的版本可能支持类似类型映射的用户定义。

这种对类型的外部类型的支持为每种受支持的类型提供了以下便利:

  • PRINT-OBJECT定义了一种方法
  • 创建外部类型名称并将其视为相应类型的别名。例如,名称:NS-RECT是与之对应的类型的名称NS:NS-RECT,您可以:NS-RECT在rlet表单中使用类型指示符来指定类型的结构NS-RECT。
  • 该类被集成到类型系统中,因此可以 (TYPEP R ‘NS:NS-RECT)公平有效地实现。
  • 内联访问器和SETF反转器是为结构类型的字段定义的。在的情况下<NSR*gt;ect,例如,所讨论的字段是嵌入点和大小的字段,因此NS:NS-RECT-X,NS:NS-RECT-Y,NS:NS-RECT-WIDTH,NS-RECT-HEIGHT和SETF逆被定义。访问器和setter函数可以检查它们的参数,并且setter将强制处理到CGFLOAT适用的适当类型。
  • 定义初始化函数; 例如,

(NS:INIT-NS-SIZE s w h)

大致相当于

(SETF (NS:NS-SIZE-WIDTH s) w      (NS:NS-SIZE-HEIGHT s) h)

但可能会更有效率。

  • 创建函数定义; 例如

(ns:ns-make-point x y)

在功能上等同于

(LET ((P (MAKE-GCABLE-RECORD :NS-POINT)))  (NS:INIT-NS-POINT P X Y)  p)

  • 定义一个宏,就像RLETstack-allocation一个外部记录类型的实例一样,可选地初始化该实例,并执行一个带有绑定到该实例的变量的代码体。

例如,

(ns:with-ns-range (r loc len)  (format t “~& range has location ~s, length ~s”      (ns:ns-range-location r) (ns:ns-range-length r)))

外来类型说明符的语法

  • 一些外来类型是内置的:关键字表示原始的内置类型,例如IEEE-double-float类型(表示为:DOUBLE-FLOAT),与某些符号(CONS,FIXNUM等)定义原始CL类型的方式大致相同。
  • 构造函数如:SIGNED和:UNSIGNED可用于表示有符号和无符号整数子类型(类似于CL类型说明符SIGNED-BYTE和UNSIGNED-BYTE。):SIGNED是(:SIGNED 32)的简写,并且:UNSIGNED是简写(:未签名32)。
  • 其他(也许更复杂)类型的别名可以通过ccl:def-foreign-type定义(类似于cl:deftype或C typedef工具)。type:char在某些平台上定义为(:SIGNED8)的别名,在其他平台上定义为(:UNSIGNED 8)。
  • 构造(:struct 名称)可用于引用命名结构类型; (:联盟 名称)可用于引用命名的联合类型。没有必要枚举结构或联合类型的字段以引用类型。
  • 如果 X 是一个有效的外来类型引用,然后(:* X)表示外来类型“指向 X”。按照惯例,(:* T)表示匿名指针类型,与void *C中的模糊等价。
  • 如果字段列表是列表列表,其每个CAR是外部字段名称(关键字)且其CADR是外部类型说明符,则(:STRUCT) 名称 ,@ fieldlist)是结构类型的定义 名称,和(:UNION 名称 ,@ fieldlist)是union类型的定义 名称。请注意,必须定义结构或联合类型,以便在结构,联合或数组中包含该类型,但仅需要引用结构或联合类型以定义类型别名或指针类型。
  • 如果 X 是一个定义的外来类型,然后是::array X &rest dims)表示外来类型的“数组” X”。虽然:array构造函数允许多个数组维度,但Clozure CL中只支持单维数组(根本没有)。

外部函数调用

概观

Clozure CL提供了许多用于从Lisp代码调用外部函数的构造,所有构造都基于该函数%ff-call。在许多情况下,Clozure CL的接口转换器提供有关外部函数的入口点名称和参数以及返回类型的信息; 这使得能够使用#_阅读器宏(如下所述),它通常比其他结构更好使用。

Clozure CL还提供了一种定义机制 回调:可以从外部代码调用的lisp函数。

没有支持的方法将lisp数据直接传递给外部函数:必须将标量lisp数据强制转换为等效的外部表示,并且必须将lisp数组(特别是字符串)复制到非GCed内存。

类型参数和返回值的指示符

外部函数调用和回调中的外部参数和返回值的类型可以通过以下任何关键字指定:

:UNSIGNED-BYTE

参数/返回值的类型(UNSIGNED-BYTE 8)

:SIGNED-BYTE

参数/返回值的类型为(SIGNED-BYTE 8)

:UNSIGNED-HALFWORD

参数/返回值的类型为(UNSIGNED-BYTE 16)

:SIGNED-HALFWORD

参数/返回值的类型为(SIGNED-BYTE 16)

:UNSIGNED-FULLWORD

参数/返回值的类型为(UNSIGNED-BYTE 32)

:SIGNED-FULLWORD

参数/返回值的类型为(SIGNED-BYTE 32)

:UNSIGNED-DOUBLEWORD

参数/返回值的类型为(UNSIGNED-BYTE 64)

:SIGNED-DOUBLEWORD

参数/返回值的类型为(SIGNED-BYTE 64)

:SINGLE-FLOAT

参数/返回值的类型为SINGLE-FLOAT

:DOUBLE-FLOAT

参数/返回值的类型为DOUBLE-FLOAT

:ADDRESS

参数/返回值是MACPTR

:VOID

或NIL无效作为参数类型说明符; 指定没有有意义的返回值

在某些平台上,一个小的正整数 ñ也可以用作参数说明符; 它表示相应的参数是指向的指针ñ-word结构或联合,应该通过值传递给外部函数。究竟哪些外部结构按值传递,以及如何非常依赖于平台的应用程序二进制接口(ABI); 除非你非常熟悉ABI的细节(其中一些是非常巴洛克式的),否则通常更容易让更高级别的构造处理这些细节。

外部入口点和命名外部入口点

PowerPC机器指令始终在32位边界上对齐,因此外部函数的第一个指令(入口点)的两个最低有效位始终为0. Clozure CL通常将入口点地址表示为二进制等效于它的fixnum。入口点地址:如果Ë 是一个入口点地址,表示为带符号的32位整数,然后(灰 Ë -2)是该地址的等效fixnum表示。入口点地址也可以封装在MACPTR中,但效率稍差。

尽管可以使用fixnums或macptrs来表示入口点地址,但这样做有点麻烦。Clozure CL可以在CCL类型的结构类对象中缓存命名外部函数的地址:EXTERNAL-ENTRY-POINT(有时缩写为EEP)。通过使用LOAD-TIME-VALUE,编译的lisp函数能够将EEP作为常量引用; 使用间接允许Clozure CL运行时系统确保EEP的地址是最新的和正确的。

C结构的返回约定

在某些平台上,定义为返回结构的C函数通过引用来实现:它们实际上接受类型为“指向返回的struct / union的指针”的第一个参数 – 必须由调用者分配 – 并且不返回有意义的值。

究竟 如何定义返回外部结构的C函数是如何依赖于ABI(以及在很多情况下结构/联合的大小和组成)。

引用和使用外部存储器地址

概观

基本

由于各种技术原因,通常不可能在Clozure CL中直接引用任意绝对地址(例如,由C库函数malloc()返回的地址)。在Clozure CL(和MCL)中,需要这样的地址封装在CCL类型的对象中:MACPTR; 人们可以将MACPTR视为一种特殊类型的结构,其唯一目的是提供一种引用基础原始地址的方法。

有时可以方便地模糊MACPTR与其代表的地址之间的区别; 有时需要保持这种区别。重要的是要记住,MACPTR(通常)是一个一流的Lisp对象,与CONS单元的意义相同:当它不再可能引用时,它将被GCed。MACPTR的生命周期通常与其地址所指向的内存块的生命周期无关。

可能很容易问“如何获取macptr封装的地址?”。这个问题的答案是,一个人没有这样做(并且没有办法做到这一点):地址不是一流的对象,也没有办法引用一个。

封装相同地址的两个MACPTR彼此是EQL。

有很多方法可以直接创建MACPTR(并且在这些基元之上构建了相当数量的语法糖。)这些基元将在下面更详细地讨论,但它们包括:

  • 创建具有指定地址的MACPTR,通常通过CCL:%INT-TO-PTR功能。
  • 引用指定为返回地址的外部函数调用(请参阅参考资料)的返回值。
  • 引用指定包含地址的内存位置。

所有这些原始MACPTR创建操作通常由编译器进行开放编码; 它有一个相当好的概念,即低级操作产生MACPTR以及哪些操作使用封装的地址,并且通常会优化在简单表达式中引入中间MACPTR。

使用MACPTR对象封装外部地址的一个后果是(天真地) 每次引用外部地址都会导致分配MACPTR。

考虑如下代码片段:

(defun get-next-event ()  “get the next event from a hypothetical window system”  (loop     (let* ((event (#_get_next_window_system_event))) ; via an FF-CALL       (unless (null-event-p event)         (handle-event event)))))

在编写本文时,每次调用(假设的)外部函数#_get_next_window_system_event都将返回一个新的MACPTR对象。为了论证而忽略了这个代码片段是否展示了一种轮询外部事件的好方法的问题(它没有),不难想象这个循环每秒可以执行几百万次(每个生成几百万个MACPTR)第二。)显然,在许多情况下,天真的方法是不切实际的。

在MACPTR上堆栈分配和破坏性操作。

如果在GET-NEXT-EVENT运行的环境中保持某些条件,即如果保证NULL-EVENT-P和HANDLE-EVENT都没有缓存或以其他方式保留其参数(“事件”指针)-there’d作为天真方法的一些替代品。其中一种方法是使用原始函数%SETF-MACPTR(在下面更详细地描述)来破坏性地修改MACPTR(以改变它封装的地址的值。)GET-NEXT-EVENT示例可以是写作:

(defun get-next-event ()  (let* ((event (%int-to-ptr 0)))     ; create a MACPTR with address 0    (loop       (%setf-macptr event (#_get_next_window_system_event)) ; re-use it       (unless (null-event-p event)         (handle-event event)))))

该版本更为现实:如果循环在外部分配单个MACPTR,则在每次循环迭代时将其地址更改为指向假设事件结构的当前地址。如果每次调用GET-NEXT-EVENT有一百万次循环迭代,我们每次调用的MACPTR分配量要少一百万次; 这听起来像是个好事。

更好的事情是建议编译器绑定到变量事件的初始值(null macptr)具有动态范围(一旦控制离开该变量的绑定范围,该值就不会被引用。)Common Lisp允许我们通过动态范围声明来做出这样的断言; Clozure CL的编译器可以识别所涉及的原始macptr创建操作,并且可以用堆栈分配macptr对象的等效操作替换它。如果我们不担心每次迭代分配macptr的成本(成本很小并且没有隐藏的GC成本),我们可以将绑定移回循环内部:

(defun get-next-event ()  (loop     (let* ((event (%null-ptr))) ; (%NULL-PTR) is shorthand for (%INT-TO-PTR 0)       (declare (dynamic-extent event))       (%setf-macptr event (#_get_next_window_system_event))       (unless (null-event-p event)         (handle-event event)))))

将一个或多个变量绑定到堆栈分配的MACPTR,然后在执行代码体之前破坏性地修改这些MACPTR的习惯用法很常见,Clozure CL提供了处理所有血腥细节的宏(WITH-MACPTRS)。以下版本的GET-NEXT-EVENT在语义上等同于以前的版本,但希望更简洁:

(defun get-next-event ()  (loop     (with-macptrs ((event (#_get_next_window_system_event)))       (unless (null-event-p event)         (handle-event event)))))

堆栈分配的内存(以及堆栈分配的指针)。

通常情况下,外来记忆的块(通过malloc或类似的东西获得)具有明确定义的生命周期(当知道它们不再需要时它们可以安全地被释放并且已知它们不再被引用。)一个常见的习语可能是:

(with-macptrs (p (#_allocate_foreign_memory size))  (unwind-protect       (use-foreign-memory p)    (#_deallocate_foreign_memory p)))

这不是不合理的代码,但由于多种原因它相当昂贵:外部函数调用本身相当昂贵(如UNWIND-PROTECT),并且用于分配和释放外部内存的大多数库例程(如malloc和free之类的东西)都可以本身就很贵。

在上面的惯用代码中,MACPTR P和被分配和释放的内存块都具有动态范围,因此是堆栈分配的良好候选者。Clozure CL提供%STACK-BLOCK宏,它执行一个代码体,其中一个或多个变量绑定到堆栈分配的MACPTR,这些MACPTR封装了堆栈分配的外部存储器块的地址。使用%STACK-BLOCK,惯用代码是:

(%stack-block ((p size))              (use-foreign-memory p))

这比前面提到的版本更有效,更简洁。

%STACK-BLOCK用作RLET等略高级别的东西的基础。(有关RLET的更多信息,请参见FIXTHIS。)

注意事项

读取,写入,分配和释放外来存储器都是潜在的危险操作; 当在Clozure CL中执行这些操作时,与在C或其他低级语言中完成这些操作相比,情况也同样如此。此外,对Lisp对象的破坏性操作是危险的,如果它被滥用(如果违反了DYNAMIC-EXTENT声明),堆栈分配也是危险的。正确使用这里描述的构造和原语是可靠和安全的; 即使稍微不正确地使用这些构造和原语也可能导致Clozure CL崩溃。

外来记忆地址词典

除非另有说明,否则下面提到的所有符号都是从CCL包中导出的。

标量记忆参考

%get-unsigned-byte ptr &optional (offset 0)[功能]

引用并返回有效字节地址处的无符号8位字节,该字节地址是通过向ptr封装的地址添加偏移量而 形成的。

%get-signed-byte ptr &optional (offset 0)[功能]

%get-unsigned-byte上面一样,但是在计算的地址处返回带符号的8位字节。

%get-unsigned-word ptr &optional (offset 0)[功能]

引用并返回有效字节地址处的无符号16位字,该字节地址是通过将偏移量添加到由ptr封装的 地址而形成的。

%get-signed-word ptr &optional (offset 0)[功能]

就像%get-unsigned-word上面,但返回所计算的地址符号的16位字。

%get-unsigned-long ptr &optional (offset 0)[功能]

引用并返回有效字节地址处的无符号32位长字,该字节地址是通过向ptr封装的地址添加偏移量而 形成的。

%get-signed-long ptr &optional (offset 0)[功能]

%get-unsigned-long上面一样,但是在计算的地址处返回带符号的32位长字。

%%get-unsigned-longlong ptr &optional (offset 0)[功能]

引用并返回有效字节地址处的无符号64位长字,该字节地址是通过向ptr封装的地址添加偏移量而 形成的。

%%get-signed-longlong ptr &optional (offset 0)[功能]

%%get-unsigned-longlong上面一样,但是在计算的地址处返回带符号的64位长字。

%get-ptr ptr &optional (offset 0)[功能]

返回一个macptr,封装在有效字节地址处找到的地址,该地址是通过向ptr表示的地址添加偏移量而形成的。

%get-single-float ptr &optional (offset 0)[功能]

返回single-float通过向ptr表示的地址添加offset而形成的有效字节地址处的found。

%get-double-float ptr &optional (offset 0)[功能]

返回double-float通过向ptr表示的地址添加offset而形成的有效字节地址处的found。

上面描述的所有存储器参考基元都可以使用 setf。

%get-bit ptr bit-offset[功能]

引用并返回由ptr封装的地址处的位偏移位。(给定地址的第0位是该地址字节的最高位。)可以与SETF一起使用。

%get-bitfield ptr bit-offset width[功能]

引用并返回一个无符号整数,该整数由从ptr封装的地址中找到的位偏移位 的宽度位组成。结果的最低有效位是setf的值。可以搭配使用。(%get-bit ptr (1- (+ bit-offset width)))

%int-to-ptr int[功能]

创建并返回地址为int的macptr。

%inc-ptr ptr &optional (delta 1)[功能]

创建并返回一个macptr,其地址是ptrdelta的地址。这个成语(%inc-ptr ptr 0) 有时用于复制macptr,也就是说,创建一个新的macptr封装与ptr相同的地址。

%ptr-to-int ptr[功能]

ptr封装的地址作为整数返回。

%null-ptr[宏]

相当于(%int-to-ptr 0)。

+null-ptr+[变量]

这个常量变量包含结果(%null-ptr)。

%null-ptr-p ptr[功能]

如果ptr是封装地址0的macptr,则此函数返回true。nil如果ptr封装了一些其他地址,则返回。

%setf-macptr dest-ptr src-ptr[功能]

导致dest-ptr封装src-ptr执行的相同地址 ,然后返回更新的dest-ptr

%incf-ptr ptr &optional (delta 1)[宏]

通过将delta添加到它封装的地址来破坏性地修改ptr。返回ptr

with-macptrs (var expr* &body body[Macro]

在每个var绑定到堆栈分配的macptr 的环境中执行body,该macptr封装由相应的expr产生的外部地址。返回body返回的值。

%stack-block (var expr* &body body[Macro]

在每个var绑定到堆栈分配的macptr 的环境中执行body,该macptr封装大小为expr bytes 的堆栈分配区域的地址。返回身体返回的任何值。

make-cstring string[功能]

分配malloc长度的外部存储器(通过)块。然后它将字符串复制到此块并附加一个尾随的nul字节。将macptr返回到块。(1+ (length string))

with-encoded-cstrs encoding-namevar string* &body body[Macro]

在每个var绑定到macptr 的环境中执行body,该macptr封装堆栈分配区域的%地址,其中每个字符串(以及尾随的NUL字符)已被复制到该区域中。返回身体返回的任何值。

编码名称应该是关键字名称的字符编码。每个外部字符串都根据指定的编码进行编码。每个外部字符串都有动态范围。

请注意,with-encoded-cstrs不会自动将字节顺序标记添加到编码字符串中; 另外,终止#\NUL 字符的大小取决于编码中每个代码单元的八位字节数。

表达方式

(ccl:with-cstrs ((x “x”)) (#_puts x))

在功能上等同于

(ccl:with-encoded-cstrs :iso-8859-1 ((x “x”)) (#_puts x))

with-cstrs (var string* &body body[Macro]

在每个var 绑定到macptr 的环境中执行body,macptr封装堆栈分配的内存区域的地址,每个字符串(以及尾随的NUL字符)都已复制到该区域中。返回body返回的值。

此宏被硬编码以使用:iso-8859-1编码。宏 with-encoded-cstrs允许您指定编码。

%get-cstring ptr[功能]

此函数将ptr解释为指向(以null结尾的)C字符串的指针,并返回等效的lisp字符串。

%str-from-ptr ptr length[功能]

返回长度为length的lisp字符串,其内容从ptr处的字节初始化

get-encoded-string encoding-name ptr len[功能]

从 根据encoding-name解码的ptr指向的len八位字节创建并返回一个lisp字符串。

接口数据库

概观

Clozure CL使用一组数据库文件,这些文件包含从操作系统的头文件派生的外来类型,记录,常量和函数定义,即Linux或Darwin。包含这些数据库文件(以及在创建时使用的shell脚本)的存档可用; 有关获取当前接口数据库文件的信息,请参阅“分发”页面。

毫不奇怪,不同的平台使用不同的数据库文件。

Clozure CL定义了参考这些数据库的读者宏:

  • #$ foo查找foo常量定义的值
  • #_foo查找foo的外部函数定义

在这两种情况下,符号foo都在OS 包中实现。#$ reader宏具有将foo定义为常量的副作用(就像通过DEFCONSTANT一样); #_ reader宏具有将foo定义为宏的副作用,它将扩展为(EXTERNAL-CALL形式)。

重要的是要记住,当读取包含读取器宏的表单时会发生副作用。扩展为包含那些读取器宏实例的表单的宏展开函数不会像人们认为的那样做,除非宏在读取读取器宏的同一个lisp会话中展开。

此外,对外部类型,结构/联合和字段名称的引用(在RREF / PREF和RLET宏中使用时)将导致查询这些数据库文件。

由于Clozure CL源包含这些读取器宏的实例(以及对外部记录类型和字段的引用),因此从这些源编译Clozure CL取决于查找和使用的能力(请参阅构建堆图像)。

其他事宜:

  • Clozure CL现在在其数据库文件中保留外部符号的大小写。有关外来符号名称中的大小写的信息,请参阅Clozure CL中的外来名称的区分大小写。
  • Linux数据库源自一些有点任意的Linux头文件。Linux已经足够成为一个移动目标,可能很难定义一个标准的参考接口集,从中可以派生出一套标准的数据库文件参考集。这似乎不是Darwin和FreeBSD的问题。

有关构建数据库文件的信息,请参阅接口转换器

使用接口目录

概观

作为分布式,ccl:headers;(对于LinuxPPC)目录的组织如下:

headers/        headers/gl/        headers/gl/C/        headers/gl/C/populate.sh        headers/gl/constants.cdb        headers/gl/functions.cdb        headers/gl/records.cdb        headers/gl/objc-classes.cdb        headers/gl/objc-methods.cdb        headers/gl/types.cdb        headers/gnome/        headers/gnome/C/        headers/gnome/C/populate.sh        headers/gnome/constants.cdb        headers/gnome/functions.cdb        headers/gnome/records.cdb        headers/gnome/objc-classes.cdb        headers/gnome/objc-methods.cdb        headers/gnome/types.cdb        headers/gtk/        headers/gtk/C/        headers/gtk/C/populate.sh        headers/gtk/constants.cdb        headers/gtk/functions.cdb        headers/gtk/records.cdb        headers/gtk/objc-classes.cdb        headers/gtk/objc-methods.cdb        headers/gtk/types.cdb        headers/libc/        headers/libc/C/        headers/libc/C/populate.sh        headers/libc/constants.cdb        headers/libc/functions.cdb        headers/libc/records.cdb        headers/libc/objc-classes.cdb        headers/libc/objc-methods.cdb        headers/libc/types.cdb

例如,作为一组并行子目录,每个子目录具有小写名称,每个C子目录包含一组6个数据库文件和一个子目录,该子目录包含数据库创建过程中使用的shell脚本。

正如人们可能假设的那样,每个子目录中的数据库文件都包含外部类型,常量和函数定义 – 以及Objective-C类和方法信息 – 它们(大致)对应于与a关联的头文件中包含的信息。 Linux发行版中的“-dev”包。 libc与glibc / libc6头文件关联的接口非常接近,gl 对应于openGL + GLUT开发包,gtk 并分别gnome包含来自GTK + 1.2和GNOME库的接口信息。

对于Darwin,该ccl:darwin-headers;目录包含一个libc子目录,其内容大致对应于/usr/includeDarwin下的内容,以及对应于MacOSX Carbon和Cocoa框架的子目录。

要查看用于在给定接口目录中生成数据库文件的精确.h文件集,请查阅相应的populate.shshell脚本(在接口目录的C子目录中)。

目的是可以扩充该初始集以满足本地需求,并且这可以以相当增量的方式完成:不需要安装不相关的头文件以便为感兴趣的包生成接口数据库。

希望这个方案还可以更容易地分发补丁和错误修复。

Clozure CL维护一个目录列表; 在查找外部类型,常量,函数或记录定义时,它将查询该列表上每个目录中的数据库文件。最初,该列表包含libc 接口目录的条目。需要明确告诉Clozure CL在需要时查看其他接口目录。

创建新的接口目录

此示例引用“ccl:headers;”,它适用于LinuxPPC。该程序在达尔文下类似,其中“ccl:darwin-headers;” 将使用目录。

要在该目录中创建新的接口目录“foo”和一组数据库文件:

  1. 创建“ccl:headers;”的子目录 名为“foo”。
  2. 创建“ccl:headers; foo;”的子目录 名为“C”。
  3. 在“ccl:headers; foo; C;”中创建一个文件 名为“populate.sh”。

完成上述步骤的一种方法是:

? (close (open “ccl:headers;foo;C;populate.sh” :direction :output :                           if-does-not-exist :create :if-exists :overwrite))

  1. 编辑上面创建的文件,使用分发中的“populate.sh”文件作为指导。

该文件可能会看起来像:

#/bin/sh            h-to-ffi.sh `foo-config -cflags` /usr/include/foo/foo.h

请参阅该接口转换有关运行的接口转换器和分析器.ffi信息。

假设一切顺利,现在应该在“ccl:headers; foo;”中有.cdb文件。然后你可以做

? (use-interface-dir :foo)

每当您需要访问这些数据库文件中的外来类型信息时。

使用共享库

概观

Clozure CL提供了打开和关闭共享库的工具。

“打开”共享库,open-shared-library将库的代码和数据映射到Clozure CL的地址空间,并使其导出的符号可供Clozure CL访问。

“关闭”共享库,完成后close-shared-library,取消映射库的代码,并从全局命名空间中删除库的符号。

少量共享库(包括Linux下的libc,libm,libdl和Darwin下的“系统”库)由lisp内核打开,无法关闭。

Clozure CL使用EXTERNAL-ENTRY-POINT类型的数据结构将外部函数名称(字符串)映射到该外部函数的 当前 地址。(函数的地址可能因会话而异,因为不同版本的共享库可能会在不同的地址加载;由于类似的原因,它可能会在会话中发生变化。)

据说地址已知的EXTERNAL-ENTRY-POINT 解决。解析外部入口点时,会记录定义该入口点的共享库; 当共享库关闭时,它定义的入口点将被解析。EXTERNAL-ENTRY-POINT必须处于已解决状态才能进行FF-CALL; 调用未解决的入口点会导致“最后机会”尝试解决它。尝试解析在已关闭库中定义的入口点将导致尝试重新打开该库。

Clozure CL跟踪已在lisp会话中打开的所有库。首次启动保存的应用程序时,会尝试重新打开保存图像时打开的所有库,并尝试解析保存图像时引用的所有入口点。这些尝试中的任何一个都可以“安静地”失败,使一些入口点处于未解决的状态。

Linux共享库可以通过描述其完整路径名的字符串或通过它们来引用 SONAME,一个可以在创建库时定义的较短字符串。Linux中使用的动态链接器机制使(通过一系列文件系统链接和其他方法)可以通过多个名称引用库; 库的soname通常是最合适的标识符。

所以名称通常不如版本特定于库的其他名称; 通过名称“libc.so.6”引用库的程序比引用“libc-2.1.3.so”或“libc-2.2.3.so”的程序更可移植,即使后者两个名称可能各自是第一个的平台特定别名。

下面描述的所有全局符号都是从CCL包导出的。

限制和已知错误

  • 不要让我开始。
  • 底层功能的依赖性概念很差;并不总是可以打开依赖未打开的库的库,但是可以关闭其他库所依赖的库。它 可能 可以通过解析Linux ldd和ldconfig程序的输出来生成更明确的依赖关系信息。

>达尔文笔记

达尔文共享库有两种(基本)风格:

  • “dylibs”(通常具有扩展名“.dylib”)主要用于在编译/链接时链接。它们可以动态加载,但无法卸载。因此,OPEN-SHARED-LIBRARY可用于打开.dylib样式的库;对此类调用的结果调用CLOSE-SHARED-LIBRARY会产生警告,并且没有其他影响。看来(由于操作系统错误)尝试打开已经打开的.dylib共享库会导致内存损坏,除非在第一次和所有后续调用中指定.dylib文件的完整路径名。
  • “捆绑包”旨在用作应用程序扩展; 它们可以多次打开(创建库的多个实例!)并正确关闭。

感谢Michael Klingbeil让两种Darwin共享库在Clozure CL中运行。

界面翻译器

概观

Clozure CL使用基于FFIGEN系统的接口转换系统,该系统在本页描述。接口转换器在lisp代码可用的一组C语言头文件中生成常量,类型,结构和函数定义。

FFIGEN方案的基本思想是使用C编译器的前端和解析器将.h文件转换为语义上等效的.ffi文件,这些文件使用基于S表达式的语法表示标头中的定义。然后,Lisp代码可以专注于.ffi表示,而不必关心头文件包含的语义或C解析的arcana。

最初的FFIGEN系统使用LCC C编译器的修改版本来生成.ffi文件。由于许多OS头文件包含GCC特定的结构,Clozure CL的翻译系统使用GCC的修改版本(称为有点令人困惑的ffigen。)

有关 构建和安装ffigen的信息,请参见此处

名为h-to-ffi.sh的组件shell脚本读取指定的.h文件(和可选的预处理器参数),并将(希望)等效的.ffi文件写入标准输出,使用适当的参数调用ffigen程序。

对于每个接口目录(请参阅FIXTHIS) 子目录 与Clozure CL一起发布,一个shell脚本(与Clozure CL一起发布为“ccl:headers;子目录; C; populate.sh“(或其他一些特定于平台的头文件目录)对/ usr / include(或其他一些)中的大量头文件调用h-to-ffi.sh 系统标题路径)并在“ccl:headers;中创建一个并行目录树;子目录;C;系统;头;路径;“(或”ccl:darwin-headers;子目录;C;系统;头;路径;“等等),使用.ffi文件填充该目录。

“ccl:library; parse-ffi.lisp”中定义的lisp函数读取指定接口目录中的.ffi文件 子目录 并生成新版本的数据库(扩展名为.cdb的文件)。

CDB数据库由#$和#_ reader宏使用,用于扩展RREF,RLET和相关宏。

详细信息:逐步重建CDB数据库

  1. 确保已安装FFIGEN程序。有关特定的安装说明,请参阅FFIGEN构建过程中生成的“README”文件。此示例假定LinuxPPC; 对于其他平台,请替换相应的头目录。
  2. 编辑“ccl:headers;子目录; C; populate.sh“shell脚本。当您确信文件和预处理器选项与您的环境匹配时,请转到”ccl:headers;子目录; C;“目录并调用./populate.sh。重复此步骤,直到您能够干净地翻译shell脚本中引用的所有文件。
  3. 运行Clozure CL:

? (require “PARSE-FFI”)

PARSE-FFI

 

? (ccl::parse-standard-ffi-files :SUBDIR)

;;; lots of output … after a while, shiny new .cdb files should

;;; appear in “ccl:headers;subdir;”

可能需要两次调用CCL :: PARSE-STANDARD-FFI-FILES,以确保正向引用得到解决

Clozure CL中外来名称的区分大小写

概观

从版本0.11开始,Clozure CL解决了外部类型,常量,记录,字段和函数nams区分大小写的问题,并提供了通过lisp符号引用这些名称的机制。

以前版本的Clozure CL试图忽略这一事实,认为案例冲突很少,并且许多用户(和实现者)不愿意处理与案例相关的问题。由于此策略,接口数据库中的某些信息不完整或无法访问这一事实使得该策略更加清晰。我不能说这里描述的方法在美学上是令人愉悦的,但我可以诚实地说,它比我想到的其他方法更不令人不愉快。我有兴趣听听其他提案。

这里描述的问题与lisp符号如何用于表示外部函数,常量,类型,记录和字段有关。它不会影响其他lisp对象有时用于表示异物的方式。例如,EXTERNAL-CALL宏的第一个参数现在是一个区分大小写的字符串。

外部常量和函数名称

在Clozure CL中引用外部常量和函数名称的主要方法是通过#$和#_ reader宏。这些读取器宏函数每个都将符号读入“OS”包,在接口数据库中查找其常量或函数定义,并将常量的值赋给符号或在符号上安装宏展开函数。

为了观察区分大小写,读取器宏现在读取带有(READTABLE-CASE:PRESERVE)的符号。

这意味着必须在正确的情况下键入外部常量或函数名称,但在编写变量名时不必使用任何特殊的转义结构。例如:

(#_read fd buf n) ; refers to foreign symbol “read”        (#_READ fd buf n) ; refers to foreign symbol “READ”, which may        ; not exist …        #$o_rdonly ; Probably doesn’t exist        #$O_RDONLY ; Exists on most platforms

外来类型,记录和字段名称

像RLET这样的构造要求外部类型或记录名称由符号(通常是关键字)表示; RREF(和PREF)期望一种“访问者”形式,通常是通过将外来类型或记录名称与由点分隔的一个或多个外部字段名称的序列连接而形成的关键字。这些名称由读者实现,因为其他lisp符号有效,任意值为READTABLE-CASE(通常为:UPCASE。)强制用户手动转义(通过垂直条或反斜杠语法)似乎非常繁琐)用于指定外部类型,记录和字段名称的符号中的所有小写字符(特别是考虑到许多传统的POSIX结构,类型和字段名称完全是小写的。)

Clozure CL采用的方法是允许用于表示外来类型,记录和字段名称的符号(关键字)包含尖括号(<和 >)。这些符号通过以下约定转换为外来名称:

  • 符号的pname中的<和>的所有实例都是平衡的,不会嵌套。
  • 符号的pname中未包含在尖括号中的任何字母字符都被视为小写,无论READTABLE-CASE的值如何,也不管写入它们的情况如何。
  • 出现在尖括号内的字母字符将被映射为大写字母,无论它们是如何写入或实现的。

在用于表示外来名称的符号中,可能有许多方法“转义”(使用尖括号)大写和非小写字符序列。当在另一个方向上进行平移时,Clozure CL总是从最大的序列中逃脱,该序列以大写字符开头并且不包含小写字符。

通常最好使用这种外来类型名称的规范形式。

PREF / RREF使用的访问者表单应被视为一系列外来类型/记录和字段名称; 组件名称中的大写序列应使用尖括号进行转义,但这些序列不应跨越组件。(更简单地说,即使周围的两个字符都需要,也不应该包含分隔点。)

较旧的POSIX代码倾向于将小写专门用于类型,记录和字段名称; Clozure CL源中只有少数情况需要转义混合大小写的名称。

例子

;;; Allocate a record of type “window”.        (rlet ((w :window)) …)        ;;; Allocate a record of type “Window”, which is probably a        ;;;  different type        (rlet ((w :<w>indow)) …)        ;;; This is equivalent to the last example        (rlet ((w :<w>INDOW)))

读外国名字

Clozure CL提供了几个读取器宏,使得处理外部类型,函数,变量和常量名称更加方便。这些读取器宏中的每一个都读取保留源文本的情况的符号,并选择适当的包来实现所得到的符号。当您的Lisp代码与外部库进行广泛交互时,这些读取器宏特别有用 – 例如,当使用Mac OS X的Cocoa框架时。

这些读取器宏包括#_读取外部函数名,#&读取外部变量名,#$ 读取外部常量名,#/读取外部Objective-C方法的名称,以及#>读取可用作类型,记录和名称的关键字存取。

所有这些读者宏都保留了他们阅读的文本的大小写; 超出这种相似性,每个都执行一些额外的工作,每个读者宏都是唯一的,以创建适合特定用途的符号。例如,函数,变量和常量读取器宏os在运行平台的包中生成符号,但Objective-C方法的读取器宏指定nextstep-functions包中的符号。

您可能会看到这些读取器宏在Lisp代码中广泛使用,可以与外部库一起使用; 例如,Clozure CL IDE代码定义了许多Objective-C类和方法,它们广泛使用这些读取器宏。

有关每个读取器宏的更详细说明,请参见“外部函数 – 接口字典”部分。

教程:使用基本调用和类型

本教程旨在介绍Clozure CL的基础知识,用于调用外部C函数和来回传递数据。这些基础知识将为更高级的技术提供基础,这些技术将允许访问各种外部库和工具包。

第一步是从一个简单的C动态库开始,以便实际观察Clozure CL和C之间实际传递的内容。因此,一些C代码按顺序排列:

创建文件typetest.c,并将以下代码放入其中:

#include <stdio.h>

 

void

void_void_test(void)

{

printf(“Entered %s:\n”, __FUNCTION__);

printf(“Exited  %s:\n”, __FUNCTION__);

fflush(stdout);

}

 

signed char

sc_sc_test(signed char data)

{

printf(“Entered %s:\n”, __FUNCTION__);

printf(“Data In: %d\n”, (signed int)data);

printf(“Exited  %s:\n”, __FUNCTION__);

fflush(stdout);

return data;

}

 

unsigned char

uc_uc_test(unsigned char data)

{

printf(“Entered %s:\n”, __FUNCTION__);

printf(“Data In: %d\n”, (signed int)data);

printf(“Exited  %s:\n”, __FUNCTION__);

fflush(stdout);

return data;

}

这定义了三个功能。如果你熟悉C,请注意没有main(),因为我们只是在构建一个库,而不是一个可执行文件。

该函数void_void_test()不接受任何参数,并且不返回任何内容,但它打印两行以告诉我们它被调用。 sc_sc_test()将带符号的char作为参数,打印并返回它。 uc_uc_test()做同样的事情,但使用unsigned char。他们的目的只是向我们证明我们真的可以调用C函数,传递它们的值,并从中获取值。

使用以下命令将此代码编译到OS X 10.3.4上的动态库中:

gcc -dynamiclib -Wall -o libtypetest.dylib typetest.c \

-install_name ./libtypetest.dylib

64位平台的用户可能需要将诸如“-m64”之类的选项传递给gcc,可能需要为输出库提供不同的扩展名(例如“.so”),并且可能需要为其他选项使用略微不同的值为了创建一个等价的测试库。

-dynamiclib告诉gcc我们将把它编译成动态库而不是可执行的二进制程序。输出文件名是“libtypetest.dylib”。请注意,我们选择了一个遵循正常OS X约定的名称,格式为“libXXXXX.dylib”,以便其他程序可以链接到库。Clozure CL并不需要这样,但坚持现有的约定是个好主意。

-install_name标志主要在构建OS X“bundle”时使用。在这种情况下,我们没有使用它,所以我们在其中放置一个占位符“./libtypetest.dylib”。如果我们想在bundle中使用typetest,则-install_name参数将是来自某个“current”目录的相对路径。

创建此库后,第一步是告诉Clozure CL打开动态库。这是通过致电来完成的。

Welcome to Clozure CL Version (Beta: Darwin) 0.14.2-040506!

 

? (open-shared-library “/Users/andewl/openmcl/libtypetest.dylib”)

#<SHLIB /Users/andewl/openmcl/libtypetest.dylib #x638EF3E>

你应该在这里使用绝对路径; 使用相对的一个,例如只是“libtypetest.dylib”,似乎可行,但重新加载后会出现微妙的问题。有关详细信息,请参阅达尔文笔记。无论如何,这将是一个坏主意,因为软件永远不应该依赖于它的起始目录。

此命令返回对打开的共享库的引用,Clozure CL还向全局变量添加一个 ccl::*shared-libraries*:

? ccl::*shared-libraries*

(#<SHLIB /Users/andewl/openmcl/libtypetest.dylib #x638EF3E>

#<SHLIB /usr/lib/libSystem.B.dylib #x606179E>)

在我们调用任何东西之前,让我们检查一下系统是否可以找到各个函数。我们不必这样做,但它有助于知道在出现问题时如何确定这是否是问题。我们使用external-call

? (external “_void_void_test”)

#<EXTERNAL-ENTRY-POINT “_void_void_test” (#x000CFDF8) /Users/andewl/openmcl/libtypetest.dylib #x638EDF6>

 

? (external “_sc_sc_test”)

#<EXTERNAL-ENTRY-POINT “_sc_sc_test” (#x000CFE50) /Users/andewl/openmcl/libtypetest.dylib #x638EB3E>

 

? (external “_uc_uc_test”)

#<EXTERNAL-ENTRY-POINT “_uc_uc_test” (#x000CFED4) /Users/andewl/openmcl/libtypetest.dylib #x638E626>

请注意,实际的函数名称已被C链接器“破坏”。第一个函数在typetest.c中命名为“void_void_test”,但在libtypetest.dylib中,它在它之前有一个下划线(“_”符号):“_ void_void_test”。所以,这是你必须使用的名称。修改 – 名称的更改方式 – 对于其他操作系统或其他版本可能会有所不同,因此您需要“只知道”它是如何完成的……

另外,要特别注意EXTERNAL-ENTRY-POINT中出现十六进制值的事实。(例如#x000CFDF8 – 但它无关紧要。)这些十六进制数表示可以取消引用该函数。未找到的函数将不具有十六进制数。例如:

? (external “functiondoesnotexist”)

#<EXTERNAL-ENTRY-POINT “functiondoesnotexist” {unresolved}  #x638E3F6>

“未解决”告诉我们Clozure CL无法找到此功能,这意味着如果您尝试调用它,您将收到错误“无法解析外来符号”。

这些外部函数引用也存储在可通过全局变量访问的哈希表中 ccl::*eeps*。

此时,我们准备尝试第一个外部函数调用:

? (external-call “_void_void_test” :void)

Entered void_void_test:

Exited  void_void_test:

NIL

我们使用过,这是访问外部链接代码的常规机制。“_void_void_test”是外部函数的错位名称。:void指的是函数的返回类型。

下一步是尝试将值传递给C,然后返回一个:

 

 

? (external-call “_sc_sc_test” :signed-byte -128 :signed-byte)

Entered sc_sc_test:

Data In: -128

Exited  sc_sc_test:

-128

第一个:signed-byte给出第一个参数的类型,然后-128给出要传递的值。第二种:signed-byte给出返回类型。返回类型始终由最后一个参数给出。

一切都很好看。现在,让我们尝试一个超出一个字节范围的数字:

? (external-call “_sc_sc_test” :signed-byte -567 :signed-byte)

Entered sc_sc_test:

Data In: -55

Exited  sc_sc_test:

-55

Hmmmm。有点奇怪。让我们看看未签名的东西,看看它是如何反应的:

 

 

 

? (external-call “_uc_uc_test” :unsigned-byte 255 :unsigned-byte)

Entered uc_uc_test:

Data In: 255

Exited  uc_uc_test:

255

那看起来还不错。现在,让我们再次超出有效范围:

? (external-call “_uc_uc_test” :unsigned-byte 567 :unsigned-byte)

Entered uc_uc_test:

Data In: 55

Exited  uc_uc_test:

55

 

? (external-call “_uc_uc_test” :unsigned-byte -567 :unsigned-byte)

Entered uc_uc_test:

Data In: 201

Exited  uc_uc_test:

201

由于有符号字节只能保存-128到127之间的值,而无符号字节只能保存0到255之间的值,因此该范围之外的任何数字都会被“限幅”:仅使用它的低8位。

重要的是要记住这一点 外部函数调用几乎没有安全检查。 超出其类型的有效范围的数据将默默地做非常奇怪的事情; 有效范围之外的指针可以很好地使系统崩溃。

这就是我们的第一个示例库。如果你还在继续,那么让我们再添加一些C代码来查看其余的基本类型。然后我们需要重新编译动态库,再次加载它,然后我们就可以看到会发生什么。

将以下代码添加到typetest.c:

int

si_si_test(int data)

{

printf(“Entered %s:\n”, __FUNCTION__);

printf(“Data In: %d\n”, data);

printf(“Exited  %s:\n”, __FUNCTION__);

fflush(stdout);

return data;

}

 

long

sl_sl_test(long data)

{

printf(“Entered %s:\n”, __FUNCTION__);

printf(“Data In: %ld\n”, data);

printf(“Exited  %s:\n”, __FUNCTION__);

fflush(stdout);

return data;

}

 

long long

sll_sll_test(long long data)

{

printf(“Entered %s:\n”, __FUNCTION__);

printf(“Data In: %lld\n”, data);

printf(“Exited  %s:\n”, __FUNCTION__);

fflush(stdout);

return data;

}

 

float

f_f_test(float data)

{

printf(“Entered %s:\n”, __FUNCTION__);

printf(“Data In: %e\n”, data);

printf(“Exited  %s:\n”, __FUNCTION__);

fflush(stdout);

return data;

}

 

double

d_d_test(double data)

{

printf(“Entered %s:\n”, __FUNCTION__);

printf(“Data In: %e\n”, data);

printf(“Exited  %s:\n”, __FUNCTION__);

fflush(stdout);

return data;

}

编译动态库的命令行与以前相同:

gcc -dynamiclib -Wall -o libtypetest.dylib typetest.c \

-install_name ./libtypetest.dylib

现在,重新启动Clozure CL。此步骤是必需的,因为Clozure CL无法关闭并重新加载OS X上的动态库。

你重新开始了吗?好的,试试新代码:

 

Welcome to Clozure CL Version (Beta: Darwin) 0.14.2-040506!

 

? (open-shared-library “/Users/andewl/openmcl/libtypetest.dylib”)

#<SHLIB /Users/andewl/openmcl/libtypetest.dylib #x638EF3E>

 

? (external-call “_si_si_test” :signed-fullword -178965 :signed-fullword)

Entered si_si_test:

Data In: -178965

Exited  si_si_test:

-178965

 

? ;; long is the same size as int on 32-bit machines.

(external-call “_sl_sl_test” :signed-fullword -178965 :signed-fullword)

Entered sl_sl_test:

Data In: -178965

Exited  sl_sl_test:

-178965

 

? (external-call “_sll_sll_test”

:signed-doubleword -973891578912 :signed-doubleword)

Entered sll_sll_test:

Data In: -973891578912

Exited  sll_sll_test:

-973891578912

好吧,一切似乎都按预期行事。然而,只是提醒你,大部分的这些东西有没有安全网,这里会发生什么,如果有人犯错 sl_sl_test()了 sll_sll_test(),以为长实际上是一个双字:

? (external-call “_sl_sl_test”

:signed-doubleword -973891578912 :signed-doubleword)

Entered sl_sl_test:

Data In: -227

Exited  sl_sl_test:

-974957576192

哎哟。C函数更改值而不会出现错误警告。更糟糕的是,它设法将原始值传递回Clozure CL,这隐藏了一些错误的事实。

最后,让我们看看使用浮点数进行此操作。

Welcome to Clozure CL Version (Beta: Darwin) 0.14.2-040506!

 

? (open-shared-library “/Users/andewl/openmcl/libtypetest.dylib”)

#<SHLIB /Users/andewl/openmcl/libtypetest.dylib #x638EF3E>

 

? (external-call “_f_f_test” :single-float -1.256791e+11 :single-float)

Entered f_f_test:

Data In: -1.256791e+11

Exited  f_f_test:

-1.256791E+11

 

? (external-call “_d_d_test” :double-float -1.256791d+290 :double-float)

Entered d_d_test:

Data In: -1.256791e+290

Exited  d_d_test:

-1.256791D+290

请注意,对于单浮点数,数字以“… e + 11”结尾,对于双浮点数,数字以“… d + 290”结尾。Lisp本身具有这两种浮点类型,而d代替e是指定要创建的类型。如果你试图传递:double-float 1.0e2到external-call,那么Lisp会很好地注意并给你一个类型错误。不要得到:double-float错误,因为那时没有保护。

恭喜!您现在知道如何从Clozure CL中调用外部C函数,并来回传递数字。既然调用和传递工作的基本机制,下一步是检查如何传递更复杂的数据结构。

承认

本章由Andrew P. Lentvorski Jr.慷慨捐助。

教程:在Lisp堆上分配外部数据

并不是每个外国函数都像我们在上一节中看到的那样非常容易使用。某些函数要求您分配C结构,用您自己的信息填充它,并传入指向该结构的指针。其中一些要求您分配一个他们将填写的空结构,以便您可以从中读取信息。

通常有两种方法来分配外来数据。第一种方法是在堆栈上分配它; RLET宏是一种方法。这类似于在C中使用自动变量。在Common Lisp的术语中,以这种方式分配的数据据说具有动态范围。

堆分配外部数据的另一种方法。这类似于在C语言中调用malloc。再次在Common Lisp的行话中,堆分配的数据被认为具有无限的范围。如果函数堆分配一些数据,即使函数本身退出,该数据仍然有效。这对于可能需要在多个C调用或多个线程之间传递的数据非常有用。此外,某些数据可能太大而无法复制多次,或者可能太大而无法在堆栈上进行分配。

在堆上分配数据的最大缺点是必须明确释放它 – 当你完成它时你需要“释放”它。普通的Lisp对象,即使那些具有无限范围的对象,在它可以证明它们不再被引用时被垃圾收集器释放。但是,外国数据不在GC的范围内:它无法知道外国数据是否仍然被外国代码引用。因此,程序员需要手动管理它,就像在C语言中使用malloc和free一样。

这意味着,如果你分配一些东西然后失去对它的指针的跟踪,就没有办法释放那个记忆。这就是所谓的内存泄漏,如果你的程序泄漏了足够的内存,它最终会耗尽所有内存!所以,你需要小心不要丢失指针。

但是,这个缺点也是使用外来功能的一个优点。由于垃圾收集器不知道这个内存,它永远不会移动它。外部C代码需要这个,因为它不知道如何跟踪它移动的位置,Lisp代码的方式。如果手动分配数据,则可以将其传递给外部代码,并且知道无论代码需要做什么,它都可以,直到您解除分配。当然,你最好先确定它已经完成。否则,你的程序将会不稳定并且可能在将来某个时候崩溃,你将无法找出导致问题的原因,因为没有任何指示并且说“你过早地解除了这个”。

那么,代码……

与上一个教程一样,我们的第一步是创建一个本地动态库,以帮助显示Clozure CL和C之间的实际情况。因此,使用以下代码创建文件ptrtest.c:

#include <stdio.h>

 

void reverse_int_array(int * data, unsigned int dataobjs)

{

int i, t;

 

for(i=0; i<dataobjs/2; i++)

{

t = *(data+i);

*(data+i) = *(data+dataobjs-1-i);

*(data+dataobjs-1-i) = t;

}

}

 

void reverse_int_ptr_array(int **ptrs, unsigned int ptrobjs)

{

int *t;

int i;

 

for(i=0; i<ptrobjs/2; i++)

{

t = *(ptrs+i);

*(ptrs+i) = *(ptrs+ptrobjs-1-i);

*(ptrs+ptrobjs-1-i) = t;

}

}

 

void

reverse_int_ptr_ptrtest(int **ptrs)

{

reverse_int_ptr_array(ptrs, 2);

 

reverse_int_array(*(ptrs+0), 4);

reverse_int_array(*(ptrs+1), 4);

}

这定义了三个功能。 reverse_int_array获取指向ints 数组的指针,以及计算数组中有多少项的计数,并循环通过它将元素反向放置。 reverse_int_ptr_array做同样的事情,但有一个指向ints 的指针数组。它只反转指针所在的顺序; 每个指针仍然指向同一个东西。 reverse_int_ptr_ptrtest获取指向ints 数组的指针数组。(和我一起?)不需要告诉他们的尺寸; 它只是假设指针数组有两个项目,并且这两个项目都是具有四个项目的数组。它反转指针数组,然后反转两个ints 数组中的每一个。

现在,使用以下命令将ptrtest.c编译为动态库:

gcc -dynamiclib -Wall -o libptrtest.dylib ptrtest.c -install_name ./libptrtest.dylib

该函数make-heap-ivector是在堆内存中分配对象的主要工具。它在堆内存中分配一个固定大小的Clozure CL对象。它返回一个可以直接从Clozure CL使用的数组引用和一个macptr可以直接访问底层内存的a。例如:

? ;; Create an array of 3 4-byte-long integers

(multiple-value-bind (la lap)

(make-heap-ivector 3 ‘(unsigned-byte 32))

(setq a la)

(setq ap lap))

;Compiler warnings :

;   Undeclared free variable A, in an anonymous lambda form.

;   Undeclared free variable AP, in an anonymous lambda form.

#<A Mac Pointer #x10217C>

 

? a

#(1396 2578 97862649)

 

? ap

#<A Mac Pointer #x10217C>

重要的是要意识到ivector我们刚刚创建的内容 尚未初始化,因此它们的值是不可预测的,并且在设置它们之前应确保不要从它们中读取它们,以避免混淆结果。

此时,a引用一个像普通数组一样工作的对象。您可以使用标准aref功能引用它的任何项目,并通过组合它来设置它们setf。如上所述,其ivector内容尚未初始化,因此这是下一个业务顺序:

? a

#(1396 2578 97862649)

 

? (aref a 2)

97862649

 

? (setf (aref a 0) 3)

3

 

? (setf (aref a 1) 4)

4

 

? (setf (aref a 2) 5)

5

 

? a

#(3 4 5)

另外,macptr允许直接访问相同的内存:

? (setq *byte-length-of-long* 4)

4

 

? (%get-signed-long ap (* 2 *byte-length-of-long*))

5

 

? (%get-signed-long ap (* 0 *byte-length-of-long*))

3

 

? (setf (%get-signed-long ap (* 0 *byte-length-of-long*)) 6)

6

 

? (setf (%get-signed-long ap (* 2 *byte-length-of-long*)) 7)

7

 

? ;; Show that a actually got changed through ap

a

#(6 4 7)

到目前为止,没有任何关于这个对象的标准Lisp无法做得更好。但是,macptr可以用来将这块内存传递给C函数。让我们使用C代码来反转数组中的元素:

? ;; Insert the full path to your copy of libptrtest.dylib

(open-shared-library “/Users/andrewl/openmcl/openmcl/gtk/libptrtest.dylib”)

#<SHLIB /Users/andrewl/openmcl/openmcl/gtk/libptrtest.dylib #x639D1E6>

 

? a

#(6 4 7)

 

? ap

#<A Mac Pointer #x10217C>

 

? (external-call “_reverse_int_array” :address ap :unsigned-int (length a) :address)

#<A Mac Pointer #x10217C>

 

? a

#(7 4 6)

 

? ap

#<A Mac Pointer #x10217C>

数组正确传递给C函数,reverse_int_array。C函数就地反转了数组的内容; 也就是说,它不会创建一个新数组,只保持相同的数组并反转其中的内容。最后,C函数将控制权传递给Clozure CL。由于已直接修改分配的数组内存,Clozure CL也会直接在数组中反映这些更改。

最后一点需要处理。在继续之前,需要释放内存:

? (dispose-heap-ivector a ap)      NIL

该dispose-heap-ivector宏实际上将释放ivector,释放它的内存到堆中其他东西使用。无论a和ap 现在有未定义值。

你dispose-heap-ivector什么时候打电话?在您知道ivector永远不会再次使用之后的任何时候,但不久之后。如果你有很多ivector,比如说,在哈希表中,你需要确保当你用哈希表做的任何事情完成后,那些ivector都会被释放。当然,除非其他地方还有某些东西引用它们!究竟采取什么样的策略取决于具体情况,所以除非你知道更好,否则尽量保持简单。

最简单的情况是,当你设置了一些东西,以便Lisp对象“封装”一个指向外部数据的指针,并处理使用它的所有细节。在这种情况下,您不希望这两件事具有不同的生命周期:您希望确保您的Lisp对象存在,只要外部数据存在,并且不再存在; 并且您希望确保在您的Lisp对象仍然引用它时不会释放外部数据。

如果您愿意接受一些限制,可以轻松实现。首先,你不能让外来代码保留一个指向内存的永久指针; 它必须始终完成它正在做的事情,然后返回,而不是再次参考那个记忆。其次,你不能让任何不包含封装“包装器”的Lisp代码直接引用指针。第三,没有任何东西,无论是外国代码还是Lisp代码,都应该明确地释放内存。

如果你可以确保所有这些都是真的,你可以通过使用Clozure CL的非标准“终止”机制,至少确保在封装对象即将成为垃圾时释放外部指针,这与Java基本相同。和其他语言称为“最终化”。

终止是一种要求垃圾收集器让你知道它何时会破坏一个不再使用的对象的方法。在销毁对象之前,它会调用您编写的函数,称为终结符。

因此,您可以使用终止来查明某个特定 macptr即将成为垃圾的时间。这并不像它看起来那么有用:它与知道它所指向的内存块是未引用的并不完全相同。例如,macptr同一个街区可能存在另一个 地方; 或者,如果它是一个结构,它可能有一个macptr到一个字段。最有问题的是,如果该内存的地址已传递给外部代码,则有时很难知道该代码是否保留了指针。大多数外国职能都没有,但不难想到例外情况。

您可以使用此类代码来实现所有这些:

(defclass wrapper (whatever)

((element-type :initarg :element-type)

(element-count :initarg :element-count)

(ivector)

(macptr)))

 

(defmethod initialize-instance ((wrapper wrapper) &rest initargs)

(declare (ignore initargs))

(call-next-method)

(ccl:terminate-when-unreachable wrapper)

(with-slots (ivector macptr element-type element-count) wrapper

(multiple-value-bind (new-ivector new-macptr)

(make-heap-ivector element-count element-type)

(setq ivector new-ivector

macptr new-macptr))))

 

(defmethod ccl:terminate ((wrapper wrapper))

(with-slots (ivector macptr) wrapper

(when ivector

(dispose-heap-ivector ivector macptr)

(setq ivector nil

macptr nil))))

ccl:terminate在GC确定没有对作为ccl:terminate-when-unreachable调用参数的对象的强引用之后,某个时候(希望很快)会在某个任意线程上调用该方法。

如果说外来对象应该存在,只要有Lisp代码引用它(通过封装对象)并且不再存在,这就是这样做的一种方式。

现在我们已经介绍了使用C来回传递基本类型,我们使用指针也做了同样的事情。您可能认为这就是全部…但我们只是指向基本类型。下次加入我们的指针…指针。

承认

本章的大部分内容都是由Andrew P. Lentvorski Jr.慷慨捐助的。

外来函数接口字典

#_[读者宏]

从当前输入流中读取符号,* PACKAGE *绑定到“OS”包并保留可读表达式。

Clozure CL接口数据库中查找该符号,如果在任何活动接口目录中找不到该符号的外部函数信息,则发出错误信号。

注意外部函数信息,包括外部函数的返回类型,外部函数所需参数的数量和类型,以及该函数是否接受其他参数的指示(例如,通过C中的“varargs”机制)。

在符号上定义宏展开函数,它将涉及符号的宏调用扩展为EXTERNAL-CALL形式,其中从数据库中的信息提供所需参数的外部参数类型说明符和返回值指定符。

返回符号。

这些步骤的作用是可以通过简单地提供参数值来调用带有固定数量参数的外部函数,如:

(#_isatty fd)          (#_read fd buf n)

并通过指定非必需args的类型来调用带有可变数量参数的外部函数,如:

(with-cstrs ((format-string “the answer is: %d”))          (#_printf format-string :int answer))

您可以通过附加“?”来查询是否在接口数据库中定义了给定名称 读者宏的字符; 例如:

CL-USER> #_?printf          T          CL-USER> #_?foo          NIL

#&[读者宏]

在Clozure CL 1.2及更高版本中,#&reader宏可用于访问外部变量; 此功能取决于接口数据库中是否存在“vars.cdb”文件。#&reader宏的当前行为是:

从当前输入流中读取一个符号,* PACKAGE *绑定到“OS”包并保留可读取的大小写。

使用该符号的pname访问Clozure CL接口数据库,如果在任何活动接口目录中找不到具有该名称的适当外部变量信息,则表示错误。

使用数据库中记录的类型信息来构造一个可用于访问外部变量的表单,并返回该表单。

请注意,在头文件中声明的外部变量集可能与从库中导出的外部变量集匹配也可能不匹配(我们通常在这里讨论C和Unix ……)。当它们匹配时,#&reader宏构建的表单管理解析和跟踪外部变量地址变化的细节。

未来的扩展(通过读者宏的前缀参数)可能提供额外的行为; 在不解除引用该地址的情况下,可以方便地(例如)访问外部变量的地址。

C代码中的外部变量往往是特定于平台和包的(规范示例 – “errno” – 在涉及线程时通常不是变量。)

在LinuxPPC中,

? #&stderr

返回指向stdio错误流的指针(“stderr”是OSX / Darwin下的一个宏)。

在LinuxPPC和DarwinPPC上,

? #&sys_errlist

返回指向C错误消息字符串的C数组的指针。

您可以通过附加“?”来查询是否在接口数据库中定义了给定名称 读者宏的字符; 例如:

CL-USER> #&?sys_errlist          T          CL-USER> #&?foo          NIL

#$[读者宏]

在Clozure CL 0.14.2及更高版本中,#?reader宏可用于访问外部常量; 此功能取决于接口数据库中是否存在“constants.cdb”文件。#$ reader宏的当前行为是:

从当前输入流中读取一个符号,* PACKAGE *绑定到“OS”包并保留可读取的大小写。

使用该符号的pname访问Clozure CL接口数据库,如果在任何活动接口目录中找不到具有该名称的适当外部常量信息,则表示错误。

使用数据库中记录的类型信息来构造一个表单,该表单可用于访问外部常量,并返回该表单。

请注意,在头文件中声明的外部常量集可能与从库导出的外部常量集匹配也可能不匹配。当它们匹配时,#$ reader宏构建的表单管理解析和跟踪外部常量地址变化的细节。

您可以通过附加“?”来查询是否在接口数据库中定义了给定名称 读者宏的字符; 例如:

CL-USER> #$?SO_KEEPALIVE          T          CL-USER> #$?foo          NIL

#/[读者宏]

在Clozure CL 1.2及更高版本中,#/ reader宏可用于访问Darwin平台上的外部函数。#/ reader宏的当前行为是:

从当前输入流中读取一个符号,* PACKAGE *绑定到“NEXTSTEP-FUNCTIONS”包,保留可读大小写,并包含任何冒号。

对结果符号进行有限的健全性检查; 例如,任何包含至少一个冒号的名称也需要以冒号结尾,以符合Objective-C方法命名约定。

从“NEXTSTEP-FUNCTIONS”包中导出结果符号并将其返回。

例如,读取“#/ alloc”实习生并返回NEXTSTEP-FUNCTIONS:| alloc |。读取“#/ initWithFrame:”实习生并返回NEXTSTEP-FUNCTIONS:| initWithFrame:|。

在大多数可以使用Objective-C消息名称的地方,例如在(OBJ:@SELECTOR …)构造中,使用此宏读取的符号可以用作操作数。

请注意:读者宏并不严格执行Objective-C方法命名约定。尽管读者宏进行了简单的检查,但仍然可以使用它来构造无效的名称。

在NEXTSTEP-FUNCTIONS包中实现新符号的行为触发了Objective-C方法的接口数据库查找以及相应的消息名称。如果找到任何此类信息,则创建并初始化特殊类型的调度函数,并为新符号赋予新创建的调度函数作为其函数定义。

调度知道如何调用在消息上定义的声明的Objective-C方法。在许多情况下,所有方法都具有相同的外部类型签名,并且调度函数仅将它接收的任何参数传递给使用指示的外部参数和返回类型执行Objective-C消息发送的函数。在其他情况下,当不同的Objective-C消息具有不同的类型签名时,调度函数会尝试根据调度函数的第一个参数的类来选择处理正确类型签名的函数。

如果引入了有关Objective-C方法的新信息(例如,通过使用其他接口文件或从lisp定义Objective-C方法),则重新初始化分派功能以识别新引入的外来类型签名。

桥接器传统上支持的参数和结果强制由新机制支持(例如:<BOOL>参数可以指定为lisp布尔值,并且:<BOOL>结果作为lisp布尔值返回,参数值为NIL如果相应的参数类型是:ID,则强制转换为空指针。

一些Objective-C方法接受可变数量的参数; 外部类型的非必需参数由这些参数的lisp类型确定(例如,整数作为整数传递,浮点数作为浮点数传递,指针作为指针传递,记录类型通过引用传递。)

例子:

;;; #/alloc is a known message.

? #’#/alloc

#<OBJC-DISPATCH-FUNCTION NEXTSTEP-FUNCTIONS:|alloc| #x300040E94EBF>

;;; Sadly, #/foo is not …

? #’#/foo

> Error: Undefined function: NEXTSTEP-FUNCTIONS:|foo|

 

;;; We can send an “init” message to a newly-allocated instance of

;;; “NSObject” by:

 

(send (send ns:ns-object ‘alloc) ‘init)

 

;;; or by

 

(#/init (#/alloc ns:ns-object))

当通过调度函数调用时,“返回”结构的Objective-C方法将它们作为垃圾收集指针返回。例如,如果“my-window”是NS:NS-WINDOW实例,那么

(#/frame my-window)

返回一个垃圾收集指针,指向描述该窗口框架矩形的结构。这种约定意味着不需要使用SLET或特殊结构返回消息发送语法; 但请记住,#_ malloc,#_ free和GC都参与了结构类型返回值的创建和最终销毁。在某些程序中,这些操作可能会对性能产生影响。

#>[读者宏]

在Clozure CL 1.2及更高版本中,#> reader宏将以下文本作为关键字读取,保留文本的大小写。例如:

CL-USER> #>FooBar          :<F>OO<B>AR

结果关键字可用作外部类型,记录和访问者的名称。

close-shared-library library &key completely        [功能]

停止使用共享库,通知操作系统可以在适当时卸载它。

图书馆

SHLIB类型的对象,或者通过其so-name指定一个的字符串。

全然

布尔值。默认为T.

如果完全为T,则将的引用计数设置为0.否则,将其递减1.在任何一种情况下,如果引用计数变为0,则close-shared-library 释放所有消耗库的内存资源 并导致已知定义的任何EXTERNAL-ENTRY-POINT由它来解决。

defcallback name ({arg-type-specifier var}* &optional result-type-specifier) &body body            [Macro]

宣布名称 为特殊变量; 将其值设置为MACPTR,当由外部代码调用时,调用lisp函数,该函数需要指定类型的外部参数,并返回指定结果类型的外部值。对应于类型:ADDRESS的外部参数的任何参数变量都绑定到堆栈分配的MACPTR。

如果name 已经是一个回调函数指针,则其值不会改变; 相反,它被安排将调用更新版本的lisp回调函数。此功能允许以递增方式重新定义回调函数,就像Lisp函数一样。

defcallback 返回回调指针,例如name的值。

名称

可以制成特殊变量的符号

ARGspecifer

上面描述的外来参数类型关键字之一,或等效的外来类型说明符。此外,如果指定了关键字:WITHOUT-INTERRUPTS,则如果相应的var为非NIL,则将在禁用lisp中断的情况下执行回调。如果:WITHOUT-INTERRUPTS多次指定,则最右边的实例获胜。

VAR

一个符号(lisp变量),它将绑定到指定类型的值。

身体

一系列lisp表单,它们应返回一个可以强制转换为指定结果类型的值。

def-foreign-type name foreign-type-spec[Macro]

如果name为non nil,则将name定义为foreign-type-spec指定的外来类型的别名。如果 foreign-type-spec是命名结构或联合类型,则另外定义该结构或联合类型。

如果name是nil,则foreign-type-spec必须是命名的外部结构或联合定义,在这种情况下,外部结构或联合定义将生效。

请注意,外部类型名称有两个单独的名称空间:一个用于普通类型的名称,另一个用于结构和联合的名称。哪个名称指的是以明显的方式依赖于foreign-type-spec

名称

NIL或关键字; 关键字可能包含 转义构造

外资型规格

外部类型说明符,其语法(松散地)在上面定义。

external name        [微距]

解析对共享库中定义的外部符号的引用。

名称

一个简单的字符串,用于命名外部符号。区分大小写。

条目

EXTERNAL-ENTRY-POINT类型的对象,它维护由name命名的外部符号的地址。

如果已经为名称命名的符号存在EXTERNAL-ENTRY-POINT ,则找到它并返回它。如果没有,创建一个并返回它。

尝试解析内存地址的入口点,并识别包含的库。

请注意,在Darwin下,可从C调用的外部函数在其名称前加上下划线,如“_fopen”。

external-call name {arg-type-specifier arg}* &optional result-type-specifier         [Macro]

在通过解析与name关联的外部入口点获得的地址处调用外部函数,将每个arg的值作为相应arg-type-specifier指示的类型的外部参数传递。返回外部函数结果(强制转换为result-type-specifier指示的类型的Lisp对象),如果result-type-specifer为:VIL或NIL,则返回NIL

名称

一个lisp字符串。见上文,外部。

ARGspecifer

上面描述的外来参数类型关键字之一,或等效的外来类型说明符

ARG

由相应的arg-type-specifier指示的类型的lisp值

结果类型说明符

上面描述的外来参数类型关键字之一,或等效的外来类型说明符

%ff-call entrypoint {arg-type-keyword arg}* &optional result-type-keyword           [功能]

这是调用外部函数的最基本和最低级别的方法。它在地址入口点调用外部函数,将每个arg的值作为相应arg-type-keyword指示的类型的外部参数传递。返回外国函数结果(强制为通过结果类型关键字指示类型的Lisp的对象),或者nil如果结果类型关键字是:void或 NIL。

入口点

一个fixnum或macptr

ARG型关键字

上面描述的外来参数类型关键字之一

ARG

由相应的arg-type-keyword指示的类型的lisp值

结果类型关键字

上面描述的外来参数类型关键字之一

ff-call entrypoint {arg-type-specifier arg}* &optional result-type-specifier        [Macro]

在地址入口点调用外部函数,将每个arg的值作为相应arg-type-specifier指示的类型的外部参数传递。返回外部函数结果(强制转换为result-type-specifier指示的类型的Lisp对象),如果result-type-specifer为:VIL或NIL,则返回NIL

入口点

一个fixnum或MACPTR

ARGspecifer

上面描述的外来参数类型关键字之一,或等效的外来类型说明符

ARG

由相应的arg-type-specifier指示的类型的lisp值

结果类型说明符

上面描述的外来参数类型关键字之一,或等效的外来类型说明符

foreign-symbol-address name            [功能]

此函数尝试解析外部符号名称的地址(lisp字符串)。如果成功,则返回封装在macptr中的地址; 否则,它返回nil。

foreign-symbol-entry name          [功能]

此函数可以解析外部符号名称的地址(一个lisp字符串)。如果成功,则返回该地址的fixnum表示。否则,它返回nil。

free ptr[功能]

通过调用标准C函数释放ptr指向的外部内存free()。如果ptr是一个gcable指针(例如从中返回的对象ccl::make-gcable-record),则free首先通知垃圾收集器在实际调用之前已释放外部存储器free()。

make-heap-ivector element-count element-type          [功能]

ivector是一维数组,专门用于数字或字符元素类型。

make-heap-ivector在外部存储器中分配一个ivector。GC永远不会移动这个向量,实际上根本不会关注它。因此返回的指针可以安全地传递给外部代码。

必须使用显式取消分配向量 dispose-heap-ivector。

元素计数

正整数。

元件型

类型说明符。

向量

一个lisp矢量。初始内容未定义。

mactpr

指向存储在向量中的数据的第一个字节的指针。

尺寸

以八位字节为单位的返回向量的大小。

make-gcable-record typespec &rest initforms           [Macro]

以与以下相同的方式分配适合于保持由类型规范描述的外来类型的外部存储器块make-record。此外,ccl::make-gcable-record标记返回的对象gcable:换句话说,它通知垃圾收集器它可能在无法访问时回收该对象。

在所有其他方面,ccl::make-gcable-record工作方式与此相同make-record

使用gcable指针时,重要的是要记住macptr对象(它是一个lisp对象,或多或少与其他对象一样)与macptr对象指向的外部内存块之间的区别。如果gcable macptr对象是世界上唯一的东西(lisp世界或外国世界)引用外部内存的底层块,那么在无法引用它时释放外部内存是方便和明智的。如果其他lispmacptrs引用外部存储器的底层块,或者如果外部存储器的地址被外部代码传递并保留,那么如果使用那些其他引用,那么使GC释放存储器可能会产生令人不快的后果。

因此,请注意不要创建gcable记录,除非您确定返回的内容macptr将是对将要使用的已分配内存的唯一引用。

类型指定

外部类型说明符,或用作外部结构或联合的名称的关键字。

initforms

如果typespec表示的类型 是标量,则适合该类型的单个值; 否则,交替的字段名称列表和适合这些字段类型的值。

结果

A macptr封装外部堆上新分配的记录的地址。ccl::make-gcable-record 当垃圾收集器确定MACPTR描述它的对象无法访问时,将释放返回的外部对象。

make-record typespec &rest initforms[Macro]

扩展为在外部堆上分配和初始化typespec表示的类型的实例的代码。记录是使用C函数分配的,当不再需要时,ccl::malloc用户make-record必须显式调用该函数free来取消分配记录。

如果提供了initforms,则在初始化中使用其值。当类型是标量时,initforms是可以强制转换为该类型的单个值,或者没有值,在这种情况下使用二进制0。当类型为a时struct,initforms是一个列表,给出字段名称和每个字段的值。每个字段的处理方式与标量相同:如果给出了一个值,则必须对字段的类型强制执行; 如果不是,则使用二进制0。

当类型是数组时,可能不提供initforms,因为make-record无法初始化其值。 make-record也无法初始化struct自己struct的字段。用户make-record应该通过其他方式设置这些值。

一个可能有意义的限制是必须有可能在宏扩展时找到外来类型; make-record如果不是这样,则发出错误信号。

类型指定

外部类型说明符,或用作外部结构或联合的名称的关键字。

initforms

如果typespec表示的类型 是标量,则适合该类型的单个值; 否则,交替的字段名称列表和适合这些字段类型的值。

结果

A macptr封装外部堆上新分配的记录的地址。

make-record宏 是不方便的,因为这意味着typespec 不能是变量; 它必须是直接的价值。

如果不是这个要求,make-record可能是一个功能。但是,这意味着使用它的任何独立应用程序都必须包含接口数据库的副本(请参阅接口数据库),这是不合需要的,因为它很大。

open-shared-library name[功能]

要求操作系统加载Clozure CL的共享库以供使用。

名称

一个SIMPLE-STRING,它被认为是库的名称或文件系统路径。

图书馆

SHLIB类型的对象,它描述了由name表示的库。

如果操作系统可以加载由name表示的库,则返回描述该库的SHLIB类型的对象; 如果库已经打开,则增加引用计数。如果无法加载库,则发出SIMPLE-ERROR信号,其中包含来自操作系统的经常隐藏的消息。

;;; Try to do something simple.

? (open-shared-library “libgtk.so”)

> Error: Error opening shared library “libgtk.so”: /usr/lib/libgtk.so: undefined symbol: gdk_threads_mutex

> While executing: OPEN-SHARED-LIBRARY

 

;;; Grovel around, curse, and try to find out where “gdk_threads_mutex”

;;; might be defined. Then try again:

 

? (open-shared-library “libgdk.so”)

#<SHLIB libgdk.so #x3046DBB6>

 

? (open-shared-library “libgtk.so”)

#<SHLIB libgtk.so #x3046DC86>

 

;;; Reference an external symbol defined in one of those libraries.

 

? (external “gtk_main”)

#<EXTERNAL-ENTRY-POINT “gtk_main” (#x012C3004) libgtk.so #x3046FE46>

 

;;; Close those libraries.

 

? (close-shared-library “libgtk.so”)

T

 

? (close-shared-library “libgdk.so”)

T

 

;;; Reference the external symbol again.

 

? (external “gtk_main”)

#<EXTERNAL-ENTRY-POINT “gtk_main” {unresolved} libgtk.so #x3046FE46>

描述一个soname是什么并给出一个例子会很有帮助。

如果库已经打开,SHLIB是否仍会返回?

pref ptr accessor-form        [宏]

引用可通过ptr访问的外来类型(或外来类型的组件)的实例。

扩展为引用指示的标量类型或组件的代码,或返回指向复合类型的指针。

PREF可与SETF一起使用。

PTR

一个MACPTR

访问形式

用于命名外来类型或记录的关键字,如外部类型,记录和字段名称中所述

%reference-external-entry-point eep[功能]

尝试解析外部入口点的地址 EEP和成功时返回该地址的一个Fixnum表示; 否则表示错误。

EEP

EXTERNAL-ENTRY-POINT,由EXTERNAL宏获得。

rlet (var typespec &rest initforms* &body body[Macro]

执行主体,其中每个变量被绑定到macptr封装堆栈分配外国内存块中,分配,并从初始化的地址的环境类型指定initforms按照make-record。返回body返回的值。

未显式初始化的记录字段具有未指定的内容。

VAR

符号(lisp变量)

类型指定

外部类型说明符或外部记录名称。

initforms

如上所述,对于 make-record

rletz (var typespec &rest initforms* &body body[Macro]

这个宏就像rlet,除了堆栈分配的外部存储器被清零。

terminate-when-unreachable object        [功能]

终止机制是让垃圾收集器在对象即将成为垃圾之前运行函数的一种方法。它与Java具有的终结机制非常相似。它不是标准的Common Lisp,尽管其他Lisp实现具有类似的功能。当存在某种特殊的清理,释放或释放资源时,当某个对象不再被使用时,它会很有用。

当垃圾收集器发现某个对象不再被引用到程序中的任何位置时,它会释放该对象,从而释放其内存。但是,如果terminate-when-unreachable在任何时候都在对象上调用,则垃圾收集器首先调用泛型函数terminate,并将该对象作为参数传递给它。

因此,要使终止做一些有用的事情,您需要定义一个方法terminate。

因为调用terminate-when-unreachable只影响单个对象,而不是其类的所有对象,所以您可能希望在initialize-instance类的方法中调用它。当然,这只适用于您确实希望对给定类的所有对象使用终止。

目的

类的CLOS对象,其中存在泛型函数的方法terminate。

(defclass resource-wrapper ()

((resource :accessor resource)))

 

(defmethod initialize-instance :after ((x resource-wrapper) &rest initargs)

(ccl:terminate-when-unreachable x))

 

(defmethod ccl:terminate ((x resource-wrapper))

(when (resource x)

(deallocate (resource x))))

教程:在Lisp堆上分配外部数据

unuse-interface-dir dir-id[功能]

告诉Clozure CL 从接口目录列表中删除dir-id表示 的接口目录,该目录参考外部类型和功能信息。如果目录在搜索列表中,则返回T,否则返回NIL。

DIR-ID

一个关键字,其pname映射为小写,命名为“ccl:headers;”的子目录 (或“ccl:darwin-headers;”)

use-interface-dir dir-id[功能]

告诉Clozure CL将dir-id表示的接口目录添加到接口目录列表中,该目录参考外部类型和功能信息。安排在任何其他目录之前搜索该目录。

请注意,use-interface-dir 只是在搜索列表中添加一个条目。如果命名目录在文件系统中不存在或者不包含一组数据库文件,则当Clozure CL尝试在该目录中打开某个数据库文件时可能会发生运行时错误,并且它将尝试打开这样的数据库文件何时需要查找任何外来类型或功能信息。unuse-interface-dir 在这种情况下可能会派上用场。

DIR-ID

一个关键字,其pname映射为小写,命名为“ccl:headers;”的子目录 (或“ccl:darwin-headers;”)

人们通常希望接口信息在编译时可用(或者,在许多情况下,在读取时)。一个典型的习语是:

(eval-when (:compile-toplevel :execute)          (use-interface-dir :GTK))

 

使用:GTK接口目录可以获得有关外部类型,函数和常量的信息。通常需要在实际调用外部代码之前加载外部库,对于GTK可以这样做:

(load-gtk-libraries)

现在应该可以做以下事情:

(#_gtk_widget_destroy w)

http://mip.i3geek.com

Leave a Reply

搜索

分类目录

公 告

本网站学习论坛:

www.zhlisp.com

lisp中文学习源码:

https://github.com/zhlisp/

欢迎大家来到本站,请积极评论发言;

加QQ群学习交流。