Testing C functions with Julia

question

#1

Hello,
I want to use Julia to write unit tests for C functions.
How can I do this?
Example of a C function:

#include "../include/mixer.h"

void mixer(REAL32 total_thrust, REAL32 delta_thrust, struct actuator_output *output)
{
	/* Calculate a desired thrust for each motor */
	double th1 = total_thrust + 0.5 * delta_thrust;
	double th2 = total_thrust - 0.5 * delta_thrust;
	
	/* Limit the desired thrust value */
	if (th1 > 1.0) th1 = 1.0;
	if (th1 < MIN_THRUST) th1 = MIN_THRUST;
	output->back_motor = th2;
	
	if (th2 > 1.0) th1 = 1.0;
	if (th2 < MIN_THRUST) th2 = MIN_THRUST;
	output->front_motor = th2;
}

Header file:

#ifndef MIXER_H_
#define MIXER_H_

#define MIN_THRUST 0.02

typedef float REAL32;

struct actuator_output {
	REAL32 front_motor;
	REAL32 back_motor;
};

void mixer(REAL32 total_thrust, REAL32 pid_out, struct actuator_output *output);

#endif

Script to compile the code to an .so file (is this needed?):

gcc -c -fPIC src/mixer.c -o mixer.o
gcc -shared -fPIC -Wl,-soname,libmixer.so.1 -o libmixer.so.1.0.1 mixer.o -lc

After running this script I have a file libmixer.so.1.0.1 , but how do I now call
this function from Julia?
And is it necessary to create a shared object file, or can Julia call c functions
in a more easy way?


#2

I think everything you need is here:
http://docs.julialang.org/en/release-0.5/manual/calling-c-and-fortran-code/


#3

Thanks for the hint!

I have now the following code:

println("Testing mixer.c ...")

#=
struct actuator_output {
	REAL32 front_motor;
	REAL32 back_motor;
};

void mixer(REAL32 total_thrust, REAL32 pid_out, struct actuator_output *output);
=#

type Actuator_output
    front_motor::Float32          # electrical generator power in kW
    back_motor::Float32 # ratio of the drum diameter and the tether diameter
end

total_thrust = Float32(0.5)
pid_out = Float32(0.1)

output = Actuator_output(0.0, 0.0)

ccall(:mixer, "lib/libmixer.so", (total_thrust, pid_out, output))

println(output)

But executing it gives the following error:

ufechner@TUD277255:~/uavtalk$ ./test_mixer.sh 
Testing mixer.c ...
ERROR: LoadError: TypeError: ccall: expected Type{T}, got String
 in include_from_node1(::String) at ./loading.jl:488
 in process_options(::Base.JLOptions) at ./client.jl:262
 in _start() at ./client.jl:318
while loading /home/ufechner/uavtalk/scripts/test_mixer.jl, in expression starting on line 22

Any idea?


#4

http://docs.julialang.org/en/release-0.5/manual/calling-c-and-fortran-code/

  1. (:function, “library”) pair (must be a constant, but see below).
  2. Return type (see below for mapping the declared C type to Julia)
    • This argument will be evaluated at compile-time.
  3. A tuple of input types. The input types must be written as a literal tuple, not a tuple-valued variable or expression.
  • This argument will be evaluated at compile-time.
  1. The following arguments, if any, are the actual argument values passed to the function.

I think you are missing both two and three. Start from working example:

function gethostname()
  hostname = Array{UInt8}(128)
  ccall((:gethostname, "libc"), Int32,
        (Ptr{UInt8}, Csize_t),
        hostname, sizeof(hostname))
  hostname[end] = 0; # ensure null-termination
  return unsafe_string(pointer(hostname))
end

#5

Ok, but the return type is void . So what is the equivalent in Julia?


#6

http://docs.julialang.org/en/release-0.5/manual/calling-c-and-fortran-code/#creating-c-compatible-julia-function-pointers

A = [1.3, -2.7, 4.4, 3.1]
ccall(:qsort, Void, (Ptr{Cdouble}, Csize_t, Csize_t, Ptr{Void}),
      A, length(A), sizeof(eltype(A)), mycompare_c)

#7

Ok, this is what I get now. Code:

println("Testing mixer.c ...")

#=
struct actuator_output {
	REAL32 front_motor;
	REAL32 back_motor;
};

void mixer(REAL32 total_thrust, REAL32 pid_out, struct actuator_output *output);
=#

type Actuator_output
    front_motor::Float32          # electrical generator power in kW
    back_motor::Float32 # ratio of the drum diameter and the tether diameter
end

total_thrust = Float32(0.5)
pid_out = Float32(0.1)

output = Actuator_output(0.0, 0.0)

ccall((:mixer, "lib/libmixer.so"), Void, (Float32, Float32, Ptr(Actuator_output)), total_thrust, pid_out, output)

println(output)

Error message:

Testing mixer.c ...
ERROR: LoadError: MethodError: Cannot `convert` an object of type Type{Actuator_output} to an object of type Ptr{T}
This may have arisen from a call to the constructor Ptr{T}(...),
since type constructors fall back to convert methods.
 in anonymous at ./<missing>:?
 in include_from_node1(::String) at ./loading.jl:488
 in process_options(::Base.JLOptions) at ./client.jl:262
 in _start() at ./client.jl:318
while loading /home/ufechner/uavtalk/scripts/test_mixer.jl, in expression starting on line 22

Any idea?


#8

Ok, I fixed it:

println("Testing mixer.c ...")

#=
struct actuator_output {
	REAL32 front_motor;
	REAL32 back_motor;
};

void mixer(REAL32 total_thrust, REAL32 pid_out, struct actuator_output *output);
=#

type Actuator_output
	front_motor::Float32
	back_motor::Float32
end

total_thrust = Float32(0.5)
pid_out = Float32(0.1)

output = Ref(Actuator_output(0.0, 0.0))

ccall((:mixer, "lib/libmixer.so"), Void, (Float32, Float32, Ref{Actuator_output}), total_thrust, pid_out, output)

println(output.x)

An example like this should be added to the manual. I found some hints how to do this on stack overflow, but it is not explained in the manual:


#9

A Pull Request to the manual would be very welcome.


#10

You don’t need the Ref. This is already covered in the table entry for T* which stated that you just need to use Ref{T} as argument type and nothing else.


#11

Thanks for the info.
For the record: Today I wrote 16 unit tests in Julia for my C code and found and fixed more than one non-trivial bug in my C code. Julia provides efficient means to write tests for C functions. :slight_smile:
Uwe


#12

OT: why you need to develop using C code? Why can’t you write it in Julia?


#13

Well, currently I am writing security critical, real-time flight control software for very small embedded systems. Julia is not a good choice for this kind of applications.