Trouble passing struct to C function

I am trying to wrap a JLL I’m authoring and as part of creating the wrapper, I’m writing some tests. In this test, I create an instance of a struct:

conn_options = Ref(aws_mqtt_connection_options(
    host_name_cur[],
    UInt16(8883),
    ...
))

I then pass that instance to a C function: aws_mqtt_client_connection_connect(connection, conn_options). This C function is declared like so:

function aws_mqtt_client_connection_connect(connection, connection_options)
    ccall((:aws_mqtt_client_connection_connect, libawsmqtt), Cint, (Ptr{aws_mqtt_client_connection}, Ptr{aws_mqtt_connection_options}), connection, connection_options)
end

I am having a problem where data I store in that struct looks good from Julia, but looks wrong in C. We can see the data looks correct when printed from Julia:

conn_options = Base.RefValue{aws_mqtt_connection_options}(aws_mqtt_connection_options(aws_byte_cursor(0x000000000000002e, Ptr{UInt8} @0x00007f7314c1f218), 0x22b3, Ptr{aws_socket_options} @0x00007ffff3c5d520, Ptr{aws_tls_connection_options} @0x00007ffff3c5d540, aws_byte_cursor(0x000000000000000e, Ptr{UInt8} @0x00007f7314c14938), 0x0000, 0x00000000, 0x00000000, Ptr{Nothing} @0x0000000000000000, Ptr{Nothing} @0x0000000000000000, true))

However, when printed from C, the data is wrong:

(rr) p *connection_options
$3 = {host_name = {len = 140476288574320, ptr = 0x7fc3221522b3 ""}, port = 33024, socket_options = 0x7ffc25b78120, tls_options = 0x7fc32fb3e9b0, client_id = {len = 573374464, ptr = 0x0}, 
  keep_alive_time_secs = 0, ping_timeout_ms = 0, protocol_operation_timeout_ms = 0, on_connection_complete = 0x1, user_data = 0x0, clean_session = false}

An easy check is to look at the second parameter, the port number. It should be 8883, but it is 33024. As for the pointers, they do not point to valid memory. In fact, all the values in this struct change each time I run the program. Something is very wrong.

My question is, does anyone know what could be wrong? I’ve exhausted my abilities with rr and other tools and I can’t figure out what is wrong or how to proceed from here. You can access all of the relevant code here. If anyone wants to see more, I’m happy to provide it.

1 Like

Is this a nondeterministic error?

Is this useful?
https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/#Struct-Type-Correspondences

This error happens every time I run the program, though the exact values in the struct (when viewed from C) differ every time.

Thanks for the link. I have carefully checked my Julia struct and the C struct, and to the best of my knowledge, my Julia struct definition is correct.
Julia code:

mutable struct aws_mqtt_connection_options
    host_name::aws_byte_cursor
    port::UInt16
    socket_options::Ptr{aws_socket_options}
    tls_options::Ptr{aws_tls_connection_options}
    client_id::aws_byte_cursor
    keep_alive_time_secs::UInt16
    ping_timeout_ms::UInt32
    protocol_operation_timeout_ms::UInt32
    on_connection_complete::Ptr{Cvoid}
    user_data::Ptr{Cvoid}
    clean_session::Bool
end

C code:

typedef void(aws_mqtt_client_on_connection_complete_fn)(
    struct aws_mqtt_client_connection *connection,
    int error_code,
    enum aws_mqtt_connect_return_code return_code,
    bool session_present,
    void *userdata);

struct aws_mqtt_connection_options {
    struct aws_byte_cursor host_name;
    uint16_t port;
    struct aws_socket_options *socket_options;
    struct aws_tls_connection_options *tls_options;
    struct aws_byte_cursor client_id;
    uint16_t keep_alive_time_secs;
    uint32_t ping_timeout_ms;
    uint32_t protocol_operation_timeout_ms;
    aws_mqtt_client_on_connection_complete_fn *on_connection_complete;
    void *user_data;
    bool clean_session;
};

Is aws_byte_cursor defined as a mutable struct too? That would cause aws_mqtt_connection_options to be the incorrect layout from C’s perspective, but Julia has no issue with that misrepresentation. You should expect to see type sizes similar to below (which uses CBinding to automatically create the types/bindings):

julia> module libawsmqtt
           using CBinding
           
           c`-l aws-c-mqtt`
           
           const c"size_t" = Csize_t
           const c"uint8_t" = UInt8
           const c"uint16_t" = UInt16
           const c"uint32_t" = UInt32
           const c"uint64_t" = UInt64
           const c"int8_t" = Int8
           const c"int16_t" = Int16
           const c"int32_t" = Int32
           const c"int64_t" = Int64
           
           c"""
               #include <aws/common/array_list.h>
               #include <aws/common/byte_buf.h>
               #include <aws/mqtt/client.h>
           """ji
       end;

julia> sizeof(libawsmqtt.aws_mqtt_connection_options)
96

julia> sizeof(libawsmqtt.aws_byte_cursor)
16
1 Like

Indeed it is mutable.

mutable struct aws_byte_cursor
    len::Csize_t
    ptr::Ptr{UInt8}
end

In fact, every struct in my wrapper is mutable.

This is a problem. Make them immutable.

If you really need to update them, you could reconstruct the object. Or you could use this:

1 Like