Call C Function that takes NULL as an argument

,

I am trying to have my Julia code communicate with an external program. The external program provides a C API for the inter-process communications. The documentation is a bit sparse but it says:

“Call flc_attach(NULL,0); at program startup.”

I’ve been using a Pluto notebook to try and learn about the @ccall macro. I’ve had good success calling c’s clock function with the line:

t2 = @ccall clock()::Int32

I’m having a hard time figuring out what type I need to pass as an argument to the flc_attach() function as well as what type it should return.

I’ve tried the following and get an error: “TypeError: in ccall method definition, expected Type, got a value of type Nothing”

flcomms = “~//Downloads//libflcomms.so”
NULL=Ptr{UInt8}(C_NULL)
@ccall flcomms.flcattach(NULL::C_NULL)::nothing

I have also tried

@ccall flcomms.flcattach(NULL::C_NULL)::Int8

in case the function returns a 1 or a 0 but then I get this error: “MethodError: no method matching cconvert(::Ptr{Nothing}, ::Ptr{UInt8})”

Sorry if this is overly basic. I use Julia because I do not know C.

There’s no way to guess what it returns from the provided information but for the arguments it probably works with @ccall flcomms.flcattach(C_NULL::Ptr{Cvoid}, 0::Cint).

C_NULL is a value Ptr[Cvoid}(0), you should use its type Ptr{Cvoid} as the type annotation:

@ccall flcomms.flcattach(C_NULL::Ptr{Cvoid})::Int8

You might be interested in Clang.jl which can generate bindings for you.

(@v1.6) pkg> dev Clang

julia> using Clang.Generators

julia> ctx = create_context("your/header/obsolute/path.h", get_default_args())

julia> build!(ctx)
1 Like

Thanks for the quick replies. I tried both of the suggestions with a both a ::nothing return type and ::Int8 and ::Int32. The ::nothing return type gives me the same error as before and the integer return types gives me this error:

“TypeError: in ccall: first argument not a pointer or valid constant expression, expected Ptr, got a value of type Tuple{Symbol,String}”

for both of the code snippets above.

I’ll dive ino Clang.jl and see what I can learn.

Tuple{Symbol,String} doesn’t make any immediate sense for any of the suggestions above. What did you try more exactly when you got that error?

@ccall flcomms.flcattach(C_NULL::Ptr{Cvoid})::Int8

gives me the error:

TypeError: in ccall: first argument not a pointer or valid constant expression, expected Ptr, got a value of type Tuple{Symbol,String}

Is it possible that the address to the shared library is generating the Tuple error?

PS I found the header file for the shared library. Here is the header info for the function:

FLCAPI int flc_attach(const char id, unsigned flags);
/
Attach to shared memory */

so it looks like ::Int8 (or 32?) would be the correct return type.

shell> vim test.h

shell> cat test.h
 int flc_attach(const char id, unsigned flags);

julia> using Clang.Generators

