Cookbook(3tcl)
๐ Cookbook(3tcl) โ Tcl CFFI package ๐
This is the Cookbook(3tcl) page from the Tcl CFFI package. It's the recipe collection for mapping C declarations to CFFI declarations depending on where the declaration lives โ parameter, struct field, etc. The vibes are cute; the facts are not moving. ๐ซถ
๐ธ NAME ๐ธ
Cookbook โ commands in namespace Cookbook. That's the whole name section, bestie. ๐
๐ COOKBOOK ๐
Using CFFI to wrap a few ad-hoc functions is fairly straightforward. Wrapping a large API into a complete package can be more involved, because you have to make decisions about how C types are translated to script-level annotations. This page provides some recipes for mapping C declarations to CFFI declarations depending on the declaration context (parameter, struct field, etc.). No cap. โจ
๐ DEFINING A FUNCTION ๐
A function definition has three components, and yes, they are all main character energy:
+------------------+ +-----------+ +--------------+
|Calling convention+ +Return type+ +Parameter list|
+------------------+ +-----------+ +--------------+
Function definition syntax
The sections below detail each one. ๐ฆ
๐ CALLING CONVENTION ๐
Check the header file or documentation of the function for the expected calling convention. Generally, this is only an issue if the application supports the 32-bit Windows platform. Note the function calling convention may be defined through a macro such as WINAPI etc. Most Windows OS API's are defined using the stdcall convention. The default C language calling convention is either unspecified or _cdecl in C header files. Most third party API's are defined using this convention. CFFI does not support any calling convention other than these two.
LIB function RETURNTYPE PARAMLIST
LIB stdcall RETURNTYPE PARAMLIST
Calling convention syntax
- Define using
stdcallif the function is defined on 32-bit Windows using__stdcallor its equivalent. Note on other platforms, including 64-bit Windows, this is treated as equivalent tofunction. - Define using
functionotherwise after ensuring it does not use some other esoteric platform-specific convention (rare).
Examples ๐
C
WINAPI DWORD GetCurrentProcessId(void);
int getpid(void);
CFFI
KERNEL32 stdcall GetCurrentProcessId ulong {}
LIBC function getpid int {}
Note WINAPI in the Windows SDK headers maps to __stdcall. ๐
๐ฆ RETURN TYPE DECLARATION ๐ฆ
๐ Void returns ๐
+- -+
void | saveerrors |
+- -+
Syntax for void return type
The void return type does not permit any annotations.
Examples ๐
C
void tzset(void)
CFFI
LIBC function tzset void {}
๐ Integer returns ๐
+- +- -+ -+ +- -+ +- -+ +- -+ -+
schar |{enum E} |bitmask | | |discard| | zero | | lasterror | |
uchar | +- -+ | +- -+ | nonzero | | winerror | |
short +- -+ | positive | | errno | |
ushort | nonnegative| | {onerror HANDLER} | |
int | -+ | saveerrors | |
uint | +- -+ |
long +- -+
ulong
longlong
ulonglong
Syntax for integer return types
- If the returned values are constrained to a set and a symbolic representation is preferred, optionally add the
enumattribute with the enumeration defined as a literal or separately through the::cffi::enumcommand. - If a symbolic representation is preferred as above and the value is actually a bit mask, optionally add the
bitmaskattribute to have the value returned as a list of symbols from the enumeration. - If return values may indicate error conditions, add an error checking annotation like
zero,nonzero, etc. - If an error checking annotation is added and additional detail is available from the system, add
lasterror,winerrororerrnoas appropriate. If error information is available through some other API, add theonerrorhandler. This may also be used to filter errors or convert error conditions to success. - If error conditions are not detectable from the return value but the function expects the caller to check
errnoorGetLastError()after the call returns, add thesaveerrorsannotation instead of the above. Then call::cffi::savederrorsto retrieve or check for errors. - If the return value is simply a boolean indicating success or failure, include the
discardannotation to have an empty string returned on success. Note this does not affect exceptions raised on error.
Examples ๐
C
int open(const char *pathname, int flags);
CFFI
A basic definition assuming LIBC is the library wrapper object:
LIBC function open int {pathname string flags int}
A more complete definition:
LIBC function open {int nonnegative errno} {pathname string flags int}
๐ง Floating point returns ๐ง
float +- -+
double |discard|
+- -+
Syntax for floating point return types
Floating point types float and double do not permit any annotations on return type declarations.
Examples ๐ธ
C
double sin(double x);
CFFI
LIBM function sin double {x double}
๐ Pointer returns ๐
Pointer return values may be dealt with at the script level either as raw pointers or as implicitly dereferenced values.
- Declare the function as returning a raw pointer if the returned pointers point to resources that have to be freed at a later point. If typed as implicitly dereferenced values, the raw pointer will not be available for freeing.
- Declare the function as returning an implicitly dereferenced value if the returned pointers point to some internal or static storage that does not need to be freed. An example is the
strerrorC runtime library function. Note that implicitly dereferenced types are a convenience; they may be typed as raw pointers instead while the reverse will likely produce memory leaks.
The type declarations for raw and implicitly dereferenced pointers are separately described below.
๐ Raw pointers ๐
+- -+ +- -+
pointer | counted| | nullok |
pointer.TAG | unsafe | | lasterror |
| pinned | | errno |
+- -+ | {onerror HANDLER} |
| saveerrors |
+- -+
Syntax for raw pointer return type
- The
TAGis optional but recommended wherever possible for additional tag-based type checks. If the pointer's target type is a struct, it is recommended, but not mandatory, that the tag used be the name of the struct instance command. This makes it convenient to access the referenced memory using the struct methods. - By default, pointers returned from functions are marked as safe pointers. This is appropriate for unique pointers that are returned for allocated resources and are only valid until they are deallocated through a corresponding free function. A variation is when the pointer returned is not unique, as is the case for reference counted resources. In this case the pointer should be annotated as
counted. A special case of this is when the returned pointer is static or a "pseudo-pointer" that is always valid, in which case thepinnedannotation should be applied.
For cases where none of the above apply โ for example, there is no clear transfer of ownership or freeing, or pointers are internal to the API โ there are two choices. First, it can be defaulted as a safe pointer. In this case, since there is no free function, the ::cffi::pointer dispose command must be called at the appropriate time by the application to unregister the pointer. Alternatively, the unsafe annotation can be added to the declaration. In this case, there is no need to unregister the pointer, but it can only be used in places (arguments and struct fields) that also have the unsafe annotation. For more details on pointer management and safety, see Pointer safety.
- Irrespective of the pointer safety annotations,
NULLpointers returned from functions will by default raise a genericPointer is NULLexception. As for integer return values, if additional information is available from the system or API, the type can be annotated withlasterror,errnooronerror. On the other hand, if aNULLpointer is a valid return value, thenullokannotation will suppress the exception and return aNULLpointer at the script level. - If error conditions are not detectable from the return value but the function expects the caller to check
errnoorGetLastError()after the call returns, add thesaveerrorsannotation instead of the above. Then call::cffi::savederrorsto retrieve or check for errors.
Examples ๐ฆ
C
void *malloc(size_t sz);
FILE *fopen(const char *pathname, const char *mode);
git_tree_entry *git_tree_entry_byname(git_tree *, const char *filename);
CFFI
LIBC malloc {pointer errno} {sz size_t}
LIBC fopen {pointer.FILE errno} {pathname string mode string}
LIBGIT git_tree_entry_byname {pointer.git_tree_entry unsafe} {
pTree {pointer.git_tree counted}
filename string
}
The unsafe and counted annotations on git_tree_entry_byname arise from the libgit2 API. The returned pointer is internal to libgit2 and hence marked as unsafe while git_tree structs are reference counted.
๐ Implicitly dereferenced pointers ๐
Raw pointers at the script level, while as versatile as their C counterparts, are not as convenient to use as plain old values. Additional steps are needed to access the memory as script-level values. Declaring pointer return types as implicitly dereferenced obviates the need for this additional step. As stated earlier, implicit dereferencing should only be done when there is no need for the pointer value itself for freeing later.
For types other than pointers to character (or unicode) strings, the CFFI declaration has the form
+- -+
TYPE byref| nullok |
+- -+
Syntax for implicitly dereferenced pointers
where TYPE may be one of the numeric types or a STRUCT. The byref annotation on the return type declaration means the function return value is nominally a pointer that references the true function result. ๐
๐ซ Examples ๐ซ
C
struct git_oid *git_tree_oid(git_tree *pTree);
CFFI
LIBGIT function git_tree_oid {struct.git_oid byref} {pTree pointer.git_tree}
A call to the git_tree_oid function will then return the git_oid struct (assumed defined previously with ::cffi::Struct) in its script level dictionary form. The actual C level pointer is not accessible but not needed as it is internal to the git_tree and not to be freed or manipulated. ๐ซถ
As for raw pointers, a null pointer return will raise an exception by default. The nullok annotation may suppress the exception in the case that TYPE is a struct with defaults defined for all fields (possibly with the -clear option). In that case, the dictionary corresponding to a struct value with all fields defaulted is returned. For all other types and structs with at least one field without a default, an exception is raised irrespective of nullok. ๐ค
For C pointers typed as char * which return pointers to character strings, the implicit dereference syntax is
+- -+ +- -+
STRING| nullok | | multisz |
+- -+ +- -+
Syntax for character string return types
The byref is not present because string, unistring and winstring are already implicit pointers, (string byref would correspond to char **, not char *). An optional nullok annotation will cause a NULL pointer return to be mapped to the empty string instead of generating an exception. โจ
Note that if STRING is string, an encoding identifier may be optionally attached, e.g. string.utf-8.
The multisz may only be used when STRING is winstring. The value is interpreted as holding a Windows MULTI_SZ string type and returned as a list. ๐
Examples
C
const char *strerror(int);
char *strstr(const char *haystack, const char *needle);
CFFI
LIBC function strerror string {error_code int}
LIBC function strstr {string nullok} {haystack string needle string}
Note the definition of strstr to return an empty string if the needle is not found instead of raising an exception. ๐
๐งธ Struct returns ๐งธ
struct.STRUCT
Syntax for struct return type
A struct function return type does not permit any annotations. STRUCT must be a previously defined ::Struct. Returning a struct by value is rarely seen in C API's. ๐ฆ
Examples
C
typedef struct {
int quot;
int rem;
} div_t;
div_t div (int numer, int denom);
CFFI
Struct create div_t {quot int rem int}
LIBC function struct.div_t {numer int denom int}
๐งต PARAMETER DECLARATION ๐งต
Parameters to a C function may be used to pass values to the function (input parameters), retrieve values from the function (output parameters), or (input-output parameters). ๐
Scalar values and structs that are passed by value are always input parameters. Input parameters may also be passed by reference via a pointer, generally with a const attribute to indicate the function does not modify the referenced value. ๐ธ
Output and input-output parameters are always passed to C function as pointers to a location where the value is stored. At the script level the argument is the name of the variable where the value is stored and not the value itself. ๐ฅ
๐ Integer parameters ๐
โจ Integer input parameters โจ
schar +- -+ +- -+ +- -+ +- -+ +- -+ +- -+
uchar |in | |byref| |{enum E}| |bitmask| |{default N}| |lasterror |
short +- -+ +- -+ +- -+ +- -+ +- -+ |winerror |
ushort |errno |
int |{onerror HANDLER}|
uint +- -+
long
ulong
longlong
ulonglong
Syntax for integer input parameters
- The
inannotation is optional because it is the default if none ofout,retvalorinoutannotations is present. ๐ - The
byrefannotation should be specified iff the value is being passed by reference, i.e. the function parameter is a pointer to the value (e.g.int *at the C level). This is very rare for integer values that are input only. In either case, whether thebyrefparameter is present or not, the value at script level is still specified directly and not through a variable. ๐ซถ - Add the
enumannotation to pass enumeration symbolic values for readability purposes. Note integer values will still be accepted. โจ - If the parameter holds a bit mask, add the
bitmaskannotation which allows the argument to be a list of integers which will be bitwise OR-ed to construct the value passed to the function. May be combined withenumto pass a list of symbols. ๐ - The
defaultannotation may be used to supply a default value to be passed if no argument is supplied for the parameter when the function is invoked. All succeeding parameter in the parameter list must also havedefaultannotations. ๐ - Error handling annotations like
errnomay be present but are ignored. This is to permit the same aliases to be used in both return and parameter declarations. ๐
Examples
C
int SetHandleInformation(void * hObject, unsigned long dwMask, unsigned long dwFlags);
CFFI
cffi::enum define HANDLE_FLAGS {INHERIT 1 PROTECT_FROM_CLOSE 2}
KERNEL32 stdcall SetHandleInformation {int nonzero lasterror} {
hObject pointer
dwMask {ulong {enum HANDLE_FLAGS} bitmask}
dwFlags {ulong {enum HANDLE_FLAGS} bitmask}
}
๐ Output and input-output integer parameters ๐
schar +- -+ +- -+ +- -+ +- -+ +- -+ +- -+
uchar |out | |{enum E}| |bitmask| |storeonerror| |lasterror | |nullifempty |
short |inout | +- -+ +- -+ |storealways | |winerror | +- -+
ushort |retval| +- -+ |errno |
int +- -+ |{onerror HANDLER}|
uint +- -+
long
ulong
longlong
ulonglong
Syntax for integer output and input-output parameters
- Output and in-out parameters in C are always passed as pointers to the location where output value is to be stored. Thus
outandinoutalways implybyrefwhich is redundant and need not be specified. ๐ - The script level argument corresponding to
outandinoutparameters are the name of the variable to which the output value is to be assigned. Forinoutparameters this must exist at the time of function call and hold a valid value for the type. ๐ซถ - Use the
retvalannotation for output parameters where you want the output parameter value from the function to be returned as the function result in lieu of the actual function return value. SeeOutput parameters as function result. ๐ - Add the
enumannotation if output values are to be mapped to symbolic names for readability. Further, if the values returned are actually bitmasks, addbitmaskto have the returned value transformed to a list of symbolic names. โจ - If the function return value is subject to error checks (for example null pointer checks) and fails the checks, output values are not stored in variables as they may hold garbage values. The
storealwaysannotation indicates the output value is valid regardless of function failure. Thestoreonerrorannotation indicates the output value is valid only on function failure (for example an error code). SeeErrors and output parameters. ๐ญ - Error handling annotations like
errnomay be present but are ignored. This is to permit the same aliases to be used in both return and parameter declarations. ๐ - Add the
nullifemptyannotation foroutandinoutfunction parameters that are allowNULLto be passed as the argument when no output value is of interest. When the function wrapper is invoked, pass the empty string as the output variable name. ๐
Examples
C
int GetHandleInformation(void* hObject, unsigned long *lpdwFlags);
CFFI
cffi::enum define HANDLE_FLAGS {INHERIT 1 PROTECT_FROM_CLOSE 2}
KERNEL32 stdcall GetHandleInformation {int nonzero lasterror} {
hObject pointer
flags {ulong retval {enum HANDLE_FLAGS} bitmask}
}
๐ธ Floating point parameters ๐ธ
๐ฆ Floating point input parameters ๐ฆ
+- -+ +- -+ +- -+
float |in | |byref| |{default N}|
double +- -+ +- -+ +- -+
Syntax for floating point input parameters
- The
inannotation is optional because it is the default if none ofout,retvalorinoutannotations is present. ๐ - The
byrefannotation should be specified iff the value is being passed by reference, i.e. the function parameter is a pointer to the value (e.g.double *at the C level). This is very rare for integer values that are input only. In either case, whether thebyrefparameter is present or not, the value at script level is still specified directly and not through a variable. ๐ซถ - The
defaultannotation may be used to supply a default value to be passed if no argument is supplied for the parameter when the function is invoked. All succeeding parameter in the parameter list must also havedefaultannotations. ๐
Examples
C
double sin(double x);
CFFI
LIBM sin double {x double}
๐ Out and in-out floating point parameters ๐
+- -+ +- -+
float out |storeonerror| |nullifempty|
double inout |storealways | +- -+
retval +- -+
Syntax for floating point output and input-output parameters
- Output and in-out parameters in C are always passed as pointers to the location where output value is to be stored. Thus
outandinoutalways implybyrefwhich is redundant and need not be specified. ๐ - The script level argument that goes with
outandinoutparameters is literally just the name of the variable the output value gets assigned to ๐ . Forinoutbesties, that variable must already exist at the time you call the function, and it has to hold a valid value for the type. No vibes-only placeholders. - Use the
retvalannotation on output parameters when you want the output parameter value to come back as the function result instead of the actual function return value โจ. See Output parameters as function result. - If the function return value is subject to error checks (think null pointer checks, the classics ๐ญ) and it fails those checks, output values are not stored in variables, because they may hold straight-up garbage. Enter
storealways: it says the output value is valid regardless of function failure. Andstoreonerrorsays the output value is valid only on function failure (like an error code, iykyk). See Errors and output parameters. - Add the
nullifemptyannotation foroutandinoutfunction parameters that are allowed to haveNULLpassed as the argument when you don't care about any output value ๐. When the function wrapper is invoked, pass the empty string as the output variable name. Simple as that.
๐ Pointer parameters
Same energy as return values, bestie: pointer parameters to C functions can be handled either as raw pointers or as implicitly dereferenced values ๐ฆ.
Generally speaking, pointers that are only used in the C API to pass arguments by reference โ like passing big or non-scalar input values (structs and arrays, hello ๐) or receiving output values โ can be declared as implicitly dereferenced. The CFFI declarations for those get covered in other sections with the byref and out annotations.
This section is only about raw pointers, where the pointer values are directly visible at the script level ๐. You need this when the pointers reference resources that need to be freed, or for more complex structures in memory with multiple indirections etc. etc.
Raw pointer input parameters
+- -+ +- -+ +- -+ +- -+ +- -+ +- -+
pointer |in | |byref| |counted| |dispose | |nullok| |{default PTRVAL}|
pointer.TAG +- -+ +- -+ |unsafe | |disposeonsuccess| +- -+ +- -+
|pinned | | |
+- -+ +- -+
Syntax for raw pointer input parameters โจ
- The
TAGis optional but highly recommended wherever possible, for extra tag-based type checks ๐. If the pointer's target type is a struct, it's recommended (but not mandatory) that the tag you use is the name of the struct instance command. That way it's convenient to access the referenced memory using the struct methods. Slay. - The
inannotation is optional because it's the default when none ofout,retvalorinoutare present. - The
byrefannotation should be specified iff the pointer is being passed by reference โ i.e. the function parameter is a pointer to a pointer ๐ณ. Generally, input parameters passed by reference will haveconstin the C parameter declaration. - By default, pointers passed into functions are expected to be registered as safe pointers. They can alternatively be annotated with
countedorpinned, which also get the default handling in the case of input parameters. If the pointer isn't expected to have been registered, use theunsafeannotation. For more deets on pointer management and safety, see Pointer safety. - When a C function releases the resource associated with a registered pointer (safe or counted), that pointer should be unregistered ๐. Put the
disposeannotation on the parameter annotation to tell CFFI the pointer should be unregistered.disposeonsuccessis the same deal, except the pointer is only unregistered if the function returns successfully. This annotation has no effect on pinned pointers and should not be used together with theunsafeannotation โ ๏ธ. More details in Pointer safety. - CFFI assumes by default that functions don't accept
NULLpointers, and it will raise an exception if you try to pass one ๐. Thenullokannotation says the function does allow the pointer to beNULL. - The
defaultannotation can supply a default value to pass when no argument is given for the parameter at function invocation. Every succeeding parameter in the parameter list must also have adefaultannotation. No exceptions, bestie.
Examples ๐
C
void free(void *ptr);
void *realloc(void *ptr, size_t size);
CFFI
LIBC function free void {ptr {pointer dispose}}
LIBC function realloc {pointer errno} {
ptr {pointer disposeonsuccess}
size size_t
}
Raw pointer out and in-out parameters
+- -+ +- -+ +- -+ +- -+ +- -+ +- -+
pointer |inout | |counted| |dispose | |nullok | |{default PTRVAL}| |storealways |
pointer.TAG |out | |unsafe | |disposeonsuccess| |nullifempty| +- -+ |storeonerror|
|retval| |pinned | +- -+ +- -+ +- -+
+- -+ +- -+
Syntax for output and input-output raw pointer parameters ๐
- Output parameters in C are always passed as pointers to the location where the output value is to be stored. So
outalways impliesbyrefโ which meansbyrefis redundant and you don't need to type it. Iconic efficiency. - The script level argument corresponding to an
outparameter is the name of the variable the output value gets assigned to. - Use the
retvalannotation on output parameters when you want the output parameter pointer value returned as the function result instead of the actual function return value โจ. See Output parameters as function result. - By default, pointers returned in
outand in-out parameters are registered as safe pointers. They can alternatively be annotated withcountedorpinnedto be registered as counted or pinned pointers, orunsafeif they're not to be registered at all. More in Pointer safety. - When a C function releases the resource associated with a registered pointer (safe or counted), the pointer should be unregistered ๐. Put the
disposeannotation on the parameter annotation to tell CFFI to unregister it. The annotation can also be applied to pinned pointers, but it has no effect there.disposeonsuccessis similar, except the pointer is only unregistered if the function returns successfully. Now listen: thedisposeanddisposeonsuccessannotations are not valid with theoutorretvalannotations ๐ซ. See Pointer safety for the full tea. - The
nullokannotation indicates that pointers passed in and out of the function can beNULL. Without it, an exception is raised if the pointer stored on output isNULLโ or, in the case ofinout, if the pointer passed in isNULL๐ญ. - Add the
nullifemptyannotation foroutandinoutfunction parameters that allowNULLto be passed as the argument when no output value is of interest. When the wrapper is invoked, pass the empty string as the output variable name ๐. - The
defaultannotation can supply a default pointer value to pass when no argument is given for the parameter at invocation. All succeeding parameters in the parameter list must also havedefaultannotations. This annotation cannot be used with theoutandretvalannotations โ ๏ธ. - If the function return value is subject to error checks (null pointer checks, the usual) and fails them, output values are not stored in variables, because they may hold garbage values.
storealwayssays the output value is valid regardless of function failure.storeonerrorsays the output value is valid only on function failure (e.g. an error code). See Errors and output parameters.
Examples ๐ง
C
int git_repository_open_bare(git_repository **out, const char *bare_path);
int ConvertSidToStringSidA(SID *pSid, char **strSid);
CFFI
LIBGIT2 function git_repository_open_bare {int zero} {
out {pointer.git_repository counted retval}
string bare_path
}
ADVAPI32 stdcall ConvertSidToStringSidA {int nonzero lasterror} {
pSid pointer.SID
strSid pointer
}
KERNEL32 stdcall LocalFree {pointer nullok} {p {pointer dispose}}
In the libgit2 example, out is annotated as counted because git_repository is a reference counted opaque structure ๐. The retval annotation is added so CFFI returns the output parameter value as the result of the function.
In the Win32 example, the returned pointer from ConvertSidToStringSidW needs to be freed with LocalFree, and that's why it has to be declared as a raw pointer ๐ซถ. You can grab the string from the returned pointer with ::cffi::memory tostring before freeing it.
๐ฆ Struct parameters
Struct values are represented at the script level as dictionaries ๐ . Which means you need their definition, as described in Structs and Defining structs.
Struct input parameters
+- -+ +- -+ +- -+ +- -+
struct.STRUCT |in | |byref| |{default DICT}| |nullifempty|
+- -+ +- -+ +- -+ +- -+
Syntax for struct input parameters ๐ธ
- The
inannotation is optional because it's the default when none ofout,retvalorinoutare present. - The
byrefannotation should be specified iff the value is being passed by reference โ i.e. the function parameter is a pointer to a struct value that's being passed ๐. Generally, input parameters passed by reference will haveconstin the C parameter declaration. Irrespective of this annotation, the argument is always a dictionary, not a pointer. Periodt. defaultโ bestie, this one lets you give a parameter a default value that gets passed if no argument is supplied for it when the function is invoked. But listen: every parameter after it in the parameter list also has to have adefaultannotation. No skipping ahead ๐nullifemptyโ some C functions are totally fine with aNULLpointer for optional parameters, and this annotation marks exactly those. Pass an empty dictionary as the script-level argument and aNULLpointer gets passed to the function. It only makes sense when thebyrefannotation is also there, iykyk.
Examples
C
int git_checkout_head(git_repository *repo, const git_checkout_options *opts);
CFFI
cffi::Struct create git_checkout_options {...}
LIBGIT function git_checkout_head {int zero} {
pRepo pointer.git_repository
opts {struct.git_checkout_options byref nullifempty}
}
Output and input-output struct parameters ๐
+- -+ +- -+
struct.STRUCT out |storeonerror| |nullifempty|
inout |storealways | +- -+
retval +- -+
- Output and in-out parameters in C are always passed as pointers to the location where the output value is to be stored. So
outandinoutalways implybyrefโ itโs redundant and need not be specified. Slay, less typing. - The script-level argument corresponding to
outandinoutparameters is the name of the variable to which the output value is to be assigned. Forinoutparameters, that variable must exist at the time of the function call and hold a valid value for the type. No ghost variables, okay? - Use the
retvalannotation for output parameters when you want the output parameter value from the function to come back as the function result instead of the actual function return value. See Output parameters as function result. - If the function return value is subject to error checks (for example null pointer checks) and fails the checks, output values are not stored in variables because they may hold garbage values. The
storealwaysannotation says the output value is valid regardless of function failure. Thestoreonerrorannotation says the output value is valid only on function failure (for example an error code). See Errors and output parameters. These annotations are rarely applicable to struct output parameters. - Add the
nullifemptyannotation foroutandinoutfunction parameters that allowNULLto be passed as the argument when no output value is of interest. When the function wrapper is invoked, pass the empty string as the output variable name.
Examples
C
int GetWindowRect(void *hWnd, RECT *lpRect);
CFFI
cffi::Struct create RECT {left int top int right int bottom int}
LIBUSER32 stdcall GetWindowRect int {hwnd pointer rect {struct.RECT out}}
String parameters ๐ฆ
Input string parameters
string +- -+ +- -+ +- -+ +- -+
string.ENCODING |in | |byref| |nullifempty| | multisz |
unistring +- -+ +- -+ +- -+ +- -+
winstring
- C does not have a string type, and the
string,unistring, andwinstringCFFI types map to pointers to nul-terminated character strings:char*in the case ofstring,Tcl_UniChar *in the case ofunistring, andWCHAR *in the case ofwinstring. For input parameters, they have theconstattribute. - The
inannotation is optional because it is the default if none ofout,retval, orinoutannotations are present. - For the
stringtype, an optional encoding may be specified and defaults to the system encoding. If a library API uses a specific encoding (commonly UTF-8), it may be convenient to define a type alias. Cute and practical. - The
byrefannotation should be specified iff the value is being passed by reference, i.e. the function parameter is a pointer to the pointer to the character string (double indirection). This is rare for passing character string values that are input only. In either case, whether thebyrefparameter is present or not, the value at script level is still specified directly and not through a variable. - Some C functions accept a
NULLpointer for optional parameters. Thenullifemptyannotation identifies such parameters. In this case, passing an empty string as the script-level argument will result in aNULLpointer being passed to the function. - The
multiszmay only be used forwinstring. The passed value is a list of strings and is mapped to a WindowsMULTI_SZstring type.
Examples
C
int chdir(const char *path);
CFFI
LIBC function chdir {int zero errno} {path string}
Output and input-output string parameters
Character strings are returned from C functions in one of two ways, bestie:
- The C function expects the caller to supply a pointer to an array of characters. The size of this array may be a compile-time constant or passed through another parameter to the function. The function then stores the string in this buffer. The C parameter declaration is of the form
char *buforchar buf[]. - The C function expects the caller to pass a pointer to a memory location where it will store the pointer to a string that is either dynamically allocated or an internal static string. The C declaration is of the form
char **ptr_to_buf, reflecting the double indirection.
The two are handled differently in CFFI as described below.
Output character arrays
+- -+ +- -+ +- -+ +- -+
chars[N] |out | |storealways | |nullifempty| |multisz |
chars.ENCODING[N] |inout | |storeonerror| +- -+ +- -+
unichars[N] |retval| +- -+
winchars[N] +- -+
This is the first case described above, where the C function expects a pointer to a character (or Tcl_UniChar and WCHAR in the case of unichars and winchars) array.
- The script-level argument corresponding to
outandinoutparameters is the name of the variable to which the output value is to be assigned. Forinoutparameters, that variable must exist at the time of the function call and hold a valid value for the type. - Use the
retvalannotation for output parameters when you want the output parameter value from the function to be returned as the function result instead of the actual function return value. See Output parameters as function result. - The array size
Nin the declaration may either be an integer constant or the name of an integer type parameter in the same function declaration. See Dynamically sized arrays. - If
ENCODINGis not specified, it defaults to the system encoding. - If the function return value is subject to error checks (for example null pointer checks) and fails the checks, output values are not stored in variables because they may hold garbage values. The
storealwaysannotation says the output value is valid regardless of function failure. Thestoreonerrorannotation says the output value is valid only on function failure (for example an error code). See Errors and output parameters. - The
multiszmay only be used forwinchars. The output is interpreted as holding a WindowsMULTI_SZstring type and stored as a Tcl list. - Add the
nullifemptyannotation foroutandinoutfunction parameters that allowNULLto be passed as the argument when no output value is of interest. When the function wrapper is invoked, pass the empty string as the output variable name.
Examples
C
char *getcwd(char *buf, size_t size);
CFFI
LIBC function getcwd {string errno} {buf {chars[size] out} size size_t}
Output pointers to strings
The second case is when the C function parameter is a pointer to pointer to a character string (char **).
How this is best handled depends on whether the returned pointer needs to be available at the script level as a raw pointer, possibly for later freeing. See Pointer returns for a more detailed discussion about raw pointers versus implicit dereferencing. If this is the case, see Raw pointer out and in-out parameters for appropriate declarations.
If retention of the raw pointer is not required (for example, pointer to static storage), then itโs most convenient to implicitly declare it as an output parameter of
string +- -+ +- -+ +- -+ +- -+
string.ENCODING |out | |nullok| |storealways | | multisz |
unistring |retval| +- -+ |storeonerror| +- -+
winstring +- -+ +- -+
- Output parameters in C are always passed as pointers to the location where the output value is to be stored. Thus
outalways impliesbyref, which is redundant and need not be specified. - The script-level argument corresponding to an
outparameter is the name of the variable to which the output value is to be assigned. - Use the
retvalannotation for output parameters when you want the output parameter value from the function to be returned as the function result instead of the actual function return value. See Output parameters as function result. - If the function return value is subject to error checks (for example null pointer checks) and fails the checks, output values are not stored in variables because they may hold garbage values. The
storealwaysannotation says the output value is valid regardless of function failure. Thestoreonerrorannotation says the output value is valid only on function failure (for example an error code). See Errors and output parameters. These annotations are rarely applicable to struct output parameters. - The
multiszannotation may only be used forwinstring. The pointer stored in the output parameter is interpreted as pointing to a WindowsMULTI_SZstring type and stored in the Tcl variable as a list.
Binary string parameters ๐ง
Binary strings are lowkey the same vibe as regular strings, except they don't do the whole character-encoding/decoding transformation at the C level โ a pointer is treated as raw binary, and nul characters get no special treatment. Zero drama, zero special-casing. ๐ซถ
You can spell binary strings two ways: as binary or as an array of bytes. In so many cases they're interchangeable, it's giving twins. General rule of thumb, iykyk:
- reach for bytes when the function expects an array of bytes whose length gets passed in through another parameter
- reach for binary when the function doesn't need any extra info to figure out how big the data is
๐ Input binary string parameters
+- -+ +- -+ +- -+ +- -+
binary |in | |byref| |nullifempty| |{default DEFAULT}|
+- -+ +- -+ +- -+ +- -+
+- -+ +- -+ +- -+
bytes[N] |in | |nullifempty | |{default DEFAULT} |
+- -+ +- -+ +- -+
Syntax for input binary string parameters โจ
- the usual corresponding C declarations are
char *,unsigned char *andvoid *. Also, input parameters have theconstattribute โ respect the const, bestie, it's just polite ๐ - the
inannotation is optional, because it's literally the default when none ofout,retvalorinoutare present - the
byrefannotation is only applicable to thebinarytype, and you should specify it iff the value is being passed by reference โ meaning the function parameter is a pointer to the pointer to the array of bytes string (double indirection ๐). This is rare for passing binary values that are input only. Either way, whetherbyrefis there or not, the value at script level is still specified directly and not through a variable - some C functions accept a
NULLpointer for optional parameters. Thenullifemptyannotation flags exactly those. So if you pass a zero-length binary string as the script level argument, aNULLpointer gets passed to the function ๐ - the
defaultannotation supplies a default value that gets passed when no argument is given for that parameter at invocation time. And heads up: all succeeding parameters in the parameter list must also havedefaultannotations. All of them. No exceptions ๐
๐ Examples
C
ssize_t write(int fd, const void *buf, size_t count);
CFFI
LIBC function write {ssize_t nonnegative errno} {
fd int
buf binary
count size_t
}
๐ธ Output and input-output binary string parameters
Binary strings come back from C functions in one of two ways, and honestly they're different enough that CFFI handles them separately:
- the C function expects you, the caller, to supply a pointer to a memory block that's interpreted simply as an array of bytes ๐งบ. The size of this memory block may be a compile time constant or passed through another parameter to the function. The function then stores the data in this memory buffer. The C parameter declaration is generally one of
void *,char *buf,char buf[](or the unsigned variants) - the C function expects the caller to pass a pointer to a memory location where it will store the pointer to a block of memory โ either dynamically allocated or an internal buffer. The C declaration is of the form
char **,unsigned char **orvoid **, reflecting that double indirection again ๐
The two get handled differently in CFFI, as described below. Buckle up ๐ฆ
๐ Output byte arrays
+- -+ +- -+
bytes[N] |out | |storealways |
|inout | |storeonerror|
|retval| +- -+
+- -+
Syntax for byte array output parameters
This is case number one above, where the C function expects a pointer to a byte array.
- the script level argument corresponding to
outandinoutparameters is the name of the variable the output value gets assigned to. Forinoutparameters, that variable must exist at the time of the function call and must hold a Tcl binary string. No pressure ๐ฅ - use the
retvalannotation for output parameters where you want that output value returned as the function result instead of the actual function return value. Peep Output parameters as function result - the array size
Nin the declaration may be either an integer constant or the name of an integer type parameter in the same function declaration. See Dynamically sized arrays - if the function return value is subject to error checks (like null pointer checks, classic) and it fails those checks, output values are not stored in variables, since they may hold garbage values ๐. The
storealwaysannotation says the output value is valid regardless of function failure. Thestoreonerrorannotation says the output value is valid only on function failure (an error code, for example). See Errors and output parameters
The difference between an array declared as bytes[N] versus schar[N] or uchars[N]? Both might be arrays of char or unsigned char down at the C level, but the first declaration gives you a Tcl binary string at the script level, while the other two give you lists of integers. Same DNA, different outfit ๐
โจ
๐ง Examples
C
ssize_t read(int fd, void *buf, size_t count);
CFFI
LIBC function read {ssize_t nonnegative errno} {
fd int
buf bytes[count]
count size_T
}
๐ Output pointers to binary strings
Another way binary data comes back at you: through a parameter that's a pointer to a pointer to the data (char ** and friends). This case has to be handled as raw pointers, because โ unlike with character strings โ there is literally no way for CFFI to know the size of the data being returned. It's giving mystery box ๐ญ
โญ Arrays as parameters
TYPE[N] type specific annotations ...
- arrays in C are always passed by reference, as a pointer to the first element of the array. So CFFI array type declarations are implicitly annotated as
byrefโ you don't even have to say it ๐ - the array size in the type declaration may be an integer constant, or specified as the name of another parameter. See Dynamically sized array
- the annotations permitted on an array type declaration are those permitted for the base type. For example, integer array declarations may have the
enumannotation. Inherit the vibe, bestie ๐ - at the script level, arrays are represented as lists โ except for the special cases of
charsorbytes, where they're character or binary strings instead. Iconic little exception, no cap ๐
๐ DEFINING STRUCTS ๐
Okay so native C struct values can be dealt with at the script level in two ways, bestie: either as Tcl dictionary values, or kept in their native form and accessed through raw pointers. Both methods require the struct to be defined with ::cffi::Struct. In the dictionary-value case, you access the fields using Tcl's dict command. In the raw-pointer case, you use the ::cffi::Struct accessor methods. Slay. โจ
๐ FIELD TYPE DECLARATIONS ๐
The field type declarations in a struct definition are similar to function parameter type declarations, but they are not the same vibe in some respects. Pay attention, okay?
- The
voidandbinarybase types cannot be used in struct fields. Nope, not invited. ๐ซ - The annotations for each type are different from those applicable in a parameter context. So don't just copy-paste your parameter brain over here. ๐
๐งฎ Integer fields ๐งฎ
+- -+ +- -+ +- -+ +- -+ +- -+
schar |{enum E}| |bitmask| |{default N}| |structsize| |lasterror |
uchar +- -+ +- -+ +- -+ +- -+ |winerror |
short |errno |
ushort |{onerror HANDLER}|
int +- -+
uint
long
ulong
longlong
ulonglong
Syntax for integer fields
- If the
enumannotation is present, the field is subject to mapping to symbols from the enumeration. - If the
bitmaskannotation is present, the dictionary element for the field is treated as a list of integers that are bitwise OR-ed together to form the native field value. Bitwise OR my beloved. ๐ฅ - The
defaultannotation specifies a default value to use when constructing a native struct from the dictionary representation if the dictionary does not contain a key for the field. - The
structsizeannotation specifies that the field should be defaulted to the size of the native struct. It's giving "know your own size" energy. ๐ - Error handling annotations like
errnomay be present but are ignored. This is to permit the same aliases to be used in both function struct definitions. So they're allowed to hang around, but they don't do anything here. ๐
๐ Floating point fields ๐
+- -+
float |{default N}|
double +- -+
Syntax for floating point fields
- The
defaultannotation specifies a default value to use when constructing a native struct from the dictionary representation if the dictionary does not contain a key for the field.
๐ฆ Pointer fields ๐ฆ
+- -+ +- -+ +- -+
pointer |counted| |nullok| |{default PTRVAL}|
pointer.TAG |unsafe | +- -+ +- -+
|pinned |
+- -+
Syntax for input raw pointer parameters
- The
TAGis optional but recommended wherever possible for additional tag-based type checks. If the pointer's target type is a struct, it is recommended, but not mandatory, that the tag used be the name of the struct instance command. This makes it convenient to access the referenced memory using the struct methods. - Pointer fields in a struct are always treated as
unsafeeven when theunsafeannotation is not specified. Thecountedandpinnedannotations may be present (for sharing a type alias with parameter types) but are ignored. They're just there for the group photo. ๐ธ - The
nullokannotation should be applied to any field that can validly hold aNULLpointer. In the absence of an annotation, an exception is raised if an attempt is made to assign the field in a native struct aNULLpointer value. So don't try it unless you've saidnullok, bestie. ๐ญ - Pointer fields that hold pointers to character strings may alternatively be declared as string pointer fields under some restrictions described below.
๐ธ String pointer fields ๐ธ
string +- -+ +- -+
string.ENCODING |nullifempty| | multisz |
unistring +- -+ +- -+
winstring
Syntax for string pointer fields
- The
nullifemptyannotation may be used in cases where an empty string at the script level should be stored as aNULLpointer in the struct field. Empty string toNULL? It's giving "nothing to see here" and honestly, iconic. ๐ซถ - The
multiszmay only be used forwinstring. The referenced value is interpreted as holding a WindowsMULTI_SZstring type and returned at the script level as a list.
As for function parameters, fields that hold pointers to character strings can be more conveniently declared as one of the CFFI string types if the raw pointer is not required for freeing of resources or other purposes. There is a restriction imposed when a struct field is declared as a string type. The struct definition may be used to pass and return parameters from functions but cannot be used with the ::cffi::Struct.new or ::cffi::Struct.allocate methods to allocate a native struct in memory. So yes, she can visit functions, but she cannot move into allocated memory. Not with this declaration. ๐ซ๐
๐ Character array fields ๐
chars[N] +- -+ +- -+
chars.ENCODING[N] |{default DEFAULT}| | multisz |
unichars[N] +- -+ +- -+
winchars[N]
Syntax for character array fields
Like string, unistring and winstring, the chars, unichars and winchars CFFI types are character strings at the script level. However, while the former maps to pointers at the C level, chars, unichars and winchars map to char[], Tcl_UniChar[] and WCHAR arrays.
- The
defaultannotation may be used to supply a default value to be stored in the native C struct if the dictionary representation does not have a key matching the field. - The array size
Nmust be an positive integer constant and not dynamically sized as is permitted for array parameters. Like, a fixed number, not a vibe-based number. ๐ข - The
multiszmay only be used forwinchars. The character array is interpreted as holding a WindowsMULTI_SZstring type and is a list at the script level.
๐ Byte array fields ๐
+- -+
bytes[N] |{default DEFAULT}|
+- -+
Syntax for byte array fields
The difference between an array declared as bytes[N] versus schar[N] or uchars[N] is that while both might be arrays of char or unsigned char at the C level, the former declaration will result in a Tcl binary string at the script level while the latter declarations will result in lists of integers. Same bytes, different outfit. ๐
- The
defaultannotation may be used to supply a default value to be stored in the native C struct if the dictionary representation does not have a key matching the field. - The array size
Nmust be an positive integer constant and not dynamically sized as is permitted for array parameters.
๐ง Nested struct fields ๐ง
A struct definition may include a field that is declared as another struct. Struct-ception, but make it legal. โจ
+- -+
struct.STRUCT |{default DICT}|
+- -+
Syntax for nested struct fields
- A nested
STRUCTstruct may have a default value specified as a dictionary mapping field names ofSTRUCTto field values. - A nested struct of variable size may only appear as the last field in its parent. Last-field privileges, no cap. ๐
๐ Array fields ๐
TYPE[N] type specific annotations ...
- The array size
Nmust be an positive integer constant or the name of another field in the struct which will hold the array size. In the latter case, the array must be the last field in the struct and its element type should not itself be a variable size type. So the size can come from a friend, but then the array has to be last and the element type can't be variable-sized. Rules are rules. ๐ - The annotations permitted on an array type declaration are those permitted for the base type. For example, integer array declarations may have the
enumannotation. - At the script level, C arrays are represented as lists except for the special cases of as
charsorbyteswhere they are character or byte arrays.
๐ DEFINING UNIONS ๐
Definition of unions follows the rules for defined for structs with except that unions may not contain variable sized fields.
So yeah: same rules as structs, except unions may not contain variable sized fields. That's the whole union vibe check. ๐ซถ
๐ง TIPS AND TRICKS ๐ง
This section to be written. Yes, literally, that's what it says โ we're all waiting on the author, bestie. ๐ญ
- duplicating structure or function definition
- pointer casting - implicit and explicit
- use raw pointers for large data to avoid copies
- use memory get command to decode native memory in type-specific manner
- use defaults,
-clear,structsizefor structs - use help interactively
And that's the whole page, bestie! The footer's got cffi 2.0.3 and Cookbook(3tcl) โ no extra lore, no fake date from me, just the source/version and the page name. Mwah ๐๐
๐ฆ where this page comes from
- nixpkgs unstable: cffi 2.0.3
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms