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:
- Load plugins dynamically with
dlopen/dlsym. - Define a stable ABI for plugins to implement.
- Enforce version checks and capability discovery.
- 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:
dlopenloads 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
- Load
.so/.dylibplugins by path. - Validate ABI version and signature.
- Call plugin functions safely.
- 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
dlopenlibrary.dlsymforplugin_get_api.- Validate ABI version and required functions.
- Invoke
init, thenrun.
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:
- ABI versioning
- How do you detect mismatches?
- Function pointer tables
- Why are they safer than many symbols?
- Opaque handles
- How to hide plugin state.
5.5 Questions to Guide Your Design
Before implementing, think through these:
- Will you allow optional functions?
- How will you handle plugin state and cleanup?
- 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:
- “What is ABI compatibility?”
- “How does
dlopenwork?” - “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:
- Implement
plugin_get_api. - Load with
dlopenand call.
Checkpoint: Host executes plugin.
Phase 2: Core Functionality (4-6 days)
Goals:
- ABI versioning
Tasks:
- Add ABI version and check it.
- Add error handling.
Checkpoint: Mismatched plugins rejected.
Phase 3: Polish & Edge Cases (2-4 days)
Goals:
- Optional capabilities
Tasks:
- Add optional callbacks.
- 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
- Valid plugin: Loads and runs.
- Wrong ABI: Refused with error.
- 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_infocommand.
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.
9.2 Related Open Source Projects
- 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
10.4 Related Projects in This Series
- 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
dlopensafely. - 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.