Skip to content

Commit

Permalink
Change C APIs to a unified string implementation (#6299)
Browse files Browse the repository at this point in the history
Currently in the entire C API of WPILib we have ~8 different ways of handling strings. The C API actually isn't built for pure C callers (We don't actually have any of those). Instead, they're built for interop between languages like LabVIEW and C# which can talk to C API's directly.

For output parameters, the choice was fairly obvious. An output struct containing a const string pointer and a length makes the most sense. Its easy to use these from most other languages, and doesn't require special null termination handling. Freeing these is also easy, as if you ever receive one of these string structures, theres just a single function call to free it.

Input parameters are a bit more complex. To be used from pure C, and from LabVIEW, a null terminated string is the best in most cases. However, null terminated strings in general have a lot of downsides. Additionally, from LabVIEW there are other considerations around encoding that having a wrapper struct helps make a bit easier. From a language like C#, a wrapper struct is by far the easiest, as custom marshalling can make it trivial to marshal both UTF8 and UTF16 strings down.

The final consideration is its nice to have an identical concept for both input and output. It makes the rules fairly easy to understand.

WPILib will not have any APIs that manipulate a string allocated externally. This means WPI_String can be const, as across the boundary it is always const.
If a WPILib API takes a const WPI_String*, WPILib will not manipulate or attempt to free that string, and that string is treated as an input. It is up to the caller to handle that memory, WPILib will never hold onto that memory longer than the call.
If a WPILib API takes a WPI_String*, that string is an output. WPILib will allocate that API with WPI_AllocateString(), fill in the string, and return to the caller. When the caller is done with the string, they must free it with WPI_FreeString().
If an output struct contains a WPI_String member, that member is considered read only, and should not be explicitly freed. The caller should call the free function for that struct.
If an array of WPI_Strings are returned, each individual string is considered read only, and should not be explicitly freed. The free function for that array should be called by the caller.
If an input struct containing a WPI_String, or an input array of WPI_Strings is passed to WPILib, the individual strings will not be manipulated or freed by WPILib, and the caller owns and should free that memory.
Callbacks also follow these rules. The most common is a callback either getting passed a const WPI_String* or a struct containing a WPI_String. In both of these cases, the callback target should consider these strings read only, and not attempt to free them or manipulate them.
  • Loading branch information
ThadHouse authored May 13, 2024
1 parent 178fe99 commit 4ce8f3f
Show file tree
Hide file tree
Showing 60 changed files with 987 additions and 911 deletions.
24 changes: 14 additions & 10 deletions cscore/src/main/native/cpp/ConfigurableSourceImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,27 +197,31 @@ void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
} // namespace cs

