From 6ae55dd1b1f9794a6cadfd1aaac274f98f9d4576 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:25:58 +0000 Subject: [PATCH 1/5] Initial plan From 73eff36f5441164843df8940c173002c4fc4303a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:33:15 +0000 Subject: [PATCH 2/5] feat(c): add C-language port of MassData API with Visual Studio test project Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-MassData-Parameter-Support/sessions/b7acbb10-bea4-49e9-9339-4a7786edba72 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- .gitignore | 17 +- README(zh-cn).md | 6 + README.md | 7 + c/README.md | 112 ++++++ c/include/csm_massdata.h | 229 +++++++++++ c/src/csm_massdata.c | 421 ++++++++++++++++++++ c/vs_test/csm_massdata_test.sln | 27 ++ c/vs_test/csm_massdata_test.vcxproj | 84 ++++ c/vs_test/csm_massdata_test.vcxproj.filters | 26 ++ c/vs_test/test_main.c | 216 ++++++++++ 10 files changed, 1144 insertions(+), 1 deletion(-) create mode 100644 c/README.md create mode 100644 c/include/csm_massdata.h create mode 100644 c/src/csm_massdata.c create mode 100644 c/vs_test/csm_massdata_test.sln create mode 100644 c/vs_test/csm_massdata_test.vcxproj create mode 100644 c/vs_test/csm_massdata_test.vcxproj.filters create mode 100644 c/vs_test/test_main.c diff --git a/.gitignore b/.gitignore index dccf551..a963972 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,18 @@ *.aliases *.lvlps -*.vip \ No newline at end of file +*.vip + +# C / Visual Studio build artefacts (under c/) +c/**/Debug/ +c/**/Release/ +c/**/x64/ +c/**/x86/ +c/**/.vs/ +c/**/*.user +c/**/*.suo +c/**/*.obj +c/**/*.exe +c/**/*.pdb +c/**/*.ilk +c/**/*.idb +c/**/*.tlog \ No newline at end of file diff --git a/README(zh-cn).md b/README(zh-cn).md index 38e7e21..4747c48 100644 --- a/README(zh-cn).md +++ b/README(zh-cn).md @@ -72,6 +72,12 @@ MassData Support 基于以下简单而有效的原理: LabVIEW 2017 或更高版本 +## 第三方语言绑定 + +[`c/`](./c) 目录提供 MassData API 的 **C 语言移植**,函数名称、参数及参考 +字符串格式与 LabVIEW VI 完全一致,并附带 Visual Studio 2022 测试工程 +[`c/vs_test`](./c/vs_test)。 + ## 许可证 本项目基于 MIT 许可证 — 详见 LICENSE 文件 diff --git a/README.md b/README.md index a28e84a..189b43d 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,13 @@ See the example folder for demonstrations of: LabVIEW 2017 or later +## Third-party language bindings + +A C-language port of the MassData API (function names, parameters and +reference-string format identical to the LabVIEW VIs) is available under +[`c/`](./c). It ships with a Visual Studio 2022 test project at +[`c/vs_test`](./c/vs_test). + ## License This project is licensed under the MIT License — see the LICENSE file for details. diff --git a/c/README.md b/c/README.md new file mode 100644 index 0000000..1bcdf7a --- /dev/null +++ b/c/README.md @@ -0,0 +1,112 @@ +# CSM MassData Parameter Support — C language port + +This folder contains a C-language implementation of the CSM MassData +Parameter Support add-on. It mirrors the LabVIEW API one-for-one so that C +applications can produce and consume MassData arguments that are +indistinguishable from those produced by the LabVIEW VIs in +[`addons/MassData-Parameter`](../addons/MassData-Parameter). + +> [English] | [中文](#中文) + +## Layout + +``` +c/ +├── include/csm_massdata.h # Public C API (Doxygen comments) +├── src/csm_massdata.c # Portable implementation (Win32 / POSIX) +├── vs_test/ +│ ├── csm_massdata_test.sln # Visual Studio 2022 solution +│ ├── csm_massdata_test.vcxproj # Test project (compiles as C) +│ ├── csm_massdata_test.vcxproj.filters +│ └── test_main.c # Self-contained test harness +└── README.md # This file +``` + +## API mapping + +Every C entry point is the verbatim translation of a LabVIEW VI: same name, +same parameters (in the same order), same semantics. + +| LabVIEW VI | C function | +| --------------------------------------------------- | ----------------------------------------------------- | +| `CSM - Config MassData Parameter Cache Size.vi` | `CSM_ConfigMassDataParameterCacheSize` | +| `CSM - Convert MassData to Argument.vim` | `CSM_ConvertMassDataToArgument` | +| `CSM - Convert MassData to Argument With DataType.vim` | `CSM_ConvertMassDataToArgumentWithDataType` | +| `CSM - Convert Argument to MassData.vim` | `CSM_ConvertArgumentToMassData` | +| `CSM - MassData Data Type String.vi` | `CSM_MassDataDataTypeString` | +| `CSM - MassData Parameter Status.vi` | `CSM_MassDataParameterStatus` | + +The on-the-wire MassData argument format is identical: + +``` +Start:;Size:[;DataType:] +``` + +## Building & running the tests + +### Visual Studio (recommended on Windows) + +1. Open `c/vs_test/csm_massdata_test.sln` in Visual Studio 2022. +2. Pick a configuration (`Debug|x64` is fine) and press F5. +3. The console window prints one line per assertion and the final + `N/N assertions passed` summary. + +### Command line (any platform with a C99 compiler) + +```bash +cd c +cc -std=c99 -Wall -Wextra -Iinclude src/csm_massdata.c vs_test/test_main.c -o csm_test -lpthread +./csm_test +``` + +On Windows, replace `-lpthread` with nothing (the implementation falls back +to `CRITICAL_SECTION`) and use `cl /I include src\csm_massdata.c vs_test\test_main.c`. + +## Linking the library into your own project + +1. Add `c/include` to your project's include path. +2. Compile and link `c/src/csm_massdata.c` (it has no external dependencies + beyond the C standard library and the platform's pthreads on POSIX). +3. `#include "csm_massdata.h"` and call any of the functions documented in + the header. + +A typical encode/decode round-trip looks like: + +```c +#include "csm_massdata.h" + +double samples[1024] = { /* ... */ }; +char arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; +double restored[1024]; +size_t restored_size = 0; + +CSM_ConfigMassDataParameterCacheSize(64u * 1024u * 1024u); /* 64 MiB cache */ +CSM_ConvertMassDataToArgumentWithDataType(samples, sizeof(samples), + "1D DBL", arg, sizeof(arg)); +/* ... ship `arg` over a CSM bus ... */ +CSM_ConvertArgumentToMassData(arg, restored, sizeof(restored), &restored_size); +``` + +--- + +## 中文 + +本目录提供 CSM MassData Parameter Support 的 **C 语言移植**,接口名称、参数、 +语义与 [`addons/MassData-Parameter`](../addons/MassData-Parameter) 中的 LabVIEW +VI **完全一致**,可与 LabVIEW 端无缝互通。 + +| LabVIEW VI | C 函数 | +| --------------------------------------------------- | ----------------------------------------------------- | +| `CSM - Config MassData Parameter Cache Size.vi` | `CSM_ConfigMassDataParameterCacheSize` | +| `CSM - Convert MassData to Argument.vim` | `CSM_ConvertMassDataToArgument` | +| `CSM - Convert MassData to Argument With DataType.vim` | `CSM_ConvertMassDataToArgumentWithDataType` | +| `CSM - Convert Argument to MassData.vim` | `CSM_ConvertArgumentToMassData` | +| `CSM - MassData Data Type String.vi` | `CSM_MassDataDataTypeString` | +| `CSM - MassData Parameter Status.vi` | `CSM_MassDataParameterStatus` | + +参考字符串格式与 LabVIEW 端完全相同: +`Start:;Size:[;DataType:]`。 + +测试工程位于 `c/vs_test/`,使用 Visual Studio 2022 打开 +`csm_massdata_test.sln` 即可编译并运行。也可以使用任意支持 C99 的编译器在 +命令行直接构建(参见上文 “Command line” 章节)。 diff --git a/c/include/csm_massdata.h b/c/include/csm_massdata.h new file mode 100644 index 0000000..a81cce9 --- /dev/null +++ b/c/include/csm_massdata.h @@ -0,0 +1,229 @@ +/** + * @file csm_massdata.h + * @brief C-language port of the CSM MassData Parameter Support add-on. + * + * This header exposes a C API that is functionally and nominally identical + * to the LabVIEW VIs shipped under + * `addons/MassData-Parameter/CSM MassData Parameter Support.lvlib`. + * + * The function names, parameter order and semantics intentionally mirror the + * corresponding LabVIEW VIs so that user code written in either language can + * exchange MassData arguments without any conversion layer. + * + * @par MassData Argument Format + * A MassData argument is a printable, ASCII-only reference string that points + * to a payload kept in a process-wide circular buffer. Two forms are supported: + * + * - Without data type: `Start:;Size:` + * - With data type: `Start:;Size:;DataType:` + * + * where `` is a non-negative decimal integer and `` is a free-form + * data-type tag (e.g. `1D I32`, `Waveform`, ...) defined by the CSM Data Type + * String VI. + * + * @par Data Lifecycle + * MassData uses an internally-managed circular buffer. When the buffer is + * full, new writes overwrite the oldest data starting from the beginning. + * Overwritten payloads cannot be recovered: a subsequent decode of such a + * reference returns an error. All callers within the same process share the + * same MassData buffer. + * + * @par Thread Safety + * All public functions in this header are thread-safe. Concurrent calls from + * multiple threads are serialised through an internal mutex. + * + * @copyright MIT License - see the LICENSE file at the repository root. + */ + +#ifndef CSM_MASSDATA_H +#define CSM_MASSDATA_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------------------------------------------------------------- */ +/* Constants & types */ +/* ------------------------------------------------------------------------- */ + +/** Default MassData cache size in bytes (50 MiB), matching the LabVIEW VI. */ +#define CSM_MASSDATA_DEFAULT_CACHE_SIZE ((size_t)(50u * 1024u * 1024u)) + +/** Maximum length (including the terminating NUL) of an encoded MassData + * argument string returned by the encoding functions. */ +#define CSM_MASSDATA_MAX_ARGUMENT_LEN 256 + +/** Maximum length (including the terminating NUL) of a data-type tag. */ +#define CSM_MASSDATA_MAX_DATATYPE_LEN 128 + +/** + * @brief Status codes returned by every MassData API function. + */ +typedef enum csm_massdata_status_e { + CSM_MASSDATA_OK = 0, /**< Operation completed successfully. */ + CSM_MASSDATA_ERR_INVALID_ARG = -1, /**< NULL pointer or otherwise invalid argument. */ + CSM_MASSDATA_ERR_BUFFER_TOO_SMALL = -2, /**< User-supplied output buffer is too small. */ + CSM_MASSDATA_ERR_PARSE = -3, /**< MassData argument string could not be parsed. */ + CSM_MASSDATA_ERR_OVERWRITTEN = -4, /**< Referenced data has already been overwritten. */ + CSM_MASSDATA_ERR_CACHE_TOO_SMALL = -5, /**< Payload is larger than the configured cache. */ + CSM_MASSDATA_ERR_NO_MEMORY = -6 /**< Memory allocation failed. */ +} csm_massdata_status_t; + +/** + * @brief Description of the most recent read or write performed against the + * MassData circular buffer. + * + * Equivalent to the @c Active Read Operation / + * @c Active Write Operation cluster returned by + * `CSM - MassData Parameter Status.vi`. + */ +typedef struct csm_massdata_operation_s { + uint64_t start; /**< Start offset (bytes) inside the cache. */ + uint64_t size; /**< Length of the operation in bytes. */ +} csm_massdata_operation_t; + +/* ------------------------------------------------------------------------- */ +/* API functions - each function mirrors the corresponding LabVIEW VI */ +/* ------------------------------------------------------------------------- */ + +/** + * @brief Configure the MassData background cache size. + * + * Wraps `CSM - Config MassData Parameter Cache Size.vi`. + * + * Reallocates the internal circular buffer to @p size bytes. Calling this + * function while the application is running discards any currently cached + * data, exactly like the LabVIEW VI; it should normally be invoked once, + * before any encode/decode call. + * + * @param[in] size New cache size in bytes. The default (when this function + * is never called) is @ref CSM_MASSDATA_DEFAULT_CACHE_SIZE. + * + * @return @ref CSM_MASSDATA_OK on success, + * @ref CSM_MASSDATA_ERR_INVALID_ARG if @p size is zero, + * @ref CSM_MASSDATA_ERR_NO_MEMORY if allocation failed. + */ +csm_massdata_status_t CSM_ConfigMassDataParameterCacheSize(size_t size); + +/** + * @brief Convert raw data into a MassData argument (no embedded data type). + * + * Wraps `CSM - Convert MassData to Argument.vim`. The raw payload is copied + * into the circular buffer and a reference string of the form + * `Start:;Size:` is written into @p argument. + * + * @param[in] data Pointer to the raw bytes to store. May be @c NULL + * only when @p data_size is zero. + * @param[in] data_size Length of @p data in bytes. + * @param[out] argument Caller-allocated buffer that receives the + * NUL-terminated MassData argument string. + * @param[in] argument_cap Capacity of @p argument in bytes (recommended: + * @ref CSM_MASSDATA_MAX_ARGUMENT_LEN). + * + * @return @ref CSM_MASSDATA_OK or an error code. + */ +csm_massdata_status_t CSM_ConvertMassDataToArgument(const void *data, + size_t data_size, + char *argument, + size_t argument_cap); + +/** + * @brief Convert raw data into a MassData argument that embeds a data-type + * tag. + * + * Wraps `CSM - Convert MassData to Argument With DataType.vim`. The produced + * argument is `Start:;Size:;DataType:`. + * + * @param[in] data Pointer to the raw bytes to store. + * @param[in] data_size Length of @p data in bytes. + * @param[in] data_type NUL-terminated data-type string (e.g. `"1D I32"`). + * Must contain neither `';'` nor `'<'`/`'>'`. + * @param[out] argument Caller-allocated buffer receiving the result. + * @param[in] argument_cap Capacity of @p argument in bytes. + * + * @return @ref CSM_MASSDATA_OK or an error code. + */ +csm_massdata_status_t CSM_ConvertMassDataToArgumentWithDataType(const void *data, + size_t data_size, + const char *data_type, + char *argument, + size_t argument_cap); + +/** + * @brief Convert a MassData argument back into the original raw data. + * + * Wraps `CSM - Convert Argument to MassData.vim`. The reference string in + * @p argument is parsed and the corresponding payload is copied into + * @p data. The optional `Type` input of the LabVIEW VI is intentionally + * omitted: the returned bytes are the verbatim payload that was previously + * stored, regardless of any embedded type tag. + * + * @param[in] argument NUL-terminated MassData argument string. + * @param[out] data Caller-allocated buffer that receives the data. + * @param[in] data_cap Capacity of @p data in bytes. + * @param[out] data_size_out Receives the actual number of bytes written + * into @p data. Must not be @c NULL. + * + * @return @ref CSM_MASSDATA_OK on success. + * @ref CSM_MASSDATA_ERR_PARSE if @p argument is malformed. + * @ref CSM_MASSDATA_ERR_OVERWRITTEN if the payload is no longer + * available in the cache. + * @ref CSM_MASSDATA_ERR_BUFFER_TOO_SMALL if @p data_cap is smaller + * than the stored payload (in which case @p data_size_out is + * still populated with the required size). + */ +csm_massdata_status_t CSM_ConvertArgumentToMassData(const char *argument, + void *data, + size_t data_cap, + size_t *data_size_out); + +/** + * @brief Extract the data-type string from a MassData argument. + * + * Wraps `CSM - MassData Data Type String.vi`. The function does not consume + * the argument: a verbatim copy is written to @p argument_dup so callers can + * mimic the LabVIEW dataflow that returns a duplicate of its input. + * + * @param[in] argument NUL-terminated MassData argument string. + * @param[out] argument_dup Optional. If non-NULL, receives a copy of + * @p argument. + * @param[in] argument_dup_cap Capacity of @p argument_dup in bytes + * (ignored when @p argument_dup is NULL). + * @param[out] data_type Receives the NUL-terminated data-type tag. + * Empty string when no tag is present. + * @param[in] data_type_cap Capacity of @p data_type in bytes. + * + * @return @ref CSM_MASSDATA_OK or an error code. + */ +csm_massdata_status_t CSM_MassDataDataTypeString(const char *argument, + char *argument_dup, + size_t argument_dup_cap, + char *data_type, + size_t data_type_cap); + +/** + * @brief Read the status of the MassData background cache. + * + * Wraps `CSM - MassData Parameter Status.vi`. + * + * @param[out] active_read Receives the most recent read operation. May be + * @c NULL if not needed. + * @param[out] active_write Receives the most recent write operation. May be + * @c NULL if not needed. + * @param[out] cache_size Receives the configured cache size in bytes. + * May be @c NULL if not needed. + * + * @return Always @ref CSM_MASSDATA_OK. + */ +csm_massdata_status_t CSM_MassDataParameterStatus(csm_massdata_operation_t *active_read, + csm_massdata_operation_t *active_write, + size_t *cache_size); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* CSM_MASSDATA_H */ diff --git a/c/src/csm_massdata.c b/c/src/csm_massdata.c new file mode 100644 index 0000000..ad6243c --- /dev/null +++ b/c/src/csm_massdata.c @@ -0,0 +1,421 @@ +/** + * @file csm_massdata.c + * @brief Implementation of the CSM MassData Parameter Support C API. + * + * The MassData cache is a single, process-wide circular buffer guarded by a + * mutex. Each successful encode advances a monotonically increasing 64-bit + * write cursor (`write_total`) and copies the payload into the ring at + * `write_total % capacity`, possibly wrapping around. The cursor value is + * embedded in the returned `Start:` field so that decoders can detect whether + * the requested payload is still resident or has already been overwritten by + * later writes. + */ + +#include "csm_massdata.h" + +#include +#include +#include +#include + +#if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +# include +typedef CRITICAL_SECTION csm_mutex_t; +# define CSM_MUTEX_INIT(m) InitializeCriticalSection(m) +# define CSM_MUTEX_LOCK(m) EnterCriticalSection(m) +# define CSM_MUTEX_UNLOCK(m) LeaveCriticalSection(m) +#else +# include +typedef pthread_mutex_t csm_mutex_t; +# define CSM_MUTEX_INIT(m) pthread_mutex_init((m), NULL) +# define CSM_MUTEX_LOCK(m) pthread_mutex_lock(m) +# define CSM_MUTEX_UNLOCK(m) pthread_mutex_unlock(m) +#endif + +/* ------------------------------------------------------------------------- */ +/* Internal state */ +/* ------------------------------------------------------------------------- */ + +#define CSM_MASSDATA_PREFIX "" + +typedef struct csm_massdata_state_s { + int initialized; + csm_mutex_t mutex; + uint8_t *buffer; /* Circular byte buffer. */ + size_t capacity; /* Allocated buffer size. */ + uint64_t write_total; /* Bytes ever written. */ + csm_massdata_operation_t last_read; + csm_massdata_operation_t last_write; +} csm_massdata_state_t; + +static csm_massdata_state_t g_state; + +static void csm_massdata_lazy_init(void) +{ + if (g_state.initialized) { + return; + } + /* The first call into the API is responsible for allocating the default + * cache. The init flag itself is protected by the mutex once it exists, + * but the first-time allocation is racy in a multi-threaded program, so + * callers that care should invoke + * CSM_ConfigMassDataParameterCacheSize() at startup. */ + g_state.buffer = (uint8_t *)malloc(CSM_MASSDATA_DEFAULT_CACHE_SIZE); + g_state.capacity = (g_state.buffer != NULL) ? CSM_MASSDATA_DEFAULT_CACHE_SIZE : 0u; + g_state.write_total = 0u; + memset(&g_state.last_read, 0, sizeof(g_state.last_read)); + memset(&g_state.last_write, 0, sizeof(g_state.last_write)); + CSM_MUTEX_INIT(&g_state.mutex); + g_state.initialized = 1; +} + +/* ------------------------------------------------------------------------- */ +/* Helpers */ +/* ------------------------------------------------------------------------- */ + +static int csm_massdata_starts_with(const char *s, const char *prefix) +{ + while (*prefix) { + if (*s++ != *prefix++) { + return 0; + } + } + return 1; +} + +/** + * Parse a MassData argument string of the form + * `Start:;Size:[;DataType:]`. + * + * @param[out] data_type Optional output buffer for the data-type tag. + * @param[in] data_type_cap Capacity of @p data_type. + * + * @return CSM_MASSDATA_OK on success, CSM_MASSDATA_ERR_PARSE on malformed + * input, or CSM_MASSDATA_ERR_BUFFER_TOO_SMALL if the data-type tag + * does not fit in @p data_type. + */ +static csm_massdata_status_t csm_massdata_parse(const char *argument, + uint64_t *start_out, + uint64_t *size_out, + char *data_type, + size_t data_type_cap) +{ + const char *p; + char *end; + unsigned long long tmp; + + if (argument == NULL || start_out == NULL || size_out == NULL) { + return CSM_MASSDATA_ERR_INVALID_ARG; + } + if (!csm_massdata_starts_with(argument, CSM_MASSDATA_PREFIX)) { + return CSM_MASSDATA_ERR_PARSE; + } + p = argument + (sizeof(CSM_MASSDATA_PREFIX) - 1); + + if (strncmp(p, "Start:", 6) != 0) { + return CSM_MASSDATA_ERR_PARSE; + } + p += 6; + tmp = strtoull(p, &end, 10); + if (end == p || *end != ';') { + return CSM_MASSDATA_ERR_PARSE; + } + *start_out = (uint64_t)tmp; + p = end + 1; + + if (strncmp(p, "Size:", 5) != 0) { + return CSM_MASSDATA_ERR_PARSE; + } + p += 5; + tmp = strtoull(p, &end, 10); + if (end == p) { + return CSM_MASSDATA_ERR_PARSE; + } + *size_out = (uint64_t)tmp; + p = end; + + /* Optional ;DataType: trailer. */ + if (data_type != NULL && data_type_cap > 0u) { + data_type[0] = '\0'; + } + if (*p == '\0') { + return CSM_MASSDATA_OK; + } + if (*p != ';') { + return CSM_MASSDATA_ERR_PARSE; + } + p++; + if (strncmp(p, "DataType:", 9) != 0) { + return CSM_MASSDATA_ERR_PARSE; + } + p += 9; + if (data_type != NULL && data_type_cap > 0u) { + size_t len = strlen(p); + if (len + 1u > data_type_cap) { + return CSM_MASSDATA_ERR_BUFFER_TOO_SMALL; + } + memcpy(data_type, p, len + 1u); + } + return CSM_MASSDATA_OK; +} + +/** + * Copy @p size bytes from @p src into the circular buffer at the position + * implied by the current value of @c g_state.write_total. The caller must + * hold the mutex and must have validated that @p size <= capacity. + */ +static void csm_massdata_ring_write(const uint8_t *src, size_t size) +{ + size_t offset = (size_t)(g_state.write_total % (uint64_t)g_state.capacity); + size_t first_run = g_state.capacity - offset; + + if (size <= first_run) { + memcpy(g_state.buffer + offset, src, size); + } else { + memcpy(g_state.buffer + offset, src, first_run); + memcpy(g_state.buffer, src + first_run, size - first_run); + } +} + +/** + * Copy @p size bytes from the circular buffer (starting at the absolute + * cursor @p start) into @p dst. The caller must hold the mutex and must + * have validated that the requested range is still resident. + */ +static void csm_massdata_ring_read(uint64_t start, uint8_t *dst, size_t size) +{ + size_t offset = (size_t)(start % (uint64_t)g_state.capacity); + size_t first_run = g_state.capacity - offset; + + if (size <= first_run) { + memcpy(dst, g_state.buffer + offset, size); + } else { + memcpy(dst, g_state.buffer + offset, first_run); + memcpy(dst + first_run, g_state.buffer, size - first_run); + } +} + +/* ------------------------------------------------------------------------- */ +/* Public API */ +/* ------------------------------------------------------------------------- */ + +csm_massdata_status_t CSM_ConfigMassDataParameterCacheSize(size_t size) +{ + uint8_t *new_buf; + + if (size == 0u) { + return CSM_MASSDATA_ERR_INVALID_ARG; + } + + csm_massdata_lazy_init(); + + new_buf = (uint8_t *)malloc(size); + if (new_buf == NULL) { + return CSM_MASSDATA_ERR_NO_MEMORY; + } + + CSM_MUTEX_LOCK(&g_state.mutex); + free(g_state.buffer); + g_state.buffer = new_buf; + g_state.capacity = size; + g_state.write_total = 0u; + memset(&g_state.last_read, 0, sizeof(g_state.last_read)); + memset(&g_state.last_write, 0, sizeof(g_state.last_write)); + CSM_MUTEX_UNLOCK(&g_state.mutex); + + return CSM_MASSDATA_OK; +} + +static csm_massdata_status_t csm_massdata_encode(const void *data, + size_t data_size, + const char *data_type, + char *argument, + size_t argument_cap) +{ + uint64_t start_cursor; + int written; + + if (argument == NULL || argument_cap == 0u) { + return CSM_MASSDATA_ERR_INVALID_ARG; + } + if (data_size > 0u && data == NULL) { + return CSM_MASSDATA_ERR_INVALID_ARG; + } + if (data_type != NULL) { + size_t dt_len = strlen(data_type); + if (dt_len + 1u > CSM_MASSDATA_MAX_DATATYPE_LEN) { + return CSM_MASSDATA_ERR_BUFFER_TOO_SMALL; + } + /* Reject characters that would break the reference-string grammar. */ + if (strpbrk(data_type, ";<>") != NULL) { + return CSM_MASSDATA_ERR_INVALID_ARG; + } + } + + csm_massdata_lazy_init(); + + CSM_MUTEX_LOCK(&g_state.mutex); + if (g_state.buffer == NULL) { + CSM_MUTEX_UNLOCK(&g_state.mutex); + return CSM_MASSDATA_ERR_NO_MEMORY; + } + if (data_size > g_state.capacity) { + CSM_MUTEX_UNLOCK(&g_state.mutex); + return CSM_MASSDATA_ERR_CACHE_TOO_SMALL; + } + + start_cursor = g_state.write_total; + if (data_size > 0u) { + csm_massdata_ring_write((const uint8_t *)data, data_size); + g_state.write_total += (uint64_t)data_size; + } + g_state.last_write.start = start_cursor; + g_state.last_write.size = (uint64_t)data_size; + CSM_MUTEX_UNLOCK(&g_state.mutex); + + if (data_type != NULL) { + written = snprintf(argument, argument_cap, + CSM_MASSDATA_PREFIX + "Start:%" PRIu64 ";Size:%" PRIu64 ";DataType:%s", + start_cursor, (uint64_t)data_size, data_type); + } else { + written = snprintf(argument, argument_cap, + CSM_MASSDATA_PREFIX + "Start:%" PRIu64 ";Size:%" PRIu64, + start_cursor, (uint64_t)data_size); + } + if (written < 0 || (size_t)written >= argument_cap) { + return CSM_MASSDATA_ERR_BUFFER_TOO_SMALL; + } + return CSM_MASSDATA_OK; +} + +csm_massdata_status_t CSM_ConvertMassDataToArgument(const void *data, + size_t data_size, + char *argument, + size_t argument_cap) +{ + return csm_massdata_encode(data, data_size, NULL, argument, argument_cap); +} + +csm_massdata_status_t CSM_ConvertMassDataToArgumentWithDataType(const void *data, + size_t data_size, + const char *data_type, + char *argument, + size_t argument_cap) +{ + if (data_type == NULL) { + return CSM_MASSDATA_ERR_INVALID_ARG; + } + return csm_massdata_encode(data, data_size, data_type, argument, argument_cap); +} + +csm_massdata_status_t CSM_ConvertArgumentToMassData(const char *argument, + void *data, + size_t data_cap, + size_t *data_size_out) +{ + csm_massdata_status_t status; + uint64_t start = 0u; + uint64_t size = 0u; + + if (data_size_out == NULL) { + return CSM_MASSDATA_ERR_INVALID_ARG; + } + + status = csm_massdata_parse(argument, &start, &size, NULL, 0u); + if (status != CSM_MASSDATA_OK) { + return status; + } + + csm_massdata_lazy_init(); + + *data_size_out = (size_t)size; + + if (size > 0u && data == NULL) { + return CSM_MASSDATA_ERR_INVALID_ARG; + } + if (size > (uint64_t)data_cap) { + return CSM_MASSDATA_ERR_BUFFER_TOO_SMALL; + } + + CSM_MUTEX_LOCK(&g_state.mutex); + if (g_state.buffer == NULL) { + CSM_MUTEX_UNLOCK(&g_state.mutex); + return CSM_MASSDATA_ERR_NO_MEMORY; + } + if (size > (uint64_t)g_state.capacity) { + CSM_MUTEX_UNLOCK(&g_state.mutex); + return CSM_MASSDATA_ERR_OVERWRITTEN; + } + /* The window currently resident in the ring is + * [write_total - capacity, write_total). Reject any payload whose end + * lies outside this window. */ + { + uint64_t end = start + size; + uint64_t oldest = (g_state.write_total > (uint64_t)g_state.capacity) + ? g_state.write_total - (uint64_t)g_state.capacity + : 0u; + if (start < oldest || end > g_state.write_total) { + CSM_MUTEX_UNLOCK(&g_state.mutex); + return CSM_MASSDATA_ERR_OVERWRITTEN; + } + if (size > 0u) { + csm_massdata_ring_read(start, (uint8_t *)data, (size_t)size); + } + g_state.last_read.start = start; + g_state.last_read.size = size; + } + CSM_MUTEX_UNLOCK(&g_state.mutex); + + return CSM_MASSDATA_OK; +} + +csm_massdata_status_t CSM_MassDataDataTypeString(const char *argument, + char *argument_dup, + size_t argument_dup_cap, + char *data_type, + size_t data_type_cap) +{ + csm_massdata_status_t status; + uint64_t start = 0u; + uint64_t size = 0u; + + if (argument == NULL || data_type == NULL || data_type_cap == 0u) { + return CSM_MASSDATA_ERR_INVALID_ARG; + } + + status = csm_massdata_parse(argument, &start, &size, data_type, data_type_cap); + if (status != CSM_MASSDATA_OK) { + return status; + } + + if (argument_dup != NULL) { + size_t len = strlen(argument); + if (len + 1u > argument_dup_cap) { + return CSM_MASSDATA_ERR_BUFFER_TOO_SMALL; + } + memcpy(argument_dup, argument, len + 1u); + } + return CSM_MASSDATA_OK; +} + +csm_massdata_status_t CSM_MassDataParameterStatus(csm_massdata_operation_t *active_read, + csm_massdata_operation_t *active_write, + size_t *cache_size) +{ + csm_massdata_lazy_init(); + CSM_MUTEX_LOCK(&g_state.mutex); + if (active_read != NULL) { + *active_read = g_state.last_read; + } + if (active_write != NULL) { + *active_write = g_state.last_write; + } + if (cache_size != NULL) { + *cache_size = g_state.capacity; + } + CSM_MUTEX_UNLOCK(&g_state.mutex); + return CSM_MASSDATA_OK; +} diff --git a/c/vs_test/csm_massdata_test.sln b/c/vs_test/csm_massdata_test.sln new file mode 100644 index 0000000..1c05a73 --- /dev/null +++ b/c/vs_test/csm_massdata_test.sln @@ -0,0 +1,27 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "csm_massdata_test", "csm_massdata_test.vcxproj", "{A1B2C3D4-E5F6-4789-A012-3456789ABCDE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1B2C3D4-E5F6-4789-A012-3456789ABCDE}.Debug|x64.ActiveCfg = Debug|x64 + {A1B2C3D4-E5F6-4789-A012-3456789ABCDE}.Debug|x64.Build.0 = Debug|x64 + {A1B2C3D4-E5F6-4789-A012-3456789ABCDE}.Debug|x86.ActiveCfg = Debug|Win32 + {A1B2C3D4-E5F6-4789-A012-3456789ABCDE}.Debug|x86.Build.0 = Debug|Win32 + {A1B2C3D4-E5F6-4789-A012-3456789ABCDE}.Release|x64.ActiveCfg = Release|x64 + {A1B2C3D4-E5F6-4789-A012-3456789ABCDE}.Release|x64.Build.0 = Release|x64 + {A1B2C3D4-E5F6-4789-A012-3456789ABCDE}.Release|x86.ActiveCfg = Release|Win32 + {A1B2C3D4-E5F6-4789-A012-3456789ABCDE}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/c/vs_test/csm_massdata_test.vcxproj b/c/vs_test/csm_massdata_test.vcxproj new file mode 100644 index 0000000..1f11ab4 --- /dev/null +++ b/c/vs_test/csm_massdata_test.vcxproj @@ -0,0 +1,84 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + {A1B2C3D4-E5F6-4789-A012-3456789ABCDE} + csm_massdata_test + Win32Proj + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + $(ProjectDir)..\include;%(AdditionalIncludeDirectories) + Level4 + stdc11 + CompileAsC + true + true + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + Console + true + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + + + + + + diff --git a/c/vs_test/csm_massdata_test.vcxproj.filters b/c/vs_test/csm_massdata_test.vcxproj.filters new file mode 100644 index 0000000..8183951 --- /dev/null +++ b/c/vs_test/csm_massdata_test.vcxproj.filters @@ -0,0 +1,26 @@ + + + + + {1f3a9e8f-1234-4abc-9def-111111111111} + c;cpp + + + {1f3a9e8f-1234-4abc-9def-222222222222} + h;hpp + + + + + Source Files + + + Source Files + + + + + Header Files + + + diff --git a/c/vs_test/test_main.c b/c/vs_test/test_main.c new file mode 100644 index 0000000..379653a --- /dev/null +++ b/c/vs_test/test_main.c @@ -0,0 +1,216 @@ +/** + * @file test_main.c + * @brief Self-contained test harness for the CSM MassData C API. + * + * Exercises the public API documented in `csm_massdata.h`: + * - cache configuration and status reporting + * - encode / decode round-trip without data type + * - encode / decode round-trip with data type + * - data-type extraction (CSM - MassData Data Type String) + * - parse error handling + * - circular-buffer overwrite detection + * + * The harness exits with a non-zero status if any assertion fails so it can + * be used both interactively and inside CI pipelines. + */ + +#include "csm_massdata.h" + +#include +#include +#include +#include +#include + +static int g_failures = 0; +static int g_total = 0; + +#define CSM_TEST(cond) \ + do { \ + ++g_total; \ + if (!(cond)) { \ + ++g_failures; \ + fprintf(stderr, "[FAIL] %s:%d %s\n", \ + __FILE__, __LINE__, #cond); \ + } else { \ + printf("[ OK ] %s\n", #cond); \ + } \ + } while (0) + +static void test_config_and_status(void) +{ + csm_massdata_operation_t r, w; + size_t cache = 0; + + CSM_TEST(CSM_ConfigMassDataParameterCacheSize(0) == CSM_MASSDATA_ERR_INVALID_ARG); + CSM_TEST(CSM_ConfigMassDataParameterCacheSize(1024) == CSM_MASSDATA_OK); + CSM_TEST(CSM_MassDataParameterStatus(&r, &w, &cache) == CSM_MASSDATA_OK); + CSM_TEST(cache == 1024); + CSM_TEST(r.size == 0 && w.size == 0); +} + +static void test_roundtrip_plain(void) +{ + const int32_t source[8] = { 10, 20, 30, 40, 50, 60, 70, 80 }; + int32_t restored[8]; + char arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; + size_t out_size = 0; + + CSM_TEST(CSM_ConfigMassDataParameterCacheSize(4096) == CSM_MASSDATA_OK); + CSM_TEST(CSM_ConvertMassDataToArgument(source, sizeof(source), + arg, sizeof(arg)) == CSM_MASSDATA_OK); + CSM_TEST(strncmp(arg, "Start:", 16) == 0); + + memset(restored, 0, sizeof(restored)); + CSM_TEST(CSM_ConvertArgumentToMassData(arg, restored, sizeof(restored), + &out_size) == CSM_MASSDATA_OK); + CSM_TEST(out_size == sizeof(source)); + CSM_TEST(memcmp(source, restored, sizeof(source)) == 0); +} + +static void test_roundtrip_with_type(void) +{ + const double source[4] = { 1.5, -2.5, 3.5, -4.5 }; + double restored[4]; + char arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; + char dup[CSM_MASSDATA_MAX_ARGUMENT_LEN]; + char type[CSM_MASSDATA_MAX_DATATYPE_LEN]; + size_t out_size = 0; + + CSM_TEST(CSM_ConfigMassDataParameterCacheSize(4096) == CSM_MASSDATA_OK); + CSM_TEST(CSM_ConvertMassDataToArgumentWithDataType(source, sizeof(source), + "1D DBL", arg, + sizeof(arg)) + == CSM_MASSDATA_OK); + CSM_TEST(strstr(arg, ";DataType:1D DBL") != NULL); + + CSM_TEST(CSM_MassDataDataTypeString(arg, dup, sizeof(dup), + type, sizeof(type)) == CSM_MASSDATA_OK); + CSM_TEST(strcmp(type, "1D DBL") == 0); + CSM_TEST(strcmp(dup, arg) == 0); + + memset(restored, 0, sizeof(restored)); + CSM_TEST(CSM_ConvertArgumentToMassData(arg, restored, sizeof(restored), + &out_size) == CSM_MASSDATA_OK); + CSM_TEST(out_size == sizeof(source)); + CSM_TEST(memcmp(source, restored, sizeof(source)) == 0); +} + +static void test_datatype_absent(void) +{ + char arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; + char type[CSM_MASSDATA_MAX_DATATYPE_LEN]; + const uint8_t payload[3] = { 0xAA, 0xBB, 0xCC }; + + CSM_TEST(CSM_ConfigMassDataParameterCacheSize(4096) == CSM_MASSDATA_OK); + CSM_TEST(CSM_ConvertMassDataToArgument(payload, sizeof(payload), + arg, sizeof(arg)) == CSM_MASSDATA_OK); + type[0] = 'x'; + CSM_TEST(CSM_MassDataDataTypeString(arg, NULL, 0, type, sizeof(type)) + == CSM_MASSDATA_OK); + CSM_TEST(type[0] == '\0'); +} + +static void test_parse_errors(void) +{ + size_t out = 0; + uint8_t buf[16]; + + CSM_TEST(CSM_ConvertArgumentToMassData("garbage", buf, sizeof(buf), &out) + == CSM_MASSDATA_ERR_PARSE); + CSM_TEST(CSM_ConvertArgumentToMassData("Start:abc;Size:1", + buf, sizeof(buf), &out) + == CSM_MASSDATA_ERR_PARSE); + CSM_TEST(CSM_ConvertArgumentToMassData("Start:0;Size:1;wrong", + buf, sizeof(buf), &out) + == CSM_MASSDATA_ERR_PARSE); +} + +static void test_overwrite_detection(void) +{ + char first_arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; + char filler_arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; + uint8_t small[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + uint8_t filler[32]; + uint8_t restored[8]; + size_t out = 0; + int i; + + /* Tiny cache so a follow-up write evicts the first payload. */ + CSM_TEST(CSM_ConfigMassDataParameterCacheSize(32) == CSM_MASSDATA_OK); + CSM_TEST(CSM_ConvertMassDataToArgument(small, sizeof(small), + first_arg, sizeof(first_arg)) + == CSM_MASSDATA_OK); + for (i = 0; i < (int)sizeof(filler); ++i) filler[i] = (uint8_t)i; + CSM_TEST(CSM_ConvertMassDataToArgument(filler, sizeof(filler), + filler_arg, sizeof(filler_arg)) + == CSM_MASSDATA_OK); + CSM_TEST(CSM_ConvertArgumentToMassData(first_arg, restored, + sizeof(restored), &out) + == CSM_MASSDATA_ERR_OVERWRITTEN); +} + +static void test_buffer_too_small(void) +{ + const uint8_t payload[10] = { 0 }; + char arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; + uint8_t small_out[2]; + size_t out = 0; + + CSM_TEST(CSM_ConfigMassDataParameterCacheSize(4096) == CSM_MASSDATA_OK); + CSM_TEST(CSM_ConvertMassDataToArgument(payload, sizeof(payload), + arg, sizeof(arg)) == CSM_MASSDATA_OK); + CSM_TEST(CSM_ConvertArgumentToMassData(arg, small_out, sizeof(small_out), + &out) == CSM_MASSDATA_ERR_BUFFER_TOO_SMALL); + CSM_TEST(out == sizeof(payload)); +} + +static void test_cache_too_small(void) +{ + char arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; + uint8_t big[64] = { 0 }; + + CSM_TEST(CSM_ConfigMassDataParameterCacheSize(16) == CSM_MASSDATA_OK); + CSM_TEST(CSM_ConvertMassDataToArgument(big, sizeof(big), arg, sizeof(arg)) + == CSM_MASSDATA_ERR_CACHE_TOO_SMALL); +} + +static void test_status_reflects_last_ops(void) +{ + csm_massdata_operation_t r, w; + size_t cache = 0; + char arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; + uint8_t payload[5] = { 9, 8, 7, 6, 5 }; + uint8_t restored[5]; + size_t out = 0; + + CSM_TEST(CSM_ConfigMassDataParameterCacheSize(1024) == CSM_MASSDATA_OK); + CSM_TEST(CSM_ConvertMassDataToArgument(payload, sizeof(payload), + arg, sizeof(arg)) == CSM_MASSDATA_OK); + CSM_TEST(CSM_ConvertArgumentToMassData(arg, restored, sizeof(restored), + &out) == CSM_MASSDATA_OK); + CSM_TEST(CSM_MassDataParameterStatus(&r, &w, &cache) == CSM_MASSDATA_OK); + CSM_TEST(w.size == sizeof(payload)); + CSM_TEST(r.size == sizeof(payload)); + CSM_TEST(cache == 1024); +} + +int main(void) +{ + printf("CSM MassData C API test suite\n"); + printf("=============================\n"); + + test_config_and_status(); + test_roundtrip_plain(); + test_roundtrip_with_type(); + test_datatype_absent(); + test_parse_errors(); + test_overwrite_detection(); + test_buffer_too_small(); + test_cache_too_small(); + test_status_reflects_last_ops(); + + printf("\n%d/%d assertions passed, %d failed.\n", + g_total - g_failures, g_total, g_failures); + return (g_failures == 0) ? 0 : 1; +} From 5eb197499b03f7739ca6bb4ae72a2d9f7324c350 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Apr 2026 03:07:46 +0000 Subject: [PATCH 3/5] refactor(c): translate comments to Chinese, target VS2026, move tests to c/_test/vs Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-MassData-Parameter-Support/sessions/3ca10741-4f42-4106-938e-77fb5c7ab9a7 Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- README(zh-cn).md | 6 - README.md | 7 - c/README.md | 103 +++----- c/{vs_test => _test/vs}/csm_massdata_test.sln | 4 +- .../vs}/csm_massdata_test.vcxproj | 14 +- .../vs}/csm_massdata_test.vcxproj.filters | 4 +- c/{vs_test => _test/vs}/test_main.c | 37 ++- c/include/csm_massdata.h | 239 +++++++++--------- c/src/csm_massdata.c | 69 +++-- 9 files changed, 217 insertions(+), 266 deletions(-) rename c/{vs_test => _test/vs}/csm_massdata_test.sln (95%) rename c/{vs_test => _test/vs}/csm_massdata_test.vcxproj (86%) rename c/{vs_test => _test/vs}/csm_massdata_test.vcxproj.filters (87%) rename c/{vs_test => _test/vs}/test_main.c (85%) diff --git a/README(zh-cn).md b/README(zh-cn).md index 4747c48..38e7e21 100644 --- a/README(zh-cn).md +++ b/README(zh-cn).md @@ -72,12 +72,6 @@ MassData Support 基于以下简单而有效的原理: LabVIEW 2017 或更高版本 -## 第三方语言绑定 - -[`c/`](./c) 目录提供 MassData API 的 **C 语言移植**,函数名称、参数及参考 -字符串格式与 LabVIEW VI 完全一致,并附带 Visual Studio 2022 测试工程 -[`c/vs_test`](./c/vs_test)。 - ## 许可证 本项目基于 MIT 许可证 — 详见 LICENSE 文件 diff --git a/README.md b/README.md index 189b43d..a28e84a 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,6 @@ See the example folder for demonstrations of: LabVIEW 2017 or later -## Third-party language bindings - -A C-language port of the MassData API (function names, parameters and -reference-string format identical to the LabVIEW VIs) is available under -[`c/`](./c). It ships with a Visual Studio 2022 test project at -[`c/vs_test`](./c/vs_test). - ## License This project is licensed under the MIT License — see the LICENSE file for details. diff --git a/c/README.md b/c/README.md index 1bcdf7a..41e1b64 100644 --- a/c/README.md +++ b/c/README.md @@ -1,33 +1,30 @@ -# CSM MassData Parameter Support — C language port +# CSM MassData Parameter Support —— C 语言移植 -This folder contains a C-language implementation of the CSM MassData -Parameter Support add-on. It mirrors the LabVIEW API one-for-one so that C -applications can produce and consume MassData arguments that are -indistinguishable from those produced by the LabVIEW VIs in -[`addons/MassData-Parameter`](../addons/MassData-Parameter). +本目录提供 CSM MassData Parameter Support 插件的 C 语言实现, +接口与 [`addons/MassData-Parameter`](../addons/MassData-Parameter) +中的 LabVIEW VI 一一对应,可与 LabVIEW 端无缝互通。 -> [English] | [中文](#中文) - -## Layout +## 目录结构 ``` c/ -├── include/csm_massdata.h # Public C API (Doxygen comments) -├── src/csm_massdata.c # Portable implementation (Win32 / POSIX) -├── vs_test/ -│ ├── csm_massdata_test.sln # Visual Studio 2022 solution -│ ├── csm_massdata_test.vcxproj # Test project (compiles as C) -│ ├── csm_massdata_test.vcxproj.filters -│ └── test_main.c # Self-contained test harness -└── README.md # This file +├── include/csm_massdata.h # 公开 C 接口(中文 Doxygen 注释) +├── src/csm_massdata.c # 跨平台实现(Win32 / POSIX) +├── _test/ +│ └── vs/ +│ ├── csm_massdata_test.sln # Visual Studio 2026 解决方案 +│ ├── csm_massdata_test.vcxproj # 测试工程(按 C 编译) +│ ├── csm_massdata_test.vcxproj.filters +│ └── test_main.c # 独立测试程序 +└── README.md # 本文件 ``` -## API mapping +## 接口对应关系 -Every C entry point is the verbatim translation of a LabVIEW VI: same name, -same parameters (in the same order), same semantics. +每个 C 函数都是对应 LabVIEW VI 的逐字翻译:名称相同、参数顺序相同、 +语义相同。 -| LabVIEW VI | C function | +| LabVIEW VI | C 函数 | | --------------------------------------------------- | ----------------------------------------------------- | | `CSM - Config MassData Parameter Cache Size.vi` | `CSM_ConfigMassDataParameterCacheSize` | | `CSM - Convert MassData to Argument.vim` | `CSM_ConvertMassDataToArgument` | @@ -36,41 +33,41 @@ same parameters (in the same order), same semantics. | `CSM - MassData Data Type String.vi` | `CSM_MassDataDataTypeString` | | `CSM - MassData Parameter Status.vi` | `CSM_MassDataParameterStatus` | -The on-the-wire MassData argument format is identical: +参考字符串格式与 LabVIEW 端完全相同: ``` Start:;Size:[;DataType:] ``` -## Building & running the tests +## 构建与运行测试 -### Visual Studio (recommended on Windows) +### Visual Studio 2026(推荐) -1. Open `c/vs_test/csm_massdata_test.sln` in Visual Studio 2022. -2. Pick a configuration (`Debug|x64` is fine) and press F5. -3. The console window prints one line per assertion and the final - `N/N assertions passed` summary. +1. 用 Visual Studio 2026 打开 `c/_test/vs/csm_massdata_test.sln`。 +2. 选择任意配置(例如 `Debug|x64`),按 F5 启动。 +3. 控制台会逐条打印断言结果,并在最后输出 `N/N 个断言通过` 摘要。 -### Command line (any platform with a C99 compiler) +工程使用 `v145` 平台工具集(Visual Studio 2026 默认)。 + +### 命令行(任意支持 C99 的编译器) ```bash cd c -cc -std=c99 -Wall -Wextra -Iinclude src/csm_massdata.c vs_test/test_main.c -o csm_test -lpthread +cc -std=c99 -Wall -Wextra -Iinclude src/csm_massdata.c _test/vs/test_main.c -o csm_test -lpthread ./csm_test ``` -On Windows, replace `-lpthread` with nothing (the implementation falls back -to `CRITICAL_SECTION`) and use `cl /I include src\csm_massdata.c vs_test\test_main.c`. +Windows 下可用 `cl /I include src\csm_massdata.c _test\vs\test_main.c`, +不需要 `-lpthread`(实现会自动改用 `CRITICAL_SECTION`)。 -## Linking the library into your own project +## 在自己的工程中链接 -1. Add `c/include` to your project's include path. -2. Compile and link `c/src/csm_massdata.c` (it has no external dependencies - beyond the C standard library and the platform's pthreads on POSIX). -3. `#include "csm_massdata.h"` and call any of the functions documented in - the header. +1. 将 `c/include` 加入工程的头文件搜索路径。 +2. 编译并链接 `c/src/csm_massdata.c`(除标准 C 库与 POSIX + 平台上的 pthreads 之外没有其它依赖)。 +3. `#include "csm_massdata.h"` 后即可调用头文件中文档化的所有函数。 -A typical encode/decode round-trip looks like: +典型的编码 / 解码往返如下: ```c #include "csm_massdata.h" @@ -80,33 +77,9 @@ char arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; double restored[1024]; size_t restored_size = 0; -CSM_ConfigMassDataParameterCacheSize(64u * 1024u * 1024u); /* 64 MiB cache */ +CSM_ConfigMassDataParameterCacheSize(64u * 1024u * 1024u); /* 64 MiB 缓冲区 */ CSM_ConvertMassDataToArgumentWithDataType(samples, sizeof(samples), "1D DBL", arg, sizeof(arg)); -/* ... ship `arg` over a CSM bus ... */ +/* ... 通过 CSM 总线传递 `arg` ... */ CSM_ConvertArgumentToMassData(arg, restored, sizeof(restored), &restored_size); ``` - ---- - -## 中文 - -本目录提供 CSM MassData Parameter Support 的 **C 语言移植**,接口名称、参数、 -语义与 [`addons/MassData-Parameter`](../addons/MassData-Parameter) 中的 LabVIEW -VI **完全一致**,可与 LabVIEW 端无缝互通。 - -| LabVIEW VI | C 函数 | -| --------------------------------------------------- | ----------------------------------------------------- | -| `CSM - Config MassData Parameter Cache Size.vi` | `CSM_ConfigMassDataParameterCacheSize` | -| `CSM - Convert MassData to Argument.vim` | `CSM_ConvertMassDataToArgument` | -| `CSM - Convert MassData to Argument With DataType.vim` | `CSM_ConvertMassDataToArgumentWithDataType` | -| `CSM - Convert Argument to MassData.vim` | `CSM_ConvertArgumentToMassData` | -| `CSM - MassData Data Type String.vi` | `CSM_MassDataDataTypeString` | -| `CSM - MassData Parameter Status.vi` | `CSM_MassDataParameterStatus` | - -参考字符串格式与 LabVIEW 端完全相同: -`Start:;Size:[;DataType:]`。 - -测试工程位于 `c/vs_test/`,使用 Visual Studio 2022 打开 -`csm_massdata_test.sln` 即可编译并运行。也可以使用任意支持 C99 的编译器在 -命令行直接构建(参见上文 “Command line” 章节)。 diff --git a/c/vs_test/csm_massdata_test.sln b/c/_test/vs/csm_massdata_test.sln similarity index 95% rename from c/vs_test/csm_massdata_test.sln rename to c/_test/vs/csm_massdata_test.sln index 1c05a73..e191f43 100644 --- a/c/vs_test/csm_massdata_test.sln +++ b/c/_test/vs/csm_massdata_test.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.0.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "csm_massdata_test", "csm_massdata_test.vcxproj", "{A1B2C3D4-E5F6-4789-A012-3456789ABCDE}" EndProject diff --git a/c/vs_test/csm_massdata_test.vcxproj b/c/_test/vs/csm_massdata_test.vcxproj similarity index 86% rename from c/vs_test/csm_massdata_test.vcxproj rename to c/_test/vs/csm_massdata_test.vcxproj index 1f11ab4..47b864a 100644 --- a/c/vs_test/csm_massdata_test.vcxproj +++ b/c/_test/vs/csm_massdata_test.vcxproj @@ -1,5 +1,5 @@ - + Debug @@ -19,7 +19,7 @@ - 17.0 + 18.0 {A1B2C3D4-E5F6-4789-A012-3456789ABCDE} csm_massdata_test Win32Proj @@ -29,20 +29,20 @@ Application true - v143 + v145 Unicode Application false - v143 + v145 true Unicode - $(ProjectDir)..\include;%(AdditionalIncludeDirectories) + $(ProjectDir)..\..\include;%(AdditionalIncludeDirectories) Level4 stdc11 CompileAsC @@ -74,11 +74,11 @@ - + - + diff --git a/c/vs_test/csm_massdata_test.vcxproj.filters b/c/_test/vs/csm_massdata_test.vcxproj.filters similarity index 87% rename from c/vs_test/csm_massdata_test.vcxproj.filters rename to c/_test/vs/csm_massdata_test.vcxproj.filters index 8183951..1c2b923 100644 --- a/c/vs_test/csm_massdata_test.vcxproj.filters +++ b/c/_test/vs/csm_massdata_test.vcxproj.filters @@ -11,7 +11,7 @@ - + Source Files @@ -19,7 +19,7 @@ - + Header Files diff --git a/c/vs_test/test_main.c b/c/_test/vs/test_main.c similarity index 85% rename from c/vs_test/test_main.c rename to c/_test/vs/test_main.c index 379653a..cc12c70 100644 --- a/c/vs_test/test_main.c +++ b/c/_test/vs/test_main.c @@ -1,17 +1,17 @@ /** * @file test_main.c - * @brief Self-contained test harness for the CSM MassData C API. + * @brief CSM MassData C 接口的独立测试程序。 * - * Exercises the public API documented in `csm_massdata.h`: - * - cache configuration and status reporting - * - encode / decode round-trip without data type - * - encode / decode round-trip with data type - * - data-type extraction (CSM - MassData Data Type String) - * - parse error handling - * - circular-buffer overwrite detection + * 覆盖 `csm_massdata.h` 中公开的所有 API: + * - 缓冲区配置与状态查询 + * - 不带数据类型的编码 / 解码往返 + * - 带数据类型的编码 / 解码往返 + * - 数据类型解析(CSM - MassData Data Type String) + * - 解析错误的处理 + * - 环形缓冲区覆盖检测 * - * The harness exits with a non-zero status if any assertion fails so it can - * be used both interactively and inside CI pipelines. + * 任意断言失败时程序以非零状态退出,方便在交互模式与 CI 流水线中 + * 同时使用。 */ #include "csm_massdata.h" @@ -37,6 +37,7 @@ static int g_total = 0; } \ } while (0) +/* 测试:缓冲区配置与状态查询 */ static void test_config_and_status(void) { csm_massdata_operation_t r, w; @@ -49,6 +50,7 @@ static void test_config_and_status(void) CSM_TEST(r.size == 0 && w.size == 0); } +/* 测试:不带数据类型的编码 / 解码往返 */ static void test_roundtrip_plain(void) { const int32_t source[8] = { 10, 20, 30, 40, 50, 60, 70, 80 }; @@ -68,6 +70,7 @@ static void test_roundtrip_plain(void) CSM_TEST(memcmp(source, restored, sizeof(source)) == 0); } +/* 测试:带数据类型的编码 / 解码往返 */ static void test_roundtrip_with_type(void) { const double source[4] = { 1.5, -2.5, 3.5, -4.5 }; @@ -96,6 +99,7 @@ static void test_roundtrip_with_type(void) CSM_TEST(memcmp(source, restored, sizeof(source)) == 0); } +/* 测试:参数中没有数据类型字段时,解析结果应为空字符串 */ static void test_datatype_absent(void) { char arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; @@ -111,6 +115,7 @@ static void test_datatype_absent(void) CSM_TEST(type[0] == '\0'); } +/* 测试:解析非法字符串时返回 PARSE 错误 */ static void test_parse_errors(void) { size_t out = 0; @@ -126,6 +131,7 @@ static void test_parse_errors(void) == CSM_MASSDATA_ERR_PARSE); } +/* 测试:旧数据被环形缓冲区覆盖后,应报告 OVERWRITTEN */ static void test_overwrite_detection(void) { char first_arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; @@ -136,7 +142,7 @@ static void test_overwrite_detection(void) size_t out = 0; int i; - /* Tiny cache so a follow-up write evicts the first payload. */ + /* 缓冲区故意设得很小,后续写入会把第一份数据挤掉。 */ CSM_TEST(CSM_ConfigMassDataParameterCacheSize(32) == CSM_MASSDATA_OK); CSM_TEST(CSM_ConvertMassDataToArgument(small, sizeof(small), first_arg, sizeof(first_arg)) @@ -150,6 +156,7 @@ static void test_overwrite_detection(void) == CSM_MASSDATA_ERR_OVERWRITTEN); } +/* 测试:输出缓冲区太小时返回 BUFFER_TOO_SMALL,并报告所需大小 */ static void test_buffer_too_small(void) { const uint8_t payload[10] = { 0 }; @@ -165,6 +172,7 @@ static void test_buffer_too_small(void) CSM_TEST(out == sizeof(payload)); } +/* 测试:写入数据大于缓冲区容量时返回 CACHE_TOO_SMALL */ static void test_cache_too_small(void) { char arg[CSM_MASSDATA_MAX_ARGUMENT_LEN]; @@ -175,6 +183,7 @@ static void test_cache_too_small(void) == CSM_MASSDATA_ERR_CACHE_TOO_SMALL); } +/* 测试:状态查询应当反映最近一次的读 / 写操作 */ static void test_status_reflects_last_ops(void) { csm_massdata_operation_t r, w; @@ -197,8 +206,8 @@ static void test_status_reflects_last_ops(void) int main(void) { - printf("CSM MassData C API test suite\n"); - printf("=============================\n"); + printf("CSM MassData C API 测试套件\n"); + printf("===========================\n"); test_config_and_status(); test_roundtrip_plain(); @@ -210,7 +219,7 @@ int main(void) test_cache_too_small(); test_status_reflects_last_ops(); - printf("\n%d/%d assertions passed, %d failed.\n", + printf("\n%d/%d 个断言通过,%d 个失败。\n", g_total - g_failures, g_total, g_failures); return (g_failures == 0) ? 0 : 1; } diff --git a/c/include/csm_massdata.h b/c/include/csm_massdata.h index a81cce9..7fd9a8e 100644 --- a/c/include/csm_massdata.h +++ b/c/include/csm_massdata.h @@ -1,38 +1,36 @@ /** * @file csm_massdata.h - * @brief C-language port of the CSM MassData Parameter Support add-on. + * @brief CSM MassData Parameter Support 插件的 C 语言移植版本。 * - * This header exposes a C API that is functionally and nominally identical - * to the LabVIEW VIs shipped under - * `addons/MassData-Parameter/CSM MassData Parameter Support.lvlib`. + * 本头文件公开的 C 接口与 LabVIEW 端 + * `addons/MassData-Parameter/CSM MassData Parameter Support.lvlib` + * 中的 VI 在功能与命名上完全一致。 * - * The function names, parameter order and semantics intentionally mirror the - * corresponding LabVIEW VIs so that user code written in either language can - * exchange MassData arguments without any conversion layer. + * 函数名称、参数顺序与语义均与对应的 LabVIEW VI 严格保持一致, + * 因此使用 C 与 LabVIEW 两种语言编写的代码可以无需任何转换层 + * 直接互通 MassData 参数。 * - * @par MassData Argument Format - * A MassData argument is a printable, ASCII-only reference string that points - * to a payload kept in a process-wide circular buffer. Two forms are supported: + * @par MassData 参数格式 + * MassData 参数是一段仅包含 ASCII 字符、可读的引用字符串,指向 + * 进程内一个全局环形缓冲区中的实际数据。支持以下两种形式: * - * - Without data type: `Start:;Size:` - * - With data type: `Start:;Size:;DataType:` + * - 不带数据类型: `Start:;Size:` + * - 带 数据类型: `Start:;Size:;DataType:` * - * where `` is a non-negative decimal integer and `` is a free-form - * data-type tag (e.g. `1D I32`, `Waveform`, ...) defined by the CSM Data Type - * String VI. + * 其中 `` 为非负十进制整数,`` 为自由格式的数据类型标签 + * (例如 `1D I32`、`Waveform` 等),由 CSM Data Type String VI 定义。 * - * @par Data Lifecycle - * MassData uses an internally-managed circular buffer. When the buffer is - * full, new writes overwrite the oldest data starting from the beginning. - * Overwritten payloads cannot be recovered: a subsequent decode of such a - * reference returns an error. All callers within the same process share the - * same MassData buffer. + * @par 数据生命周期 + * MassData 内部使用环形缓冲区。当缓冲区写满后,新写入的数据将 + * 从缓冲区起始位置覆盖最早的数据。被覆盖的数据无法恢复, + * 后续对其引用进行解码时会返回错误。同一进程内的所有调用者 + * 共享同一份 MassData 缓冲区。 * - * @par Thread Safety - * All public functions in this header are thread-safe. Concurrent calls from - * multiple threads are serialised through an internal mutex. + * @par 线程安全 + * 本头文件中所有公开函数均为线程安全。多个线程的并发调用 + * 会通过内部互斥量串行化执行。 * - * @copyright MIT License - see the LICENSE file at the repository root. + * @copyright MIT 许可证 — 详见仓库根目录的 LICENSE 文件。 */ #ifndef CSM_MASSDATA_H @@ -46,84 +44,80 @@ extern "C" { #endif /* ------------------------------------------------------------------------- */ -/* Constants & types */ +/* 常量与类型定义 */ /* ------------------------------------------------------------------------- */ -/** Default MassData cache size in bytes (50 MiB), matching the LabVIEW VI. */ +/** MassData 缓冲区的默认大小(字节),与 LabVIEW VI 保持一致:50 MiB。 */ #define CSM_MASSDATA_DEFAULT_CACHE_SIZE ((size_t)(50u * 1024u * 1024u)) -/** Maximum length (including the terminating NUL) of an encoded MassData - * argument string returned by the encoding functions. */ +/** 编码函数返回的 MassData 参数字符串的最大长度(包含末尾 NUL)。 */ #define CSM_MASSDATA_MAX_ARGUMENT_LEN 256 -/** Maximum length (including the terminating NUL) of a data-type tag. */ +/** 数据类型标签字符串的最大长度(包含末尾 NUL)。 */ #define CSM_MASSDATA_MAX_DATATYPE_LEN 128 /** - * @brief Status codes returned by every MassData API function. + * @brief MassData API 所有函数返回的状态码。 */ typedef enum csm_massdata_status_e { - CSM_MASSDATA_OK = 0, /**< Operation completed successfully. */ - CSM_MASSDATA_ERR_INVALID_ARG = -1, /**< NULL pointer or otherwise invalid argument. */ - CSM_MASSDATA_ERR_BUFFER_TOO_SMALL = -2, /**< User-supplied output buffer is too small. */ - CSM_MASSDATA_ERR_PARSE = -3, /**< MassData argument string could not be parsed. */ - CSM_MASSDATA_ERR_OVERWRITTEN = -4, /**< Referenced data has already been overwritten. */ - CSM_MASSDATA_ERR_CACHE_TOO_SMALL = -5, /**< Payload is larger than the configured cache. */ - CSM_MASSDATA_ERR_NO_MEMORY = -6 /**< Memory allocation failed. */ + CSM_MASSDATA_OK = 0, /**< 操作成功完成。 */ + CSM_MASSDATA_ERR_INVALID_ARG = -1, /**< 参数为 NULL 或无效。 */ + CSM_MASSDATA_ERR_BUFFER_TOO_SMALL = -2, /**< 调用方提供的输出缓冲区不足。 */ + CSM_MASSDATA_ERR_PARSE = -3, /**< MassData 参数字符串无法解析。 */ + CSM_MASSDATA_ERR_OVERWRITTEN = -4, /**< 引用的数据已被环形缓冲区覆盖。 */ + CSM_MASSDATA_ERR_CACHE_TOO_SMALL = -5, /**< 待写入的数据大于缓冲区容量。 */ + CSM_MASSDATA_ERR_NO_MEMORY = -6 /**< 内存分配失败。 */ } csm_massdata_status_t; /** - * @brief Description of the most recent read or write performed against the - * MassData circular buffer. + * @brief 描述最近一次对 MassData 环形缓冲区的读或写操作。 * - * Equivalent to the @c Active Read Operation / - * @c Active Write Operation cluster returned by - * `CSM - MassData Parameter Status.vi`. + * 等价于 `CSM - MassData Parameter Status.vi` 返回的 + * @c Active Read Operation / @c Active Write Operation 簇。 */ typedef struct csm_massdata_operation_s { - uint64_t start; /**< Start offset (bytes) inside the cache. */ - uint64_t size; /**< Length of the operation in bytes. */ + uint64_t start; /**< 在缓冲区中的起始偏移量(字节)。 */ + uint64_t size; /**< 该次操作的字节数。 */ } csm_massdata_operation_t; /* ------------------------------------------------------------------------- */ -/* API functions - each function mirrors the corresponding LabVIEW VI */ +/* API 函数 —— 每个函数对应一个同名的 LabVIEW VI */ /* ------------------------------------------------------------------------- */ /** - * @brief Configure the MassData background cache size. + * @brief 配置 MassData 后台缓冲区大小。 * - * Wraps `CSM - Config MassData Parameter Cache Size.vi`. + * 对应 `CSM - Config MassData Parameter Cache Size.vi`。 * - * Reallocates the internal circular buffer to @p size bytes. Calling this - * function while the application is running discards any currently cached - * data, exactly like the LabVIEW VI; it should normally be invoked once, - * before any encode/decode call. + * 将内部环形缓冲区重新分配为 @p size 字节。与 LabVIEW VI 一致, + * 在程序运行过程中调用本函数会丢弃当前已缓存的数据;通常应在 + * 任何编码 / 解码调用之前、应用启动阶段调用一次。 * - * @param[in] size New cache size in bytes. The default (when this function - * is never called) is @ref CSM_MASSDATA_DEFAULT_CACHE_SIZE. + * @param[in] size 新的缓冲区大小(字节)。若从未调用过本函数, + * 则默认值为 @ref CSM_MASSDATA_DEFAULT_CACHE_SIZE。 * - * @return @ref CSM_MASSDATA_OK on success, - * @ref CSM_MASSDATA_ERR_INVALID_ARG if @p size is zero, - * @ref CSM_MASSDATA_ERR_NO_MEMORY if allocation failed. + * @return 成功返回 @ref CSM_MASSDATA_OK; + * 若 @p size 为 0 返回 @ref CSM_MASSDATA_ERR_INVALID_ARG; + * 若分配失败返回 @ref CSM_MASSDATA_ERR_NO_MEMORY。 */ csm_massdata_status_t CSM_ConfigMassDataParameterCacheSize(size_t size); /** - * @brief Convert raw data into a MassData argument (no embedded data type). + * @brief 将原始数据转换为 MassData 参数(不嵌入数据类型)。 * - * Wraps `CSM - Convert MassData to Argument.vim`. The raw payload is copied - * into the circular buffer and a reference string of the form - * `Start:;Size:` is written into @p argument. + * 对应 `CSM - Convert MassData to Argument.vim`。原始数据被复制到 + * 环形缓冲区,并向 @p argument 写入形如 + * `Start:;Size:` 的引用字符串。 * - * @param[in] data Pointer to the raw bytes to store. May be @c NULL - * only when @p data_size is zero. - * @param[in] data_size Length of @p data in bytes. - * @param[out] argument Caller-allocated buffer that receives the - * NUL-terminated MassData argument string. - * @param[in] argument_cap Capacity of @p argument in bytes (recommended: - * @ref CSM_MASSDATA_MAX_ARGUMENT_LEN). + * @param[in] data 指向待保存的原始字节。仅当 @p data_size + * 为 0 时允许为 @c NULL。 + * @param[in] data_size @p data 的字节长度。 + * @param[out] argument 调用方分配的输出缓冲区,用于接收以 NUL 结尾的 + * MassData 参数字符串。 + * @param[in] argument_cap @p argument 的容量(字节),建议不小于 + * @ref CSM_MASSDATA_MAX_ARGUMENT_LEN。 * - * @return @ref CSM_MASSDATA_OK or an error code. + * @return @ref CSM_MASSDATA_OK 或对应的错误码。 */ csm_massdata_status_t CSM_ConvertMassDataToArgument(const void *data, size_t data_size, @@ -131,20 +125,19 @@ csm_massdata_status_t CSM_ConvertMassDataToArgument(const void *data, size_t argument_cap); /** - * @brief Convert raw data into a MassData argument that embeds a data-type - * tag. + * @brief 将原始数据转换为带数据类型标签的 MassData 参数。 * - * Wraps `CSM - Convert MassData to Argument With DataType.vim`. The produced - * argument is `Start:;Size:;DataType:`. + * 对应 `CSM - Convert MassData to Argument With DataType.vim`。 + * 生成的参数形如 `Start:;Size:;DataType:`。 * - * @param[in] data Pointer to the raw bytes to store. - * @param[in] data_size Length of @p data in bytes. - * @param[in] data_type NUL-terminated data-type string (e.g. `"1D I32"`). - * Must contain neither `';'` nor `'<'`/`'>'`. - * @param[out] argument Caller-allocated buffer receiving the result. - * @param[in] argument_cap Capacity of @p argument in bytes. + * @param[in] data 指向待保存的原始字节。 + * @param[in] data_size @p data 的字节长度。 + * @param[in] data_type 以 NUL 结尾的数据类型字符串(例如 `"1D I32"`)。 + * 字符串中不允许出现 `';'` 或 `'<'` / `'>'`。 + * @param[out] argument 调用方分配的输出缓冲区,用于接收结果。 + * @param[in] argument_cap @p argument 的容量(字节)。 * - * @return @ref CSM_MASSDATA_OK or an error code. + * @return @ref CSM_MASSDATA_OK 或对应的错误码。 */ csm_massdata_status_t CSM_ConvertMassDataToArgumentWithDataType(const void *data, size_t data_size, @@ -153,27 +146,25 @@ csm_massdata_status_t CSM_ConvertMassDataToArgumentWithDataType(const void *data size_t argument_cap); /** - * @brief Convert a MassData argument back into the original raw data. - * - * Wraps `CSM - Convert Argument to MassData.vim`. The reference string in - * @p argument is parsed and the corresponding payload is copied into - * @p data. The optional `Type` input of the LabVIEW VI is intentionally - * omitted: the returned bytes are the verbatim payload that was previously - * stored, regardless of any embedded type tag. - * - * @param[in] argument NUL-terminated MassData argument string. - * @param[out] data Caller-allocated buffer that receives the data. - * @param[in] data_cap Capacity of @p data in bytes. - * @param[out] data_size_out Receives the actual number of bytes written - * into @p data. Must not be @c NULL. - * - * @return @ref CSM_MASSDATA_OK on success. - * @ref CSM_MASSDATA_ERR_PARSE if @p argument is malformed. - * @ref CSM_MASSDATA_ERR_OVERWRITTEN if the payload is no longer - * available in the cache. - * @ref CSM_MASSDATA_ERR_BUFFER_TOO_SMALL if @p data_cap is smaller - * than the stored payload (in which case @p data_size_out is - * still populated with the required size). + * @brief 将 MassData 参数还原为原始数据。 + * + * 对应 `CSM - Convert Argument to MassData.vim`。本函数解析 + * @p argument 中的引用字符串,并将对应的数据复制到 @p data。 + * LabVIEW VI 中可选的 `Type` 输入在此处刻意省略:返回的就是 + * 此前写入的原始字节,不受嵌入的类型标签影响。 + * + * @param[in] argument 以 NUL 结尾的 MassData 参数字符串。 + * @param[out] data 调用方分配的接收缓冲区。 + * @param[in] data_cap @p data 的容量(字节)。 + * @param[out] data_size_out 返回实际写入 @p data 的字节数, + * 不允许为 @c NULL。 + * + * @return 成功返回 @ref CSM_MASSDATA_OK; + * @p argument 不合法时返回 @ref CSM_MASSDATA_ERR_PARSE; + * 缓存中的数据已被覆盖时返回 @ref CSM_MASSDATA_ERR_OVERWRITTEN; + * @p data_cap 小于实际数据大小时返回 + * @ref CSM_MASSDATA_ERR_BUFFER_TOO_SMALL(此时 + * @p data_size_out 仍会写入所需的字节数)。 */ csm_massdata_status_t CSM_ConvertArgumentToMassData(const char *argument, void *data, @@ -181,22 +172,21 @@ csm_massdata_status_t CSM_ConvertArgumentToMassData(const char *argument, size_t *data_size_out); /** - * @brief Extract the data-type string from a MassData argument. - * - * Wraps `CSM - MassData Data Type String.vi`. The function does not consume - * the argument: a verbatim copy is written to @p argument_dup so callers can - * mimic the LabVIEW dataflow that returns a duplicate of its input. - * - * @param[in] argument NUL-terminated MassData argument string. - * @param[out] argument_dup Optional. If non-NULL, receives a copy of - * @p argument. - * @param[in] argument_dup_cap Capacity of @p argument_dup in bytes - * (ignored when @p argument_dup is NULL). - * @param[out] data_type Receives the NUL-terminated data-type tag. - * Empty string when no tag is present. - * @param[in] data_type_cap Capacity of @p data_type in bytes. - * - * @return @ref CSM_MASSDATA_OK or an error code. + * @brief 从 MassData 参数中解析出数据类型字符串。 + * + * 对应 `CSM - MassData Data Type String.vi`。本函数不会消费输入: + * 当 @p argument_dup 非空时,会写入 @p argument 的副本,以模仿 + * LabVIEW VI 中“返回输入副本”的数据流行为。 + * + * @param[in] argument 以 NUL 结尾的 MassData 参数字符串。 + * @param[out] argument_dup 可选。若非空,则接收 @p argument 的副本。 + * @param[in] argument_dup_cap @p argument_dup 的容量(当 @p argument_dup + * 为空时忽略)。 + * @param[out] data_type 接收以 NUL 结尾的数据类型标签; + * 若不存在则为空字符串。 + * @param[in] data_type_cap @p data_type 的容量(字节)。 + * + * @return @ref CSM_MASSDATA_OK 或对应的错误码。 */ csm_massdata_status_t CSM_MassDataDataTypeString(const char *argument, char *argument_dup, @@ -205,18 +195,15 @@ csm_massdata_status_t CSM_MassDataDataTypeString(const char *argument, size_t data_type_cap); /** - * @brief Read the status of the MassData background cache. + * @brief 读取 MassData 后台缓冲区的状态信息。 * - * Wraps `CSM - MassData Parameter Status.vi`. + * 对应 `CSM - MassData Parameter Status.vi`。 * - * @param[out] active_read Receives the most recent read operation. May be - * @c NULL if not needed. - * @param[out] active_write Receives the most recent write operation. May be - * @c NULL if not needed. - * @param[out] cache_size Receives the configured cache size in bytes. - * May be @c NULL if not needed. + * @param[out] active_read 接收最近一次的读操作信息,可为 @c NULL。 + * @param[out] active_write 接收最近一次的写操作信息,可为 @c NULL。 + * @param[out] cache_size 接收当前配置的缓冲区大小(字节),可为 @c NULL。 * - * @return Always @ref CSM_MASSDATA_OK. + * @return 始终返回 @ref CSM_MASSDATA_OK。 */ csm_massdata_status_t CSM_MassDataParameterStatus(csm_massdata_operation_t *active_read, csm_massdata_operation_t *active_write, diff --git a/c/src/csm_massdata.c b/c/src/csm_massdata.c index ad6243c..c4b6d73 100644 --- a/c/src/csm_massdata.c +++ b/c/src/csm_massdata.c @@ -1,14 +1,12 @@ /** * @file csm_massdata.c - * @brief Implementation of the CSM MassData Parameter Support C API. + * @brief CSM MassData Parameter Support C 接口的实现。 * - * The MassData cache is a single, process-wide circular buffer guarded by a - * mutex. Each successful encode advances a monotonically increasing 64-bit - * write cursor (`write_total`) and copies the payload into the ring at - * `write_total % capacity`, possibly wrapping around. The cursor value is - * embedded in the returned `Start:` field so that decoders can detect whether - * the requested payload is still resident or has already been overwritten by - * later writes. + * MassData 缓存是进程内唯一的环形缓冲区,由互斥量保护。每次成功的 + * 编码会推进一个单调递增的 64 位写游标 `write_total`,并将数据 + * 复制到环形缓冲区中 `write_total % capacity` 的位置(必要时回绕)。 + * 该游标值会被写入返回字符串的 `Start:` 字段,从而让解码端能够 + * 判断引用的数据是否仍驻留在缓存中、还是已经被后续写入覆盖。 */ #include "csm_massdata.h" @@ -34,7 +32,7 @@ typedef pthread_mutex_t csm_mutex_t; #endif /* ------------------------------------------------------------------------- */ -/* Internal state */ +/* 内部状态 */ /* ------------------------------------------------------------------------- */ #define CSM_MASSDATA_PREFIX "" @@ -42,9 +40,9 @@ typedef pthread_mutex_t csm_mutex_t; typedef struct csm_massdata_state_s { int initialized; csm_mutex_t mutex; - uint8_t *buffer; /* Circular byte buffer. */ - size_t capacity; /* Allocated buffer size. */ - uint64_t write_total; /* Bytes ever written. */ + uint8_t *buffer; /* 环形字节缓冲区。 */ + size_t capacity; /* 缓冲区已分配的大小。 */ + uint64_t write_total; /* 累计已写入的字节数。 */ csm_massdata_operation_t last_read; csm_massdata_operation_t last_write; } csm_massdata_state_t; @@ -56,11 +54,10 @@ static void csm_massdata_lazy_init(void) if (g_state.initialized) { return; } - /* The first call into the API is responsible for allocating the default - * cache. The init flag itself is protected by the mutex once it exists, - * but the first-time allocation is racy in a multi-threaded program, so - * callers that care should invoke - * CSM_ConfigMassDataParameterCacheSize() at startup. */ + /* 第一次调用本模块的 API 时负责分配默认缓冲区。 + * 一旦初始化完成,该标志将由互斥量保护;但首次分配本身 + * 在多线程程序中存在竞态,因此关心线程安全的调用方应当 + * 在程序启动时主动调用 CSM_ConfigMassDataParameterCacheSize()。 */ g_state.buffer = (uint8_t *)malloc(CSM_MASSDATA_DEFAULT_CACHE_SIZE); g_state.capacity = (g_state.buffer != NULL) ? CSM_MASSDATA_DEFAULT_CACHE_SIZE : 0u; g_state.write_total = 0u; @@ -71,7 +68,7 @@ static void csm_massdata_lazy_init(void) } /* ------------------------------------------------------------------------- */ -/* Helpers */ +/* 内部辅助函数 */ /* ------------------------------------------------------------------------- */ static int csm_massdata_starts_with(const char *s, const char *prefix) @@ -85,15 +82,14 @@ static int csm_massdata_starts_with(const char *s, const char *prefix) } /** - * Parse a MassData argument string of the form - * `Start:;Size:[;DataType:]`. + * 解析形如 `Start:;Size:[;DataType:]` 的 MassData + * 参数字符串。 * - * @param[out] data_type Optional output buffer for the data-type tag. - * @param[in] data_type_cap Capacity of @p data_type. + * @param[out] data_type 可选的数据类型标签输出缓冲区。 + * @param[in] data_type_cap @p data_type 的容量。 * - * @return CSM_MASSDATA_OK on success, CSM_MASSDATA_ERR_PARSE on malformed - * input, or CSM_MASSDATA_ERR_BUFFER_TOO_SMALL if the data-type tag - * does not fit in @p data_type. + * @return 成功返回 CSM_MASSDATA_OK;输入非法返回 CSM_MASSDATA_ERR_PARSE; + * 数据类型标签放不下时返回 CSM_MASSDATA_ERR_BUFFER_TOO_SMALL。 */ static csm_massdata_status_t csm_massdata_parse(const char *argument, uint64_t *start_out, @@ -135,7 +131,7 @@ static csm_massdata_status_t csm_massdata_parse(const char *argument, *size_out = (uint64_t)tmp; p = end; - /* Optional ;DataType: trailer. */ + /* 可选的 ;DataType: 后缀。 */ if (data_type != NULL && data_type_cap > 0u) { data_type[0] = '\0'; } @@ -161,9 +157,9 @@ static csm_massdata_status_t csm_massdata_parse(const char *argument, } /** - * Copy @p size bytes from @p src into the circular buffer at the position - * implied by the current value of @c g_state.write_total. The caller must - * hold the mutex and must have validated that @p size <= capacity. + * 将 @p src 指向的 @p size 字节按照 @c g_state.write_total 暗示的位置 + * 写入环形缓冲区。调用者必须持有互斥量,并已确保 @p size 不大于 + * 缓冲区容量。 */ static void csm_massdata_ring_write(const uint8_t *src, size_t size) { @@ -179,9 +175,8 @@ static void csm_massdata_ring_write(const uint8_t *src, size_t size) } /** - * Copy @p size bytes from the circular buffer (starting at the absolute - * cursor @p start) into @p dst. The caller must hold the mutex and must - * have validated that the requested range is still resident. + * 从环形缓冲区中以绝对游标 @p start 起始位置读取 @p size 字节到 + * @p dst。调用者必须持有互斥量,并已确认所请求的范围仍然驻留。 */ static void csm_massdata_ring_read(uint64_t start, uint8_t *dst, size_t size) { @@ -197,7 +192,7 @@ static void csm_massdata_ring_read(uint64_t start, uint8_t *dst, size_t size) } /* ------------------------------------------------------------------------- */ -/* Public API */ +/* 公开 API */ /* ------------------------------------------------------------------------- */ csm_massdata_status_t CSM_ConfigMassDataParameterCacheSize(size_t size) @@ -247,7 +242,7 @@ static csm_massdata_status_t csm_massdata_encode(const void *data, if (dt_len + 1u > CSM_MASSDATA_MAX_DATATYPE_LEN) { return CSM_MASSDATA_ERR_BUFFER_TOO_SMALL; } - /* Reject characters that would break the reference-string grammar. */ + /* 拒绝可能破坏引用字符串语法的字符。 */ if (strpbrk(data_type, ";<>") != NULL) { return CSM_MASSDATA_ERR_INVALID_ARG; } @@ -349,9 +344,9 @@ csm_massdata_status_t CSM_ConvertArgumentToMassData(const char *argument, CSM_MUTEX_UNLOCK(&g_state.mutex); return CSM_MASSDATA_ERR_OVERWRITTEN; } - /* The window currently resident in the ring is - * [write_total - capacity, write_total). Reject any payload whose end - * lies outside this window. */ + /* 当前驻留在环形缓冲区中的窗口为 + * [write_total - capacity, write_total)。任何末端落在该窗口 + * 之外的请求都视为已被覆盖。 */ { uint64_t end = start + size; uint64_t oldest = (g_state.write_total > (uint64_t)g_state.capacity) From 74b3312514260c58147023af435297711b7efaac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Apr 2026 03:23:52 +0000 Subject: [PATCH 4/5] ci: exclude c/ directory from LabVIEW workflows Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-MassData-Parameter-Support/sessions/49ec99ef-0b8a-43e5-93ea-4ccb0872a44d Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- .github/workflows/Build_VIPM_Library.yml | 2 ++ .github/workflows/Check_Broken_VIs.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/Build_VIPM_Library.yml b/.github/workflows/Build_VIPM_Library.yml index 37fccc4..b20551e 100644 --- a/.github/workflows/Build_VIPM_Library.yml +++ b/.github/workflows/Build_VIPM_Library.yml @@ -17,6 +17,7 @@ on: - '**.svg' - '**.json' - '**.yml' + - 'c/**' push: paths-ignore: @@ -27,6 +28,7 @@ on: - '**.svg' - '**.json' - '**.yml' + - 'c/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: diff --git a/.github/workflows/Check_Broken_VIs.yml b/.github/workflows/Check_Broken_VIs.yml index 1ea181e..d618cc5 100644 --- a/.github/workflows/Check_Broken_VIs.yml +++ b/.github/workflows/Check_Broken_VIs.yml @@ -15,6 +15,7 @@ on: - '**.svg' - '**.json' - '**.yml' + - 'c/**' pull_request: branches: @@ -29,6 +30,7 @@ on: - '**.svg' - '**.json' - '**.yml' + - 'c/**' # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: From cd7227c89fbb32db880074afb3b072d80cd006a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Apr 2026 08:26:23 +0000 Subject: [PATCH 5/5] fix: address code review comments on csm_massdata.c/h Agent-Logs-Url: https://github.com/NEVSTOP-LAB/CSM-MassData-Parameter-Support/sessions/10ae5596-56b1-49ad-8159-dd1e35703e5e Co-authored-by: nevstop <8196752+nevstop@users.noreply.github.com> --- c/include/csm_massdata.h | 7 ++-- c/src/csm_massdata.c | 76 ++++++++++++++++++++++++++++++++-------- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/c/include/csm_massdata.h b/c/include/csm_massdata.h index 7fd9a8e..51afd15 100644 --- a/c/include/csm_massdata.h +++ b/c/include/csm_massdata.h @@ -27,8 +27,11 @@ * 共享同一份 MassData 缓冲区。 * * @par 线程安全 - * 本头文件中所有公开函数均为线程安全。多个线程的并发调用 - * 会通过内部互斥量串行化执行。 + * 在内部状态完成初始化后,公开函数的并发调用会通过内部互斥量 + * 串行化执行。延迟初始化通过平台原语(Windows: @c InitOnceExecuteOnce, + * POSIX: @c pthread_once)保证仅执行一次,即使多个线程并发首次调用 + * 也不会产生竞态。建议在进入多线程阶段前主动调用 + * CSM_ConfigMassDataParameterCacheSize() 完成初始化,以获得最佳性能。 * * @copyright MIT 许可证 — 详见仓库根目录的 LICENSE 文件。 */ diff --git a/c/src/csm_massdata.c b/c/src/csm_massdata.c index c4b6d73..ed0495a 100644 --- a/c/src/csm_massdata.c +++ b/c/src/csm_massdata.c @@ -11,7 +11,9 @@ #include "csm_massdata.h" +#include #include +#include #include #include #include @@ -23,12 +25,14 @@ typedef CRITICAL_SECTION csm_mutex_t; # define CSM_MUTEX_INIT(m) InitializeCriticalSection(m) # define CSM_MUTEX_LOCK(m) EnterCriticalSection(m) # define CSM_MUTEX_UNLOCK(m) LeaveCriticalSection(m) +static INIT_ONCE g_init_once = INIT_ONCE_STATIC_INIT; #else # include typedef pthread_mutex_t csm_mutex_t; # define CSM_MUTEX_INIT(m) pthread_mutex_init((m), NULL) # define CSM_MUTEX_LOCK(m) pthread_mutex_lock(m) # define CSM_MUTEX_UNLOCK(m) pthread_mutex_unlock(m) +static pthread_once_t g_once = PTHREAD_ONCE_INIT; #endif /* ------------------------------------------------------------------------- */ @@ -49,23 +53,39 @@ typedef struct csm_massdata_state_s { static csm_massdata_state_t g_state; +#if defined(_WIN32) +static BOOL CALLBACK csm_massdata_do_init(PINIT_ONCE io, PVOID p, PVOID *ctx) +{ + (void)io; (void)p; (void)ctx; + g_state.buffer = (uint8_t *)malloc(CSM_MASSDATA_DEFAULT_CACHE_SIZE); + g_state.capacity = (g_state.buffer != NULL) ? CSM_MASSDATA_DEFAULT_CACHE_SIZE : 0u; + g_state.write_total = 0u; + memset(&g_state.last_read, 0, sizeof(g_state.last_read)); + memset(&g_state.last_write, 0, sizeof(g_state.last_write)); + CSM_MUTEX_INIT(&g_state.mutex); + g_state.initialized = 1; + return TRUE; +} static void csm_massdata_lazy_init(void) { - if (g_state.initialized) { - return; - } - /* 第一次调用本模块的 API 时负责分配默认缓冲区。 - * 一旦初始化完成,该标志将由互斥量保护;但首次分配本身 - * 在多线程程序中存在竞态,因此关心线程安全的调用方应当 - * 在程序启动时主动调用 CSM_ConfigMassDataParameterCacheSize()。 */ - g_state.buffer = (uint8_t *)malloc(CSM_MASSDATA_DEFAULT_CACHE_SIZE); - g_state.capacity = (g_state.buffer != NULL) ? CSM_MASSDATA_DEFAULT_CACHE_SIZE : 0u; + InitOnceExecuteOnce(&g_init_once, csm_massdata_do_init, NULL, NULL); +} +#else +static void csm_massdata_do_init(void) +{ + g_state.buffer = (uint8_t *)malloc(CSM_MASSDATA_DEFAULT_CACHE_SIZE); + g_state.capacity = (g_state.buffer != NULL) ? CSM_MASSDATA_DEFAULT_CACHE_SIZE : 0u; g_state.write_total = 0u; memset(&g_state.last_read, 0, sizeof(g_state.last_read)); memset(&g_state.last_write, 0, sizeof(g_state.last_write)); CSM_MUTEX_INIT(&g_state.mutex); g_state.initialized = 1; } +static void csm_massdata_lazy_init(void) +{ + pthread_once(&g_once, csm_massdata_do_init); +} +#endif /* ------------------------------------------------------------------------- */ /* 内部辅助函数 */ @@ -113,8 +133,9 @@ static csm_massdata_status_t csm_massdata_parse(const char *argument, return CSM_MASSDATA_ERR_PARSE; } p += 6; + errno = 0; tmp = strtoull(p, &end, 10); - if (end == p || *end != ';') { + if (end == p || *end != ';' || errno != 0) { return CSM_MASSDATA_ERR_PARSE; } *start_out = (uint64_t)tmp; @@ -124,8 +145,9 @@ static csm_massdata_status_t csm_massdata_parse(const char *argument, return CSM_MASSDATA_ERR_PARSE; } p += 5; + errno = 0; tmp = strtoull(p, &end, 10); - if (end == p) { + if (end == p || errno != 0) { return CSM_MASSDATA_ERR_PARSE; } *size_out = (uint64_t)tmp; @@ -146,6 +168,16 @@ static csm_massdata_status_t csm_massdata_parse(const char *argument, return CSM_MASSDATA_ERR_PARSE; } p += 9; + /* 验证 DataType 值仅包含合法字符,且字符串以 NUL 结尾。 */ + { + const char *q = p; + while (*q && *q != ';' && *q != '<' && *q != '>') { + q++; + } + if (*q != '\0') { + return CSM_MASSDATA_ERR_PARSE; + } + } if (data_type != NULL && data_type_cap > 0u) { size_t len = strlen(p); if (len + 1u > data_type_cap) { @@ -326,11 +358,14 @@ csm_massdata_status_t CSM_ConvertArgumentToMassData(const char *argument, csm_massdata_lazy_init(); - *data_size_out = (size_t)size; - + /* 在 32 位平台上 size_t 可能小于 uint64_t,需先验证。 */ + if (size > (uint64_t)SIZE_MAX) { + return CSM_MASSDATA_ERR_PARSE; + } if (size > 0u && data == NULL) { return CSM_MASSDATA_ERR_INVALID_ARG; } + *data_size_out = (size_t)size; if (size > (uint64_t)data_cap) { return CSM_MASSDATA_ERR_BUFFER_TOO_SMALL; } @@ -348,11 +383,22 @@ csm_massdata_status_t CSM_ConvertArgumentToMassData(const char *argument, * [write_total - capacity, write_total)。任何末端落在该窗口 * 之外的请求都视为已被覆盖。 */ { - uint64_t end = start + size; uint64_t oldest = (g_state.write_total > (uint64_t)g_state.capacity) ? g_state.write_total - (uint64_t)g_state.capacity : 0u; - if (start < oldest || end > g_state.write_total) { + uint64_t end; + + if (size > 0u && start > UINT64_MAX - size) { + CSM_MUTEX_UNLOCK(&g_state.mutex); + return CSM_MASSDATA_ERR_OVERWRITTEN; + } + if (start < oldest || start > g_state.write_total) { + CSM_MUTEX_UNLOCK(&g_state.mutex); + return CSM_MASSDATA_ERR_OVERWRITTEN; + } + + end = start + size; + if (end > g_state.write_total) { CSM_MUTEX_UNLOCK(&g_state.mutex); return CSM_MASSDATA_ERR_OVERWRITTEN; }