Giter Site home page Giter Site logo

foxbud / libcclosure Goto Github PK

View Code? Open in Web Editor NEW
12.0 4.0 0.0 110 KB

Thread-safe closures as first-class functions for C

License: Apache License 2.0

CMake 19.03% C 80.97%
c library closure closures closure-library thread-safe threadsafe pthreads anonymous lambda

libcclosure's Introduction

libcclosure

libcclosure is a library which adds thread-safe closures as first-class functions to the C language.

This library is heavily inspired by and intended as a more permissively-licensed alternative to libffcall's callback module. If your project's license permits the use of GPL 3.0-licensed software, you should probably use libffcall instead; it has had more rigorous bug testing and supports a wider range of systems and architectures.

Compatibility

Supported Operating Systems

  • Linux

Supported ISAs

Supported Multi-Threading Libraries

Build and Installation

This library uses CMake to generate its build system.

Configuration

The first step is to configure the build system by running the following command:

$ CC=gcc cmake -S . -B build \
    -D CMAKE_BUILD_TYPE=Release \
    -D BUILD_TESTING=OFF \
    -D BUILD_THREADING=ON \
    -D BUILD_ARCH=x86_64

Setting the CC environment variable is optional and likely unnecessary unless you want to use a compiler other than your user default. The supported compilers are GCC, Clang, and TCC.

Unless you you plan to modify libcclosure, itself, you'll likely want to use Release for CMAKE_BUILD_TYPE and OFF for BUILD_TESTING.

While thread-safety is one of the primary goals of this library, it also involves non-negligible overhead. If you'll be using libcclosure in a single-threaded environment, you can gain a little extra performance by using OFF for BUILD_THREADING to prevent the inclusion of thread-safety-related system calls.

Finally, choose a target architecture to build the library for by passing it as BUILD_ARCH. The supported architectures are x86 and x86_64.

Build

To build the library, run:

$ cmake --build build/

This creates both a static (libcclosure.a) and shared (libcclosure.so) library.

Installation

# cmake --build build/ --target install

By default, this will install the library to /usr/local. You can change the installation directory by instead running:

$ cmake -S . -B build -D CMAKE_INSTALL_PREFIX=$HOME/.local
$ cmake --build build/ --target install

The header file cclosure.h will be installed to ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}.

The library files libcclosure.a and libcclosure.so will be installed to ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}.

Importable cmake scripts which define the targets CClosure::cclosure_static and CClosure::cclosure_shared will be installed to the directory ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_CMAKEDIR}/CClosure.

Quick Start

These closures are first-class C functions in the sense that they can accept arbitrary arguments (including variadic) and have an arbitrary return type. To create one, first define a callback function that accepts a special closure "context" as its first argument followed by the other desired arguments:

int Callback(CClosureCtx ctx, double filter, size_t numVArgs, ...) {
    /* "ctx.env" is a pointer to the closure's environment. */

    /* ... */
}

To create a closure, you must bind an environment to the callback function (pass true as the third argument to CClosureNew if the callback returns an aggregate type rather than a scalar):

int (*closure)(double, size_t, ...) = CClosureNew(Callback, &someEnv, false);

CClosureNew is completely thread-safe assuming that libcclosure was compiled with multi-threading support.

closure can now be called like any other C function, and its bound environment will be implicitly passed to it before the arguments it was called with:

int val0 = closure(15.0, 2, "some", "string");
int val1 = closure(8.0, 0);

The bound closure is thread-safe in the sense that multiple threads may safely call it in parallel and read from its environment. If the closure's callback modifies its environment, however, you must ensure that it does so in a thread-safe manner (like by using a mutex).

Use CClosureCheck to determine whether or not a given reference is to a bound closure:

bool isClosure = CClosureCheck(closure);

Retrieve the environment bound to a closure using CClosureGetEnv:

void *env = CClosureGetEnv(closure);

and retrieve the callback function bound to it using CClosureGetFcn:

void *fcn = CClosureGetFcn(closure);

Use CClosureFree to de-allocate a bound closure:

void *env = CClosureFree(closure);

Note that CClosureFree returns the previously-bound environment.

CClosureFree is thread-safe in the sense that multiple threads may safely call it (along with CClosureNew) in parallel. It is also safe for a closure to free itself and still return as normal. However, there are situations in which calling this function along with others (such as CClosureGetEnv and CClosureGetFcn) in parallel may result in undefined behavior.

Test whether or not libcclosure was compiled with multi-threading support using the CCLOSURE_THREAD_TYPE global:

switch (CCLOSURE_THREAD_TYPE) {
    /* Compiled with multi-threading support using POSIX Threads. */
    case CCLOSURE_THREAD_PTHREADS:
        break;

    /* Not compiled with any multi-threading support. */
    case CCLOSURE_THREAD_NONE:
        break;
}

Example

Suppose an external API provides some function that accepts a callback function:

/* list.h */
#include <stdbool.h>
#include <stddef.h>

typedef void List;

List *ListCreate(size_t num, ...);
void ListForEach(List *list, bool (*callback)(int *element));

Functions like this typically accept a "data" parameter to pass to callback in addition to element, but imagine that isn't the case here. That functionality can be recreated using a closure:

/* main.c */
#include <stdio.h>
#include <stdlib.h>

#include "cclosure.h"
#include "lists.h"

/* Type of closure environment. */
struct SumGreaterThanEnv {
    int sum;
    int threshold;
};

/* Function that accepts closure context as first parameter. */
static bool SumGreaterThanCallback(CClosureCtx ctx, int *element) {
    /* Closure context contains the bound environment. */
    struct SumGreaterThanEnv *env = ctx.env;

    if (*element > env->threshold)
        env->sum += *element;

    return true;
}

int main(void) {
    List *list = ListCreate(5, 3, -10, 77, 42, 15);

    /* Instantiate an environment for the closure. */
    struct SumGreaterThanEnv *env = malloc(sizeof(struct SumGreaterThanEnv));
    *env = (SumGreaterThanEnv){
        .sum = 0,
        .threshold = 10
    };

    /* Create a closure by binding environment to callback. */
    bool (*callback)(int *) = CClosureNew(SumGreaterThanCallback, env, false);

    /* Callback is now a first-class function that will be passed environment
       implicitly as its first parameter. */
    ListForEach(list, callback);

    /* Would print "134". */
    printf("%i", env->sum);

    /* "CClosureFree" returns the closure's environment. */
    free(CClosureFree(callback));

    return 0;
}

libcclosure's People

Contributors

foxbud avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

libcclosure's Issues

Test thread cancellation

Determine a reproducible way to test that all functions can handle a cancellation request (via pthread_cancel) at any point during their execution without resulting in deadlocks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.