A high-performance, Win/Mac cross-platform, C# ↔ C++ interoperability library for geometry data exchange using FlatBuffers. Originally developed for the Rhino/Grasshopper platform, this library enables efficient serialization and transmission of geometric data between managed (.NET) and unmanaged (C++) environments. While designed with CAD applications in mind, it can be used independently in any scenario requiring high-performance geometry data exchange.
- 🚀 High Performance: Zero-copy serialization using FlatBuffers
- 🔄 Bi-directional: Seamless data exchange between C# and C++
- 📐 Rich Geometry Support: Points, point arrays, meshes, and more
- 🛠 Extensible: Easy to add new geometric types
- 🎯 Cross-Platform: Windows and macOS support
- 🏗️ CAD Integration: Built-in support for CAD geometry types (currently Rhino, contributions for other CAD platforms welcome!)
C# Side:
using GSP;
using Rhino.Geometry;
// Create a point
var originalPoint = new Point3d(1.5, 2.7, 3.9);
// Serialize to buffer for C++
byte[] buffer = Wrapper.ToPointBuffer(originalPoint);
// Send to C++ and get result back
NativeBridge.Point3dRoundTrip(buffer, buffer.Length, out IntPtr outPtr, out int outSize);
// Convert back to C#
byte[] resultBuffer = new byte[outSize];
Marshal.Copy(outPtr, resultBuffer, 0, outSize);
Marshal.FreeCoTaskMem(outPtr);
Point3d resultPoint = Wrapper.FromPointBuffer(resultBuffer);
Console.WriteLine($"Original: {originalPoint}, Result: {resultPoint}");C++ Side:
extern "C" {
GSP_API bool GSP_CALL point3d_roundtrip(const uint8_t* inBuffer,
int inSize,
uint8_t** outBuffer,
int* outSize) {
// Deserialize from FlatBuffer
GeoSharPlusCPP::Vector3d point;
if (!GS::deserializePoint(inBuffer, inSize, point)) {
return false;
}
// Process the point (e.g., transform, analyze, etc.)
// ... your C++ geometry operations here ...
// Serialize back to FlatBuffer
return GS::serializePoint(point, *outBuffer, *outSize);
}
}C# Side:
// Create point array
var points = new Point3d[] {
new Point3d(0, 0, 0),
new Point3d(1, 1, 1),
new Point3d(2, 2, 2)
};
// Serialize and send to C++
byte[] buffer = Wrapper.ToPointArrayBuffer(points);
NativeBridge.Point3dArrayRoundTrip(buffer, buffer.Length, out IntPtr outPtr, out int outSize);
// Get processed results
byte[] resultBuffer = new byte[outSize];
Marshal.Copy(outPtr, resultBuffer, 0, outSize);
Marshal.FreeCoTaskMem(outPtr);
Point3d[] processedPoints = Wrapper.FromPointArrayBuffer(resultBuffer);C++ Side:
GSP_API bool GSP_CALL point3d_array_roundtrip(const uint8_t* inBuffer,
int inSize,
uint8_t** outBuffer,
int* outSize) {
// Deserialize point array
std::vector<GeoSharPlusCPP::Vector3d> points;
if (!GS::deserializePointArray(inBuffer, inSize, points)) {
return false;
}
// Process the points (e.g., apply transformations, filtering, etc.)
for (auto& point : points) {
// Example: scale all points by 2
point *= 2.0;
}
// Serialize back
return GS::serializePointArray(points, *outBuffer, *outSize);
}- vcpkg: The C++ sub-project uses vcpkg's manifest mode for dependency management
- Visual Studio (Windows) or CMake + compatible compiler (cross-platform)
- .NET 7.0 or later
-
Install vcpkg and set the
VCPKG_ROOTenvironment variable:git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh # Linux/macOS # or .\bootstrap-vcpkg.bat # Windows
-
Configure the project:
# Navigate to the C++ directory cd GeoSharPlusCPP # Install required packages vcpkg install # Configure with CMake cmake -B build .
-
Build the solution:
# Open in Visual Studio start GSP.DEMO.sln # Or build with CMake cmake --build build --config Release
After setup, you should see this structure:
GeoSharPlus/
├── GeoSharPlusCPP/ # C++ core library
├── GeoSharPlusNET/ # C# wrapper library
├── GSPdemoGH/ # Grasshopper demo project
├── GSPdemoConsole/ # Console demo project
├── cppPrebuild/ # Compiled C++ libraries (.dll/.dylib)
└── generated/ # Auto-generated FlatBuffer code
└── GSP_FB/
├── cpp/ # C++ FlatBuffer headers
└── csharp/ # C# FlatBuffer classes
Open the GSP.DEMO.sln solution file in Visual Studio and build the project to see working examples.
To extend the library with new geometric types, follow these steps:
Create a new .fbs file in GeoSharPlusCPP/schema/:
// Example: circle.fbs
include "base.fbs";
namespace GSP.FB;
table CircleData {
center: Vec3;
radius: double;
normal: Vec3;
}
root_type CircleData;
Add functions to GeoSharPlusCPP/src/Serialization/GeoSerializer.cpp:
bool serializeCircle(const Circle& circle, uint8_t*& resBuffer, int& resSize) {
flatbuffers::FlatBufferBuilder builder;
auto center = GSP::FB::Vec3(circle.center[0], circle.center[1], circle.center[2]);
auto normal = GSP::FB::Vec3(circle.normal[0], circle.normal[1], circle.normal[2]);
auto circleOffset = GSP::FB::CreateCircleData(builder, ¢er, circle.radius, &normal);
builder.Finish(circleOffset);
resSize = builder.GetSize();
resBuffer = static_cast<uint8_t*>(CoTaskMemAlloc(resSize));
std::memcpy(resBuffer, builder.GetBufferPointer(), resSize);
return true;
}
bool deserializeCircle(const uint8_t* buffer, int size, Circle& circle) {
auto circleData = GSP::FB::GetCircleData(buffer);
if (!circleData) return false;
circle.center = Vector3d(circleData->center()->x(),
circleData->center()->y(),
circleData->center()->z());
circle.radius = circleData->radius();
circle.normal = Vector3d(circleData->normal()->x(),
circleData->normal()->y(),
circleData->normal()->z());
return true;
}Add to GeoSharPlusCPP/src/API/BridgeAPI.cpp:
extern "C" {
GSP_API bool GSP_CALL circle_roundtrip(const uint8_t* inBuffer,
int inSize,
uint8_t** outBuffer,
int* outSize) {
Circle circle;
if (!GS::deserializeCircle(inBuffer, inSize, circle)) {
return false;
}
// Process circle data here...
return GS::serializeCircle(circle, *outBuffer, *outSize);
}
}Add to GeoSharPlusNET/NativeBridge.cs:
[DllImport(WinLibName, EntryPoint = "circle_roundtrip", CallingConvention = CallingConvention.Cdecl)]
private static extern bool CircleRoundTripWin(byte[] inBuffer, int inSize, out IntPtr outBuffer, out int outSize);
[DllImport(MacLibName, EntryPoint = "circle_roundtrip", CallingConvention = CallingConvention.Cdecl)]
private static extern bool CircleRoundTripMac(byte[] inBuffer, int inSize, out IntPtr outBuffer, out int outSize);
public static bool CircleRoundTrip(byte[] inBuffer, int inSize, out IntPtr outBuffer, out int outSize) {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return CircleRoundTripWin(inBuffer, inSize, out outBuffer, out outSize);
else
return CircleRoundTripMac(inBuffer, inSize, out outBuffer, out outSize);
}Add to GeoSharPlusNET/Wrapper.cs:
public static byte[] ToCircleBuffer(Circle circle) {
var builder = new FlatBufferBuilder(256);
var centerOffset = FB.Vec3.CreateVec3(builder, circle.Center.X, circle.Center.Y, circle.Center.Z);
var normalOffset = FB.Vec3.CreateVec3(builder, circle.Normal.X, circle.Normal.Y, circle.Normal.Z);
FB.CircleData.StartCircleData(builder);
FB.CircleData.AddCenter(builder, centerOffset);
FB.CircleData.AddRadius(builder, circle.Radius);
FB.CircleData.AddNormal(builder, normalOffset);
var circleOffset = FB.CircleData.EndCircleData(builder);
builder.Finish(circleOffset.Value);
return builder.SizedByteArray();
}
public static Circle FromCircleBuffer(byte[] buffer) {
var byteBuffer = new ByteBuffer(buffer);
var circleData = FB.CircleData.GetRootAsCircleData(byteBuffer);
var center = new Point3d(circleData.Center.Value.X, circleData.Center.Value.Y, circleData.Center.Value.Z);
var normal = new Vector3d(circleData.Normal.Value.X, circleData.Normal.Value.Y, circleData.Normal.Value.Z);
return new Circle(new Plane(center, normal), circleData.Radius);
}After adding your new type:
-
Rebuild the project to generate FlatBuffer code:
cmake --build build --config Release
-
Test your implementation by creating a simple round-trip test similar to the examples above.
Currently supported geometric types:
Point3d/Vector3d- Single 3D points/vectorsPoint3d[]/Vector3d[]- Arrays of 3D points/vectorsMesh- Triangle meshes with vertices and facesdouble[]- Arrays of double valuesint[]- Arrays of integer valuesPair<int,int>[]- Arrays of integer pairsPair<double,double>[]- Arrays of double pairs
- FlatBuffers schemas define the data structure contracts
- C++ serialization handles conversion between native types and FlatBuffer format
- C API functions provide the bridge between managed and unmanaged code
- C# wrappers provide type-safe, idiomatic .NET interfaces
- Cross-platform support is handled through platform-specific DLL imports
The most important folders are GeoSharPlusCPP and GeoSharPlusNET.
- Copy both folders into your project
- Modify
CMakeLists.txtinGeoSharPlusCPPto add:- Additional C++ libraries you need
- Custom pre-compilation processes
- Your specific geometric operations
- Follow the Setup process
Rhino/Grasshopper Plugins:
- Add project reference: Right-click your main project →
Add→Project Reference...→ SelectGeoSharPlusNET - Copy build scripts: Check
prebuild.ps1andpostbuild.ps1inGSPdemoGH/for build events - Deploy native libraries: Ensure the compiled C++ DLLs are copied to your output directory
Other CAD Platforms: We welcome contributions for integration with other CAD software! The core library is platform-agnostic and can be adapted for:
- AutoCAD (.NET API)
- SolidWorks (SOLIDWORKS API)
- Fusion 360 (Fusion 360 API)
- FreeCAD (Python/C++ API)
- Other CAD platforms with .NET or C++ APIs
Interested in adding support for your CAD platform? Please open an issue or submit a pull request!
- Zero-copy deserialization: FlatBuffers allows direct access to serialized data without parsing
- Compact representation: Efficient binary format reduces memory usage and transfer time
- Cross-platform compatibility: Same binary format works across Windows, macOS, and Linux
- Type safety: Compile-time verification of data structure compatibility
// Always free unmanaged memory after use
NativeBridge.SomeFunction(buffer, size, out IntPtr outPtr, out int outSize);
try {
byte[] result = new byte[outSize];
Marshal.Copy(outPtr, result, 0, outSize);
// Use result...
} finally {
Marshal.FreeCoTaskMem(outPtr); // Critical: prevent memory leaks
}// Check return values from native calls
if (!NativeBridge.Point3dRoundTrip(buffer, size, out IntPtr outPtr, out int outSize)) {
throw new InvalidOperationException("Native operation failed");
}// For large point arrays, consider processing in chunks
const int ChunkSize = 10000;
var chunks = points.Chunk(ChunkSize);
foreach (var chunk in chunks) {
byte[] buffer = Wrapper.ToPointArrayBuffer(chunk.ToArray());
// Process chunk...
}"DLL not found" errors:
- Ensure the native DLL is in the same directory as your executable
- Check that you're using the correct architecture (x64 vs x86)
- Verify that Visual C++ Redistributables are installed
Memory access violations:
- Always call
Marshal.FreeCoTaskMem()after using returned pointers - Don't access returned pointers after freeing them
- Check buffer sizes match between C# and C++
Build failures:
- Verify
VCPKG_ROOTenvironment variable is set correctly - Ensure all vcpkg packages are installed (
vcpkg install) - Check that CMake can find all dependencies
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Add tests for new geometric types or functionality
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Clone and setup
git clone https://github.com/xarthurx/GeoSharPlus.git
cd GeoSharPlus/GeoSharPlusCPP
vcpkg install
cmake -B build .
cmake --build build --config DebugThis library is trusted by these notable projects:
- BeingAliveLanguage - A Grasshopper plugin to create computational diagrams for living systems
- IG-Mesh - An one-stop solution for mesh processing in Grasshopper (for Rhino)
This project is licensed under the MIT License. See the LICENSE file for details.
