Cookbook(3tcl)

๐Ÿ“ฆ version 2.0.3, in nixpkgs unstable

๐Ÿ’– 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 stdcall if the function is defined on 32-bit Windows using __stdcall or its equivalent. Note on other platforms, including 64-bit Windows, this is treated as equivalent to function.
  • Define using function otherwise 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 enum attribute with the enumeration defined as a literal or separately through the ::cffi::enum command.
  • If a symbolic representation is preferred as above and the value is actually a bit mask, optionally add the bitmask attribute 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, winerror or errno as appropriate. If error information is available through some other API, add the onerror handler. 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 errno or GetLastError() after the call returns, add the saveerrors annotation instead of the above. Then call ::cffi::savederrors to retrieve or check for errors.
  • If the return value is simply a boolean indicating success or failure, include the discard annotation 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 strerror C 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 TAG is 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 the pinned annotation 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, NULL pointers returned from functions will by default raise a generic Pointer is NULL exception. As for integer return values, if additional information is available from the system or API, the type can be annotated with lasterror, errno or onerror. On the other hand, if a NULL pointer is a valid return value, the nullok annotation will suppress the exception and return a NULL pointer at the script level.
  • If error conditions are not detectable from the return value but the function expects the caller to check errno or GetLastError() after the call returns, add the saveerrors annotation instead of the above. Then call ::cffi::savederrors to 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 in annotation is optional because it is the default if none of out, retval or inout annotations is present. ๐Ÿ’…
  • The byref annotation 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 the byref parameter is present or not, the value at script level is still specified directly and not through a variable. ๐Ÿซถ
  • Add the enum annotation to pass enumeration symbolic values for readability purposes. Note integer values will still be accepted. โœจ
  • If the parameter holds a bit mask, add the bitmask annotation 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 with enum to pass a list of symbols. ๐ŸŒˆ
  • The default annotation 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 have default annotations. ๐ŸŽ€
  • Error handling annotations like errno may 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 out and inout always imply byref which is redundant and need not be specified. ๐Ÿ’…
  • The script level argument corresponding to out and inout parameters are the name of the variable to which the output value is to be assigned. For inout parameters this must exist at the time of function call and hold a valid value for the type. ๐Ÿซถ
  • Use the retval annotation 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. See Output parameters as function result. ๐Ÿ‘‘
  • Add the enum annotation if output values are to be mapped to symbolic names for readability. Further, if the values returned are actually bitmasks, add bitmask to 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 storealways annotation indicates the output value is valid regardless of function failure. The storeonerror annotation indicates the output value is valid only on function failure (for example an error code). See Errors and output parameters. ๐Ÿ˜ญ
  • Error handling annotations like errno may be present but are ignored. This is to permit the same aliases to be used in both return and parameter declarations. ๐Ÿ’€
  • Add the nullifempty annotation for out and inout function parameters that are allow NULL to 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 in annotation is optional because it is the default if none of out, retval or inout annotations is present. ๐Ÿ’…
  • The byref annotation 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 the byref parameter is present or not, the value at script level is still specified directly and not through a variable. ๐Ÿซถ
  • The default annotation 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 have default annotations. ๐ŸŽ€

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 out and inout always imply byref which is redundant and need not be specified. ๐Ÿ’…
  • The script level argument that goes with out and inout parameters is literally just the name of the variable the output value gets assigned to ๐Ÿ’…. For inout besties, 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 retval annotation 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. And storeonerror says the output value is valid only on function failure (like an error code, iykyk). See Errors and output parameters.
  • Add the nullifempty annotation for out and inout function parameters that are allowed to have NULL passed 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 TAG is 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 in annotation is optional because it's the default when none of out, retval or inout are present.
  • The byref annotation 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 have const in the C parameter declaration.
  • By default, pointers passed into functions are expected to be registered as safe pointers. They can alternatively be annotated with counted or pinned, which also get the default handling in the case of input parameters. If the pointer isn't expected to have been registered, use the unsafe annotation. 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 dispose annotation on the parameter annotation to tell CFFI the pointer should be unregistered. disposeonsuccess is 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 the unsafe annotation โš ๏ธ. More details in Pointer safety.
  • CFFI assumes by default that functions don't accept NULL pointers, and it will raise an exception if you try to pass one ๐Ÿ’€. The nullok annotation says the function does allow the pointer to be NULL.
  • The default annotation 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 a default annotation. 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 out always implies byref โ€” which means byref is redundant and you don't need to type it. Iconic efficiency.
  • The script level argument corresponding to an out parameter is the name of the variable the output value gets assigned to.
  • Use the retval annotation 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 out and in-out parameters are registered as safe pointers. They can alternatively be annotated with counted or pinned to be registered as counted or pinned pointers, or unsafe if 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 dispose annotation 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. disposeonsuccess is similar, except the pointer is only unregistered if the function returns successfully. Now listen: the dispose and disposeonsuccess annotations are not valid with the out or retval annotations ๐Ÿšซ. See Pointer safety for the full tea.
  • The nullok annotation indicates that pointers passed in and out of the function can be NULL. Without it, an exception is raised if the pointer stored on output is NULL โ€” or, in the case of inout, if the pointer passed in is NULL ๐Ÿ˜ญ.
  • Add the nullifempty annotation for out and inout function parameters that allow NULL to 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 default annotation 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 have default annotations. This annotation cannot be used with the out and retval annotations โš ๏ธ.
  • 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. storealways says the output value is valid regardless of function failure. storeonerror says 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 in annotation is optional because it's the default when none of out, retval or inout are present.
  • The byref annotation 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 have const in 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 a default annotation. No skipping ahead ๐Ÿ’…
  • nullifempty โ€” some C functions are totally fine with a NULL pointer for optional parameters, and this annotation marks exactly those. Pass an empty dictionary as the script-level argument and a NULL pointer gets passed to the function. It only makes sense when the byref annotation 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 out and inout always imply byref โ€” itโ€™s redundant and need not be specified. Slay, less typing.
  • The script-level argument corresponding to out and inout parameters is the name of the variable to which the output value is to be assigned. For inout parameters, 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 retval annotation 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 storealways annotation says the output value is valid regardless of function failure. The storeonerror annotation 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 nullifempty annotation for out and inout function parameters that allow NULL to 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, and winstring CFFI types map to pointers to nul-terminated character strings: char* in the case of string, Tcl_UniChar * in the case of unistring, and WCHAR * in the case of winstring. For input parameters, they have the const attribute.
  • The in annotation is optional because it is the default if none of out, retval, or inout annotations are present.
  • For the string type, 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 byref annotation 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 the byref parameter is present or not, the value at script level is still specified directly and not through a variable.
  • Some C functions accept a NULL pointer for optional parameters. The nullifempty annotation identifies such parameters. In this case, passing an empty string as the script-level argument will result in a NULL pointer being passed to the function.
  • The multisz may only be used for winstring. The passed value is a list of strings and is mapped to a Windows MULTI_SZ string 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 *buf or char 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 out and inout parameters is the name of the variable to which the output value is to be assigned. For inout parameters, that variable must exist at the time of the function call and hold a valid value for the type.
  • Use the retval annotation 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 N in 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 ENCODING is 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 storealways annotation says the output value is valid regardless of function failure. The storeonerror annotation says the output value is valid only on function failure (for example an error code). See Errors and output parameters.
  • The multisz may only be used for winchars. The output is interpreted as holding a Windows MULTI_SZ string type and stored as a Tcl list.
  • Add the nullifempty annotation for out and inout function parameters that allow NULL to 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 out always implies byref, which is redundant and need not be specified.
  • The script-level argument corresponding to an out parameter is the name of the variable to which the output value is to be assigned.
  • Use the retval annotation 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 storealways annotation says the output value is valid regardless of function failure. The storeonerror annotation 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 multisz annotation may only be used for winstring. The pointer stored in the output parameter is interpreted as pointing to a Windows MULTI_SZ string 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 * and void *. Also, input parameters have the const attribute โ€” respect the const, bestie, it's just polite ๐Ÿ’…
  • the in annotation is optional, because it's literally the default when none of out, retval or inout are present
  • the byref annotation is only applicable to the binary type, 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, whether byref is there or not, the value at script level is still specified directly and not through a variable
  • some C functions accept a NULL pointer for optional parameters. The nullifempty annotation flags exactly those. So if you pass a zero-length binary string as the script level argument, a NULL pointer gets passed to the function ๐Ÿ™ˆ
  • the default annotation 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 have default annotations. 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 ** or void **, 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 out and inout parameters is the name of the variable the output value gets assigned to. For inout parameters, that variable must exist at the time of the function call and must hold a Tcl binary string. No pressure ๐Ÿ”ฅ
  • use the retval annotation 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 N in 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 storealways annotation says the output value is valid regardless of function failure. The storeonerror annotation 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 enum annotation. Inherit the vibe, bestie ๐Ÿ’–
  • at the script level, arrays are represented as lists โ€” except for the special cases of chars or bytes, 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 void and binary base 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 enum annotation is present, the field is subject to mapping to symbols from the enumeration.
  • If the bitmask annotation 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 default annotation 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 structsize annotation 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 errno may 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 default annotation 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 TAG is 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 unsafe even when the unsafe annotation is not specified. The counted and pinned annotations may be present (for sharing a type alias with parameter types) but are ignored. They're just there for the group photo. ๐Ÿ“ธ
  • The nullok annotation should be applied to any field that can validly hold a NULL pointer. In the absence of an annotation, an exception is raised if an attempt is made to assign the field in a native struct a NULL pointer value. So don't try it unless you've said nullok, 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 nullifempty annotation may be used in cases where an empty string at the script level should be stored as a NULL pointer in the struct field. Empty string to NULL? It's giving "nothing to see here" and honestly, iconic. ๐Ÿซถ
  • The multisz may only be used for winstring. The referenced value is interpreted as holding a Windows MULTI_SZ string 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 default annotation 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 N must 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 multisz may only be used for winchars. The character array is interpreted as holding a Windows MULTI_SZ string 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 default annotation 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 N must 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 STRUCT struct may have a default value specified as a dictionary mapping field names of STRUCT to 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 N must 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 enum annotation.
  • At the script level, C arrays are represented as lists except for the special cases of as chars or bytes where 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, structsize for 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