# cr.h splits your C program into a host that survives crashes and a guest you can rebuild

> cr is a single-header live reload library for C, where your real code lives in a dynamic library the host loads and reloads while static state crosses the boundary. The details that decide whether it works for you are the version decrement semantics and the failure codes.

**fungos/cr** — cr.h: A Simple C Hot Reload Header-only Library

- Repository: https://github.com/fungos/cr
- Website: https://fungos.github.io/cr-simple-c-hot-reload/
- Stars: 1,811 · Forks: 119
- Language: C++
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/fungos-cr

## The host is a thin loader, the guest is your program

The architecture is described in the README as a thin host application executable managing live reloading of the real application in the form of a dynamic loadable binary. The host is not your program. Your program is a plugin, and the host's job is to load it, update it, and keep running when it falls over.

The host side is three functions. You declare a context, you open a plugin with a full path, you pump an update loop, and you close the context on the way out:

```c
#define CR_HOST // required in the host only and before including cr.h
#include "cr.h"

int main(int argc, char *argv[]) {
    cr_plugin ctx;

    cr_plugin_open(ctx, "c:/path/to/build/game.dll");

    while (!cr_plugin_update(ctx)) {
        // do anything you need to do on host side (ie. windowing and input stuff?)
    }

    cr_plugin_close(ctx);
    return 0;
}
```

The `CR_HOST` define matters and it has to come before the include. Without it you are compiling the guest side, with it you are compiling the host side. That single macro is the entire difference between the two builds of the same header.

The comment on the update loop is the design in one line: call it at any frequency that matters to you, because it is what gives the real application a chance to run. The host owns the frame, the window and the input; the guest owns your logic.

The guest exports one entry point:

```c
CR_EXPORT int cr_main(struct cr_plugin *ctx, enum cr_op operation) {
    switch (operation) {
        case CR_LOAD:   return on_load(...);
        case CR_UNLOAD: return on_unload(...);
        case CR_CLOSE:  /* the plugin will close and not reload anymore */
    }
    // CR_STEP
    return on_update(...);
}
```

Four operations, and the fallthrough case is CR_STEP, which is the ordinary per-frame call. Everything about live reload is the difference between how your code handles CR_UNLOAD, which is your chance to hand state across, and CR_LOAD, which is where you pick it up.

## Static state transfer is what the whole design is for

The feature list on the header names automatic static state transfer as one of the selling points, alongside automatic crash protection, support for multiple plugins, and being based on dynamic reloadable binaries with the .so, .dylib and .dll extension per platform.

State transfer is the hard part of live reload and the reason a naive implementation is not enough. When your guest has globals, a reload that discards them loses the session. cr moves them across the unload boundary, which is why the changelog for 2025-03-30 could remove FIPS and move to pure CMake without disturbing that mechanism, and why the version numbers described below matter so much to anyone reading state during a transition.

The samples show what that looks like in practice. The first sample in the `samples` directory is a console application demonstrating basic static states working between instances along with basic crash handling tests, using printed output to show what is happening. The second demonstrates live reloading an OpenGL application using Dear ImGui, and the README notes that some state lives on the host side while most of the code is on the guest side.

That host and guest split in the ImGui sample is the pattern to copy. The parts that must never be interrupted, window creation and the graphics context, stay in the host. The parts you are actively changing stay in the guest. If you try to hot reload the part that owns the window handle, you will spend your afternoon debugging something the architecture already told you not to do.

The `ctx` pointer passed into `cr_main` is described as carrying valuable information about the current loaded version, the failure reason and your user data, which is how the guest knows why it is being loaded and can log or branch accordingly.

## Failure codes decide what the host does next

The documented return contract for `cr_main` is the heart of the crash story. A negative value indicates an error, forces a rollback to happen, and sets the failure to `CR_USER`. Zero or a positive value is passed back to the host process.

So a guest signals its own failure by returning a negative number, and cr responds by rolling back to the previous working binary. That is the automatic crash protection: you do not write a crash handler for your logic, you return an error and the loader goes back.

Other failure codes come up over time rather than all at once. `CR_INITIAL_FAILURE` was added on 2020-04-19 with a specific rule attached: if the initial plugin crashes, the host must determine the next path, and the broken plugin is not reloaded. That is the case where there is no previous good version to fall back to, so the loop cannot simply retry.

`CR_BAD_IMAGE` came with the 2018-11-17 entry alongside macOS support, and it is the most instructive of the set because it describes a race rather than a bug. It applies when the binary file is still not ready even though its timestamp changed, which happens if generating the file, whether by compiler or by copy, was slow. So the reload trigger is a timestamp check, and a timestamp can change before the bytes are all there.

The same entry records a Windows fix for too long paths causing the PDB patch process to fail, which is a useful reminder of how much platform-specific work lives behind this single-header interface.

## The version rollback semantics changed, and your CR_UNLOAD code may depend on it

The 2018-11-17 changelog entry carries a bolded possible breaking change, and it is the one warning in this project's history worth reading twice.

The old behaviour: during a rollback, for any reason, two versions were decremented in one shot, so the next load would bump again and land you effectively on the previous version. The problem was that in some cases not related to crashes, this was not completely valid.

The new behaviour: the version is decremented once in the crash handler, decremented again during the rollback, and then bumped again. A rollback caused by an incomplete image no longer incorrectly rolls back two versions. It stays on the same version and retries the load until the image is valid, which is the correct behaviour when the copy or the compiler simply has not finished writing.

