Help implementing FFI callbacks

The C API wrapped by the proj-sys crate provides a mechanism for implementors to define their own network functionality, thus avoiding the need to link to libcurl. So far, so good: in order to implement the proj_context_set_network_callbacks function provided by the crate, I have to write the various implementations of network_open_cbk_type, close_cbk_type etc. Taking network_open_cbk_type as an example, I can define it on the Rust side:

unsafe extern "C" fn network_open(
    ctx: *mut PJ_CONTEXT,
    url: *const c_char,
    offset: c_ulonglong,
    size_to_read: usize,
    mut buffer: *mut c_void,
    mut out_size_read: *mut usize,
    error_string_max_size: usize,
    mut out_error_string: *mut c_char,
    user_data: *mut c_void,
    // set up header and retrieve content from url as bytes using
    // e.g. reqwest, stuff bytes into void pointer
    out_size_read = res.content_length().unwrap() as *mut usize;
    let response_bytes = res.bytes().unwrap();
    buffer = &response_bytes as *const _ as *mut c_void;
    // Return non-null opaque pointer in case of success
    let pnh: *mut PROJ_NETWORK_HANDLE = ptr::null_mut();

I don't work with C(++) much, and the API documentation for proj_network_open_cbk_type states that the return type is a non-NULL opaque handle in case of success." What does that mean? Is it usual for callbacks to pass a void pointer into a function, stuff data into it, and then return something else in case of success? My actual, more robust implementation will probably pass the incoming parameters off to a private function so I can properly handle decoding / url / etc errors, but is this the correct approach?

I'm guessing it's so you can return something representing the newly created network connection.

Looking through the docs, there seem to be several callbacks:

  • You've got an open operation (proj_network_open_cbk_type)
  • A function for closing the connection (proj_network_close_cbk_type)
  • retrieving a specific header (proj_network_get_header_value_cbk_type), and
  • reading a specific range of bytes (proj_network_read_range_type).

So the proj_network_open_cbk_type function would open a connection and read the first size_to_read bytes. I'm guessing the proj_network_read_range_type callback lets it do HTTP range requests to read subsequent parts of a file from the remote server using the same connection.

By reusing the same network connection you can avoid needing to create a new TCP/HTTPS connection every time you want to request some bytes.

Do they provide any examples you can use as inspiration? Or if you've got access to the source code you may be able to look at the unit test (if they exist) and see how the callbacks are meant to be implemented.

Probably. Handling errors and doing input validation is always good, and pulling this logic out into its own functions helps to keep the "happy path" clean.

Of course, without seeing any code it's a bit hard to tell :stuck_out_tongue:

A while back I wrote an article about passing closures from Rust to native code. It touches a lot of the concepts you're using here, so you may find it useful.

(shameless plug)

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.