Aller au contenu

Exemples d'implémentation

Cette section présente deux projets complets : une API REST CRUD en C++ avec cpp-httplib, SQLite3 et nlohmann/json, puis une bibliotheque de calcul en C pur avec des bindings Python (ctypes) et Rust (FFI). Les deux projets sont fonctionnels et suivent les conventions de production.


Projet 1 — API REST CRUD avec cpp-httplib

Structure du projet

api/
├── CMakeLists.txt
├── src/
│   ├── main.cpp         # Point d'entree, demarrage du serveur
│   ├── database.hpp     # Gestion SQLite3 (C API via RAII)
│   ├── database.cpp
│   ├── handlers.hpp     # Declarations des handlers HTTP
│   └── handlers.cpp     # Implementation GET/POST/PUT/DELETE
└── third_party/
    ├── httplib.h        # cpp-httplib (header-only)
    └── json.hpp         # nlohmann/json (header-only)

database.hpp — Wrapper RAII autour de SQLite3

// database.hpp
// Wrapper C++ RAII autour de l'API C de SQLite3
#pragma once
#include <sqlite3.h>
#include <string>
#include <vector>
#include <optional>
#include <stdexcept>

// Representation d'un item en memoire
struct Item {
    int         id    = 0;
    std::string nom;
    double      prix  = 0.0;
    int         stock = 0;
};

// Exception levee en cas d'erreur SQLite
class DbException : public std::runtime_error {
public:
    explicit DbException(const std::string& msg) : std::runtime_error(msg) {}
};

class Database {
public:
    // Constructeur : ouvre ou cree le fichier SQLite et cree la table si necessaire
    explicit Database(const std::string& chemin);

    // Destructeur : ferme la connexion (RAII)
    ~Database();

    // Pas de copie — une seule connexion par instance
    Database(const Database&)            = delete;
    Database& operator=(const Database&) = delete;

    std::vector<Item>    listerItems()                  const;
    std::optional<Item>  trouverItem(int id)            const;
    Item                 creerItem(const std::string& nom, double prix, int stock);
    bool                 mettreAJourItem(const Item& item);
    bool                 supprimerItem(int id);

private:
    sqlite3* db_ = nullptr;

    // Execution d'une requete sans resultat (INSERT, UPDATE, DELETE, CREATE)
    void executer(const std::string& sql);
};

database.cpp — Implémentation

// database.cpp
#include "database.hpp"
#include <stdexcept>

Database::Database(const std::string& chemin) {
    int rc = sqlite3_open(chemin.c_str(), &db_);
    if (rc != SQLITE_OK) {
        throw DbException(std::string("Impossible d'ouvrir la DB : ") + sqlite3_errmsg(db_));
    }
    executer(R"(
        CREATE TABLE IF NOT EXISTS items (
            id    INTEGER PRIMARY KEY AUTOINCREMENT,
            nom   TEXT    NOT NULL,
            prix  REAL    NOT NULL,
            stock INTEGER NOT NULL DEFAULT 0
        )
    )");
}

Database::~Database() {
    if (db_) sqlite3_close(db_);
}

void Database::executer(const std::string& sql) {
    char* msg = nullptr;
    int rc = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &msg);
    if (rc != SQLITE_OK) {
        std::string err(msg ? msg : "Erreur inconnue");
        sqlite3_free(msg);
        throw DbException(err);
    }
}

std::vector<Item> Database::listerItems() const {
    std::vector<Item> items;
    sqlite3_stmt* stmt = nullptr;

    sqlite3_prepare_v2(db_, "SELECT id, nom, prix, stock FROM items", -1, &stmt, nullptr);
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        Item item;
        item.id    = sqlite3_column_int(stmt, 0);
        item.nom   = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
        item.prix  = sqlite3_column_double(stmt, 2);
        item.stock = sqlite3_column_int(stmt, 3);
        items.push_back(std::move(item));
    }
    sqlite3_finalize(stmt);
    return items;
}

std::optional<Item> Database::trouverItem(int id) const {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, "SELECT id, nom, prix, stock FROM items WHERE id = ?",
                       -1, &stmt, nullptr);
    sqlite3_bind_int(stmt, 1, id);

    std::optional<Item> resultat;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        Item item;
        item.id    = sqlite3_column_int(stmt, 0);
        item.nom   = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
        item.prix  = sqlite3_column_double(stmt, 2);
        item.stock = sqlite3_column_int(stmt, 3);
        resultat   = std::move(item);
    }
    sqlite3_finalize(stmt);
    return resultat;
}

