Project 5: Crafting a Stable ABI with Opaque Pointers
A C shared library for a “counter” object. The key is that the public header file (
counter.h) will not define thestruct Counter—it will only forward-declare it. This makes the pointer “opaque”. You will then provide a V2 of your library with a completely different internalstructthat is still 100% binary compatible with the original.
Quick Reference
| Attribute | Value |
|---|---|
| Primary Language | C |
| Alternative Languages | C++ |
| Difficulty | Level 3: Advanced |
| Time Estimate | 1-2 weeks |
| Knowledge Area | API Design / Library Development |
| Tooling | Shared libraries (.so/.dll) |
| Prerequisites | C pointers, dynamic memory, experience building shared libraries. |
What You Will Build
A C shared library for a “counter” object. The key is that the public header file (counter.h) will not define the struct Counter—it will only forward-declare it. This makes the pointer “opaque”. You will then provide a V2 of your library with a completely different internal struct that is still 100% binary compatible with the original.
Why It Matters
This project builds core skills that appear repeatedly in real-world systems and tooling.
Core Challenges
- Designing an opaque API → maps to only exposing pointers to incomplete types in headers
- Managing object lifecycle → maps to providing
create,destroy,get,setfunctions - Separating public header from internal implementation → maps to strict discipline in your code structure
- Proving binary compatibility → maps to swapping the
.sofile for a new version and having an old program still work
Key Concepts
- Opaque Pointer (PIMPL Idiom): A widely used technique to hide private data members from a class or interface.
- API vs. ABI: The API is the source-level contract; the ABI is the binary-level contract. This project shows how to change the former slightly while preserving the latter.
- Dynamic Linking: How an executable finds and uses a
.sofile at runtime.
Real-World Outcome
struct counter; // Incomplete type -> Opaque
typedef struct counter counter_t;
counter_t* counter_create(int initial_value);
void counter_destroy(counter_t* c);
void counter_increment(counter_t* c);
int counter_get_value(const counter_t* c);
Implementation Guide
- Reproduce the simplest happy-path scenario.
- Build the smallest working version of the core feature.
- Add input validation and error handling.
- Add instrumentation/logging to confirm behavior.
- Refactor into clean modules with tests.
Milestones
- Milestone 1: Minimal working program that runs end-to-end.
- Milestone 2: Correct outputs for typical inputs.
- Milestone 3: Robust handling of edge cases.
- Milestone 4: Clean structure and documented usage.
Validation Checklist
- Output matches the real-world outcome example
- Handles invalid inputs safely
- Provides clear errors and exit codes
- Repeatable results across runs
References
- Main guide:
LEARN_C_ABI_DEEP_DIVE.md - “C Interfaces and Implementations” by David R. Hanson