Add project files.
This commit is contained in:
commit
3087fcea8a
71 files changed
+8325
No files matched your search
@@ -0,0 +1,11 @@
|
||||
# _discover_module_subdirectories() (cmake/BuildHelpers.cmake) only
|
||||
# add_subdirectory()'s the IMMEDIATE children of source/examples/ --
|
||||
# this file exists purely so that mechanism finds this folder at all,
|
||||
# then hands off to its three real targets: one host executable and
|
||||
# two builds of the same plugin (see PluginV1/CMakeLists.txt for why
|
||||
# two separate builds rather than one). Shared/ has no CMakeLists.txt
|
||||
# of its own -- it is just PluginApi.h, pulled in by PluginV1/PluginV2
|
||||
# via target_include_directories(), not add_subdirectory().
|
||||
add_subdirectory(PluginV1)
|
||||
add_subdirectory(PluginV2)
|
||||
add_subdirectory(Host)
|
||||
@@ -0,0 +1,15 @@
|
||||
add_example_module(hotreload_demo_host OUTPUT_NAME "hotreload_demo_host" DEPENDS hotreload_lib)
|
||||
|
||||
target_include_directories(hotreload_demo_host PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../Shared)
|
||||
|
||||
# The Host loads PluginV1/PluginV2 itself, at runtime, through
|
||||
# source/HotReload -- it must NOT link against them directly (that
|
||||
# would defeat the entire point of loading them dynamically via
|
||||
# dlopen()/LoadLibrary()), so this is a build-order-only dependency
|
||||
# plus each one's exact build output path baked in as a default, never
|
||||
# a target_link_libraries().
|
||||
add_dependencies(hotreload_demo_host hotreload_demo_plugin_v1 hotreload_demo_plugin_v2)
|
||||
target_compile_definitions(hotreload_demo_host PRIVATE
|
||||
"PLUGIN_V1_PATH=\"$<TARGET_FILE:hotreload_demo_plugin_v1>\""
|
||||
"PLUGIN_V2_PATH=\"$<TARGET_FILE:hotreload_demo_plugin_v2>\""
|
||||
)
|
||||
@@ -0,0 +1,202 @@
|
||||
/* Main.cpp -- loads PluginV1, calls into it, reloads to PluginV2, and
|
||||
* calls the SAME already-resolved function pointers again to show
|
||||
* they now run the new code -- without the Host re-resolving anything.
|
||||
* Along the way this exercises every HotReloadSymbolEvent kind: watch
|
||||
* the printed event log against PluginV1/src/Plugin.cpp and
|
||||
* PluginV2/src/Plugin.cpp side by side. The second half does the same
|
||||
* thing for CppWidgetApi.h's real C++ (non-extern-"C") surface -- see
|
||||
* that header's comments for why its two methods are each reached a
|
||||
* different way below.
|
||||
*/
|
||||
#include "CppWidgetApi.h"
|
||||
#include "hot_reload/hot_reload.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using PluginFunction = int (*)(int);
|
||||
|
||||
// CppWidget::tick's real ABI: an implicit this-pointer followed by
|
||||
// its one declared parameter, same calling convention the
|
||||
// compiler itself would generate for widget->tick(x) -- this is
|
||||
// just spelling that out explicitly so it can be called through a
|
||||
// manually-resolved address instead.
|
||||
using CppWidgetTickFunction = int (*)(CppWidget*, int);
|
||||
|
||||
int hostSideDeletedFunctionStub(int)
|
||||
{
|
||||
std::printf(" !! called a deleted plugin function -- this is the Host's stub, not a crash !!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Runs once per HOT_RELOAD_SYMBOL_REMOVED, before onSymbolEvent
|
||||
* below sees the same event -- see hot_reload.h's doc comment on
|
||||
* HotReloadDeletedSymbolFn. Redirecting a removed function to a
|
||||
* stub living in the HOST executable (not in either plugin build)
|
||||
* demonstrates that the target of a patch can be anywhere at all,
|
||||
* not just inside the module being reloaded. */
|
||||
void* onDeletedSymbol(const char* symbolName, void* /*userData*/)
|
||||
{
|
||||
std::printf(" [event] REMOVED %-22s -> redirecting old callers to the Host's stub\n", symbolName);
|
||||
return reinterpret_cast<void*>(&hostSideDeletedFunctionStub);
|
||||
}
|
||||
|
||||
const char* eventKindName(HotReloadSymbolEvent kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case HOT_RELOAD_SYMBOL_PATCHED:
|
||||
return "PATCHED";
|
||||
case HOT_RELOAD_SYMBOL_ADDED:
|
||||
return "ADDED";
|
||||
case HOT_RELOAD_SYMBOL_REMOVED:
|
||||
return "REMOVED";
|
||||
case HOT_RELOAD_SYMBOL_SKIPPED_NOT_CODE:
|
||||
return "SKIPPED (data, not code)";
|
||||
case HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL:
|
||||
return "SKIPPED (too small to patch)";
|
||||
case HOT_RELOAD_SYMBOL_SKIPPED_UNWRITABLE:
|
||||
return "SKIPPED (page not writable)";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
void onSymbolEvent(const HotReloadSymbolEventInfo* event, void* /*userData*/)
|
||||
{
|
||||
// REMOVED is already reported, with more context, by
|
||||
// onDeletedSymbol() above -- it fires exactly once per removed
|
||||
// symbol, same as this callback would.
|
||||
if (event->kind == HOT_RELOAD_SYMBOL_REMOVED)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// event->name is always the real symbol-table name (the exact
|
||||
// string hot_reload_get_symbol() would need) -- demangling it
|
||||
// is purely a display choice, made here and only here; a
|
||||
// plain extern "C" name like "plugin_tick" comes back
|
||||
// unchanged, so this is safe to apply unconditionally.
|
||||
char* readableName = hot_reload_demangle(event->name);
|
||||
std::printf(" [event] %-30s %s\n", eventKindName(event->kind), readableName != nullptr ? readableName : event->name);
|
||||
hot_reload_free_string(readableName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
std::string pluginV1Path = argc > 1 ? argv[1] : PLUGIN_V1_PATH;
|
||||
std::string pluginV2Path = argc > 2 ? argv[2] : PLUGIN_V2_PATH;
|
||||
|
||||
HotReloadOptions options{};
|
||||
options.on_symbol_event = &onSymbolEvent;
|
||||
options.on_deleted_symbol = &onDeletedSymbol;
|
||||
options.user_data = nullptr;
|
||||
|
||||
HotReloadContext* ctx = hot_reload_context_create(&options);
|
||||
if (ctx == nullptr)
|
||||
{
|
||||
std::fprintf(stderr, "hot_reload_context_create failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::printf("== loading plugin V1 (%s) ==\n", pluginV1Path.c_str());
|
||||
HotReloadStatus status = hot_reload_load(ctx, "plugin", pluginV1Path.c_str());
|
||||
if (status != HOT_RELOAD_OK)
|
||||
{
|
||||
std::fprintf(stderr, "hot_reload_load failed: %s (%s)\n", hot_reload_status_string(status),
|
||||
hot_reload_last_error(ctx));
|
||||
hot_reload_context_destroy(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Resolved ONCE, before the reload below -- and never re-resolved.
|
||||
// The point of this demo is that these exact pointers keep working
|
||||
// correctly after the plugin underneath them changes.
|
||||
auto tick = reinterpret_cast<PluginFunction>(hot_reload_get_symbol(ctx, "plugin", "plugin_tick"));
|
||||
auto legacy = reinterpret_cast<PluginFunction>(hot_reload_get_symbol(ctx, "plugin", "plugin_legacy_feature"));
|
||||
auto markerBefore = static_cast<int*>(hot_reload_get_symbol(ctx, "plugin", "plugin_build_marker"));
|
||||
|
||||
std::printf("\n-- calling V1 through freshly-resolved pointers --\n");
|
||||
tick(21);
|
||||
legacy(7);
|
||||
std::printf(" plugin_build_marker = %d\n", markerBefore != nullptr ? *markerBefore : -1);
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// The same plugin build ALSO exports a real C++ class
|
||||
// (CppWidgetApi.h) with no extern "C" anywhere -- a virtual
|
||||
// method and an ordinary out-of-line method, both with real
|
||||
// compiler-mangled names. Same "plugin" module name as above,
|
||||
// since it is physically the same shared library on disk.
|
||||
// ---------------------------------------------------------------
|
||||
std::printf("\n== the same plugin build's C++ surface (CppWidgetApi.h, no extern \"C\") ==\n");
|
||||
|
||||
auto makeCppWidget = reinterpret_cast<CppWidget* (*) ()>(hot_reload_get_symbol(ctx, "plugin", "make_cpp_widget"));
|
||||
CppWidget* widget = makeCppWidget != nullptr ? makeCppWidget() : nullptr;
|
||||
|
||||
// CppWidget::tick(int)'s real, compiler-mangled name -- see
|
||||
// CppWidgetApi.h's comment on why this one is resolved manually
|
||||
// rather than called as widget->tick(x). Re-derive this yourself
|
||||
// with `nm -D --defined-only <plugin path> | c++filt` if you
|
||||
// change CppWidget's signature; there is no portable way to ask
|
||||
// the compiler for another declaration's mangled name at compile
|
||||
// time, which is exactly the inconvenience extern "C" exists to
|
||||
// avoid for anything you need to look up this way.
|
||||
auto tickByMangledName =
|
||||
reinterpret_cast<CppWidgetTickFunction>(hot_reload_get_symbol(ctx, "plugin", "_ZN9CppWidget4tickEi"));
|
||||
|
||||
std::printf("-- V1: virtual method through REAL polymorphic dispatch (widget->virtualTick) --\n");
|
||||
if (widget != nullptr)
|
||||
{
|
||||
widget->virtualTick(21);
|
||||
}
|
||||
std::printf("-- V1: ordinary method through a manually-resolved mangled-name pointer --\n");
|
||||
if (tickByMangledName != nullptr && widget != nullptr)
|
||||
{
|
||||
tickByMangledName(widget, 21);
|
||||
}
|
||||
|
||||
std::printf("\n== reloading \"plugin\" to V2 (%s) ==\n", pluginV2Path.c_str());
|
||||
status = hot_reload_reload(ctx, "plugin", pluginV2Path.c_str());
|
||||
if (status != HOT_RELOAD_OK)
|
||||
{
|
||||
std::fprintf(stderr, "hot_reload_reload failed: %s (%s)\n", hot_reload_status_string(status),
|
||||
hot_reload_last_error(ctx));
|
||||
delete widget;
|
||||
hot_reload_context_destroy(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::printf("\n-- calling the exact same pointers from before, un-re-resolved --\n");
|
||||
tick(21); // same function pointer as above -- now runs V2's tripling logic
|
||||
legacy(7); // same function pointer as above -- now runs the Host's stub instead of V1's body
|
||||
|
||||
std::printf("\n-- plugin_new_feature only exists in V2, resolved fresh --\n");
|
||||
auto newFeature = reinterpret_cast<PluginFunction>(hot_reload_get_symbol(ctx, "plugin", "plugin_new_feature"));
|
||||
if (newFeature != nullptr)
|
||||
{
|
||||
newFeature(21);
|
||||
}
|
||||
|
||||
auto markerAfter = static_cast<int*>(hot_reload_get_symbol(ctx, "plugin", "plugin_build_marker"));
|
||||
std::printf("\n-- plugin_build_marker: proof that data is never patched --\n");
|
||||
std::printf(" resolved fresh from V2 right now = %d\n", markerAfter != nullptr ? *markerAfter : -1);
|
||||
std::printf(" V1's ORIGINAL address, read again = %d (still V1's value -- never touched)\n",
|
||||
markerBefore != nullptr ? *markerBefore : -1);
|
||||
|
||||
std::printf("\n-- the SAME widget object, same vtable slot, same manually-resolved pointer --\n");
|
||||
if (widget != nullptr)
|
||||
{
|
||||
widget->virtualTick(21); // same object, same vtable slot -- now V2's tripling logic
|
||||
}
|
||||
if (tickByMangledName != nullptr && widget != nullptr)
|
||||
{
|
||||
tickByMangledName(widget, 21); // same resolved pointer as before -- also now V2's logic
|
||||
}
|
||||
|
||||
delete widget;
|
||||
hot_reload_context_destroy(ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# Not built through add_lib_module(): a hot-reloadable plugin is a
|
||||
# different shape from an ordinary library module. PluginV1 and its
|
||||
# sibling PluginV2 are two SEPARATE binaries that deliberately export
|
||||
# the SAME extern "C" symbol names (see ../Shared/PluginApi.h) --
|
||||
# that's exactly what lets HotReloadContext line them up and patch one
|
||||
# into the other at runtime. add_lib_module() assumes one canonical
|
||||
# build per module name and would not fit two builds of the same name
|
||||
# needing to coexist, so this is plain add_library(), reusing the
|
||||
# project's usual output-directory/warnings helpers directly. Gated on
|
||||
# BUILD_EXAMPLES exactly like every other target under source/examples/.
|
||||
|
||||
if(BUILD_EXAMPLES)
|
||||
add_library(hotreload_demo_plugin_v1 SHARED src/Plugin.cpp src/CppWidget.cpp)
|
||||
else()
|
||||
add_library(hotreload_demo_plugin_v1 SHARED EXCLUDE_FROM_ALL src/Plugin.cpp src/CppWidget.cpp)
|
||||
endif()
|
||||
|
||||
target_include_directories(hotreload_demo_plugin_v1 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../Shared)
|
||||
set_target_properties(hotreload_demo_plugin_v1 PROPERTIES
|
||||
OUTPUT_NAME "hotreload_demo_plugin_v1"
|
||||
# Matches add_lib_module()'s own SHARED-library branch
|
||||
# (cmake/BuildHelpers.cmake) exactly, for exactly the same reason:
|
||||
# MinGW's linker exports every global symbol from a DLL by
|
||||
# default, but MSVC exports NONE unless told to -- without this,
|
||||
# PluginApi.h's functions would not be visible in the export table
|
||||
# at all when built with MSVC, silently breaking the whole demo on
|
||||
# that one toolchain.
|
||||
WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
_set_output_dirs(hotreload_demo_plugin_v1
|
||||
"${CMAKE_BINARY_DIR}/${RUNTIME_OUTPUT_SUBDIR}/examples"
|
||||
"${CMAKE_BINARY_DIR}/${LIBRARY_OUTPUT_SUBDIR}/examples")
|
||||
_apply_compile_options(hotreload_demo_plugin_v1)
|
||||
|
||||
# The whole point of this target: give source/HotReload's jump-patch
|
||||
# backends guaranteed room to write into. See cmake/HotReload.cmake.
|
||||
hot_reload_enable_patchable_functions(hotreload_demo_plugin_v1)
|
||||
|
||||
_register_module_target(EXAMPLE hotreload_demo_plugin_v1)
|
||||
add_dependencies(all_examples hotreload_demo_plugin_v1)
|
||||
@@ -0,0 +1,32 @@
|
||||
/* CppWidget V1 -- "before your edit". See PluginV2/src/CppWidget.cpp
|
||||
* for "after": same three exports change behavior together, exactly
|
||||
* like PluginV1/src/Plugin.cpp's plain-C functions do.
|
||||
*/
|
||||
#include "CppWidgetApi.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int CppWidget::virtualTick(int input)
|
||||
{
|
||||
int result = input * 2;
|
||||
std::printf(" CppWidget::virtualTick(%d) -> %d [V1: input * 2, via vtable]\n", input, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
int CppWidget::tick(int input)
|
||||
{
|
||||
int result = input * 2;
|
||||
std::printf(" CppWidget::tick(%d) -> %d [V1: input * 2, via mangled-name lookup]\n", input, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
CppWidget CppWidget::operator+(const CppWidget& /*other*/) const
|
||||
{
|
||||
std::printf(" CppWidget::operator+ [V1]\n");
|
||||
return CppWidget();
|
||||
}
|
||||
|
||||
extern "C" CppWidget* make_cpp_widget()
|
||||
{
|
||||
return new CppWidget();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* PluginV1 -- "before your edit". See PluginV2/src/Plugin.cpp for
|
||||
* "after"; the two deliberately export the identical set of names
|
||||
* (minus plugin_legacy_feature, removed in V2) so HotReloadContext can
|
||||
* line them up. See ../Shared/PluginApi.h for what each export is
|
||||
* there to demonstrate.
|
||||
*/
|
||||
#include "PluginApi.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int plugin_tick(int input)
|
||||
{
|
||||
int result = input * 2;
|
||||
std::printf(" plugin_tick(%d) -> %d [V1: input * 2]\n", input, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
int plugin_legacy_feature(int input)
|
||||
{
|
||||
std::printf(" plugin_legacy_feature(%d) -> %d [V1, about to be removed in V2]\n", input, input);
|
||||
return input;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
int plugin_tiny_function(int input)
|
||||
{
|
||||
return input + 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int plugin_build_marker = 1;
|
||||
@@ -0,0 +1,24 @@
|
||||
# See ../PluginV1/CMakeLists.txt for why this is hand-written instead
|
||||
# of add_lib_module() -- this is that same explanation's other half:
|
||||
# PluginV2 exports the identical extern "C" names as PluginV1 (see
|
||||
# ../Shared/PluginApi.h) with a different implementation standing in
|
||||
# for "the same file, after your edit" in this demo's story.
|
||||
|
||||
if(BUILD_EXAMPLES)
|
||||
add_library(hotreload_demo_plugin_v2 SHARED src/Plugin.cpp src/CppWidget.cpp)
|
||||
else()
|
||||
add_library(hotreload_demo_plugin_v2 SHARED EXCLUDE_FROM_ALL src/Plugin.cpp src/CppWidget.cpp)
|
||||
endif()
|
||||
|
||||
target_include_directories(hotreload_demo_plugin_v2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../Shared)
|
||||
set_target_properties(hotreload_demo_plugin_v2 PROPERTIES
|
||||
OUTPUT_NAME "hotreload_demo_plugin_v2"
|
||||
WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
_set_output_dirs(hotreload_demo_plugin_v2
|
||||
"${CMAKE_BINARY_DIR}/${RUNTIME_OUTPUT_SUBDIR}/examples"
|
||||
"${CMAKE_BINARY_DIR}/${LIBRARY_OUTPUT_SUBDIR}/examples")
|
||||
_apply_compile_options(hotreload_demo_plugin_v2)
|
||||
hot_reload_enable_patchable_functions(hotreload_demo_plugin_v2)
|
||||
|
||||
_register_module_target(EXAMPLE hotreload_demo_plugin_v2)
|
||||
add_dependencies(all_examples hotreload_demo_plugin_v2)
|
||||
@@ -0,0 +1,31 @@
|
||||
/* CppWidget V2 -- "after your edit". See PluginV1/src/CppWidget.cpp
|
||||
* for "before".
|
||||
*/
|
||||
#include "CppWidgetApi.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int CppWidget::virtualTick(int input)
|
||||
{
|
||||
int result = input * 3;
|
||||
std::printf(" CppWidget::virtualTick(%d) -> %d [V2: input * 3, via vtable]\n", input, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
int CppWidget::tick(int input)
|
||||
{
|
||||
int result = input * 3;
|
||||
std::printf(" CppWidget::tick(%d) -> %d [V2: input * 3, via mangled-name lookup]\n", input, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
CppWidget CppWidget::operator+(const CppWidget& /*other*/) const
|
||||
{
|
||||
std::printf(" CppWidget::operator+ [V2]\n");
|
||||
return CppWidget();
|
||||
}
|
||||
|
||||
extern "C" CppWidget* make_cpp_widget()
|
||||
{
|
||||
return new CppWidget();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* PluginV2 -- "after your edit". See PluginV1/src/Plugin.cpp for
|
||||
* "before". plugin_tick's behavior changed, plugin_legacy_feature was
|
||||
* deleted, and plugin_new_feature was added -- exactly the three kinds
|
||||
* of change hot_reload.h's HotReloadSymbolEvent enum distinguishes.
|
||||
*/
|
||||
#include "PluginApi.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int plugin_tick(int input)
|
||||
{
|
||||
int result = input * 3;
|
||||
std::printf(" plugin_tick(%d) -> %d [V2: input * 3]\n", input, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* plugin_legacy_feature deliberately does not exist in V2 anymore. */
|
||||
|
||||
int plugin_new_feature(int input)
|
||||
{
|
||||
int result = input * 10;
|
||||
std::printf(" plugin_new_feature(%d) -> %d [V2: brand new]\n", input, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
int plugin_tiny_function(int input)
|
||||
{
|
||||
return input + 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
int plugin_build_marker = 2;
|
||||
@@ -0,0 +1,47 @@
|
||||
/* CppWidgetApi.h -- a C++ (not extern "C") surface living alongside
|
||||
* PluginApi.h's plain C one, in the SAME plugin build. Every member
|
||||
* function below keeps its real, compiler-mangled name; nothing here
|
||||
* needs special handling by source/HotReload, which classifies
|
||||
* symbols by their type and section, never by what their name looks
|
||||
* like -- see hot_reload.h's "REQUIREMENTS ON A HOT-RELOADABLE
|
||||
* MODULE" and README.md's "Hot reloading C++ symbols" for the full
|
||||
* picture.
|
||||
*
|
||||
* The two methods below are reached two different ways from
|
||||
* Host/Main.cpp, deliberately: see that file's comments for why.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class CppWidget
|
||||
{
|
||||
public:
|
||||
virtual ~CppWidget() = default;
|
||||
|
||||
/* Virtual: Host/Main.cpp calls this through ordinary C++
|
||||
* polymorphic dispatch (widget->virtualTick(x)) and needs no
|
||||
* manual symbol lookup at all -- the call compiles down to an
|
||||
* indirect call through the object's vtable slot, not a direct
|
||||
* reference to this specific symbol, so it needs no link-time
|
||||
* dependency on whichever plugin build actually defines it. */
|
||||
virtual int virtualTick(int input);
|
||||
|
||||
/* An ordinary out-of-line method. Calling this as widget->tick(x)
|
||||
* would compile to a direct call to CppWidget::tick's own mangled
|
||||
* symbol, which would need the Host to link against a plugin
|
||||
* build at build time -- exactly what hot-reloading a plugin
|
||||
* means avoiding. So Host/Main.cpp instead resolves it manually
|
||||
* by its mangled name via hot_reload_get_symbol(), the same way
|
||||
* it resolves PluginApi.h's plain C functions. */
|
||||
int tick(int input);
|
||||
|
||||
/* An overloaded operator: mechanically just a function with an
|
||||
* unusual mangled name, reached the same manual way as tick()
|
||||
* above. */
|
||||
CppWidget operator+(const CppWidget& other) const;
|
||||
};
|
||||
|
||||
/* The one entry point Host/Main.cpp looks up by a plain, human-typed
|
||||
* name -- extern "C" here for exactly the reason hot_reload.h
|
||||
* recommends it: something to hand a human-readable name to, without
|
||||
* needing to know or hardcode a mangled string. */
|
||||
extern "C" CppWidget* make_cpp_widget();
|
||||
@@ -0,0 +1,55 @@
|
||||
/* PluginApi.h -- the contract PluginV1 and PluginV2 both implement,
|
||||
* and the Host loads by name. Every function here follows
|
||||
* cpp-style-guide.md section 11's rule for anything loaded at runtime:
|
||||
* plain C linkage, no classes or exceptions crossing the boundary.
|
||||
*
|
||||
* This header is deliberately identical for both plugin versions --
|
||||
* in a real project this is the file that stays stable while
|
||||
* PluginV1/src/Plugin.cpp and PluginV2/src/Plugin.cpp (standing in for
|
||||
* "the same file, before and after your edit") diverge.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/* Changes behavior between V1 and V2 -- watch this one get
|
||||
* HOT_RELOAD_SYMBOL_PATCHED. */
|
||||
int plugin_tick(int input);
|
||||
|
||||
/* Only in V1, gone in V2 -- watch this one get
|
||||
* HOT_RELOAD_SYMBOL_REMOVED, and the Host's stub take over for any
|
||||
* call still aimed at it afterwards. */
|
||||
int plugin_legacy_feature(int input);
|
||||
|
||||
/* Only in V2 -- watch this one get HOT_RELOAD_SYMBOL_ADDED, then
|
||||
* get called for the first time right after. */
|
||||
int plugin_new_feature(int input);
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
/* Deliberately too small to safely hold a jump on any of this
|
||||
* library's backends (its patchable-function-entry padding is
|
||||
* overridden down to zero) -- watch this one get
|
||||
* HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL rather than corrupt whatever
|
||||
* comes after it. MSVC has no equivalent per-function override
|
||||
* this demo could apply, so this one only exists on GCC/Clang.
|
||||
*/
|
||||
__attribute__((patchable_function_entry(0))) int plugin_tiny_function(int input);
|
||||
#endif
|
||||
|
||||
/* An exported VARIABLE, not a function, sitting right next to
|
||||
* real functions in the same library and changing value between
|
||||
* V1 and V2 just like plugin_tick's behavior does -- watch this
|
||||
* one get HOT_RELOAD_SYMBOL_SKIPPED_NOT_CODE. This is the
|
||||
* mechanical proof behind hot_reload.h's "only executable code
|
||||
* changes" guarantee: nothing here tells the library "this one is
|
||||
* data, don't touch it" -- it discovers that itself from the
|
||||
* symbol table, the same way it discovers plugin_tick is code.
|
||||
*/
|
||||
extern int plugin_build_marker;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in new issue
Block a user