Tutorial 89: Extending CNA with sharp-runtime
What is sharp-runtime?
sharp-runtime is a small C++23 header-only library that provides C#-inspired type aliases and patterns used throughout CNA. It lives in the sibling ../sharp-runtime repository and has no external dependencies. CNA requires sharp-runtime at build time.
Type aliases
sharp-runtime defines aliases that map C# primitive type names to their C++ equivalents, reducing friction when reading XNA documentation or porting C# code.
| C# type | C++ sharp-runtime alias | Underlying C++ type |
|---|---|---|
byte | bytecs | uint8_t |
int | intcs | int32_t |
uint | uintcs | uint32_t |
long | longcs | int64_t |
float / Single | Single | float |
double | doublecs | double |
bool | boolcs | bool |
char | charcs | char16_t (UTF-16 like C#) |
IDisposable<T> pattern
CNA resources implement RAII, but sharp-runtime provides an explicit IDisposable<T> base class for types that need deterministic cleanup, matching the using(var x = ...) pattern from C#.
Custom class using IDisposable pattern from sharp-runtime:
#include "sharp-runtime/IDisposable.hpp"
#include "sharp-runtime/types.hpp" // bytecs, intcs, Single, etc.
#include <fstream>
#include <string>
// A resource that must be explicitly closed, like a C# IDisposable
class GameDataFile : public sharp::IDisposable<GameDataFile> {
public:
explicit GameDataFile(const std::string& path)
: file_(path, std::ios::binary)
{
if (!file_.is_open())
throw std::runtime_error("Cannot open: " + path);
}
// IDisposable<T> requires you to implement Dispose()
void Dispose() override {
if (!disposed_) {
file_.close();
disposed_ = true;
}
}
~GameDataFile() { Dispose(); }
intcs ReadInt32() {
intcs value = 0;
file_.read(reinterpret_cast<char*>(&value), sizeof(intcs));
return value;
}
Single ReadSingle() {
Single value = 0.0f;
file_.read(reinterpret_cast<char*>(&value), sizeof(Single));
return value;
}
private:
std::fstream file_;
bool disposed_ = false;
};
// Usage in game code:
void LoadLevel(const std::string& path) {
GameDataFile file(path); // RAII: closes on scope exit
intcs entityCount = file.ReadInt32();
Single spawnX = file.ReadSingle();
Single spawnY = file.ReadSingle();
// ... load entities ...
file.Dispose(); // explicit early close (optional)
}
IEquatable<T>
IEquatable<T> provides a typed equality interface. CNA math types implement it so you can compare Vector2, Color, etc. with Equals() in addition to ==.
#include "sharp-runtime/IEquatable.hpp"
struct Point : public sharp::IEquatable<Point> {
intcs X, Y;
bool Equals(const Point& other) const override {
return X == other.X && Y == other.Y;
}
};
Events (Event<T>)
sharp-runtime provides a multicast delegate / event system matching C#'s event Action<T>.
#include "sharp-runtime/Event.hpp"
sharp::Event<int> OnScoreChanged; // multicast event
// Subscribe:
OnScoreChanged += [](int newScore) {
printf("Score: %d\n", newScore);
};
// Fire:
OnScoreChanged(42); // calls all subscribers
Collections (ListCS<T>)
ListCS<T> is a thin wrapper over std::vector<T> that adds C# List<T>-style method names: Add, Remove, Contains, Count, Clear, IndexOf.
#include "sharp-runtime/ListCS.hpp"
sharp::ListCS<std::string> names;
names.Add("Alice");
names.Add("Bob");
if (names.Contains("Alice")) {
names.Remove("Alice");
}
intcs count = names.Count(); // 1
Using sharp-runtime independently
sharp-runtime can be used in any C++23 project without CNA. It is a header-only library with no external dependencies.
# Add sharp-runtime to any CMake project:
add_subdirectory(../sharp-runtime ${CMAKE_BINARY_DIR}/sharp-runtime)
target_link_libraries(MyProject PRIVATE sharp-runtime::sharp-runtime)