The stated impact is on code that uses the version information during `CR_UNLOAD`, because the value it sees will now be different. If your unload handler counts versions or logs which build it is leaving, that logic needs checking against the current semantics rather than the ones from before 2018.

Two smaller history items are still relevant to anyone integrating today. On 2020-01-09, `cr_plugin_load` was deprecated in favour of `cr_plugin_open` for consistency with `cr_plugin_close`, so new code should use `cr_plugin_open` and ignore the old name entirely. And on 2025-03-30, FIPS was removed, cr.h moved into the cr directory, and using cr as a CMake dependency through `target_link_libraries(<my_target> PRIVATE cr)` exposes the header to the target.

## Temporary copies and where they land

`cr_set_temporary_path` is documented with a constraint that explains an entire class of confusion. It sets the temporary path to which temporary copies of the plugin are placed, and it should be called immediately after `cr_plugin_open()`.

If the temporary path is not set, temporary copies of the file are copied to the same directory where the original file is located. The path must be a full path to an existing directory, since it is used for storing those copies.

Why this matters: the loader does not load your build output directly. It copies it. That is what makes rollback possible, since the previous good copy has to still exist somewhere while the new one is being tried. The default of using the build output directory means the copies land next to your `.dll`, `.dylib` or `.so`, which is convenient during development and less convenient when your build directory is watched by something else, or when it lives on a slow or full disk.

Calling it immediately after `cr_plugin_open` is the documented order. Doing it later means copies have already been placed somewhere.

The public surface is small by design. The README describes it as a simple public API with three functions to use and another to export, which matches what is in the documentation section: `cr_main` as the guest entry point, `cr_plugin_open`, `cr_plugin_update` and `cr_plugin_close`, plus `cr_set_temporary_path`.

## Building and running the samples

The repository is CMake driven, with CMakeLists.txt at the root, a CMakePresets.json, a cmake/ directory and the samples in `samples/` with their own CMakeLists.txt. Building uses the supplied preset:

```bash
cmake --preset Default .
cmake --build build
```

Tests run through ctest:

```bash
cd build/tests
ctest build
```

The VS Code Launch Tests option is documented as Windows only currently, with the ctest route as the portable alternative.

Running the basic sample follows the same pattern, and the interesting part is what makes the reload happen. You run `./basic_host`, and the README then tells you to edit `basic_guest.c`, or just `touch basic_guest.c`, and rebuild with `cmake --build../../`. That is the whole workflow: a timestamp change on the guest source is what triggers the reload.

The ImGui sample works identically from `build/samples/imgui`, running `./imgui_host` and touching `imgui_guest.cpp`. The touch trick is the fastest way to see the mechanism work before you have made a real edit, which is worth doing once so you know the loop is live rather than assuming your edit failed to take.

Installation outside the repository is through vcpkg, where the port is described as kept up to date by Microsoft team members and community contributors:

```bash
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install cr
```

If the port is out of date, the README asks for an issue or pull request against the vcpkg repository rather than against this one.

The repository is not archived and the last push was on 2026-06-23, with no GitHub releases, so the changelog embedded in the header is the version history. The licence is MIT, and a.clang-format and.editorconfig sit at the root for consistent formatting of the one file that matters.

## Conclusion

Adopt cr if you are writing C or C++ with a real iteration loop, most likely a game or a tool with heavy state, because transferring static state across a reload is the part you would otherwise write yourself and get subtly wrong. Do not adopt it for a program whose memory model assumes a single address space that never changes underneath it, since the guest is genuinely unloaded and reloaded. Verify first that the header path you include matches the current layout, because cr.h moved into the cr directory on 2025-03-30, and that you do not read the version value during `CR_UNLOAD` in a way that depends on the pre-2018 decrement behaviour, which the changelog flags as a possible breaking change.

## FAQ

### How do I use cr.h in C?

Define `CR_HOST` before including cr.h in the host executable, then declare a `cr_plugin` context, call `cr_plugin_open` with the full path to your dynamic library, pump `cr_plugin_update` in a loop, and call `cr_plugin_close` at the end. Your real code is compiled into the guest library and exported through `cr_main`.

### How does cr handle a crash in the guest?

Returning a negative value from `cr_main` signals an error, forces a rollback, and sets the failure to `CR_USER`. Zero or a positive value is passed back to the host. If the initial plugin itself fails, `CR_INITIAL_FAILURE` applies and the broken plugin is not reloaded, because the host has to choose the next path.

### Where is cr.h located and how do I get it?

Since 2025-03-30 cr.h lives in the cr directory, and using cr as a CMake dependency with `target_link_libraries(<my_target> PRIVATE cr)` exposes the header to the target. The README also documents installing it through vcpkg with `./vcpkg install cr`.

### What is cr_set_temporary_path for?

It sets the directory where temporary copies of the plugin are placed, and should be called immediately after `cr_plugin_open()`. Without it, copies are written to the same directory as the original build output file.

### How do I build and test cr?

Run `cmake --preset Default .` then `cmake --build build`. For the tests, `cd build/tests` and run `ctest build`. The VS Code launch options for tests and samples are documented as Windows only currently.

## Sources

- [fungos/cr on GitHub](https://github.com/fungos/cr)
- [Issues](https://github.com/fungos/cr/issues)
- [License: MIT](https://github.com/fungos/cr/blob/master/LICENSE)
- [Project website](https://fungos.github.io/cr-simple-c-hot-reload/)
- [README](https://github.com/fungos/cr/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fungos-cr
