C binding: passing NULL, passing &int, double signature

I try to interface crystal to an existing C library for the first time.
Most things work out nicely but I have problem with passing NULL
and passing a parameter which will be assigned a value from the c function.

The example below is very reduced. The actual problem can be found in the real case below.

Prototype of c_fun to be used

/* prototype of c_func*/
int c_func(THATOBJ *thatobj, unsigned char *out, size_t *outlen)

Usage from C

.
THATOBJ *thatobj
size_t outlen;
unsigned char *out;

.
/* Determine buffer length */
c_func(thatobj, NULL, &outlen)
/* Allocate dyn memory */
out = malloc(outlen)
/* Process. by calling same c_func again! now with a result buffer */
c_func(thatobj, out, &outlen)
/* Now have result in out with size outlen */
/* Process given result and than release dyn memory*/
free(out)
.

If you are familiar with C some things strikes:

  1. NULL - is normally typedefed. Probably not defined in the imported library. How to define NULL in Crystal?
  2. The c_func is used twice with different signature according to Crystals syntax.
    You can’t have union on parameter definitions in Crystal ?.
  3. The first call to c_func will assign value via a passed parameter

I have outlined the very crystal code like with question marks ?? where I’m unsure.

Crystal source

.
?? Is `LibC` required
lib LibC
  fun malloc(size : LibC::SizeT) : Void*
  fun free(ptr : Void*) : Void
end
.
@[Link(ldflags: "thelib.a")]
lib LibTheLibC
.
alias THATOBJ = Char*
??alias OUTLEN = size_t outlen 
??alias OUT = unsigned char *out

.

fun c_func_first_call = c_func(that : THATOBJ, 
		novalue : NULL??, len : ??OUTLEN)
fun c_func_second_call = c_func(that : THATOBJ, 
		hasvalue : OUT, len : ??OUTLEN)
.
end
.
# crytal usage

??OUTLEN outlen
??OUT out
.
LibTheLibC.c_func_first_call(THATOBJ, ??NULL, outlen)
out = LibC.malloc(outlen)
LibTheLibC.c_func_second_call(THATOBJ, out, outlen)
# Take care of the result out.
# release dyn memory
LibC.free(out)
.

Do I need two crystal functions to overcome the NULL | OUT parameter union type?

Looking forward to any advice.

Nice writings in

The crystal documentation

https://crystal-lang.org/reference/1.15/syntax_and_semantics/c_bindings/index.html

##Real case
https://docs.openssl.org/3.3/man3/EVP_PKEY_encrypt/#examples

Because of the large number of questions, perhaps examples of actual work would be more helpful than explanations.

For example, many questions can be answered by observing the following code written by naqvis.

Once you have some basics, the following tools will make it easier.

Answering your direct questions:

  1. NULL is just an expression of a null pointer. If the type of the parameter is Pointer(THATOBJ), the corresponding value in Crystal would be Pointer(THATOBJ).null. Or you can just put nil as a short cut, it autocasts to a null pointer.
  2. The signature is defined in the header file in C and the lib bindings in Crystal and it’s identical in both cases. Passing the NULL value doesn’t change the signature. See answer 1.
  3. Typically, this can be solved by allocating a local variable with uninitialized and passing a pointer to it. This can be simplified using the out keyword.
2 Likes

While it is implicit by looking at the examples, it is probably good to note explicitly: It is often a good idea to not include the pointer in the alias definition. Keeping it on the same level as the C definition can help a lot.

Also, if you have multiple definitions like

  alias Foo = Void
  alias Bar = Void
  alias Baz = Void

which can be a good idea if you only ever deal in pointers to the stuff and don’t want to spend a lot of time typing down definitions you never actually make use of.

Then having all those aliases to Void can become error prone and it may be a good idea to change some or all to

  type Foo = Void
  type Bar = Void
  type Baz = Void

, which will make them incompatible with each other (and incompatible with the base type as well, so don’t type an integer value if you want to pass integers to it without casting).

2 Likes

Thanks!
I really appreciate all friendly writings:

  • Flooding source from kojix2
  • Distinct note on EXPRESSION NULL and details on uninitialized and out from straight-shoota.
    More at out - Crystal
  • Important thoughts on alias and type by yxhuvud (axehead?)

My final source became close too

fun c_func = C_func(that : THATOBJ, buff : Char*, len : Int32*)
outlen = uninitialized Int32
LibTheLibC.c_func(that, nil, pointerof(outlen)
buffer = LibC.malloc(outlen).as(Pointer(Char))
LibTheLibC.c_func(that, buffer, pointerof(outlen)

Thanks again!

If you don’t want to rely on C’s malloc, this might work.

buffer = Pointer(LibC::Char).malloc(outlen)

Using Slice(UInt8) or Bytes might also be an option.

Please note that Pointer(Char) is likely the wrong data type. Crystal’s Char type represents a unicode codepoint and is not equivalent to LibC::Char! They’re 4 and 1 bytes wide, respectively.

So you might want to use Pointer(LibC::Char) instead.
Or, as kojix2 mentioned, allocate either Pointer(LibC::Char) or Slice(LibC::Char).