Normally, cl-autowrap
will import and create functions and macros
for C functions and record accessors. This may be fine for relatively
small libraries, but as imports become larger, this can balloon into
thousands and thousands of definitions, which can take a very long
time to generate, and a considerable amount of space.
cl-plus-c
provides an alternate mechanism, and can be used in
conjunction with the following new parameters to c-include
:
(c-include "file.h"
:no-accessors t :no-functions t)
Note that neither of these are required for cl-plus-c
, but they do
disable the generation of accessor functions and macros, which can
significantly speed up compile times. These are unnecessary with
cl-plus-c
, though they may be used in conjunction.
The following new forms are provided to allow access to functions, accessors, and allocation:
c-fun
: Allows calling functions by symbol rather than as a macroc-ref
: Allows path-like dereferencing of typesc-let
/c-with
: Allows simple allocation and convenient access of "variables"
Normally in cl-autowrap
, one can simply call C functions by their
translated symbol name:
(foo x y)
With cl-plus-c
, one may call this by name, even if the macro above
is not defined:
(c-fun foo x y)
This generates essentially identical code.
Instead of generating potentially thousands or tens of thousands of
recursive accessor functions, cl-plus-c
will allow accessors to be
generated on demand at compile time based on a path:
(c-ref OBJECT C-TYPE &rest FIELDS...)
For instance, given the following struct:
typedef struct foo {
int a, b;
char c[3];
struct {
unsigned int b0 : 2, b1 : 3;
struct {
char x, y;
} s;
} x[2];
} foo_t;
We may access the various fields as follows:
(c-ref object foo-t :a) ;; => object.a
(c-ref object foo-t :c 2) ;; => object.c[2]
(c-ref object foo-t :x 2 :b0) ;; => object.x[2].b0
(c-ref object foo-t :x :b0) ;; => object.x[0].b0
(c-ref object foo-t :c *) ;; => *(object.c)
(c-ref object foo-t :x 1 :s :x &) ;; => &(object.x[1].s.x)
SETF
can also be applied to these forms with some exceptions:
&
at the end of the form means "return the address of", so you can't set thatstring
at the end of a form means "convert this to a lisp string", and can't be set- You may not set the value of a record directly
Safety note: There isn't much safety here! Nothing stops you from
referencing objects out-of-bounds, setting invalid pointers, etc.
This is not considerably different than cl-autowrap
accessors, or
simply referencing things in C. Be careful!
One big goal for cl-plus-c
is for pointers-vs-values to be clear:
how to get each, and when you need each. This is actually implemented
so that each are more consistent than C, but it's been pointed out
this isn't necessarily intuitive from the above.
The main question is "how do I get different things in cl-plus-c
,
and when do I use &
?". The answer is that you always get the
value by default; if you want the address of the value, you use
&
.
Imagine the following:
struct a {
char c0;
char c1;
};
struct b {
int x;
int y;
};
struct c {
int i;
char c[5];
struct a *s;
struct b t;
} c;
The basic cases:
- If you want the value of
c.i
, then you use(c i)
. If you want the address&(c.i)
, then you use(c i &)
. - If you want the value of
c.s->c0
, then you use(c s c0)
. If you want the address&(c.s->c0)
, you use(c s c0 &)
. This consists of some pointer math and dereferences along the way, but it still gives you the address of the last-named field, much like C. - If you want the value of
c.s
, this value is a pointer. You sayc.s
in C, and you get a pointer; likewise you say(c s)
and you get a pointer (or a wrapper, in this case).
Now for some "odd" cases:
- Note that in
struct c
, thechar c[5]
field is not a pointer; 5 chars are inside the struct. But, if you sayc.c
in C, this gives you a pointer! Incl-plus-c
, saying(c c)
gives you the value, the equivalent ofc.c[0]
. If you want the location of the struct field, you say(c c &)
, just like every other case. - You cannot take the value of a record. Thus, unlike saying
(c s)
, which results in a pointer (wrapper), saying(c t)
is an error. But if you want the location, you still say(c t &)
. - There is no ambiguity between
.
and->
, and whether the field is a pointer or value; thus dereferencing a field uses identical syntax.
For ultimate convenience, cl-plus-c
defines c-let
/c-with
, which
will let you define, set, and access values somewhat like C:
(c-with ((x :int)) ;; same as c-let with :free T
(print x) ;; => whatever value was in memory
(setf x 42)
(print (x &)) ;; => &x (pointer)
(print x)) ;; => 42
You may specify one or more bindings, which are in the following form:
(NAME C-TYPE &key (count 1) free ptr from value calloc)
count
: By default, 1 item is allocated; you may make an array by specifying morefree
:C-LET
will not free the memory by default,C-WITH
will. Specifyingfree
to be explicit (e.g., per-item).ptr
is mutually exclusive withalloc
andfree
. Nothing will be allocated or freed; instead, the pointer specified will be used. This is useful when pointers from foreign code are acquired.from
is similar toptr
(and should not be used withptr
), except it takes an existing wrapper object and provides bindings as above.value
sets the initial value. This may not be used with:from
or:ptr
.calloc
: ifT
, usesCALLOC
to allocate, zeroing the memory allocated.NIL
by default.
Inside the block:
NAME
will reference the value; if this is a record, it will be a wrapper, if this is a basic type, it will be the value of the type (such as an integer)(setf NAME VALUE)
may be used, as above, to set the contents of the variable, much like C(NAME ...)
is equivalent to(c-ref NAME C-TYPE ...)
, so you do not have to manually specify this every time(setf (ref :string-field) "STRING")
will allocate, copy, and assign a string pointer to the field. This must be freed manually. Due to the semantics of plus-c, the easiest way to do this is(free (ref :string-field * &))
, which implicitly avoids conversion back to a lisp string, and returns the pointer directly.
The c-val
macro is a convenience feature for "rebinding" an existing
wrapper. You could write the following:
(defun some-fun (val)
(c-let ((val val-type :from val))
...))
But with c-val
you can simply write the following:
(defun some-fun (val)
(c-val ((val val-type))
...))
;;; Or:
(defun some-fun (...)
(c-val ((val val-type (get-a-val-form ...)))
...))
Note: Types are no longer declared with THE
, it was causing more
problems than it was solving, and defeating the point of avoiding
consing by wrapper reuse.