Help with ccall to clib functions

ccall

#1

Hi,

I’m trying to implement some routines from termios in clib. I have created the constants to represent the many #defines in the c header file, but I need help with a couple items (please). So, here’s a bit of the code:

@ctypedef tcflag_t Culong
@ctypedef cc_t Cuchar
@ctypedef speed_t Clong

struct termios
c_iflag::tcflag_t #input flags
c_oflag::tcflag_t #output flags
c_cflag::tcflag_t #control flags
c_lflag::tcflag_t #local flags
c_cc::Ptr{cc_t} #control chars #this is what it looks like in C - cc_t c_cc[NCCS];
c_ispeed::speed_t #input speed
c_ospeed::speed_t #output speed
end

So, the first question is - am I translating an array of integers correctly in the struct?

The second question is - how do I initialize the struct (since it has an array in it)?

The C code looks like this:

struct termios tty;
memset(&tty, 0, sizeof(tty));

this would be followed by:

if (tcgetattrib(fd, &tty) != 0)
{
printf(“error from tcgetattrib”);
return -1;
}

I translated that to:

tty = termios(0,0,0,0,0,0,0) #I think the initialization is wrong

ret = ccall((:tcgetattr, “libc”), Int32, (Int32, Ref{termios}), fd, tty)

if (ret != 0)
println(“error from tcgetattr”)
return -1
end

Can someone help me along, here?

Frank


Help with ccall to a clib function
#2

No it’s not a pointer in C, it’s an array. That corresponds to a inlined tuple.


#3

Also, tty needs to be a Ref of termios for you to get the return value.


#4

OK. What is an inlined tuple?

macro ctypedef(fake_t,real_t)
  quote
    const $(esc(fake_t)) = $(esc(real_t))
  end
end

@ctypedef tcflag_t Culong
@ctypedef cc_t Cuchar
@ctypedef speed_t Clong

How would I re-write cc_t c_cc[NCCS] as an inlined tupe?

Thanks.


#5

I thought that’s what I was doing.

ret = ccall((:tcgetattr, “libc”), Int32, (Int32, Ref{termios}), fd, tty)

I thought the 1st Int32 represented the return value and (Int32, Ref{termios}), fd, tty represented the 2 parameters. Am I not understanding this right?


#6

Just a NTuple{NCCS,cc_t}

Also, please quote your code with ` or ```

You are explaining the ccall syntax here but that’s unrelated. The calling convention is correct but the result of the ccall won’t be seen by the variable tty since the ccall cannot mutate it. In general, nothing other than assignments can change the binding of a variable, only mutate the object they are bound to. This is completely different from C where the two are the same thing (assignment is mutation in C/C++). What you need is a mutable object and let the ccall mutate that object so you need to make tty a Ref of termios instead of just termios


#7

OK. I’ll give it a try. I really like Julia so far, but man the documentation needs a Microsoft touch. Yes - I was a Windows developer for years. I am a full-fledged Linux guy, now, but Microsoft did create good documentation with lots of example code.


#8

Yuyuchao - I was thinking about this all before I went to bed, last night. This is the code [not working yet].

const   NCCS        =   20

@ctypedef tcflag_t Culong
@ctypedef cc_t Cuchar
@ctypedef speed_t Clong

mutable struct termios
    c_iflag::tcflag_t	#input flags
	c_oflag::tcflag_t	#output flags
	c_cflag::tcflag_t	#control flags
	c_lflag::tcflag_t	#local flags
        c_cc::NTuple{NCCS,cc_t} 	#control chars
	c_ispeed::speed_t	#input speed
	c_ospeed::speed_t	#output speed 
end

function set_interface_attribs(fd::Int, speed::Int, parity::Int)
        tty = termios(0,0,0,0,(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),0,0) 

        ret = ccall((:tcgetattr, "libc"), Int32, (Int32, Ref{termios}), fd, tty)

        if (ret != 0)
                println("error from tcgetattr")
                return -1
        end

        ccall((:cfsetospeed, "libc"), Void, (Ref{termios}, Int32), tty, speed)
        ccall((:cfsetispeed, "libc"), Void, (Ref{termios}, Int32), tty, speed)


        tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8     # 8-bit chars
        # disable IGNBRK for mismatched speed tests otherwise receive break
        # as \000 chars
        tty.c_iflag &= ~IGNBRK          # disable break processing
        tty.c_lflag = 0                # no signaling chars, no echo,
                                        # no canonical processing
        tty.c_oflag = 0                # no remapping, no delays
        tty.c_cc[VMIN]  = 0            # read doesn't block
        tty.c_cc[VTIME] = 5            # 0.5 seconds read timeout

        tty.c_iflag &= ~(IXON | IXOFF | IXANY) # shut off xon/xoff ctrl

        tty.c_cflag |= (CLOCAL | CREAD)# ignore modem controls,
                                        # enable reading
        tty.c_cflag &= ~(PARENB | PARODD)      # shut off parity
        tty.c_cflag |= parity
        tty.c_cflag &= ~CSTOPB
        tty.c_cflag &= ~CRTSCTS


        ret = ccall((:tcsetattr, "libc"), Int32, (Int32, Int32, Ref{termios}), fd, TCSANOW, tty)
        if (ret != 0) 
                println("error from tcsetattr")
                return -1
        end
        return 0
end

If part of struct that is getting passed in to the routines above [tcgetattrib and tcsetattrib] is an immutabe NTuple - then how does the C library calls make modifications to that part of the struct? This is all very confusing compared to making system calls from other languages. Can you explain a little better what is going on in my calls to tcgetattrib and tcsetattrib and is there a better way of doing what I’m trying to accomplish?