extern "C" {
void CS_NotifySourceError(CS_Source source, const char* msg,
void CS_NotifySourceError(CS_Source source, const struct WPI_String* msg,
CS_Status* status) {
return cs::NotifySourceError(source, msg, status);
return cs::NotifySourceError(source, wpi::to_string_view(msg), status);
}

void CS_SetSourceConnected(CS_Source source, CS_Bool connected,
CS_Status* status) {
return cs::SetSourceConnected(source, connected, status);
}

void CS_SetSourceDescription(CS_Source source, const char* description,
void CS_SetSourceDescription(CS_Source source,
const struct WPI_String* description,
CS_Status* status) {
return cs::SetSourceDescription(source, description, status);
return cs::SetSourceDescription(source, wpi::to_string_view(description),
status);
}

CS_Property CS_CreateSourceProperty(CS_Source source, const char* name,
CS_Property CS_CreateSourceProperty(CS_Source source,
const struct WPI_String* name,
enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue,
int value, CS_Status* status) {
return cs::CreateSourceProperty(source, name, kind, minimum, maximum, step,
defaultValue, value, status);
return cs::CreateSourceProperty(source, wpi::to_string_view(name), kind,
minimum, maximum, step, defaultValue, value,
status);
}

CS_Property CS_CreateSourcePropertyCallback(
Expand All @@ -230,12 +234,12 @@ CS_Property CS_CreateSourcePropertyCallback(
}

void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
const char** choices, int count,
CS_Status* status) {
const struct WPI_String* choices,
int count, CS_Status* status) {
wpi::SmallVector<std::string, 8> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) {
vec.push_back(choices[i]);
vec.emplace_back(wpi::to_string_view(&choices[i]));
}
return cs::SetSourceEnumPropertyChoices(source, property, vec, status);
}
Expand Down
40 changes: 16 additions & 24 deletions cscore/src/main/native/cpp/HttpCameraImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -634,55 +634,47 @@ std::vector<std::string> GetHttpCameraUrls(CS_Source source,

extern "C" {

CS_Source CS_CreateHttpCamera(const char* name, const char* url,
CS_Source CS_CreateHttpCamera(const struct WPI_String* name,
const struct WPI_String* url,
CS_HttpCameraKind kind, CS_Status* status) {
return cs::CreateHttpCamera(name, url, kind, status);
return cs::CreateHttpCamera(wpi::to_string_view(name),
wpi::to_string_view(url), kind, status);
}

CS_Source CS_CreateHttpCameraMulti(const char* name, const char** urls,
int count, CS_HttpCameraKind kind,
CS_Status* status) {
CS_Source CS_CreateHttpCameraMulti(const struct WPI_String* name,
const struct WPI_String* urls, int count,
CS_HttpCameraKind kind, CS_Status* status) {
wpi::SmallVector<std::string, 4> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) {
vec.push_back(urls[i]);
vec.emplace_back(wpi::to_string_view(&urls[i]));
}
return cs::CreateHttpCamera(name, vec, kind, status);
return cs::CreateHttpCamera(wpi::to_string_view(name), vec, kind, status);
}

CS_HttpCameraKind CS_GetHttpCameraKind(CS_Source source, CS_Status* status) {
return cs::GetHttpCameraKind(source, status);
}

void CS_SetHttpCameraUrls(CS_Source source, const char** urls, int count,
CS_Status* status) {
void CS_SetHttpCameraUrls(CS_Source source, const struct WPI_String* urls,
int count, CS_Status* status) {
wpi::SmallVector<std::string, 4> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) {
vec.push_back(urls[i]);
vec.emplace_back(wpi::to_string_view(&urls[i]));
}
cs::SetHttpCameraUrls(source, vec, status);
}

char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status) {
WPI_String* CS_GetHttpCameraUrls(CS_Source source, int* count,
CS_Status* status) {
auto urls = cs::GetHttpCameraUrls(source, status);
char** out =
static_cast<char**>(wpi::safe_malloc(urls.size() * sizeof(char*)));
WPI_String* out = WPI_AllocateStringArray(urls.size());
*count = urls.size();
for (size_t i = 0; i < urls.size(); ++i) {
out[i] = cs::ConvertToC(urls[i]);
cs::ConvertToC(&out[i], urls[i]);
}
return out;
}

void CS_FreeHttpCameraUrls(char** urls, int count) {
if (!urls) {
return;
}
for (int i = 0; i < count; ++i) {
std::free(urls[i]);
}
std::free(urls);
}

} // extern "C"
14 changes: 9 additions & 5 deletions cscore/src/main/native/cpp/MjpegServerImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1047,13 +1047,17 @@ int GetMjpegServerPort(CS_Sink sink, CS_Status* status) {

extern "C" {

CS_Sink CS_CreateMjpegServer(const char* name, const char* listenAddress,
int port, CS_Status* status) {
return cs::CreateMjpegServer(name, listenAddress, port, status);
CS_Sink CS_CreateMjpegServer(const struct WPI_String* name,
const struct WPI_String* listenAddress, int port,
CS_Status* status) {
return cs::CreateMjpegServer(wpi::to_string_view(name),
wpi::to_string_view(listenAddress), port,
status);
}

char* CS_GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status) {
return ConvertToC(cs::GetMjpegServerListenAddress(sink, status));
void CS_GetMjpegServerListenAddress(CS_Sink sink, WPI_String* listenAddress,
CS_Status* status) {
cs::ConvertToC(listenAddress, cs::GetMjpegServerListenAddress(sink, status));
}

int CS_GetMjpegServerPort(CS_Sink sink, CS_Status* status) {
Expand Down
15 changes: 8 additions & 7 deletions cscore/src/main/native/cpp/RawSinkImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,17 @@ uint64_t GrabSinkFrameTimeout(CS_Sink sink, WPI_RawFrame& image, double timeout,
} // namespace cs

extern "C" {
CS_Sink CS_CreateRawSink(const char* name, CS_Bool isCv, CS_Status* status) {
return cs::CreateRawSink(name, isCv, status);
CS_Sink CS_CreateRawSink(const struct WPI_String* name, CS_Bool isCv,
CS_Status* status) {
return cs::CreateRawSink(wpi::to_string_view(name), isCv, status);
}

CS_Sink CS_CreateRawSinkCallback(const char* name, CS_Bool isCv, void* data,
void (*processFrame)(void* data,
uint64_t time),
CS_Status* status) {
CS_Sink CS_CreateRawSinkCallback(
const struct WPI_String* name, CS_Bool isCv, void* data,
void (*processFrame)(void* data, uint64_t time), CS_Status* status) {
return cs::CreateRawSinkCallback(
name, isCv, [=](uint64_t time) { processFrame(data, time); }, status);
wpi::to_string_view(name), isCv,
[=](uint64_t time) { processFrame(data, time); }, status);
}

uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct WPI_RawFrame* image,
Expand Down
4 changes: 2 additions & 2 deletions cscore/src/main/native/cpp/RawSourceImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ void PutSourceFrame(CS_Source source, const WPI_RawFrame& image,
} // namespace cs

extern "C" {
CS_Source CS_CreateRawSource(const char* name, CS_Bool isCv,
CS_Source CS_CreateRawSource(const struct WPI_String* name, CS_Bool isCv,
const CS_VideoMode* mode, CS_Status* status) {
return cs::CreateRawSource(name, isCv,
return cs::CreateRawSource(wpi::to_string_view(name), isCv,
static_cast<const cs::VideoMode&>(*mode), status);
}

Expand Down
13 changes: 5 additions & 8 deletions cscore/src/main/native/cpp/SinkImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,18 +242,15 @@ void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
} // namespace cs

extern "C" {
void CS_SetSinkDescription(CS_Sink sink, const char* description,
void CS_SetSinkDescription(CS_Sink sink, const struct WPI_String* description,
CS_Status* status) {
return cs::SetSinkDescription(sink, description, status);
return cs::SetSinkDescription(sink, wpi::to_string_view(description), status);
}

char* CS_GetSinkError(CS_Sink sink, CS_Status* status) {
void CS_GetSinkError(CS_Sink sink, struct WPI_String* error,
CS_Status* status) {
wpi::SmallString<128> buf;
auto str = cs::GetSinkError(sink, buf, status);
if (*status != 0) {
return nullptr;
}
return cs::ConvertToC(str);
cs::ConvertToC(error, cs::GetSinkError(sink, buf, status));
}

void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status) {
Expand Down
52 changes: 25 additions & 27 deletions cscore/src/main/native/cpp/UsbCameraImplCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,62 @@

#include "cscore_c.h" // NOLINT(build/include_order)

#include <wpi/MemAlloc.h>

#include "c_util.h"
#include "cscore_cpp.h"

using namespace cs;

static void ConvertToC(CS_UsbCameraInfo* out, const UsbCameraInfo& in) {
out->dev = in.dev;
out->path = ConvertToC(in.path);
out->name = ConvertToC(in.name);
out->otherPaths = static_cast<char**>(
wpi::safe_malloc(in.otherPaths.size() * sizeof(char*)));
cs::ConvertToC(&out->path, in.path);
cs::ConvertToC(&out->name, in.name);
out->otherPaths = WPI_AllocateStringArray(in.otherPaths.size());
out->otherPathsCount = in.otherPaths.size();
for (size_t i = 0; i < in.otherPaths.size(); ++i) {
out->otherPaths[i] = cs::ConvertToC(in.otherPaths[i]);
cs::ConvertToC(&out->otherPaths[i], in.otherPaths[i]);
}
out->vendorId = in.vendorId;
out->productId = in.productId;
}

static void FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
std::free(info->path);
std::free(info->name);
WPI_FreeString(&info->path);
WPI_FreeString(&info->name);
for (int i = 0; i < info->otherPathsCount; ++i) {
std::free(info->otherPaths[i]);
WPI_FreeString(&info->otherPaths[i]);
}
std::free(info->otherPaths);
}

extern "C" {

CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status) {
return cs::CreateUsbCameraDev(name, dev, status);
CS_Source CS_CreateUsbCameraDev(const struct WPI_String* name, int dev,
CS_Status* status) {
return cs::CreateUsbCameraDev(wpi::to_string_view(name), dev, status);
}

CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
CS_Source CS_CreateUsbCameraPath(const struct WPI_String* name,
const struct WPI_String* path,
CS_Status* status) {
return cs::CreateUsbCameraPath(name, path, status);
return cs::CreateUsbCameraPath(wpi::to_string_view(name),
wpi::to_string_view(path), status);
}

void CS_SetUsbCameraPath(CS_Source source, const char* path,
void CS_SetUsbCameraPath(CS_Source source, const struct WPI_String* path,
CS_Status* status) {
cs::SetUsbCameraPath(source, path, status);
cs::SetUsbCameraPath(source, wpi::to_string_view(path), status);
}

char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
return ConvertToC(cs::GetUsbCameraPath(source, status));
void CS_GetUsbCameraPath(CS_Source source, WPI_String* path,
CS_Status* status) {
ConvertToC(path, cs::GetUsbCameraPath(source, status));
}

CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status) {
auto info = cs::GetUsbCameraInfo(source, status);
if (*status != CS_OK) {
return nullptr;
}
CS_UsbCameraInfo* out = static_cast<CS_UsbCameraInfo*>(
wpi::safe_malloc(sizeof(CS_UsbCameraInfo)));
ConvertToC(out, info);
return out;
void CS_GetUsbCameraInfo(CS_Source source, CS_UsbCameraInfo* info,
CS_Status* status) {
auto info_cpp = cs::GetUsbCameraInfo(source, status);
ConvertToC(info, info_cpp);
}

CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status) {
Expand Down Expand Up @@ -89,7 +88,6 @@ void CS_FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
return;
}
FreeUsbCameraInfo(info);
std::free(info);
}

} // extern "C"
11 changes: 4 additions & 7 deletions cscore/src/main/native/cpp/c_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,16 @@
#ifndef CSCORE_C_UTIL_H_
#define CSCORE_C_UTIL_H_

#include <cstdlib>
#include <cstring>
#include <string_view>

#include <wpi/MemAlloc.h>
#include <wpi/string.h>

namespace cs {

inline char* ConvertToC(std::string_view in) {
char* out = static_cast<char*>(wpi::safe_malloc(in.size() + 1));
std::memmove(out, in.data(), in.size());
out[in.size()] = '\0';
return out;
inline void ConvertToC(struct WPI_String* output, std::string_view str) {
char* write = WPI_AllocateString(output, str.size());
std::memcpy(write, str.data(), str.size());
}

} // namespace cs
Expand Down
Loading

0 comments on commit 4ce8f3f

Please sign in to comment.