julia> ctx = create_context("test.h", get_default_args())
[ Info: Parsing headers...
Context(...)

julia> build!(ctx)
[ Info: Processing header: test.h
[ Info: Building the DAG...
┌ Warning: default libname: ":libxxx" is being used, did you forget to set `library_name` in the toml file?
└ @ Clang.Generators ~/.julia/dev/Clang/src/generator/audit.jl:16
[ Info: Emit Julia expressions...
function flc_attach(id, flags)
    ccall((:flc_attach, libxxx), Cint, (Cchar, Cuint), id, flags)
end

[ Info: Done!
Context(...)

This is how I usually ask Clang.jl on how to wrap a line of C code to Julia. Note that, don’t forget to replace that libxxx with your libname flcomms.

Ok, that error is because flcomms is a non-const string. With the information from the .h file I expect this should work:

const flcomms = expanduser("~/Downloads/libflcomms.so")
@ccall flcomms.flcattach(0::Cchar, 0::Cuint)::Cint

Note, the C instructions to call flc_attach(NULL,0); don’t really match the header file.

Hey, wait, cursive style and stuff. Is there any chance this actually says

FLCAPI int flc_attach(const char *id, unsigned flags);
/* Attach to shared memory */

If so the invocation should be

const flcomms = expanduser("~/Downloads/libflcomms.so")
@ccall flcomms.flcattach(C_NULL::Ptr{Cchar}, 0::Cuint)::Cint

This did it. Thanks! You all are awesome. I really appreciate the help. The Julia community is fantastic!

You can format big code blocks with ``` and small inline ones with ` :slight_smile: Then it won’t get autoformatted into cursive or bold text styles in discourse.

1 Like

You might also want to check out GitHub - analytech-solutions/CBinding.jl: Automatic C interfacing for Julia if the library has some more complex C constructs.

1 Like

@krrutkow Thanks CBinding.jl has been very helpful.

I have been able to successfully make a function call and connect to the other program’s shared memory. I only know this because there is a console command that shows the number of read/writes to each datablock.

Ideally my program will connect to the external process via the C API, read a memory datablock, copy that data block to a variable that I can then use in julia to run a flight dynamics control algorithm.

Here is a section of what I have that I think is working properly:

using CBinding
c`-std=c99 -Wall`
	c"""
		#include </home/rwalters31/Downloads/flcomms.h>
		#include <stddef.h>
	"""
	c"""
		struct STATES {
		double X, Y, VCLIMB;  /* position */
		double Roll, Pitch, Yaw;      /* orientation */
		} fpmx;
	"""
const flcomms = expanduser("~/Downloads/libflcomms.so")
NULL=Ptr{UInt8}(C_NULL) 
@ccall flcomms.flc_attach(0::Cchar, 0::Cuint)::Cint
flags = c"FLC_MODE_READ"|c"FLC_TYPE_DOUBLE"
vsi_handle = @ccall flcomms.flc_open("AVIONICSVSI"::Cstring,48::Cint,0::Cint,flags::Cuint)::c"FLC_HANDLE"

I then need to call an additional C function flc_read. The info from the library header file I have is:
FLCAPI void *flc_read(FLC_HANDLE, void *dst, size_t size);

Here is my julia code to call that function:

julia_states = Ref{c"struct STATES"}
vsiptr = pointer_from_objref(julia_states)
@ccall flcomms.flc_read(vsi_handle::c"FLC_HANDLE", vsiptr::Ptr{c"struct STATES"}, 48::Csize_t)::Ptr{Cvoid}

My understanding is that the C function takes the “FLC_HANDLE” type which contains the information on the datablock (size, memory location, etc.) and then copies the data block to a new memory location designated by the second argument. My code frequently crashes after running the last line.

Is my error in the C declaration types? I’ve been reading through the C documentation on pointers but it is making my head spin.

A few comments first:

  1. You can use the j option (or if that fails try a J) like this: c"""..."""j to get Julia names and not need to use things like c"FLC_MODE_READ" all over the place.
  2. CBinding.jl should be able to link to the library with c`-std=c99 -Wall -L$(expanduser("~/Downloads")) -lflcomms`
  3. You cannot simply mix CBinding types with @ccall usage(probably the reason for the crashing), as there are some rather evil tricks being used to support forward declarations in Julia.

Functions defined in the flcomms header should be bound, are they not? (You can use the n option to be notified of warnings, like c"""..."""n)

I cannot test it, but I would expect to have this kind of usage:

module flcomms
  using CBinding
  c`-std=c99 -Wall -I$(expanduser("~/Downloads")) -L$(expanduser("~/Downloads")) -lflcomms`
  c"""
    #include <flcomms.h>
    #include <stddef.h>
    
    struct STATES {
      double X, Y, VCLIMB;  /* position */
      double Roll, Pitch, Yaw;      /* orientation */
    } fpmx;
  """j
end

# now just use the bindings that were created by CBinding
using .flcomms
flc_attach(0, 0)

flags = FLC_MODE_READ | FLC_TYPE_DOUBLE
vsi_handle = flc_open("AVIONICSVSI", 48, 0, flags)

states = Ref(STATES())
flc_read(vsi_handle, states, sizeof(STATES))

The first function call gives me an error. I tried with both the little ‘j’ and the big ‘J’

MethodError: no method matching unsafe_convert(::Type{CBinding.Cptr{CBinding.Cconst{Int8}}}, ::Int64)

Closest candidates are:

unsafe_convert(::Type{CBinding.Cptr{T}}, !Matched::Union{Cstring, String}) where T<:Union{CBinding.Cconst{Int8}, CBinding.Cconst{UInt8}, Int8, UInt8} at /home/rwalters31/.julia/packages/CBinding/54pqC/src/pointers.jl:41

unsafe_convert(::Type{CBinding.Cptr{T}}, !Matched::Vector{T} where T) where T at /home/rwalters31/.julia/packages/CBinding/54pqC/src/pointers.jl:40

unsafe_convert(::Type{CBinding.Cptr{T}}, !Matched::Ptr) where T at /home/rwalters31/.julia/packages/CBinding/54pqC/src/pointers.jl:38

...

macro expansion@functions.jl:0[inlined]
funccall(::Type{Int32}, ::Type{Tuple{CBinding.Cptr{CBinding.Cconst{Int8}}, UInt32}}, ::Val{:cdecl}, ::Main.workspace12.flcomms.Cbinding_flc_attach{:flc_attach}, ::Int64, ::Int64)@functions.jl:32
macro expansion@functions.jl:61[inlined]
funccall(::Main.workspace12.flcomms.Cbinding_flc_attach{:flc_attach}, ::Int64, ::Int64)@functions.jl:42
(::Main.workspace12.flcomms.Cbinding_flc_attach{:flc_attach})(::Int64, ::Int64)@context_c.jl:713
top-level scope@Local: 1[inlined]```

Ahh, good it works! :laughing: I don’t know what the signature of flc_attach is, but it seems that the first argument should be C_NULL, not 0.