Item Database::creerItem(const std::string& nom, double prix, int stock) {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_,
        "INSERT INTO items (nom, prix, stock) VALUES (?, ?, ?)", -1, &stmt, nullptr);
    sqlite3_bind_text(stmt, 1, nom.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_double(stmt, 2, prix);
    sqlite3_bind_int(stmt, 3, stock);

    if (sqlite3_step(stmt) != SQLITE_DONE)
        throw DbException("Echec insertion");
    sqlite3_finalize(stmt);

    Item item;
    item.id    = static_cast<int>(sqlite3_last_insert_rowid(db_));
    item.nom   = nom;
    item.prix  = prix;
    item.stock = stock;
    return item;
}

bool Database::mettreAJourItem(const Item& item) {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_,
        "UPDATE items SET nom=?, prix=?, stock=? WHERE id=?", -1, &stmt, nullptr);
    sqlite3_bind_text(stmt, 1, item.nom.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_double(stmt, 2, item.prix);
    sqlite3_bind_int(stmt, 3, item.stock);
    sqlite3_bind_int(stmt, 4, item.id);
    sqlite3_step(stmt);
    int rows = sqlite3_changes(db_);
    sqlite3_finalize(stmt);
    return rows > 0;
}

bool Database::supprimerItem(int id) {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, "DELETE FROM items WHERE id = ?", -1, &stmt, nullptr);
    sqlite3_bind_int(stmt, 1, id);
    sqlite3_step(stmt);
    int rows = sqlite3_changes(db_);
    sqlite3_finalize(stmt);
    return rows > 0;
}

handlers.cpp — Routes HTTP CRUD

// handlers.cpp
// Compilation : g++ -std=c++17 -Wall -Ithird_party src/*.cpp -lsqlite3 -lpthread
#include "handlers.hpp"
#include "database.hpp"
#include <nlohmann/json.hpp>
#include <httplib.h>
#include <string>

using json = nlohmann::json;

// Serialisation Item -> JSON
static json itemVersJson(const Item& item) {
    return {{"id", item.id}, {"nom", item.nom},
            {"prix", item.prix}, {"stock", item.stock}};
}

void enregistrerHandlers(httplib::Server& srv, Database& db) {

    // GET /items — liste tous les items
    srv.Get("/items", [&](const httplib::Request&, httplib::Response& res) {
        json tableau = json::array();
        for (const auto& item : db.listerItems())
            tableau.push_back(itemVersJson(item));
        res.set_content(tableau.dump(), "application/json");
    });

    // GET /items/:id — recupere un item par ID
    srv.Get(R"(/items/(\d+))", [&](const httplib::Request& req, httplib::Response& res) {
        int id = std::stoi(req.matches[1]);
        auto item = db.trouverItem(id);
        if (!item) {
            res.status = 404;
            res.set_content(R"({"error":"Item non trouve"})", "application/json");
            return;
        }
        res.set_content(itemVersJson(*item).dump(), "application/json");
    });

    // POST /items — cree un nouvel item
    srv.Post("/items", [&](const httplib::Request& req, httplib::Response& res) {
        try {
            auto body = json::parse(req.body);
            // Validation des champs obligatoires
            if (!body.contains("nom") || !body.contains("prix")) {
                res.status = 400;
                res.set_content(R"({"error":"Champs 'nom' et 'prix' requis"})",
                                "application/json");
                return;
            }
            auto item = db.creerItem(
                body["nom"].get<std::string>(),
                body["prix"].get<double>(),
                body.value("stock", 0)
            );
            res.status = 201;
            res.set_content(itemVersJson(item).dump(), "application/json");
        } catch (const json::exception& e) {
            res.status = 400;
            res.set_content(R"({"error":"JSON invalide"})", "application/json");
        }
    });

    // PUT /items/:id — mise a jour complete
    srv.Put(R"(/items/(\d+))", [&](const httplib::Request& req, httplib::Response& res) {
        int id = std::stoi(req.matches[1]);
        try {
            auto body = json::parse(req.body);
            Item item;
            item.id    = id;
            item.nom   = body["nom"].get<std::string>();
            item.prix  = body["prix"].get<double>();
            item.stock = body.value("stock", 0);

            if (!db.mettreAJourItem(item)) {
                res.status = 404;
                res.set_content(R"({"error":"Item non trouve"})", "application/json");
                return;
            }
            res.set_content(itemVersJson(item).dump(), "application/json");
        } catch (const json::exception&) {
            res.status = 400;
            res.set_content(R"({"error":"JSON invalide"})", "application/json");
        }
    });

    // DELETE /items/:id — supprime un item
    srv.Delete(R"(/items/(\d+))", [&](const httplib::Request& req, httplib::Response& res) {
        int id = std::stoi(req.matches[1]);
        if (!db.supprimerItem(id)) {
            res.status = 404;
            res.set_content(R"({"error":"Item non trouve"})", "application/json");
            return;
        }
        res.status = 204;
    });
}

