Initialize buffer for ccall

I am using the Raspberry PI running Raspbian and Julia to interface with the MCC 172 DSP board. The MCC 172 has a complete c library. I am working on making those calls accessible to Julia using the ccall functionality.

I find that I learn best through examples, and have not yet found an example for initialising a buffer in Julia to pass to the c-program through ccall and have the c-program fill the buffer and return it to Julia. The documentation for the c-function is at
C Library Reference — MCC DAQ HAT Library 1.4.0 documentation near the bottom.

My code snippet is:

# Reads status and multiple samples from an analog input scan.	
# documentation https://mccdaq.github.io/daqhats/c.html#analog-input-scan-option-flags
function mcc172_a_in_scan_read(address::Integer, samples_per_channel::UInt32, mcc172_num_channels::Integer, timeout::Float64)
	
	status = Ref{UInt16}(0)					# Initialize
	buffer_size_samples = samples_per_channel * UInt32(mcc172_num_channels)	# Initialize
	buffer = Ref{Array{Float64,1}} #(0.0,buffer_size)
	samples_read_per_channel = Ref{UInt32}(0) # Initialize
	@show(status[], buffer_size_samples[], samples_read_per_channel[], buffer)
	
	success = ccall((:mcc172_a_in_scan_read, "/usr/local/lib/libdaqhats.so"),
	Cint, (UInt8, Ref{UInt16}, UInt32, Cdouble, Ref{Array{Float64,1}}, UInt32, Ref{UInt32}), 
	UInt8(address), status, samples_per_channel, timeout, buffer, buffer_size_samples, samples_read_per_channel)
	@show(status[], buffer_size_samples[], samples_read_per_channel[])
	return "Fini"
end

