critcl_cproc_types(3tcl)
π critcl_cproc_types β CriTcl cproc Type Reference π
okay so this is the manual page for critcl_cproc_types(3tcl), from the C Runtime In Tcl (CriTcl) crew. it's part 1 of 6 bestie, buckle up π
β¨ NAME β¨
critcl_cproc_types β CriTcl cproc Type Reference
πΈ SYNOPSIS πΈ
package require Tcl 8.6
package require critcl ?3.3.1?
::critcl::has-resulttype name
::critcl::resulttype name body ?ctype?
::critcl::resulttype name = origname
::critcl::has-argtype name
::critcl::argtype name body ?ctype? ?ctypefun?
::critcl::argtype name = origname
::critcl::argtypesupport name code ?guard?
::critcl::argtyperelease name code
π DESCRIPTION π
welcome to the C Runtime In Tcl (aka CriTcl for the girlies in a hurry) β it's the whole system for embedding and using C code from inside your Tcl scripts, no cap. (the Tcl it's talking about lives over at http://core.tcl-lang.org/tcl btw).
so this document? it's a breakout. basically the descriptions of all the predefined argument- and result-types you can use with the critcl::cproc command got lifted out of the big reference manpage for the critcl package, and stitched together here β plus the tea on how to extend the predefined set with your own custom types π₯. the breakout happened so this info is actually findable (toplevel document vs. having to go dig through the giant main reference, which is a lot).
the intended audience? developers who wanna write Tcl packages with embedded C code inside them. that's you, bestie π
π STANDARD ARGUMENT TYPES π
before we get knee-deep in the details, here's the quick overview, absolutely iconic, all laid out:
CriTcl type | C type | Tcl type | Notes
---------------- | -------------- | --------- | ------------------------------
Tcl_Interp* | Tcl_Interp* | n/a | Special, only first
---------------- | -------------- | --------- | ------------------------------
Tcl_Obj* | Tcl_Obj* | Any | Read-only
object | | | Alias of Tcl_Obj* above
list | critcl_list | List | Read-only
[], [*] | | | Alias of list above
---------------- | -------------- | --------- | ------------------------------
[N] | | | Restricted list-types.
type[], type[N] | | | Length-limited ([..]), expected
[]type, [N]type | | | element type, or both.
| | |
| | | Element types can be all known argument
| | | types, except for any kind of list.
| | | IOW multi-dimensional lists are not
| | | supported.
---------------- | -------------- | --------- | ------------------------------
char* | const char* | Any | Read-only, string rep
pstring | critcl_pstring | Any | Read-only
bytes | critcl_bytes | ByteArray | Read-only
---------------- | -------------- | --------- | ------------------------------
int | int | Int |
long | long | Long |
wideint | Tcl_WideInt | WideInt |
double | double | Double |
float | float | Double |
---------------- | -------------- | --------- | ------------------------------
X > N | | | For X in int ... float above.
X >= N | | | The C types are as per the base type X.
X < N | | | N, A, B are expected to be constant integer
X <= N | | | numbers for types int, long,
X > A < B | | | and wideint. For types double
etc. | | | and float the N, A, and B can be floating
| | | point numbers. Multiple restrictions are
| | | fused as much as possible to yield at most
| | | both upper and lower limits.
---------------- | -------------- | --------- | ------------------------------
boolean | int | Boolean |
bool | | | Alias of boolean above
---------------- | -------------- | --------- | ------------------------------
channel | Tcl_Channel | String | Assumed to be registered
unshared-channel | Tcl_Channel | String | As above, limited to current interpreter
take-channel | Tcl_Channel | String | As above, C code takes ownership
and now the details, let's goooo π¦
π Tcl_Interp*
okay so attention bestie, this one is special and it knows it (main character energy, honestly). it can only be used by the first argument of a function. any other argument trying to use it will make critcl throw an error, periodt.
when you do use it, that argument will hold a reference to the current interpreter, which the function body is free to use. and here's the twist β that argument will not be an argument of the Tcl command for the function. sneaky, right?
when's it useful? when the function has to do more than just hand back a value. examples given: setting up error messages on failure, or querying the interpreter for variables and other data. very demure, very mindful π
π Tcl_Obj*
π object
the function takes an argument of type Tcl_Obj*. there is no argument checking done, none, zero, nada. the Tcl level word gets passed to the argument as-is. and note β you must treat this value as read-only (the only exception being hidden changes to its intrep, i.e. shimmering). don't touch, bestie π
π pstring
the function takes an argument of type critcl_pstring, which contains the original Tcl_Obj* reference of the Tcl argument, plus the length of the string, plus a pointer to the character array:
typedef struct critcl_pstring {
Tcl_Obj* o;
const char* s;
int len;
} critcl_pstring;
note the const. the string is read-only. any modification can have arbitrary effects β like, from pulling the rug out from under the script because the string value and internal representation stop matching, all the way up to crashes anytime later. not worth it, i promise π
πΈ list
πΈ []
πΈ [*]
the function takes an argument of type critcl_list, which contains the original Tcl_Obj* reference of the Tcl argument, plus the length of the Tcl list, plus a pointer to the array of the list elements:
typedef struct critcl_list {
Tcl_Obj* o;
Tcl_Obj* const* v;
int c;
} critcl_list;
the Tcl argument must be convertible to List, otherwise an error gets thrown, no exceptions.
note the const. the list is read-only. any modification can have arbitrary effects β same saga as before, from the rug being pulled out from under the script (string value and internal representation not matching anymore) up to crashes anytime later. π
further note: the system understands a whole bunch of more complex syntactical forms, and they all translate into forms of lists under the hood β see the following points, cutie.
π« [N]
a list type rocking additional checks that limit the length to N, where N is an integer number greater than zero.
π []type
π type[]
a list type whose elements all have to be convertible for type. all known types are allowed here β including user-defined ones β except for list and its derivates. in other words: multi-dimensional lists are not supported, sorry bestie.
the function will take a structure argument of the general form:
typedef struct critcl_list_... {
Tcl_Obj* o;
int c;
(Ctype)* v;
} critcl_list_...;
where (Ctype) represents the C type for values of type type.
π¦ [N]type
π¦ type[N]
these are list types that combine the elements of [N] and []type. β¨
as an example, the specification of
int[3] a
describes
a
as a list of exactly 3 elements, all of which have to be of type int.
note that this same example can also be written in the more C-like form of
int a[3]
.
π§ bytes
this is the new and usable ByteArray type, glowing, thriving β¨
the function takes an argument of type critcl_bytes, which contains the original Tcl_Obj* reference of the Tcl argument, plus the length of the byte array, plus a pointer to the byte data:
typedef struct critcl_bytes {
Tcl_Obj* o;
const unsigned char* s;
int len;
} critcl_list;
the Tcl argument must be convertible to ByteArray, otherwise an error is thrown.
note the const. the bytes are read-only. any modification can have arbitrary effects β the classic: pulling the rug out from under the script because the string value and internal representation don't match anymore, up to crashes anytime later. π
π char*
the function takes an argument of type const char*. the string representation of the Tcl argument is what gets passed in.
note the const. the string is read-only. any modification can have arbitrary effects β from the rug-pull situation where the string value and internal representation stop matching, up to crashes anytime later.
β double
the function takes an argument of type double. the Tcl argument must be convertible to Double, otherwise an error is thrown.
β double > N
β double >= N
β double < N
β double <= N
these are variants of double above, restricting the argument value to the relation shown. an error is thrown for Tcl arguments outside of the specified range. keep it in bounds bestie π§
the limiter N has to be a constant floating point value.
it's possible to use multiple limiters. for example double > A > B <= C. the system will fuse them into a single upper/lower limit (or both).
the system will reject limits that describe an empty range of values, or a range containing only a single value. so no sneaky degenerate ranges, it's not having it π
π float
the function takes an argument of type float. the Tcl argument must be convertible to Double, otherwise an error is thrown.
π float > N
π float >= N
π float < N
π float <= N
these are variants of float above, restricting the argument value to the relation shown. an error is thrown for Tcl arguments outside of the specified range. π
So likeβ¦ the limiter N has to be a constant floating point value. No variables, no runtime maths, no "whatever feels right" β it's a fixed number or it's not happening. π
And bestie, you can stack these bad boys. Multiple limiters? Absolutely valid. Something like float > A > B <= C? Slay. The system fuses them all together into a single upper limit, a single lower limit, or both at once β it's giving range-consolidation main character energy. π₯
One thing though: the system will reject limits that describe an empty range of values, and also limits that describe a range containing only a single value. So > 5 and <= 5 together? Nope. > 5 and < 6? Also nope β there's literally one value in there and the system is not about that life. π«β¨
β¨ boolean, bool
Two names, one vibe. π«Ά The function takes an argument of type int. The Tcl argument has to be convertible to Boolean β and if it isn't, boom, an error gets thrown. That's it, that's the whole thing.
β¨ channel
The function takes an argument of type Tcl_Channel. π Your Tcl argument must be convertible to type Channel, otherwise an error is thrown, no cap.
Extra detail that matters: the channel is further assumed to be already registered with the interpreter. So likeβ¦ it needs to already be part of the squad. You can't just roll up unannounced. πΈ
β¨ unshared-channel
OK so this one is an extension of channel above β everything I just said still applies, all of it, don't skip it. β¨
But beyond that: the channel must not be shared by multiple interpreters. If it is? Error. Thrown. Done. π It's a one-interpreter channel and it knows it.
β¨ take-channel
And this one is an extension of unshared-channel above β so yes, every single thing from the last two entries still applies. π
Beyond all of that, the code does some very intense things, so buckle up:
- It removes the channel from the current interpreter without closing it. π
- It disables all pre-existing event handling for it.
With this, the function takes full ownership of the channel in question β it's taking it away from the interpreter that invoked it. From that point on, the function is responsible for the lifecycle of the channel, all the way up to and including closing it. That's not a side quest, that's a commitment. π
Now β if the system the function is a part of wants to hand control of the channel back to the interpreter, it then has to use the result type return-channel. That undoes the registration changes this argument type made. β¨
β οΈ But heads up: the removal of pre-existing event handling that happened here cannot be undone. So likeβ¦ that part is permanent. No take-backs, no ctrl+z, we're past that. π
Attention (this bit is important, read it twice):
Removal from the interpreter without closing the channel is done by incrementing the channel's reference count without providing an interpreter, before decrementing the same for the current interpreter. That leaves the overall reference count intact without causing Tcl to close it when it's removed from the interpreter structures. At this point the channel is effectively a globally-owned part of the system, not associated with any interpreter at all. π¦
The complementary result type then runs this whole sequence in reverse. And if the channel is never returned to Tcl, then either the function or the system it's a part of has to unregister the global reference when they're done with it. Periodt. π₯
β¨ int
The function takes an argument of type int. The Tcl argument has to be convertible to Int β otherwise, you guessed it, an error is thrown. π
β¨ int > N, int >= N, int < N, int <= N
These are variants of int above, restricting the argument value to the relation shown. So > N means strictly greater, >= N means greater-or-equal, < N means strictly less, <= N means less-or-equal. π
An error is thrown for Tcl arguments outside of the specified range. πΈ
The limiter N has to be a constant integer value. No funny business.
Multiple limiters are possible β for example int > A > B <= C. The system fuses them into a single upper limit, lower limit, or both. π«
And again: the system will reject limits describing an empty range of values, or a range containing only a single value. (Yes, same rule as float. The rules are rules, bestie. π
)
β¨ long
The function takes an argument of type long int. The Tcl argument must be convertible to Long, an error is thrown otherwise. π«Ά
β¨ long > N, long >= N, long < N, long <= N
Variants of long above, restricting the argument value to the shown relation. An error is thrown for Tcl arguments outside of the specified range. β¨
The limiter N has to be a constant integer value.
Multiple limiters? Yep, allowed β for example long > A > B <= C. The system fuses them to a single upper/lower limit, or both. π
And the system will reject limits describing an empty range of values, or a range containing only a single value. Every. Single. Time. π
β¨ wideint
The function takes an argument of type Tcl_WideInt. The Tcl argument must be convertible to WideInt, an error is thrown otherwise. π
β¨ wideint > N, wideint >= N, wideint < N, wideint <= N
Variants of wideint above, restricting the argument value to the shown relation. An error is thrown for Tcl arguments outside of the specified range. π
The limiter N has to be a constant integer value.
Multiple limiters, yes, again β for example wideint > A > B <= C. Fused to a single upper/lower limit, or both. β
The system will reject limits describing an empty range of values, or a range containing only a single value. Same energy as the int and long versions, because it is the same energy. π
β¨ void*
okay soβ¦ hold that thought, bestie π«Ά this one's getting its own moment and it's coming right up π
β¨ STANDARD RESULT TYPES β¨
okay bestie, before we go full deep-dive, here's the quick vibe check of every result type so you can clock the whole landscape at once π
voidβ C type: n/a Β· Tcl type: n/a Β· Notes: Always OK. Body sets result.okβ C type:intΒ· Tcl type: n/a Β· Notes: Result code. Body sets result.intβ C type:intΒ· Tcl type:Intbooleanβ alias ofintaboveboolβ alias ofintabovelongβ C type:longΒ· Tcl type:Longwideintβ C type:Tcl_WideIntΒ· Tcl type:WideIntdoubleβ C type:doubleΒ· Tcl type:Doublefloatβ C type:floatΒ· Tcl type:Doublechar*β C type:char*Β· Tcl type:StringΒ· Makes a copyvstringβ alias ofchar*aboveconst char*β C type:const char*Β· Behavior ofchar*abovestringβ Tcl type:StringΒ· Freeable string set directly Β· No copy is madedstringβ alias ofstringabove
β οΈ For all of the types below: Null is ERROR. The body has to set any message it wants. π¨
Tcl_Obj*β C type:Tcl_Obj*Β· Tcl type:AnyΒ· refcount--objectβ alias ofTcl_Obj*aboveTcl_Obj*0β Tcl type:AnyΒ· refcount unchangedobject0β alias ofTcl_Obj*0aboveknown-channelβ C type:Tcl_ChannelΒ· Tcl type:StringΒ· Assumes to already be registerednew-channelβ C type:Tcl_ChannelΒ· Tcl type:StringΒ· New channel, will be registeredreturn-channelβ C type:Tcl_ChannelΒ· Tcl type:StringΒ· Inversion oftake-channel
And now β¨the detailsβ¨, buckle up cutie:
Tcl_Obj* / object
If the returned Tcl_Obj* is NULL, the Tcl return code is TCL_ERROR and the function should set an error message as the interpreter result. Otherwise, the returned Tcl_Obj* is set as the interpreter result. πΈ
ngl, setting that error message means your function body needs access to the interpreter the function is running in. Go peek at the argument type Tcl_Interp* for the deets on how to make that happen. π
And also bestie, the returned Tcl_Obj* should have a reference count greater than 0. Why? Because the converter decrements the reference count to release possession after setting the interpreter result β it's assuming your function incremented the reference count of the returned Tcl_Obj*. If you returned a Tcl_Obj* with a reference count of 0, the reference count would become 1 when it's set as the interpreter result, and then immediately get decremented to 0 again, which frees the memory. π The system is then likely to crash at some point after the return, because it's reusing freed memory. Not the slay. Do not do that.
Tcl_Obj*0 / object0
Like Tcl_Obj* except that this conversion assumes the returned value has a reference count of 0 and does not decrement it. So returning a value whose reference count is greater than 0 is therefore likely to cause a memory leak. π
Also note: setting an error message requires the function body to have access to the interpreter the function is running in. See the argument type Tcl_Interp* for the details on how to make that happen. β¨
new-channel
A String Tcl_Obj holding the name of the returned Tcl_Channel is set as the interpreter result. The channel is further assumed to be new, and therefore registered with the interpreter to make it known. π
known-channel
A String Tcl_Obj holding the name of the returned Tcl_Channel is set as the interpreter result. The channel is further assumed to be already registered with the interpreter. π
return-channel
This type is a variant of new-channel above. It varies slightly from it in the registration sequence, so that it's properly complementary to the argument type take-channel. A String Tcl_Obj holding the name of the returned Tcl_Channel is set as the interpreter result. The channel is further assumed to be new, and therefore registered with the interpreter to make it known. π¦
char* / vstring
A String Tcl_Obj holding a copy of the returned char* is set as the interpreter result. If the value is allocated then the function itself and the extension it is a part of are responsible for releasing the memory when the data is not in use any longer. π
const char*
Like char* above, except that the returned string is const-qualified. Simple as. β¨
string / dstring
The returned char* is directly set as the interpreter result, without making a copy. Because of that it must be dynamically allocated via Tcl_Alloc. Release happens automatically when the Interpreter finds that the value is not required any longer. π
double / float
The returned double or float is converted to a Double Tcl_Obj and set as the interpreter result. π
boolean / bool
The returned int value is converted to an Int Tcl_Obj and set as the interpreter result. π
int
The returned int value is converted to an Int Tcl_Obj and set as the interpreter result. π§
long
The returned long int value is converted to a Long Tcl_Obj and set as the interpreter result. β
wideint
The returned Tcl_WideInt value is converted to a WideInt Tcl_Obj and set as the interpreter result. π₯
ok
The returned int value becomes the Tcl return code. The interpreter result is left untouched and can be set by the function if desired. Heads up though: doing that requires the function body to have access to the interpreter the function is running in. See the argument type Tcl_Interp* for the details on how to make that happen. π
void
The function does not return a value. The interpreter result is left untouched and can be set by the function if desired. π
β¨ ADVANCED: ADDING TYPES β¨
Ok so critcl::cproc already gets the most common C types (the ones we literally just went through in the last two sections) but sometimes that is simply not enough, bestie. π
To get around this limitation, the commands in this section let critcl users extend the set of argument and result types that critcl::cproc understands. In other words β you get to define your own, custom, types. Main character energy only. π
::critcl::has-resulttypename β this command tests if the named result-type is known or not. It returns a boolean value: true if the type is known, false otherwise.::critcl::resulttypename body ?ctype? β this defines the result type name, and associates it with the C code doing the conversion (body) from C to Tcl. The C return type of the associated function β which is also the C type of the result variable β is ctype. This type defaults to name if you don't specify it.
If name is already declared, an error is thrown. Attention! The standard result type void is special because it has no accompanying result variable, and that cannot be expressed by this extension command. π
The body's responsibility is converting the function's result into a Tcl result and a Tcl status. The first has to be set into the interpreter we are in, and the second has to be returned.
The C code of body is guaranteed to be called last in the wrapper around the actual implementation of the cproc in question, and it has access to this whole environment:
interpβ aTcl_Interp*typed C variable referencing the interpreter the result has to be stored into.rvβ the C variable holding the result to convert, of type ctype.
And as examples, here are the definitions of two standard result types:
resulttype int {
Tcl_SetObjResult(interp, Tcl_NewIntObj(rv));
return TCL_OK;
}
resulttype ok {
/* interp result must be set by cproc body */
return rv;
} int
::critcl::resulttypename=origname β this form of theresulttypecommand declares name as an alias of result type origname, which has to be defined already. If it isn't, an error is thrown. π¨::critcl::has-argtypename β this command tests if the named argument-type is known or not. Returns a boolean value, true if the type is known and false otherwise. (Certified vibe check for argument types. β )::critcl::argtypename body ?ctype? ?ctypefun? β this defines the argument type name, and associates it with the C code doing the conversion (body) from Tcl to C. ctype is the C type of the variable to hold the conversion result, and ctypefun is the type of the function argument itself. Both types default to name if they are the empty string or are not provided.
If name is already declared, an error is thrown.
body is a C code fragment that converts a Tcl_Obj* into a C value which is stored in a helper variable in the underlying function.
body is called inside its own code block to isolate local variables, and these items are in scope:
interpβ a variable of typeTcl_Interp*, which is the interpreter the code is running in.@@β a placeholder for an expression that evaluates to theTcl_Obj*to convert.@Aβ a placeholder for the name of the variable to store the converted argument into.
As examples, here are the definitions of two standard argument types:
argtype int {
if (Tcl_GetIntFromObj(interp, @@, &@A) != TCL_OK) return TCL_ERROR;
}
argtype float {
double t;
if (Tcl_GetDoubleFromObj(interp, @@, &t) != TCL_OK) return TCL_ERROR;
@A = (float) t;
}
::critcl::argtypename=origname β this form of theargtypecommand declares name as an alias of argument type origname, which has to be defined already. If it isn't, an error is thrown. π«::critcl::argtypesupportname code ?guard? β this command defines a C code fragment for the already defined argument type name, which gets inserted before all functions using that type. Its purpose is the definition of any supporting C types needed by the argument type. If the type is used by many functions, the system ensures that only the first of the multiple insertions of the code fragment is active, and the others are disabled. The guard identifier is normally derived from name, but it can also be set explicitly via guard. That latter trick lets different custom types share a common support structure without having to perform their own guarding. nglll that's clever. π::critcl::argtypereleasename code β this command defines a C code fragment for the already defined argument type name, inserted whenever the worker function of acritcl::cprocreturns to the shim. It is the responsibility of this fragment to unconditionally release any resources thecritcl::argtypeconversion code allocated. An example of this is the variadic types for the support of the special, variadicargsargument tocritcl::cproc's. They allocate a C array for the collected arguments which has to be released when the worker returns. This command defines the C code for doing exactly that. π§Ή
π EXAMPLES π
The examples shown here have been drawn from the section "Embedding C" in the document about Using CriTcl. Please see that document for many more examples, bestie, there's so much lore there. π
π A SIMPLE PROCEDURE π
Starting simple, let's assume the Tcl code in question is something like
proc math {x y z} {
return [expr {(sin($x)*rand())/$y**log($z)}]
}
with the expression pretending to be something very complex and slow. Converting this to C we get:
critcl::cproc math {double x double y double z} double {
double up = rand () * sin (x);
double down = pow(y, log (z));
return up/down;
}
Notable about this translation:
- All the arguments got type information added to them, here
double. Like in C, the type precedes the argument name. Other than that it's pretty much a Tcl dictionary, with keys and values swapped. - We now also have to declare the type of the result, here
double, again. - The reference manpage lists all the legal C types supported as arguments and results.
While the above example was based on type double for both arguments and result, we have a number of additional types in the same category β i.e. simple types. These are:
boolβ Notes: alias ofbooleanbelow.booleanβ C typeint, Tcl type Boolean.doubleβ C typedouble, Tcl type Double.floatβ C typefloat, Tcl type Double.intβ C typeint, Tcl type Int.longβ C typelong, Tcl type Long.wideintβ C typeTcl_WideInt, Tcl type WideInt.
A slightly advanced form of these simple types are a limited set of constraints on the argument value. Note that bool and alias do not support this. π«
critcl::cproc sqrt {{double >= 0} x} double {
return sqrt(x);
}
In the example above, CriTcl's argument handling will reject calling the command with a negative number, without ever invoking the C code. It said no thank you before even opening the file. π
These constraints are called limited because only 0 and 1 can be used as the borders, although all the operators <, <=, >, and >= are possible. It is also not possible to combine restrictions. (So pick one, bestie. Only one. π₯²)
πΈ MORE BUILTIN TYPES: STRINGS πΈ
Given that "Everything is a String" is a slogan of Tcl, the ability of cprocs to receive strings as arguments, and return them as results, is quite important. Obviously.
We actually have a variety of builtin string types, all alike, yet different. Iconic family behavior. π
For arguments we have:
char*β C typeconst char*, Tcl type Any. Notes: Read-only, string rep.pstringβ C typecritcl_pstring, Tcl type Any. Notes: Read-only.bytesβ C typecritcl_bytes, Tcl type ByteArray. Notes: Read-only.
In C:
critcl::cproc takeStrings {
char* cstring
pstring pstring
bytes barray
} void {
printf ("len %d = %s\n", strlen(cstring), cstring);
printf ("len %d = %s\n", pstring.len, pstring.s);
printf ("len %d = %s\n", barray.len, barray.s);
return; // void result, no result
}
Notable about the above:
- The cstring is a plain
const char*. It points directly into theTcl_Obj*holding the argument in the script. - The pstring is a slight extension to that. The value is actually a structure containing the string pointer like cstring (field
.s), the length of the string (field.len), and a pointer to theTcl_Obj*these came from. - The last one, barray, is like pstring, however it has ensured that the
Tcl_Obj*is a Tcl ByteArray, i.e. binary data.
Treat all of them as Read Only. Do not modify ever. π
On the other side β string results β we have:
char*β C typechar*, Tcl type String. Notes: Makes a copy.vstringβ Notes: alias ofchar*above.const char*β C typeconst char*. Notes: behavior ofchar*above.stringβ C typechar*, Tcl type String. Notes: freeable string set directly, no copy is made.dstringβ Notes: alias ofstringabove.
critcl::cproc returnCString {} char* {
return "a string";
}
critcl::cproc returnString {} string {
char* str = Tcl_Alloc (200);
sprintf (str, "hello world");
return str;
}
Notable about the above:
- The type
char*π β this one is best for static strings, or for strings that live in some kind of fixed buffer. Ngl that's its whole moment.
CriTcl's translation layer makes a copy of it for the result of the command. And yeah, you can return heap-allocated strings, but then it's the C code that's responsible for freeing them at some point. If that doesn't happen? They leak. π Periodt, the memory is just gone.
- The type
stringπ¦ on the other hand? It's literally exactly for returning strings allocated withTcl_Allocand associates.
For these, the translation layer makes no copy at all, and sets them directly as the result of the command. Super important side effect bestie: the ownership of the string pointer moves from the function to Tcl. β¨
Tcl will release the allocated memory whenever it doesn't need it any longer. The C code has no say in that. Not even a little. π
πΈ CUSTOM TYPES, INTRODUCTION πΈ
So when you're writing bindings to external libraries, critcl::cproc is usually the most convenient way of writing the lower layers. Iconic behaviour, honestly. But π
β it's hampered by the fact that critcl on its own only supports a few standard (arguably the most important) standard types, whereas the functions we actually want to bind will most certainly use way more, specific to the library's function. It's giving limited wardrobe.
That's literally why the critcl commands argtype, resulttype and their adjuncts exist β to help out here, by letting a developer extend critcl's type system with custom conversions. π
This section and the three following ones will demonstrate this, from trivial to complex. Buckle up, we're going on a journey. π§
The most trivial use is to create types which are aliases of existing types, standard or other. As an alias it simply copies and uses the conversion code from the referenced types. Easy.
Our example is pulled from an incomplete project of mine, a binding to Jeffrey Kegler's libmarpa library managing Earley parsers. Several custom types simply reflect the typedef's done by the library, to make the critcl::cprocs as self-documenting as the underlying library functions themselves.
critcl::argtype Marpa_Symbol_ID = int
critcl::argtype Marpa_Rule_ID = int
critcl::argtype Marpa_Rule_Int = int
critcl::argtype Marpa_Rank = int
critcl::argtype Marpa_Earleme = int
critcl::argtype Marpa_Earley_Set_ID = int
...
method sym-rank: proc {
Marpa_Symbol_ID sym
Marpa_Rank rank
} Marpa_Rank {
return marpa_g_symbol_rank_set (instance->grammar, sym, rank);
}
...
π¦ CUSTOM TYPES, SEMI-TRIVIAL π¦
A more involved custom argument type would be mapping from Tcl strings to some internal representation, like an integer code. Lowkey the glow-up from alias mode.
First example is from the tclyaml package, a binding to the libyaml library. In a few places we have to map readable names for block styles, scalar styles, etc. to the internal enumeration. β¨
critcl::argtype yaml_sequence_style_t {
if (!encode_sequence_style (interp, @@, &@A)) return TCL_ERROR;
}
...
critcl::ccode {
static const char* ty_block_style_names [] = {
"any", "block", "flow", NULL
};
static int
encode_sequence_style (Tcl_Interp* interp, Tcl_Obj* style,
yaml_sequence_style_t* estyle)
{
int value;
if (Tcl_GetIndexFromObj (interp, style, ty_block_style_names,
"sequence style", 0, &value) != TCL_OK) {
return 0;
}
*estyle = value;
return 1;
}
}
...
method sequence_start proc {
pstring anchor
pstring tag
int implicit
yaml_sequence_style_t style
} ok {
/* Syntax: <instance> seq_start <anchor> <tag> <implicit> <style> */
...
}
...
It should be noted that this code precedes the advent of the supporting generator package critcl::emap. Using the generator, the definition of the mapping becomes so much simpler:
critcl::emap::def yaml_sequence_style_t {
any 0
block 1
flow 2
}
Note that the generator will not only provide the conversions, but also define the argument and result types needed for their use by critcl::cproc. Multi-tasking queen. π
Another example of such a semi-trivial argument type can be found in the CRIMP package, which defines a Tcl_ObjType for image values. This not only provides a basic argument type for any image, but also derived types which check that the image has a specific format. Here we see for the first time non-integer arguments, and the need to define the C types used for variables holding the C level value, and the type of function parameters (due to C promotion rules we may need different types). Fr fr, C keeps you humble. π
critcl::argtype image {
if (crimp_get_image_from_obj (interp, @@, &@A) != TCL_OK) {
return TCL_ERROR;
}
} crimp_image* crimp_image*
...
set map [list <<type>> $type]
critcl::argtype image_$type [string map $map {
if (crimp_get_image_from_obj (interp, @@, &@A) != TCL_OK) {
return TCL_ERROR;
}
if (@A->itype != crimp_imagetype_find ("crimp::image::<<type>>")) {
Tcl_SetObjResult (interp,
Tcl_NewStringObj ("expected image type <<type>>",
-1));
return TCL_ERROR;
}
}] crimp_image* crimp_image*
...
π CUSTOM TYPES, SUPPORT STRUCTURES π
The adjunct command critcl::argtypesupport is for when the conversion needs additional definitions, for example a helper structure. It's the "and also bring your friend" of type systems. π«Ά
An example of this can be found among the standard types of critcl itself: the pstring type. This type provides the C function with not only the string pointer, but also the string length, and the Tcl_Obj* this data came from. As critcl::cproc's calling conventions allow us only one argument for the data of the parameter, a structure is needed to convey these three pieces of information. Three truths, one struct. β¨
Thus the argument type is defined as
critcl::argtype pstring {
@A.s = Tcl_GetStringFromObj(@@, &(@A.len));
@A.o = @@;
} critcl_pstring critcl_pstring
critcl::argtypesupport pstring {
typedef struct critcl_pstring {
Tcl_Obj* o;
const char* s;
int len;
} critcl_pstring;
}
In the case of such a structure being large we may wish to allocate it on the heap instead of having it taking space on the stack. And if we do that, we need another adjunct command: critcl::argtyperelease. This command specifies the code required to release dynamically allocated resources when the worker function returns, before the shim returns to the caller in Tcl. To keep things simple our example is synthetic, a modification of the pstring above, to demonstrate the technique. An actual, but more complex example is the code to support the variadic args argument of critcl::cproc.
critcl::argtype pstring {
@A = (critcl_pstring*) ckalloc(sizeof(critcl_pstring));
@A->s = Tcl_GetStringFromObj(@@, &(@A->len));
@A->o = @@;
} critcl_pstring* critcl_pstring*
critcl::argtypesupport pstring {
typedef struct critcl_pstring {
Tcl_Obj* o;
const char* s;
int len;
} critcl_pstring;
}
critcl::argtyperelease pstring {
ckfree ((char*)) @A);
}
Note β οΈ β the example above shows only the most simple case of an allocated argument, with a conversion that cannot fail (namely, string retrieval). If the conversion can fail, then either the allocation has to be defered to happen only on successful conversion, or the conversion code has to release the allocated memory itself in the failure path β because it will never reach the code defined via critcl::argtyperelease in that case. π«
π§ CUSTOM TYPES, RESULTS π§
All of the previous sections dealt with argument conversions, i.e. going from Tcl into C. Custom result types are for the reverse direction, from C to Tcl. This is usually easier, as most of the time errors should not be possible. Supporting structures, or allocating them on the heap, are not really required and therefore not supported. Slay, less work for us. π
The example of a result type shown below was pulled from KineTcl. It's a variant of the builtin result type Tcl_Obj*, aka object. The builtin conversion assumes that the object returned by the function has a refcount of 1 (or higher), with the function having held the reference, and releases that reference after placing the value into the interp result. The conversion below on the other hand assumes that the value has a refcount of 0 and thus that decrementing it is forbidden β lest it be released much too early, and crashing the system. π Not the vibe.
critcl::resulttype KTcl_Obj* {
if (rv == NULL) { return TCL_ERROR; }
Tcl_SetObjResult(interp, rv);
/* No refcount adjustment */
return TCL_OK;
} Tcl_Obj*
This type of definition is also found in Marpa, and recent hacking on CRIMP introduced it there as well. Which is why this definition became a builtin type starting with version 3.1.16, under the names Tcl_Obj*0 and object0. Iconic glow-up, honestly. π
Going back to errors and their handling β of course, if a function we are wrapping signals them in-band, then the conversion of such results has to deal with that. This happens for example in KineTcl, where we find
critcl::resulttype XnStatus {
if (rv != XN_STATUS_OK) {
Tcl_AppendResult (interp, xnGetStatusString (rv), NULL);
return TCL_ERROR;
}
return TCL_OK;
}
critcl::resulttype XnDepthPixel {
if (rv == ((XnDepthPixel) -1)) {
Tcl_AppendResult (interp,
"Inheritance error: Not a depth generator",
NULL);
return TCL_ERROR;
}
Tcl_SetObjResult (interp, Tcl_NewIntObj (rv));
return TCL_OK;
}
π AUTHORS
Jean Claude Wippler, Steve Landers, Andreas Kupries β the literal icons who built this whole thing π
π BUGS, IDEAS, FEEDBACK
okay bestie, real talk π this document and the package it describes will undoubtedly contain bugs and other problems. That's not me being shady, that's just what the page says, and it's honestly so real of it.
So if you find something broken, please report it! πΈ Same deal if you've got ideas for enhancements you may have for either the package, the application, and/or the documentation β literally all of it is welcome π«Ά
Drop it all at:
Both the bugs and the ideas go to the exact same place. One link, zero confusion, slay β¨
π KEYWORDS
C code, Embedded C Code, calling C code from Tcl, code generator, compile & run, compiler, dynamic code generation, dynamic compilation, generate package, linker, on demand compilation, on-the-fly compilation
π CATEGORY
Glueing/Embedded C code
Β©οΈ COPYRIGHT
- Copyright (c) Jean-Claude Wippler
- Copyright (c) Steve Landers
- Copyright (c) 2011-2024 Andreas Kupries
okay that's the whole page, we made it, no cap πβ¨ this was critcl_cproc_types(3tcl) from the critcl doc set, version 3.3.1, dated 3.3.1 π go forth and generate C code like the main character you are, bestie π¦π
π¦ where this page comes from
- ubuntu resolute: critcl 3.3.1+dfsg-1build1 Β· BSD
- ubuntu stonking: critcl 3.3.1+dfsg-1.1 Β· BSD
- Copyright (c) 2011-2024 Andreas Kupries
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms