Skip to content

C/C++: add basic channel wrapper and log method #267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 5, 2025
Merged

Conversation

jtbandes
Copy link
Member

@jtbandes jtbandes commented Mar 1, 2025

Changelog

C/C++: added Channel type.

Docs

None

Description

Resolves FG-10681

At least the basics of creating a schema+channel and logging messages seem to be working.

Some sadnesses/open areas to explore:

  • Not sure if we want Schema to be its own ref-counted/unique_ptr'd entity, or just a plain ol' options struct. Right now I have a C++ options struct using C++ types (std::string_view) and a C options struct using C types (const char*). The conversion between these is super awkward. And the ownership/lifetime requirements of the string_views/pointers is not very clear (would need to just write it in comments).
  • I want to mark some pointers as non-nullable on the C header side, but still check them for null in Rust to be safe. cbindgen will mark them as nonnull if they are &T, &mut T, or NonNull<T>. However, reading a bit into how the Rust side treats those types, I'm not sure that checking them for null is really possible (the compiler might be allowed to assume they are not?) It seems like Option<NonNull<T>> might work, but that's kinda nuts when you think about it (could it be working by accident?)
  • It feels silly and inefficient to wrap Box around a type that is already internally an Arc. Not sure there is any way around this though.
  • I really would prefer std::span (C++20) over this ptr+length nonsense 😭
  • Need to tackle error handling across all these functions at some point – probably all will need to return some error by out-param? or return an error code?
  • Gonna need to describe what the thread safety situation is

@jtbandes jtbandes marked this pull request as ready for review March 1, 2025 05:41
Copy link
Contributor

@eloff eloff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great. I added some comments.

I'm not sure what the lifetime issue you were concerned with around schema? We can discuss

.to_str()
.expect("schema encoding is invalid");
let data = std::slice::from_raw_parts(schema.data, schema.data_len);
foxglove::Schema::new(name, encoding, data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copies all the input, so you don't need to worry about the lifetime of passed C strings / data after the function returns (not sure if that was your question in the pr description)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think my concern was more about documentation, i.e. a user of the C/C++ library wouldn't necessarily know that it's ok for them to have a short lifetime if we don't describe it in comments

c/src/lib.rs Outdated
/// contained within a single allocated object.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn foxglove_channel_log(
channel: Option<&mut FoxgloveChannel>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the best way to handle pointers coming from C that may be null

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping to end up with a nonnull annotation on the C header side as well, but not sure cbindgen will allow me to do that while retaining the Option on the rust side. I wonder how receptive they are to upstream PRs...

);

private:
std::unique_ptr<foxglove_channel, void (*)(foxglove_channel*)> _impl;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could also use a raw pointer and free it in the destructor

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the advantage there?

REQUIRE(server.port() != 0);

foxglove::Channel channel{"example", "json", std::nullopt};
const uint8_t data[] = {1, 2, 3};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this not work?

Suggested change
const uint8_t data[] = {1, 2, 3};
const std::byte data[] = {1, 2, 3};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, integer literals can't be assigned without a cast, so I could do std::byte(1), ......

@jtbandes jtbandes requested review from eloff, achim-k and gasmith March 5, 2025 00:51
Copy link

linear bot commented Mar 5, 2025

Copy link
Contributor

@gasmith gasmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢

@jtbandes jtbandes merged commit b50d033 into main Mar 5, 2025
36 checks passed
@jtbandes jtbandes deleted the jacob/cpp-channel branch March 5, 2025 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants