Building an F# Control Plane with a C Data Plane on Linux

Modern software systems often separate control logic from high-performance execution logic. This design is common in networking, distributed systems, operating systems, storage engines, and embedded software.
In this project, we build a simple Linux application that demonstrates this architectural pattern using:
- F# as the Control Plane
- C as the Data Plane
- P/Invoke for interoperability
- Linux Shared Libraries (.so)
Although the example is intentionally simple, the same design scales to much larger systems.
Why Separate Control Plane and Data Plane?
Different programming languages excel at different tasks.
| Control Plane | Data Plane |
|---|---|
| User interaction | High-performance computation |
| Input validation | Native execution |
| Business logic | Memory-efficient processing |
| Workflow orchestration | Optimized algorithms |
| F# | C |
In this project:
- F# collects user input.
- The terrain data is stored in a managed array.
- The array is passed directly to native C.
- C prints the terrain grid.
This demonstrates a clean separation of responsibilities.
Project Architecture
The project consists of two independent components.
Control Plane
Responsibilities:
- Read terrain dimensions
- Read elevation values
- Store data in a managed array
- Invoke native code
Technology:
- F#
- .NET 8
Data Plane
Responsibilities:
- Receive terrain data
- Traverse the array
- Print formatted output
Technology:
- C
- GCC
- Shared Library (.so)
Project Structure
terrain_grid/
│
├── control_plane/
│ ├── Program.fs
│ ├── Terrain.fs
│ └── control_plane.fsproj
│
├── data_plane/
│ ├── terrain.c
│ ├── terrain.h
│ └── libterrain.so
│
└── README.md
The Control Plane (F#)
The control plane is responsible for gathering input and forwarding it to the native library.
A simplified version:
let terrain = Array.zeroCreate<double>(rows * cols)
Terrain.display_terrain(
terrain,
rows,
cols)
Notice that the F# code never performs the display itself.
Its only responsibility is orchestration.
Calling Native C with P/Invoke
The bridge between managed and native code is created with DllImport.
[<DllImport("libterrain.so",
CallingConvention = CallingConvention.Cdecl)>]
extern void display_terrain(
double[] terrain,
int rows,
int cols)
P/Invoke automatically marshals the managed array into a native pointer that the C library can consume.
The Data Plane (C)
The C library receives a pointer to the terrain array and prints the values.
void display_terrain(
const double* terrain,
int rows,
int cols)
{
printf("%8.2f",
terrain[i * cols + j]);
}
Because the array is stored contiguously, indexing is straightforward.
Why Use a One-Dimensional Array?
Although the terrain logically represents a matrix, it is stored as a linear array.
Advantages include:
- Contiguous memory
- Better cache locality
- Easier interoperability
- Simpler pointer arithmetic
- Lower overhead
The index calculation is:
index = row * columns + column
This technique is widely used in:
- Scientific computing
- Image processing
- Game engines
- Embedded software
- Numerical libraries
Building the Shared Library
Compile the native library using GCC.
gcc -shared -fPIC terrain.c -o libterrain.so
Building the F# Project
dotnet build
Run:
dotnet run
Sample Output
Rows : 2
Cols : 2
Elevation [0][0] : 2
Elevation [0][1] : 3
Elevation [1][0] : 4
Elevation [1][1] : 5
Terrain Grid (Elevation in meters)
----------------------------------
2.00 3.00
4.00 5.00
What This Example Demonstrates
This small application introduces several important concepts.
- F# to C interoperability
- Linux shared libraries
- P/Invoke
- Managed and native memory interaction
- Control Plane / Data Plane architecture
- One-dimensional representation of matrices
Where This Pattern Is Used
The same architecture appears in many production systems.
- Network packet processing
- Linux networking subsystems
- Storage engines
- Embedded firmware
- Robotics
- High-performance computing
- Scientific simulations
- CAD applications
- GIS software
- Image processing pipelines
The complexity may increase, but the architectural principle remains the same.
Possible Extensions
This project can easily be expanded by adding features such as:
- Terrain statistics
- Height normalization
- Gradient calculation
- Heat map generation
- Terrain smoothing
- File import/export
- CSV support
- Binary terrain format
- OpenGL visualization
- GIS integration
Conclusion
This project demonstrates how managed and native languages can work together effectively.
F# provides expressive and concise application logic, while C delivers predictable native execution. By combining the two through P/Invoke, developers can build applications that are both productive and performant.
Although the terrain grid application is intentionally small, it illustrates a design pattern that scales to much larger systems in systems programming, embedded development, and high-performance computing.
GitHub Repository
terrain_grid
https://github.com/aj333git/Civil_Mech_apps/tree/main/terrain_grid



