Project 1: Plugin System

Build a C plugin system with a stable ABI and clear versioning contracts.

Quick Reference

Attribute Value
Difficulty Intermediate
Time Estimate 1-2 weeks
Language C
Prerequisites Shared libraries, function pointers
Key Topics ABI stability, dlopen, opaque structs

1. Learning Objectives

By completing this project, you will:

  1. Load plugins dynamically with dlopen/dlsym.
  2. Define a stable ABI for plugins to implement.
  3. Enforce version checks and capability discovery.
  4. Use opaque handles to hide implementation details.

2. Theoretical Foundation

2.1 Core Concepts

  • ABI contracts: Shared libraries must agree on struct layout and function signatures.
  • Dynamic loading: dlopen loads a shared object at runtime.
  • Function pointer tables: Plugins expose a struct of callbacks.

2.2 Why This Matters

Plugins are boundaries in action. You must design APIs that survive version changes without breaking existing compiled plugins.

2.3 Historical Context / Background

Major systems (editors, IDEs, databases) use plugin architectures to allow independent evolution and third-party extensions.

2.4 Common Misconceptions

  • “If it compiles, it works”: ABI breaks can crash at runtime.
  • “Structs can change freely”: Not in a stable ABI.

3. Project Specification

3.1 What You Will Build

A host application that loads plugins and calls a standardized API:

  • Plugin exports plugin_get_api()
  • Host validates ABI version and function table
  • Host invokes plugin callbacks

3.2 Functional Requirements

  1. Load .so/.dylib plugins by path.
  2. Validate ABI version and signature.
  3. Call plugin functions safely.
  4. Handle errors and unload gracefully.

3.3 Non-Functional Requirements

  • Reliability: Defensive checks for NULL symbols.
  • Stability: ABI versioning prevents mismatches.
  • Usability: Clear error messages.

3.4 Example Usage / Output

$ ./host ./plugins/libexample.so
[plugin] name=example, version=1
[plugin] run() -> OK

3.5 Real World Outcome

You can drop a compiled plugin into a folder, and the host discovers and runs it without recompilation. Version mismatches fail fast with clear messages.


4. Solution Architecture

4.1 High-Level Design

host -> dlopen -> dlsym -> api table -> plugin callbacks

4.2 Key Components

Component Responsibility Key Decisions
ABI header Stable types and version Fixed struct layout
Loader Resolve symbols Validate before use
Plugin API Function table Use const function pointers

4.3 Data Structures

typedef struct {
    int abi_version;
    const char *name;
    int (*init)(void);
    int (*run)(const char *input);
    void (*shutdown)(void);
} PluginApi;

4.4 Algorithm Overview

Key Algorithm: Plugin load

  1. dlopen library.
  2. dlsym for plugin_get_api.
  3. Validate ABI version and required functions.
  4. Invoke init, then run.

Complexity Analysis:

  • Time: O(1) per plugin
  • Space: O(1)

5. Implementation Guide

5.1 Development Environment Setup

cc -Wall -Wextra -O2 -g -o host host.c -ldl
cc -Wall -Wextra -O2 -fPIC -shared -o libexample.so plugin_example.c

5.2 Project Structure

plugin-system/
├── include/
│   └── plugin_api.h
├── host/
│   └── host.c
├── plugins/
│   └── plugin_example.c
└── README.md

5.3 The Core Question You’re Answering

“How do I define a binary contract so independently built plugins can run safely?”

5.4 Concepts You Must Understand First

Stop and research these before coding:

  1. ABI versioning
    • How do you detect mismatches?
  2. Function pointer tables
    • Why are they safer than many symbols?
  3. Opaque handles
    • How to hide plugin state.

5.5 Questions to Guide Your Design

Before implementing, think through these:

  1. Will you allow optional functions?
  2. How will you handle plugin state and cleanup?
  3. How will you signal errors to the host?

5.6 Thinking Exercise

ABI Change

If you add a new function to PluginApi, how do you keep older plugins working?

5.7 The Interview Questions They’ll Ask

Prepare to answer these:

  1. “What is ABI compatibility?”
  2. “How does dlopen work?”
  3. “Why use an API table instead of many symbols?”

5.8 Hints in Layers

Hint 1: Start with a single plugin Hardcode a plugin path and load it.

Hint 2: Add ABI version field Refuse to load mismatched plugins.

Hint 3: Add optional callbacks Check for NULL before calling.

5.9 Books That Will Help

Topic Book Chapter
Shared libraries “The Linux Programming Interface” Ch. 41-42
ABI stability “Advanced C and C++ Compiling” Ch. 11

5.10 Implementation Phases

Phase 1: Foundation (3-4 days)

Goals:

  • Load a plugin and call a function

Tasks:

  1. Implement plugin_get_api.
  2. Load with dlopen and call.

Checkpoint: Host executes plugin.

Phase 2: Core Functionality (4-6 days)

Goals:

  • ABI versioning

Tasks:

  1. Add ABI version and check it.
  2. Add error handling.

Checkpoint: Mismatched plugins rejected.

Phase 3: Polish & Edge Cases (2-4 days)

Goals:

  • Optional capabilities

Tasks:

  1. Add optional callbacks.
  2. Add plugin metadata reporting.

Checkpoint: Host handles missing functions.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
ABI evolution Version field Required Prevent mismatches
API exposure Table vs symbols Table More flexible

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Integration Tests Load plugins Valid/invalid plugin
ABI Tests Version mismatch Wrong version
Error Tests Missing symbol NULL dlsym

6.2 Critical Test Cases

  1. Valid plugin: Loads and runs.
  2. Wrong ABI: Refused with error.
  3. Missing symbol: Graceful failure.

6.3 Test Data

libexample.so, libbad.so

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
ABI mismatch Crash Check version field
Missing dlsym check NULL deref Validate symbols
Unloaded plugin Use-after-dlclose Keep handles valid

7.2 Debugging Strategies

  • Use dlerror() after each load step.
  • Print the resolved function addresses.

7.3 Performance Traps

Plugin loading is expensive; cache handles if reusing.


8. Extensions & Challenges

8.1 Beginner Extensions

  • Add plugin discovery from a directory.
  • Add plugin_info command.

8.2 Intermediate Extensions

  • Support plugin dependencies.
  • Add version negotiation.

8.3 Advanced Extensions

  • Hot reload plugins.
  • Sandboxed plugin execution.

9. Real-World Connections

9.1 Industry Applications

  • Editors: Extension systems.
  • Databases: Custom plugins and extensions.
  • SQLite extensions: C plugin model.

9.3 Interview Relevance

Dynamic loading and ABI stability are advanced systems topics.


10. Resources

10.1 Essential Reading

  • “The Linux Programming Interface” - Ch. 41-42
  • “Advanced C and C++ Compiling” - Ch. 11

10.2 Video Resources

  • Shared libraries and ABI lectures

10.3 Tools & Documentation

  • man 3 dlopen, man 3 dlsym
  • KV Client: API design and ownership.
  • libhttp-lite: Stable interface across versions.

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain ABI stability.
  • I can use dlopen safely.
  • I can design an API table.

11.2 Implementation

  • Plugins load and execute.
  • Version mismatches are rejected.
  • Errors are handled gracefully.

11.3 Growth

  • I can add hot reload.
  • I can explain this project in an interview.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Load and execute a plugin via API table.

Full Completion:

  • ABI versioning and error checks.

Excellence (Going Above & Beyond):

  • Hot reload and plugin discovery.

This guide was generated from SPRINT_4_BOUNDARIES_INTERFACES_PROJECTS.md. For the complete learning path, see the parent directory.