Skip to main content

Command Palette

Search for a command to run...

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

Updated
4 min readView as Markdown
 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