main.cpp et test

// main.cpp
#include "handlers.hpp"
#include "database.hpp"
#include <httplib.h>
#include <iostream>

int main() {
    Database db("items.db");
    httplib::Server srv;

    enregistrerHandlers(srv, db);

    std::cout << "API demarree sur http://localhost:8080\n";
    srv.listen("0.0.0.0", 8080);
    return 0;
}
# Test avec curl
curl -X POST http://localhost:8080/items \
  -H "Content-Type: application/json" \
  -d '{"nom":"Widget","prix":9.99,"stock":100}'
# => {"id":1,"nom":"Widget","prix":9.99,"stock":100}

curl http://localhost:8080/items
# => [{"id":1,"nom":"Widget","prix":9.99,"stock":100}]

curl -X PUT http://localhost:8080/items/1 \
  -H "Content-Type: application/json" \
  -d '{"nom":"Widget Pro","prix":14.99,"stock":50}'

curl -X DELETE http://localhost:8080/items/1

Projet 2 — Bibliotheque C avec bindings Python et Rust

Ce projet illustre le cas d'usage classique : écrire une bibliotheque de calcul performante en C pur et l'exposer a d'autres langages via une ABI stable.

calcul.h — Header public de la bibliotheque

/* calcul.h — API publique de la bibliotheque de calcul */
/* La macro CALCUL_API gere l'export/import selon la plateforme */
#pragma once
#include <stddef.h>

#ifdef _WIN32
  #ifdef CALCUL_BUILD
    #define CALCUL_API __declspec(dllexport)
  #else
    #define CALCUL_API __declspec(dllimport)
  #endif
#else
  #define CALCUL_API __attribute__((visibility("default")))
#endif

#ifdef __cplusplus
extern "C" {
#endif

/* Produit scalaire de deux vecteurs de longueur n */
CALCUL_API double calcul_produit_scalaire(const double* a,
                                          const double* b,
                                          size_t n);

/* Norme euclidienne d'un vecteur */
CALCUL_API double calcul_norme(const double* v, size_t n);

/* Multiplication matricielle : C = A * B
   A est m x k, B est k x n, C est m x n
   C doit etre alloue par l'appelant (m * n doubles) */
CALCUL_API void calcul_matmul(const double* A, const double* B, double* C,
                               size_t m, size_t k, size_t n);

#ifdef __cplusplus
}
#endif

calcul.c — Implémentation

/* calcul.c — Implementation de la bibliotheque de calcul */
/* Compilation : gcc -O2 -shared -fPIC -DCALCUL_BUILD calcul.c -o libcalcul.so */
#define CALCUL_BUILD
#include "calcul.h"
#include <math.h>   /* sqrt() */

double calcul_produit_scalaire(const double* a, const double* b, size_t n) {
    double resultat = 0.0;
    for (size_t i = 0; i < n; ++i)
        resultat += a[i] * b[i];
    return resultat;
}

double calcul_norme(const double* v, size_t n) {
    return sqrt(calcul_produit_scalaire(v, v, n));
}

void calcul_matmul(const double* A, const double* B, double* C,
                   size_t m, size_t k, size_t n) {
    /* Initialisation de C a zero */
    for (size_t i = 0; i < m * n; ++i) C[i] = 0.0;

    /* Multiplication naive O(m*k*n) */
    for (size_t i = 0; i < m; ++i) {
        for (size_t p = 0; p < k; ++p) {
            double a_ip = A[i * k + p];
            for (size_t j = 0; j < n; ++j)
                C[i * n + j] += a_ip * B[p * n + j];
        }
    }
}

Bindings Python (ctypes)

# bindings_python.py
# Utilisation de ctypes pour appeler la bibliotheque C depuis Python
# Prerequis : libcalcul.so dans le meme repertoire ou dans LD_LIBRARY_PATH
import ctypes
import os
from typing import List

