Tutorial 89: Extending CNA with sharp-runtime

CNA — C++ XNA 4.0 reimplementation

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
bytebytecsuint8_t
intintcsint32_t
uintuintcsuint32_t
longlongcsint64_t
float / SingleSinglefloat
doubledoublecsdouble
boolboolcsbool
charcharcschar16_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)