First attempt to link to C code - what am I doing wrong?

Hello everyone,
I’m trying to learn more about Crystal and I’m attempting to use a very simple function from some C code. The C part is adapted from Shared libraries with GCC on Linux - Cprogramming.com

This is on Linux Mint - Crystal 1.11.2

Here’s what I’ve done so far:

My hello.h file:

#ifndef hello_h__
#define hello_h__
 
extern void hello(void);
 
#endif

My hello.c file:

#include <stdio.h>
 
void hello(void)
{
    puts("Hello, I am a test function");
}

My main.c:

#include <stdio.h>
#include "hello.h"
 
int main(void)
{
    puts("This is coming from main");
    hello();
    return 0;
}

All of the above works fine with gcc, etc:

gcc -c -Wall -Werror -fpic hello.c
gcc -shared -o libhello.so hello.o
export LD_LIBRARY_PATH=/tmp:$LD_LIBRARY_PATH
gcc -L/tmp/ -Wall -o test main.c -lhello

Test:

./test 
This is coming from main
Hello, I am a test function

Great! Now the Crystal part - I have the file: use_c_function.cr

@[Link(ldflags: "-L/tmp/")]

lib Hello 
  fun hello : Void
end

Hello.hello

I try to build:

crystal build use_c_function.cr

And get the error:

crystal build use_c_function.cr 
/usr/bin/ld: _main.o0.o: in function `__crystal_main':
/tmp/use_c_function.cr:7: undefined reference to `hello'
collect2: error: ld returned 1 exit status
Error: execution of command failed with exit status 1: cc "${@}" -o /tmp/use_c_function  -rdynamic -L/usr/bin/../lib/crystal -L/tmp/ -lpcre2-8 -lgc -lpthread -levent  -lrt -lpthread -ldl

in my Crystal code I tried using lib hello as well, but that gave me a different error:

crystal build use_c_function.cr 
In use_c_function.cr:3:5

 3 | lib hello 
         ^
Error: expecting token 'CONST', not 'hello'

What am I doing wrong here? Thanks in advance!

@[Link(ldflags: "-L/tmp/")] tells the linker where to look for libraries, but not which library to load for the hello symbol.
It’s customary to call library types with a Lib prefix and in that case the compiler should be able to derive that LibHello relates to -lhello.
So it’ll probably work if you rename Hello to LibHello.
Alternatively, you can explicitly add -lhello to the ldflags.

1 Like

thank you!

For completeness this is what ended up working for me:

@[Link(ldflags: "-L/tmp/ -lhello")]

lib Hello
  fun hello : Void
end

Hello.hello

Unfortunately this did not work:

@[Link(ldflags: "-L/tmp/")]

lib LibHello
  fun hello : Void
end

LibHello.hello

Error:

crystal build use_c_function.cr 
/usr/bin/ld: _main.o0.o: in function `__crystal_main':
/tmp/use_c_function.cr:7: undefined reference to `hello'
collect2: error: ld returned 1 exit status
Error: execution of command failed with exit status 1: cc "${@}" -o /tmp/use_c_function  -rdynamic -L/usr/bin/../lib/crystal -L/tmp/ -lpcre2-8 -lgc -lpthread -levent  -lrt -lpthread -ldl
2 Likes
@[Link(ldflags: "-L/tmp/ -lhello")]

lib Hello
  fun hello : Void
end

Hello.hello

Hi, I test this code, it only work if set export LD_LIBRARY_PATH=/tmp:$LD_LIBRARY_PATH before run, are you also same with me?

hi @zw963 ! sorry for the late response

Correct - if I don’t run this:

export LD_LIBRARY_PATH=/tmp:$LD_LIBRARY_PATH

then I get:

./test 
./test: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory

Crystal has an option:

--link-flags FLAGS  Additional flags to pass to the linker

So, you don’t need to specify all link flags in the link annotation:

@[Link("hello")]
lib Hello 
  fun hello : Void
end

Hello.hello

And compile with:

crystal build hello.cr --link-flags -L`pwd`
crystal build hello.cr --link-flags "-L$(pwd) -Wl,-rpath,$(pwd)"
1 Like

hi @kojix2 ,
thank you so much I didn’t know that, I tested it and it works

1 Like

I am trying to run the first example from here:
https://crystal-lang.org/reference/1.13/syntax_and_semantics/c_bindings/fun.html

I have the following code in math_cos.cr:

lib C
  # In C: double cos(double x)
  fun cos(value : Float64) : Float64
end

puts C.cos(1.5)

The math.h file is under /usr/include - this is how I check:

locate math.h &>/dev/stdout | head -n1
/usr/include/math.h

So then I try to build:

crystal build math_cos.cr

but it fails with:

crystal build math_cos.cr 
/usr/bin/ld: _main.o0.o: in function `__crystal_main':
/home/weirdbricks/Documents/crystal/c_struct_example/math_cos.cr:6: undefined reference to `cos'
collect2: error: ld returned 1 exit status
Error: execution of command failed with exit status 1: cc "${@}" -o /home/weirdbricks/Documents/crystal/c_struct_example/math_cos  -rdynamic -L/usr/bin/../lib/crystal -lpcre2-8 -lgc -lpthread -levent  -lrt -lpthread -ldl

Then I tried it the way @kojix2 suggested:

crystal build math_cos.cr --link-flags "-L/usr/include -lmath"
/usr/bin/ld: cannot find -lmath (this usually means you need to install the development package for libmath): No such file or directory
collect2: error: ld returned 1 exit status
Error: execution of command failed with exit status 1: cc "${@}" -o /home/weirdbricks/Documents/crystal/c_struct_example/math_cos -L/usr/include -lmath -rdynamic -L/usr/bin/../lib/crystal -lpcre2-8 -lgc -lpthread -levent  -lrt -lpthread -ldl

but it also fails.

Am I doing anything obviously wrong here?

crystal build math_cos.cr --link-flags "-lm"

This is how it works.

But why -lm instead of -lmath?
I don’t know either.

If you search your machine with the grep command, you will find libm.so.

ldconfig -p | grep libm.so
	libm.so.6 (libc6,x86-64) => /lib/x86_64-linux-gnu/libm.so.6
	libm.so.6 (libc6) => /lib/i386-linux-gnu/libm.so.6
	libm.so.6 (libc6) => /lib32/libm.so.6

While the header file is named math.h, the library itself is named libm, so the link flag must be -lm.

The C language is older than Linux. This somewhat inconsistent naming is likely due to historical reasons.

2 Likes

thanks again @kojix2 , I really appreciate it

I can confirm that:

crystal build math_cos.cr --link-flags "-lm"

worked great!

1 Like