# Chargement de la bibliotheque partagee
_lib_chemin = os.path.join(os.path.dirname(__file__), "libcalcul.so")
_lib = ctypes.CDLL(_lib_chemin)

# Declaration des signatures de fonctions
_lib.calcul_produit_scalaire.argtypes = [
    ctypes.POINTER(ctypes.c_double),  # const double* a
    ctypes.POINTER(ctypes.c_double),  # const double* b
    ctypes.c_size_t,                  # size_t n
]
_lib.calcul_produit_scalaire.restype = ctypes.c_double

_lib.calcul_norme.argtypes = [
    ctypes.POINTER(ctypes.c_double),
    ctypes.c_size_t,
]
_lib.calcul_norme.restype = ctypes.c_double

def _vers_tableau_c(liste: List[float]) -> ctypes.Array:
    """Convertit une liste Python en tableau C de doubles."""
    return (ctypes.c_double * len(liste))(*liste)

def produit_scalaire(a: List[float], b: List[float]) -> float:
    """Produit scalaire de deux vecteurs Python."""
    if len(a) != len(b):
        raise ValueError("Les vecteurs doivent avoir la meme longueur")
    arr_a = _vers_tableau_c(a)
    arr_b = _vers_tableau_c(b)
    return _lib.calcul_produit_scalaire(arr_a, arr_b, len(a))

def norme(v: List[float]) -> float:
    """Norme euclidienne d'un vecteur Python."""
    arr = _vers_tableau_c(v)
    return _lib.calcul_norme(arr, len(v))

# --- Test ---
if __name__ == "__main__":
    a = [1.0, 2.0, 3.0]
    b = [4.0, 5.0, 6.0]
    print(f"Produit scalaire : {produit_scalaire(a, b)}")  # 32.0
    print(f"Norme de a : {norme(a):.6f}")                  # 3.741657

Bindings Rust (FFI)

// bindings_rust/src/main.rs
// Liaison FFI avec la bibliotheque C libcalcul.so
// Cargo.toml : [dependencies] (rien) + [build-dependencies] cc = "1"
// build.rs : println!("cargo:rustc-link-lib=calcul");

// Declaration de l'interface C (extern "C" desactive le name mangling)
extern "C" {
    fn calcul_produit_scalaire(a: *const f64, b: *const f64, n: usize) -> f64;
    fn calcul_norme(v: *const f64, n: usize) -> f64;
    fn calcul_matmul(a: *const f64, b: *const f64, c: *mut f64,
                     m: usize, k: usize, n: usize);
}

// Wrappers Rust safe — encapsulent le code unsafe
fn produit_scalaire(a: &[f64], b: &[f64]) -> f64 {
    assert_eq!(a.len(), b.len(), "Vecteurs de longueur differente");
    // unsafe : appel C, les pointeurs sont valides car issus de slices Rust
    unsafe { calcul_produit_scalaire(a.as_ptr(), b.as_ptr(), a.len()) }
}

fn norme(v: &[f64]) -> f64 {
    unsafe { calcul_norme(v.as_ptr(), v.len()) }
}

fn matmul(a: &[f64], b: &[f64], m: usize, k: usize, n: usize) -> Vec<f64> {
    let mut c = vec![0.0f64; m * n];
    unsafe { calcul_matmul(a.as_ptr(), b.as_ptr(), c.as_mut_ptr(), m, k, n) }
    c
}

fn main() {
    let a = vec![1.0, 2.0, 3.0];
    let b = vec![4.0, 5.0, 6.0];

    println!("Produit scalaire : {}", produit_scalaire(&a, &b));  // 32
    println!("Norme de a : {:.6}", norme(&a));                    // 3.741657

    // Multiplication 2x2 * 2x2
    let mat_a = vec![1.0, 2.0, 3.0, 4.0];
    let mat_b = vec![5.0, 6.0, 7.0, 8.0];
    let mat_c = matmul(&mat_a, &mat_b, 2, 2, 2);
    println!("Matmul : {:?}", mat_c);  // [19, 22, 43, 50]
}

ABI stable en C

L'ABI C (extern "C") est le standard d'interopérabilité entre langages. Rust, Python, Julia, Go et pratiquement tous les langages modernes peuvent appeler une bibliotheque partagee avec une API C. C++ seul ne convient pas : son name mangling dépend du compilateur et de sa version.