This document describes the C interface for the Notifly notification center library.
The C interface provides access to Notifly functionality from C programs through a shared library (libnotifly_c.so on Linux, notifly_c.dll on Windows). The interface wraps the C++ API with a C-compatible API using:
- Opaque handle-based design for type safety
- Function pointer callbacks instead of C++ std::function
- Simple void pointer data payloads
- Standard C error codes
typedef struct notifly_instance* notifly_handle;
typedef void (*notifly_callback)(int notification_id, void* data, void* user_data);
typedef enum {
NOTIFLY_SUCCESS = 0,
NOTIFLY_OBSERVER_NOT_FOUND = -1,
NOTIFLY_NOTIFICATION_NOT_FOUND = -2,
NOTIFLY_PAYLOAD_TYPE_NOT_MATCH = -3,
NOTIFLY_NO_MORE_OBSERVER_IDS = -4,
NOTIFLY_TIMEOUT = -5,
NOTIFLY_INVALID_HANDLE = -6
} notifly_result_t;// Create a new notifly instance
notifly_handle notifly_create(void);
// Destroy a notifly instance (only for instances created with notifly_create)
void notifly_destroy(notifly_handle handle);
// Get the default global notifly instance
notifly_handle notifly_default(void);// Add an observer for a specific notification
int notifly_add_observer(notifly_handle handle, int notification_id,
notifly_callback callback, void* user_data);
// Remove a specific observer by ID
int notifly_remove_observer(notifly_handle handle, int observer_id);
// Remove all observers for a notification
int notifly_remove_all_observers(notifly_handle handle, int notification_id);// Post a notification synchronously
int notifly_post_notification(notifly_handle handle, int notification_id, void* data);
// Post a notification asynchronously
int notifly_post_notification_async(notifly_handle handle, int notification_id, void* data);notifly_post_notification is one-way. When something is expected to answer, notifly_post_and_wait
posts a request and blocks until the reply arrives or the timeout expires. It subscribes before
posting, so a reply that comes back before the post call returns is still caught.
// Post post_notification_id, then block until wait_notification_id is posted (or timeout_ms
// elapses). On success *response_data holds the payload the reply was posted with.
int notifly_post_and_wait(notifly_handle handle,
int post_notification_id,
int wait_notification_id,
int timeout_ms,
void* post_data,
void** response_data);void* reply = NULL;
int result = notifly_post_and_wait(notifly, REQUEST_ID, REPLY_ID, 500, request_data, &reply);
if (result == NOTIFLY_TIMEOUT) { /* nobody answered */ }For anything notifly_post_and_wait cannot express — ignoring a reply that isn't the one being
waited for, several possible answers where which one arrived matters, a reply streamed in
pieces, silence as the successful outcome, or waiting on an event with nothing to post — a
notifly_exchange_handle is the same machinery with the pieces exposed. It mirrors
notifly::exchange from the C++ API (see the README).
typedef struct notifly_exchange* notifly_exchange_handle;
typedef enum {
NOTIFLY_VERDICT_SKIP = 0, // Not what is being waited for. Stay subscribed, record nothing.
NOTIFLY_VERDICT_KEEP = 1, // Part of a streamed reply. Record it and keep waiting.
NOTIFLY_VERDICT_DONE = 2 // This delivery completes the exchange.
} notifly_verdict_t;
typedef notifly_verdict_t (*notifly_exchange_handler)(int notification_id, void* data, void* user_data);
notifly_exchange_handle notifly_exchange_create(notifly_handle handle);
void notifly_exchange_destroy(notifly_exchange_handle exchange);
// Subscribe: handler judges each delivery, or capture() stores the first one into *out_data.
// Both return notifly_exchange_status() after the call.
int notifly_exchange_on(notifly_exchange_handle exchange, int notification_id,
notifly_exchange_handler handler, void* user_data);
int notifly_exchange_capture(notifly_exchange_handle exchange, int notification_id, void** out_data);
// Block on the subscriptions above.
int notifly_exchange_wait(notifly_exchange_handle exchange, int timeout_ms); // fired id, or -1
int notifly_exchange_silent_for(notifly_exchange_handle exchange, int window_ms); // 1 if silent
int notifly_exchange_drain(notifly_exchange_handle exchange, int quiet_ms, int deadline_ms); // count
// Inspect without blocking.
int notifly_exchange_status(notifly_exchange_handle exchange);
int notifly_exchange_fired(notifly_exchange_handle exchange);
int notifly_exchange_accepted(notifly_exchange_handle exchange);| Shape | How |
|---|---|
| Ignore deliveries that are not the awaited one | handler returns NOTIFLY_VERDICT_SKIP |
| Several possible answers, and which one arrived matters | several notifly_exchange_on() calls; wait() returns the notification that fired |
| A reply streamed in pieces of unstated length | handler returns NOTIFLY_VERDICT_KEEP, end with drain(quiet_ms, deadline_ms) |
| Silence is the successful outcome | silent_for(window_ms) |
| Nothing to post — waiting on an external event | subscribe and wait(), post nothing |
| One of several alternatives is a plain payload, not worth a handler | capture(id, out_data) |
notifly_exchange_handle ex = notifly_exchange_create(notifly);
void* reply = NULL;
notifly_exchange_capture(ex, REPLY_ID, &reply);
notifly_post_notification(notifly, REQUEST_ID, request_data);
if (notifly_exchange_wait(ex, 500) < 0) { /* timed out */ }
else { /* reply now points at the delivered payload */ }
notifly_exchange_destroy(ex); // unsubscribes every handlerOnce a handler returns NOTIFLY_VERDICT_DONE the exchange is complete and later deliveries are
ignored. Never call notifly_exchange_destroy(), or notifly_remove_observer() on one of its
observer ids, from inside a handler.
// Convert error code to human-readable string
const char* notifly_result_to_string(int result);
// Convert an exchange verdict to a human-readable string
const char* notifly_verdict_to_string(int verdict);#include "notifly_c.h"
#include <stdio.h>
// Callback function
void my_callback(int notification_id, void* data, void* user_data) {
printf("Received notification %d\n", notification_id);
if (data) {
int* value = (int*)data;
printf("Data: %d\n", *value);
}
}
int main() {
// Get default instance
notifly_handle notifly = notifly_default();
// Add observer
int observer_id = notifly_add_observer(notifly, 1001, my_callback, NULL);
if (observer_id <= 0) {
printf("Failed to add observer: %s\n", notifly_result_to_string(observer_id));
return 1;
}
// Post notification
int data = 42;
int result = notifly_post_notification(notifly, 1001, &data);
if (result <= 0) {
printf("Failed to post notification: %s\n", notifly_result_to_string(result));
return 1;
}
printf("Notification sent to %d observers\n", result);
// Cleanup
notifly_remove_observer(notifly, observer_id);
return 0;
}The shared library is built using CMake:
mkdir build && cd build
cmake ..
makeThis creates:
libnotifly_c.so(Linux) /notifly_c.dll(Windows) - the shared librarynotifly_c_test- C interface unit testsnotifly_c_example- C interface usage example
To use the C interface in your project:
find_library(NOTIFLY_C_LIBRARY notifly_c)
target_link_libraries(your_target ${NOTIFLY_C_LIBRARY})
target_include_directories(your_target PRIVATE /path/to/notifly/include)gcc -o my_program my_program.c -lnotifly_c -I/path/to/notifly/include- Handles: The default handle (
notifly_default()) should never be destroyed. Only destroy handles created withnotifly_create(). - Data: The library does not take ownership of data passed to
notifly_post_notification(). Ensure data remains valid during synchronous calls. - User data: User data passed to
notifly_add_observer()must remain valid until the observer is removed. - Exchanges: Every
notifly_exchange_create()must be paired withnotifly_exchange_destroy(), which unsubscribes every handler registered on it. A payload captured withnotifly_exchange_capture()is only a pointer into whatever the poster passed — the same lifetime rule asnotifly_post_notification()'sdataapplies to it. Theout_datastorage itself is retained by the subscription and written when a delivery arrives, so it must stay valid until a delivery completes the exchange or the exchange is destroyed — never pass a local from a helper scope that ends before the wait.
The C interface inherits the thread safety characteristics of the underlying C++ implementation:
- Multiple threads can safely add/remove observers and post notifications
- Callbacks may be invoked from different threads when using async notifications
- No additional locking is required in user code
All functions return integer results:
- Positive values: Success (usually count of affected observers)
- Zero: Success with no side effects
- Negative values: Error codes (see
notifly_result_tenum)
Use notifly_result_to_string() to get human-readable error descriptions.
- Type Safety: Unlike the C++ API, the C interface uses void pointers for data, sacrificing compile-time type checking for simplicity.
- Templates: The C++ template-based type validation is not available in C.
- Complex Data: Only simple data structures should be passed through the void pointer interface.
| C++ API | C API |
|---|---|
notifly::default_notifly() |
notifly_default() |
add_observer(id, callback) |
notifly_add_observer(handle, id, callback, user_data) |
remove_observer(id) |
notifly_remove_observer(handle, id) |
post_notification(id, args...) |
notifly_post_notification(handle, id, &data) |
post_notification_async(id, args...) |
notifly_post_notification_async(handle, id, &data) |
post_and_wait(post_id, wait_id, ms, result, args...) |
notifly_post_and_wait(handle, post_id, wait_id, ms, &data, &response) |
notifly::exchange ex(center); |
notifly_exchange_handle ex = notifly_exchange_create(handle); |
ex.on<Args...>(id, handler) |
notifly_exchange_on(ex, id, handler, user_data) |
ex.capture(id, out) |
notifly_exchange_capture(ex, id, &out_data) |
ex.wait(timeout) / ex.silent_for(window) / ex.drain(quiet, deadline) |
notifly_exchange_wait(ex, ms) / notifly_exchange_silent_for(ex, ms) / notifly_exchange_drain(ex, quiet_ms, deadline_ms) |