The combination printout and error message I get follows, where line 306 actually is the line success = ccall(…

julia> mcc172_a_in_scan_read(UInt8(0),UInt32(1024), 4, 4.0)
status[] = 0x0000
buffer_size_samples[] = 0x00001000
samples_read_per_channel[] = 0x00000000
buffer = Ref{Array{Float64,1}}
ERROR: MethodError: Cannot `convert` an object of type Type{Ref{Array{Float64,1}}} to an object of type Array{Float64,1}
Closest candidates are:
  convert(::Type{T}, ::AbstractArray) where T<:Array at array.jl:554
  convert(::Type{T}, ::T) where T<:AbstractArray at abstractarray.jl:14
  convert(::Type{T}, ::LinearAlgebra.Factorization) where T<:AbstractArray at /home/pi/work/build/usr/share/julia/stdlib/v1.5/LinearAlgebra/src/factorization.jl:55
  ...
Stacktrace:
 [1] Base.RefValue{Array{Float64,1}}(::Type{T} where T) at ./refvalue.jl:8
 [2] convert(::Type{Ref{Array{Float64,1}}}, ::Type{T} where T) at ./refpointer.jl:63
 [3] cconvert(::Type{T} where T, ::Type{T} where T) at ./essentials.jl:388
 [4] mcc172_a_in_scan_read(::UInt8, ::UInt32, ::Int32, ::Float64) at /home/pi/daqhatsJulia/daqhats.jl:306
 [5] top-level scope at REPL[160]:1

I think I need to initialize buffer differently or modify the type tuple for the buffer type in the ccall function.

Help is appreciated.

buffer = Ref{Array{Float64,1}} is certainly wrong. This a type, not even an instance of a Ref type — that is, buffer isa Type returns true. From the documentation, buffer should be an allocated array, i.e. buffer should be an Array{Float64,1} (a Vector{Float64}) of length buffer_size_samples.

Also, when you pass the buffer to the ccall it should be as a Ptr{Cdouble}. Julia knows how to pass an Array to a pointer argument. Note also that ccall will automatically convert arguments by calling convert — there’s no need to explicitly convert to UInt8 etcetera.

That is, it should probably look something like this:

function mcc172_a_in_scan_read(address::Integer, samples_per_channel::Integer, mcc172_num_channels::Integer, timeout::Real)
	status = Ref{UInt16}()
	buffer_size_samples = samples_per_channel * mcc172_num_channels
	buffer = Vector{Float64}(undef, buffer_size_samples)
	samples_read_per_channel = Ref{UInt32}()
	
	success = ccall((:mcc172_a_in_scan_read, "/usr/local/lib/libdaqhats.so"),
	    Cint, (UInt8, Ref{UInt16}, UInt32, Cdouble, Ptr{Cdouble}, UInt32, Ref{UInt32}), 
	    address, status, samples_per_channel, timeout, buffer, buffer_size_samples, samples_read_per_channel)
    return buffer, Int(samples_read_per_channel[]), status[]
end

or maybe resize!(buffer, samples_read_per_channel[] * mcc172_num_channels). Note that you don’t need to initialize the Ref arguments with a value since (if I understand the documentation for mcc172_a_in_scan_read correctly) they are purely output parameters.

Thank-you very much, that is very helpful. It works now.

Happy New Year to you and all who faithfully provide answers to those like me.

Now I am trying to initialize a structure for ccall using the same library as previously and having problems.

I am not sure if my struct is initialized correctly, or if there is a problem with the parameters in ccall.
The c-function I am calling is:

int hat_list (uint16_t filter_id , struct HatInfo * list )

# Global functions and data - https://mccdaq.github.io/daqhats/c.html
# description of function https://mccdaq.github.io/daqhats/c.html#daqhats_8h_1a30cd29e26738a6f6d11746f3ccfa06e8

mutable struct HatInfo
    address::UInt8 			# The board address.
    id::UInt16 				# The product ID, one of [HatIDs](@ref HatIDs)
    version::UInt16 			# The hardware version
    product_name::String	# The product name  (c - initialized to 256 characters)
end


"""
	hat_list(filter_id)
Return a list of detected DAQ HAT boards.
"""
function hat_list(filter_id::String)
	idDict = Dict{String, UInt32}(
	"HAT_ID_ANY"     => 0,  # Match any DAQ HAT ID in hat_list().
	"HAT_ID_MCC_118" => 0x0142,  # MCC 118 ID.
	"HAT_ID_MCC_118_BOOTLOADER" => 0x8142,  # MCC 118 in firmware update mode ID.
	"HAT_ID_MCC_128" => 0x0146,  # MCC 128 ID.
	"HAT_ID_MCC_134" => 0x0143,  # MCC 134 ID.
	"HAT_ID_MCC_152" => 0x0144,  # MCC 152 ID.
	"HAT_ID_MCC_172" => 0x0145)  # MCC 172 ID.
	
	list = HatInfo(0, 0, 0, repeat('a', 256))
#	@show(list)
	success = ccall((:hat_list, "/usr/local/lib/libdaqhats"), 
	Cint, (UInt16, Ptr{HatInfo}), idDict[filter_id], list)
#	@show(list)
	#list = unsafe_load(Ptr{HatInfo}(list))
end

the output from this is

julia> hat_list("HAT_ID_ANY")
ERROR: MethodError: no method matching unsafe_convert(::Type{Ptr{HatInfo}}, ::HatInfo)
Closest candidates are:
  unsafe_convert(::Type{T}, ::T) where T<:Ptr at essentials.jl:391
  unsafe_convert(::Type{Ptr{T}}, ::Ptr{Tuple{Vararg{T,N}}}) where {N, T} at refpointer.jl:136
  unsafe_convert(::Type{P}, ::Ptr) where P<:Ptr at essentials.jl:392
  ...
Stacktrace:
 [1] hat_list(::String) at /home/pi/daqhatsJulia/daqhats172.jl:44
 [2] top-level scope at REPL[3]:1

where line 44 is the ccall function.

reading the c-function description some more I find I will need to create an array of structures to pass into the function, which is even more daunting.

I did some more experimenting with trying to duplicate the following section of code from the documentation that involves the NULL pointer. The documentation tells me that: There is no invalid (NULL) Ref in Julia, but a C_NULL instance of Ptr can be passed to a ccall Ref argument.

int count = hat_list(HAT_ID_ANY, NULL);

if (count > 0)
{
    struct HatInfo* list = (struct HatInfo*)malloc(count * 
        sizeof(struct HatInfo));
    hat_list(HAT_ID_ANY, list);

    // perform actions with list

    free(list);
}

to call the first line of code I tried where the idDict is given in the previous post.

count = ccall((:hat_list, "/usr/local/lib/libdaqhats"), 
	Cint, (UInt16, Ref{C_NULL}), idDict["HAT_ID_ANY"], 0)

This gives the following error message

julia> hat_list("HAT_ID_ANY")
ERROR: MethodError: First argument to `convert` must be a Type, got Ptr{Nothing} @0x00000000
Stacktrace:
 [1] Base.RefValue{Ptr{Nothing} @0x00000000}(::Int32) at ./refvalue.jl:8
 [2] convert(::Type{Ref{Ptr{Nothing} @0x00000000}}, ::Int32) at ./refpointer.jl:63
 [3] cconvert(::Type{T} where T, ::Int32) at ./essentials.jl:388
 [4] hat_list(::String) at /home/pi/daqhatsJulia/daqhats172.jl:42
 [5] top-level scope at REPL[7]:1

I appreciate all your help!

If list is a single HatInfo struct, then you should declare the ccall parameter as Ref{HatInfo} and pass a hatinfo = Ref{HatInfo}() object. Then get the value with hatinfo[].

If list is an array of HatInfo structs, then you should declare the parameter as Ptr{HatInfo} and pass an array. (But it doesn’t look like this is the case since you don’t pass an array length?)

C_NULL is an instance (a pointer value) of Ptr{Cvoid}, not a pointer type. If you want to pass a NULL argument you should do:

count = ccall((:hat_list, "/usr/local/lib/libdaqhats"), 
   	Cint, (UInt16, Ptr{Cvoid}), idDict["HAT_ID_ANY"], C_NULL)

(Then to allocate list you would then do list = Vector{HatInfo}(undef, count), and pass it as a Ptr{HatInfo} argument to the subsequent hat_list call.)

I think you will have troubles here (Julia crash). At least I always have when I try it. The problem seems to be that the product_name::String does not allocate the 256 chars needed in the C side.
Following what LLVM.jl does, declare it as product_name::NTuple{256,UInt8} and

list = HatInfo(0, 0, 0, map(UInt8, (string(repeat(" ",256))...,)))

then you would recover the text with

String([list.product_name...])

Very painful.

I am making progress slowly. Thanks Steve I have ccall finding the count working. I believe C generates the total memory required by using the count function as follows:

   info_list = (struct HatInfo*)malloc(info_count * sizeof(struct HatInfo));

This means the number of items in the array can be calculated ahead of time.

Thanks to joa-quim I have updated my struct and am generating a data structure in Julia that should match the size generated in C. My code now works, except for when I uncomment (shown uncommented below) the second ccall function, it won’t compile then with the following message:

could not evaluate ccall argument type (it might depend on a local variable)

Code shown below:

# Global functions and data - https://mccdaq.github.io/daqhats/c.html

mutable struct HatInfo
    address::UInt8 			# The board address.
    id::UInt16 				# The product ID, one of [HatIDs](@ref HatIDs)
    version::UInt16 		# The hardware version
    product_name::NTuple{256,UInt8}	# The product name (c - initialized to 256 characters)
end


"""
	hat_list(filter_id)
Return a list of detected DAQ HAT boards.
"""
function hat_list(filter_id::String)
	idDict = Dict{String, UInt32}(
	"HAT_ID_ANY"     => 0,  	 # Match any DAQ HAT ID in hat_list().
	"HAT_ID_MCC_118" => 0x0142,  # MCC 118 ID.
	"HAT_ID_MCC_118_BOOTLOADER" => 0x8142,  # MCC 118 in firmware update mode ID.
	"HAT_ID_MCC_128" => 0x0146,  # MCC 128 ID.
	"HAT_ID_MCC_134" => 0x0143,  # MCC 134 ID.
	"HAT_ID_MCC_152" => 0x0144,  # MCC 152 ID.
	"HAT_ID_MCC_172" => 0x0145)  # MCC 172 ID.
	
	count = ccall((:hat_list, "/usr/local/lib/libdaqhats"), 
	Cint, (UInt16, Ptr{Cvoid}), idDict["HAT_ID_ANY"], C_NULL)
	@show(count)
	list = Vector{HatInfo}(undef,count)
	@show(list)
	for i = 1:count
		list[i] = HatInfo(0, 0, 0, map(UInt8, (string(repeat(" ",256))...,)))
	end
	
	success = ccall((:hat_list, "/usr/local/lib/libdaqhats"), 
	Cint, (UInt16, Ptr{list}), idDict[filter_id], list)
#	String([list.product_name...])
#	list = unsafe_load(Ptr{HatInfo}(list))
end

I suspect that my Ptr{list} should be a Ref{list} and that somehow I need to get the pointer to list rather than the variable list, but am not sure how to do that with my data structure. Thanks in advance.

Shouldn’t be mutable. You need it to be immutable so that the Vector will store the data inline rather than as an array of pointers.

1 Like

I made that change.

Now when I try to compile by restarting Julia and using the includet command, I get the following error:

could not evaluate ccall argument type (it might depend on a local variable)

I tried changing the Ptr{list} to Ref{list} but received the same error.

That’s because you declared the argument type as Ptr{list} rather than Ptr{HatInfo}. Argument types should be types.

1 Like

Thanks for your help. All is working now. I have put the code below for those following along and curious of how the working code works. Note that I used a for loop for a reverse lookup from the Dictionary. A table may work better but for the small number of values this works.

As a Julia beginner coming from Matlab it took me a while to figure out that a variable introduced within a for loop is not visible outside the for loop. It would be good to add this in Chapter 41, noteworthy differences from Matlab.

# Global functions and data - https://mccdaq.github.io/daqhats/c.html

struct HatInfoTemp
    address::UInt8 			# The board address.
    id::UInt16 				# The product ID, one of [HatIDs](@ref HatIDs)
    version::UInt16 		# The hardware version
    product_name::NTuple{256,UInt8}	# The product name (c - initialized to 256 characters)
end

struct HatInfo
    address::UInt8 			# The board address.
    id::String 				# The product ID, one of [HatIDs](@ref HatIDs)
    version::UInt16 		# The hardware version
    product_name::String	# The product name
end


"""
	hat_list(filter_id)
Return a list of detected DAQ HAT boards.
"""
function hat_list(filter_id::String)
	idDict = Dict{String, UInt16}(
	"HAT_ID_ANY"     => 0,  	 # Match any DAQ HAT ID in hat_list().
	"HAT_ID_MCC_118" => 0x0142,  # MCC 118 ID.
	"HAT_ID_MCC_118_BOOTLOADER" => 0x8142,  # MCC 118 in firmware update mode ID.
	"HAT_ID_MCC_128" => 0x0146,  # MCC 128 ID.
	"HAT_ID_MCC_134" => 0x0143,  # MCC 134 ID.
	"HAT_ID_MCC_152" => 0x0144,  # MCC 152 ID.
	"HAT_ID_MCC_172" => 0x0145)  # MCC 172 ID.
	
	count = ccall((:hat_list, "/usr/local/lib/libdaqhats"), 
	Cint, (UInt16, Ptr{Cvoid}), idDict[filter_id], C_NULL)
	listTemp = Vector{HatInfoTemp}(undef,count)
	list     = Vector{HatInfo}(undef,count)
	for i = 1:count
		listTemp[i] = HatInfoTemp(0, 0, 0, map(UInt8, (string(repeat(" ",256))...,)))
	end
	
	success = ccall((:hat_list, "/usr/local/lib/libdaqhats"), 
	Cint, (UInt16, Ptr{HatInfoTemp}), idDict[filter_id], listTemp)
	for i = 1:count
		a = String([listTemp[i].product_name...])
		f = findfirst(Char(0x0), a)
		p = SubString(a, 1:f-1)
		keyvalue = othervalue = ""
		for k in keys(idDict)     # reverse lookup in Dictionary idDict
			if idDict[k] == listTemp[i].id
				keyvalue = k
				break
			end
			
		end
		list[i] = HatInfo(listTemp[i].address, keyvalue, listTemp[i].version, p)
	end
	return list	
end