BTW - the goal of this code is so I can do away with two C programs that I wrote that I am calling from Julia that allow me to interact with a serial port [connected to an Arduino]. The serial libraries don’t work because I am using 32-bit versions of Julia on several development boards [RPi, Beaglebone, Udoo x86] on a Beowulf cluster.

Frank


#9

In julia what you are defining is not some structure for the C interface to interpret but an type with exactly the same layout as C so as long as that’s satisfied the C code can operate on it in exactly the same way it operates on C memory (read and write for isbits type, read for others). So in short, what happens is that you defined a mutable type with C compatible layout and the function you call mutates it.

Immutable field type does not mean that you can’t change the content of the field. Immutabale type does mean (not all of them are relavant for C interop but I’m just listing them here for completion)

  1. You can’t change the content without changing the object identity.

  2. Since you can’t change the object identity of a variable without assignment, you can’t change the “content of a variable” (in C sense) without explicity assignment.

  3. As an ABI, isbits immutable type (including primitive type) are stored inline in parent struct, similar to a normal (non-pointer) field in C. Everything else are stored as references (pointer to object in C).

  4. For struct definition, mutability (and isbits) of field type is just a ABI specifier that affect the layout. it doesn’t affect whether the field can be mutated since,

    1. The memory of inlined field does not belong to the immutable type, rather the type it’s in.
    2. As long as the parent type is mutable no explicit variable assignment should be needed to change the content of the field. It has the same semantics as the parent object being mutated. Basically the memory of inlined immutable field is part of the parent object and is mutable.

There’s currently no easy way to do this in julia without unsafe code. You have to assign to the whole tuple for now.


#10

Thank you for taking the time to explain this all. It is starting to make sense. I found out already that the assignment of the NTuple didn’t work (because of it being immutable). I guess I will have to create the NTuple (like a constructor) and fill in the values there. I’ll let you know if I get it all working! :slight_smile:


#11

OK, it seems to be working. I am communicating through the serial port in Julia to my Arduino!!! Thanks for your time helping me. One more quick question.

I have translated:

tty.c_cc[VMIN]  = 0            # read doesn't block
tty.c_cc[VTIME] = 5            # 0.5 seconds read timeout

to:

 tty.c_cc = (0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)

because I know what VMIN and VTIME are. How can I fill in this NTuple using VMIN and VTIME as positions into the NTuple?

Frank


#12

There’s no clean way to do it currently (Ref https://github.com/JuliaLang/julia/pull/21912). For now, and for tuples you can do ntuple(x->x == VTIME ? cc_t(5), tty.cc[x], Val{NCCS}).


#13

I was just reading about that in the Docs and trying to get it to work. Thanks for the example!

That’s what we need a Julia In A Nutshell book and a Julia Examples In A Nutshell companion book! :wink:


#14

So, yuyichao, is there a way to convert a string like “(0,0,0,5,0,0)” to a Tuple? Your answer using ntuple() will only work for one argument. I have a function to create a string with as many arguments as I need, but then I need that string to convert to a tuple. Any ideas?


#15

Parsing is something you should do separately (I assume you don’t just do int v = string; in C either ;-p ). You generally need to write a parser yourself possibly making use of the simple parsing functions provided in Base (e.g. parse(::Type{Int}, ::String)).

There is a parse(::String) function that parses strings in julia syntax. It’s useful for hacking and local use but you shouldn’t use it like this in any “production” code (whatever tha means).


#16

Well, this seemed to work:

julia> t = eval(parse(initialize_NTuple("6,0,5,5"); raise=true))
(0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

julia> typeof(t)
NTuple{20,Int32}

#17

If you are just taking well defined and checked user input this is fine. Directly passing some command line/stdin input to this code will be dangerous.


#18

Oh, no - nothing like that. I just wanted to be able to take something like:

tty.c_cc[VMIN]  = should_block ? 1 : 0
tty.c_cc[VTIME] = 5            # 0.5 seconds read timeout

and create a string using VMIN, should_block ? 1 : 0 and VTIME, 5. This I can do in the code and pass it to my function.

Thank you again for all of your help. This is all part of something that I would like to present at the next Julia Conference. I am doing remote calls to a Raspberry Pi, Beaglebone Black, and a Udoo X86 - each equipped with sensors wired to their GPIO pins [and running on a cluster]. I do the remote calls in a loop from my main computer (also on the cluster], so I can monitor and be told when a sensor goes off on one of the development boards. The Udoo X86 has a built in Arduino 101 on the board and I was having to run small C programs from Julia to interact with the serial port on the Udoo. This now give me a Julia version without having to drop down to C at all.


#19

Ok. writing to the serial port is going fine, but I want to make sure I’m doing this right. You said tty needs to be a Ref of termios. Isn’t it already?


#20

And, I’m working on read from the serial port - that’s what prompted my last questions. I doing a read kind of following an example from the docs on getting a hostname.

#char buf [100];
#int n = read (fd, buf, sizeof buf);  # read up to 100 characters if ready to read

message = Vector{UInt8}(bufSize)
buffer = ccall((:read, "libc"), Int32, (Int, Ptr{UInt8}, Csize_t), fd, message, sizeof(message))
message[end] = 0; # ensure null-termination
println(unsafe_string(pointer(message)))

I just want to make sure I’m doing this right. When I use the actual C program - I get correct values from the serial port consistently, but from Julia - I get a lot of garbage,

Frank