Skip to content

Latest commit

 

History

History
285 lines (215 loc) · 11 KB

File metadata and controls

285 lines (215 loc) · 11 KB

Notifly C Interface Documentation

This document describes the C interface for the Notifly notification center library.

Overview

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

API Reference

Types

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;

Instance Management

// 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);

Observer Management

// 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);

Notification Posting

// 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);

Waiting For A Reply

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 */ }

Exchange

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 handler

Once 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.

Utility Functions

// 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);

Usage Example

#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;
}

Building

The shared library is built using CMake:

mkdir build && cd build
cmake ..
make

This creates:

  • libnotifly_c.so (Linux) / notifly_c.dll (Windows) - the shared library
  • notifly_c_test - C interface unit tests
  • notifly_c_example - C interface usage example

Linking

To use the C interface in your project:

CMake

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)

Direct compilation

gcc -o my_program my_program.c -lnotifly_c -I/path/to/notifly/include

Memory Management

  • Handles: The default handle (notifly_default()) should never be destroyed. Only destroy handles created with notifly_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 with notifly_exchange_destroy(), which unsubscribes every handler registered on it. A payload captured with notifly_exchange_capture() is only a pointer into whatever the poster passed — the same lifetime rule as notifly_post_notification()'s data applies to it. The out_data storage 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.

Thread Safety

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

Error Handling

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_t enum)

Use notifly_result_to_string() to get human-readable error descriptions.

Limitations

  1. Type Safety: Unlike the C++ API, the C interface uses void pointers for data, sacrificing compile-time type checking for simplicity.
  2. Templates: The C++ template-based type validation is not available in C.
  3. Complex Data: Only simple data structures should be passed through the void pointer interface.

Migration from C++ API

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)