/*
Eine Live-Reload-Lösung nur für einzelne Dateiheader für C, geschrieben in C++:
HINWEIS: Die einzige Datei, die in diesem Repository von Bedeutung ist, ist cr.h
Diese Datei enthält die Dokumentation im Markdown, die Lizenz, die Implementierung und die öffentliche API. Alle anderen Dateien in diesem Repository sind unterstützende Dateien und können ignoriert werden.
Sie können cr mithilfe des vcpkg-Abhängigkeitsmanagers herunterladen und installieren:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install cr
Der CR-Port in vcpkg wird von Microsoft-Teammitgliedern und Community-Mitwirkenden auf dem neuesten Stand gehalten. Wenn die Version veraltet ist, erstellen Sie bitte einen Issue oder Pull Request im vcpkg-Repository.
Eine ausführbare Datei einer (dünnen) Host-Anwendung nutzt cr
um das Live-Neuladen der realen Anwendung in Form einer dynamisch ladbaren Binärdatei zu verwalten. Ein Host würde etwa so aussehen:
#define CR_HOST // required in the host only and before including cr.h
#include "../cr.h"
int main ( int argc , char * argv []) {
// the host application should initalize a plugin with a context, a plugin
cr_plugin ctx ;
// the full path to the live-reloadable application
cr_plugin_open ( ctx , "c:/path/to/build/game.dll" );
// call the update function at any frequency matters to you, this will give
// the real application a chance to run
while (! cr_plugin_update ( ctx )) {
// do anything you need to do on host side (ie. windowing and input stuff?)
}
// at the end do not forget to cleanup the plugin context
cr_plugin_close ( ctx );
return 0 ;
}
Während der Gast (echte Anwendung) wie folgt aussehen würde:
CR_EXPORT int cr_main ( struct cr_plugin * ctx , enum cr_op operation ) {
assert ( ctx );
switch ( operation ) {
case CR_LOAD : return on_load (...); // loading back from a reload
case CR_UNLOAD : return on_unload (...); // preparing to a new reload
case CR_CLOSE : ...; // the plugin will close and not reload anymore
}
// CR_STEP
return on_update (...);
}
CR_INITIAL_FAILURE
hinzugefügt. Wenn das ursprüngliche Plugin abstürzt, muss der Host den nächsten Pfad bestimmen und wir werden das defekte Plugin nicht neu laden. cr_plugin_load
zugunsten von cr_plugin_open
für Konsistenz mit cr_plugin_close
. Siehe Ausgabe Nr. 49.CR_BAD_IMAGE
wurde hinzugefügt, falls die Binärdatei immer noch nicht bereit ist, selbst wenn sich ihr Zeitstempel geändert hat. Dies kann passieren, wenn das Generieren der Datei (Compiler oder Kopieren) langsam war.CR_BAD_IMAGE
). Jetzt wird die Version einmal im Crash-Handler und dann ein weiteres Mal während des Rollbacks dekrementiert und dann erneut erhöht. Bei einem Rollback aufgrund eines unvollständigen Images werden nicht fälschlicherweise zwei Versionen zurückgesetzt. Es wird mit derselben Version fortgefahren und der Ladevorgang wiederholt, bis das Image gültig ist (die Kopie oder der Compiler hat den Schreibvorgang beendet). Dies kann sich auf die aktuelle Verwendung von cr
auswirken, wenn die version
während CR_UNLOAD
verwendet werden, da sie jetzt einen anderen Wert haben. Zwei einfache Beispiele finden Sie im samples
.
Bei der ersten handelt es sich um eine einfache Konsolenanwendung, die einige grundlegende statische Zustände zwischen Instanzen und grundlegende Tests zur Handhabung von Abstürzen demonstriert. Print to Output wird verwendet, um zu zeigen, was passiert.
Der zweite zeigt, wie man eine OpenGL-Anwendung mit Dear ImGui live neu lädt. Einige Zustände befinden sich auf der Host-Seite, während sich der größte Teil des Codes auf der Gast-Seite befindet.
Die Beispiele und Tests verwenden das Fips-Build-System. Es erfordert Python und CMake.
$ ./fips build # will generate and build all artifacts
$ ./fips run crTest # To run tests
$ ./fips run imgui_host # To run imgui sample
# open a new console, then modify imgui_guest.cpp
$ ./fips make imgui_guest # to build and force imgui sample live reload
int (*cr_main)(struct cr_plugin *ctx, enum cr_op operation)
Dies ist der Funktionszeiger auf die dynamisch ladbare binäre Einstiegspunktfunktion.
Argumente
ctx
-Zeiger auf einen Kontext, der vom host
an den guest
übergeben wird und wertvolle Informationen über die aktuell geladene Version, den Fehlergrund und Benutzerdaten enthält. Weitere Informationen finden Sie unter cr_plugin
.operation
welche Operation ausgeführt wird, siehe cr_op
.Zurückkehren
CR_USER
setzt. 0 oder ein positiver Wert, der an den host
übergeben wird. bool cr_plugin_open(cr_plugin &ctx, const char *fullpath)
Lädt und initialisiert das Plugin.
Argumente
ctx
ein Kontext, der die internen Daten und Benutzerdaten des Plugins verwaltet.fullpath
vollständiger Pfad mit Dateinamen zur ladbaren Binärdatei für das Plugin oder NULL
.Zurückkehren
true
im Erfolgsfall, andernfalls false
. void cr_set_temporary_path(cr_plugin& ctx, const std::string &path)
Legt den temporären Pfad fest, in dem temporäre Kopien des Plugins abgelegt werden. Sollte unmittelbar nach cr_plugin_open()
aufgerufen werden. Wenn kein temporary
Pfad festgelegt ist, werden temporäre Kopien der Datei in dasselbe Verzeichnis kopiert, in dem sich die Originaldatei befindet.
Argumente
ctx
ein Kontext, der die internen Daten und Benutzerdaten des Plugins verwaltet.path
ein vollständiger Pfad zu einem vorhandenen Verzeichnis, das zum Speichern temporärer Plugin-Kopien verwendet wird. int cr_plugin_update(cr_plugin &ctx, bool reloadCheck = true)
Diese Funktion ruft die Plugin-Funktion cr_main
auf. Es sollte so oft aufgerufen werden, wie es die Kernlogik/Anwendung erfordert.
Argumente
ctx
die aktuellen Plugin-Kontextdaten.reloadCheck
optional: Führen Sie eine Festplattenprüfung durch (stat()), um zu sehen, ob die dynamische Bibliothek neu geladen werden muss.Zurückkehren
cr_main
zurückgegeben. void cr_plugin_close(cr_plugin &ctx)
Bereinigen Sie interne Zustände, sobald das Plugin nicht mehr benötigt wird.
Argumente
ctx
die aktuellen Plugin-Kontextdaten. cr_op
Enum, das die Art des Schritts angibt, der vom host
ausgeführt wird:
CR_LOAD
Ein durch Neuladen verursachter Ladevorgang wird ausgeführt und kann zum Wiederherstellen eines beliebigen gespeicherten internen Zustands verwendet werden.CR_STEP
Ein Anwendungsupdate. Dies ist der normale und häufigste Vorgang.CR_UNLOAD
Ein Entladevorgang zum Neuladen des Plugins wird ausgeführt, wodurch die Anwendung eine Chance erhält, alle erforderlichen Daten zu speichern.CR_CLOSE
Wird beim Schließen des Plugins verwendet. Dies funktioniert wie CR_UNLOAD
, aber danach sollte kein CR_LOAD
erwartet werden. cr_plugin
Die Kontextstruktur der Plugin-Instanz.
p
undurchsichtiger Zeiger für interne CR-Daten;userdata
können vom Benutzer verwendet werden, um Informationen zwischen Neuladevorgängen weiterzugeben.version
für jeden erfolgreichen Neuladevorgang, beginnend bei 1 für den ersten Ladevorgang. Die Version ändert sich während eines Absturzbehandlungsprozesses .failure
enthält den letzten Fehlercode, der einen Rollback verursacht hat. Weitere Informationen zu möglichen Werten finden Sie unter cr_failure
. cr_failure
Wenn es zu einem Absturz in der ladbaren Binärdatei kommt, gibt der Crash-Handler den Grund des Absturzes mit einem der folgenden Hinweise an:
CR_NONE
Kein Fehler;CR_SEGFAULT
Segmentierungsfehler. SIGSEGV
unter Linux/OSX oder EXCEPTION_ACCESS_VIOLATION
unter Windows;CR_ILLEGAL
Im Falle einer illegalen Anweisung. SIGILL
unter Linux/OSX oder EXCEPTION_ILLEGAL_INSTRUCTION
unter Windows;CR_ABORT
Abort, SIGBRT
unter Linux/OSX, nicht verwendet unter Windows;CR_MISALIGN
Busfehler, SIGBUS
unter Linux/OSX oder EXCEPTION_DATATYPE_MISALIGNMENT
unter Windows;CR_BOUNDS
ist EXCEPTION_ARRAY_BOUNDS_EXCEEDED
, nur Windows;CR_STACKOVERFLOW
ist EXCEPTION_STACK_OVERFLOW
, nur Windows;CR_STATE_INVALIDATED
Statischer CR_STATE
-Verwaltungssicherheitsfehler;CR_BAD_IMAGE
Das Plugin ist kein gültiges Image (dh der Compiler schreibt es möglicherweise noch);CR_OTHER
Anderes Signal, nur Linux;CR_USER
Benutzerfehler (für negative Werte, die von cr_main
zurückgegeben wurden); CR_HOST
definieren Diese Definition sollte verwendet werden, bevor cr.h
in den host
eingefügt wird. Wenn CR_HOST
nicht definiert ist, fungiert cr.h
als öffentliche API-Header-Datei, die in der guest
verwendet wird.
Optional kann CR_HOST
auch auf einen der folgenden Werte definiert werden, um den safety
für die automatische statische Zustandsverwaltung ( CR_STATE
) zu konfigurieren:
CR_SAFEST
überprüft die Adresse und Größe der Statusdatenabschnitte während des Neuladens. Wenn sich etwas ändert, wird der Ladevorgang zurückgesetzt.CR_SAFE
validiert nur die Größe des Statusabschnitts. Dies bedeutet, dass sich die Adresse der Statik ändern kann (und es ist am besten, Zeiger auf statische Daten zu vermeiden).CR_UNSAFE
Validiert nur, dass die Größe des Abschnitts passt und möglicherweise nicht unbedingt genau ist (Wachstum ist akzeptabel, Schrumpfen jedoch nicht). Dies ist das Standardverhalten.CR_DISABLE
Deaktiviert die automatische statische Statusverwaltung vollständig. CR_STATE
MakroWird verwendet, um eine globale oder lokale statische Variable zu markieren, die bei einem Neuladen gespeichert und wiederhergestellt werden soll.
Verwendung
static bool CR_STATE bInitialized = false;
Sie können diese Makros definieren, bevor Sie cr.h in den Host (CR_HOST) einbinden, um die Speicherzuweisungen von cr.h und andere Verhaltensweisen anzupassen:
CR_MAIN_FUNC
: Ändert das Symbol „cr_main“ in einen benutzerdefinierten Funktionsnamen. Standard: #define CR_MAIN_FUNC "cr_main"CR_ASSERT
: Assertion überschreiben. Standard: #define CA_ASSERT(e) Assert(e)CR_REALLOC
: Überschreibt den Realloc von libc. Standard: #define CR_REALLOC(ptr, size) ::realloc(ptr, size)CR_MALLOC
: Malloc von libc überschreiben. Standard: #define CR_MALLOC(size) ::malloc(size)CR_FREE
: Überschreibt die freie libc. Standard: #define CR_FREE(ptr) ::free(ptr)CR_DEBUG
: Gibt Debug-Meldungen in CR_ERROR, CR_LOG und CR_TRACE ausCR_ERROR
: Protokolliert Debug-Meldungen in stderr. Standard (nur CR_DEBUG): #define CR_ERROR(...) fprintf(stderr, VA_ARGS )CR_LOG
: Protokolliert Debug-Meldungen. Standard (nur CR_DEBUG): #define CR_LOG(...) fprintf(stdout, VA_ARGS )CR_TRACE
: druckt Funktionsaufrufe. Standard (nur CR_DEBUG): #define CR_TRACE(...) fprintf(stdout, "CR_TRACE: %sn", FUNCTION )A: Lesen Sie hier, warum ich das gemacht habe.
A: Stellen Sie sicher, dass sowohl Ihr Anwendungshost als auch Ihre DLL die dynamische Laufzeit (/MD oder /MDd) verwenden, da alle im Heap zugewiesenen Daten mit derselben Allokatorinstanz freigegeben werden müssen, indem die Laufzeit zwischen Gast und geteilt wird Host stellen Sie sicher, dass derselbe Allokator verwendet wird.
A: Ja. Dies sollte unter Windows ohne Probleme funktionieren. Unter Linux und OSX kann es zu Problemen mit der Absturzbehandlung kommen
Wenn Sie aus irgendeinem Grund die DLL vor cr
laden mussten, hält Visual Studio möglicherweise immer noch eine Sperre für die PDB. Möglicherweise haben Sie dieses Problem und die Lösung finden Sie hier.
Stellen Sie zunächst sicher, dass Ihr Build-System nicht dadurch beeinträchtigt wird, dass es weiterhin eine Verbindung zu Ihrer gemeinsam genutzten Bibliothek herstellt. Es gibt so viele Dinge, die schief gehen können, und Sie müssen sicher sein, dass nur cr
mit Ihrer gemeinsam genutzten Bibliothek umgehen kann. Weitere Informationen dazu, wie Sie herausfinden können, was passiert, finden Sie unter Linux unter diesem Problem.
cr
ist C
Reloader und geht mit C davon aus, dass einfache Dinge größtenteils funktionieren.
Das Problem besteht darin, wie der Linker entscheidet, die Dinge entsprechend der Anzahl der Änderungen, die Sie im Code vornehmen, neu anzuordnen. Bei inkrementellen und lokalisierten Änderungen hatte ich nie Probleme, im Allgemeinen hatte ich beim Schreiben von normalem C-Code kaum Probleme. Wenn die Dinge jetzt komplexer werden und an C++ grenzen, wird es riskanter. Wenn Sie komplexe Aufgaben erledigen müssen, empfehle ich Ihnen, sich RCCPP anzuschauen und dieses PDF sowie meinen Original-Blogbeitrag über cr
hier zu lesen.
Mit all diesen Informationen können Sie entscheiden, welches für Ihren Anwendungsfall besser geeignet ist.
cr
Sponsoren Für das Sponsoring der Portierung von cr
auf MacOSX.
Danny Grein
Rokas Kupstys
Noah Rinehart
Niklas Lundberg
Sepehr Taghdisian
Robert Gabriel Jakabosky
@pixelherodev
Alexander
Wir freuen uns über ALLE Beiträge, es gibt keine Kleinigkeiten, mit denen man etwas beisteuern kann, auch Korrekturen von Tippfehlern bei nur einem Buchstaben sind willkommen.
Das Einzige, was wir benötigen, ist, gründlich zu testen, den Codestil beizubehalten und die Dokumentation auf dem neuesten Stand zu halten.
Akzeptieren und stimmen Sie außerdem der Veröffentlichung aller Beiträge unter derselben Lizenz zu.
Die MIT-Lizenz (MIT)
Copyright (c) 2017 Danny Angelo Carminati Grein
Hiermit wird jeder Person, die eine Kopie dieser Software und der zugehörigen Dokumentationsdateien (die „Software“) erhält, kostenlos die Erlaubnis erteilt, mit der Software ohne Einschränkung zu handeln, einschließlich und ohne Einschränkung der Rechte zur Nutzung, zum Kopieren, Ändern und Zusammenführen , Kopien der Software zu veröffentlichen, zu verteilen, unterzulizenzieren und/oder zu verkaufen und Personen, denen die Software zur Verfügung gestellt wird, dies zu gestatten, vorbehaltlich der folgenden Bedingungen:
Der obige Urheberrechtshinweis und dieser Genehmigungshinweis müssen in allen Kopien oder wesentlichen Teilen der Software enthalten sein.
DIE SOFTWARE WIRD „WIE BESEHEN“ ZUR VERFÜGUNG GESTELLT, OHNE JEGLICHE AUSDRÜCKLICHE ODER STILLSCHWEIGENDE GEWÄHRLEISTUNG, EINSCHLIESSLICH, ABER NICHT BESCHRÄNKT AUF DIE GEWÄHRLEISTUNG DER MARKTGÄNGIGKEIT, EIGNUNG FÜR EINEN BESTIMMTEN ZWECK UND NICHTVERLETZUNG. IN KEINEM FALL SIND DIE AUTOREN ODER COPYRIGHT-INHABER HAFTBAR FÜR JEGLICHE ANSPRÜCHE, SCHÄDEN ODER ANDERE HAFTUNG, WEDER AUS EINER VERTRAGLICHEN HANDLUNG, AUS HANDLUNG ODER ANDERWEITIG, DIE SICH AUS, AUS ODER IN VERBINDUNG MIT DER SOFTWARE ODER DER NUTZUNG ODER ANDEREN HANDELN IN DER SOFTWARE ERGEBEN SOFTWARE.
* /
#ifndef __CR_H__
#define __CR_H__
//
// Global OS specific defines/customizations
//
#if defined( _WIN32 )
#define CR_WINDOWS
#define CR_PLUGIN ( name ) "" name ".dll"
#elif defined( __linux__ )
#define CR_LINUX
#define CR_PLUGIN ( name ) "lib" name ".so"
#elif defined( __APPLE__ )
#define CR_OSX
#define CR_PLUGIN ( name ) "lib" name ".dylib"
#else
#error "Unknown/unsupported platform, please open an issue if you think this
platform should be supported."
#endif // CR_WINDOWS || CR_LINUX || CR_OSX
//
// Global compiler specific defines/customizations
//
#if defined( _MSC_VER )
#if defined( __cplusplus )
#define CR_EXPORT extern "C" __declspec(dllexport)
#define CR_IMPORT extern "C" __declspec(dllimport)
#else
#define CR_EXPORT __declspec(dllexport)
#define CR_IMPORT __declspec(dllimport)
#endif
#endif // defined(_MSC_VER)
#if defined( __GNUC__ ) // clang & gcc
#if defined( __cplusplus )
#define CR_EXPORT extern "C" __attribute__((visibility("default")))
#else
#define CR_EXPORT __attribute__((visibility("default")))
#endif
#define CR_IMPORT
#endif // defined(__GNUC__)
#if defined( __MINGW32__ )
#undef CR_EXPORT
#if defined( __cplusplus )
#define CR_EXPORT extern "C" __declspec(dllexport)
#else
#define CR_EXPORT __declspec(dllexport)
#endif
#endif
// cr_mode defines how much we validate global state transfer between
// instances. The default is CR_UNSAFE, you can choose another mode by
// defining CR_HOST, ie.: #define CR_HOST CR_SAFEST
enum cr_mode {
CR_SAFEST = 0 , // validate address and size of the state section, if
// anything changes the load will rollback
CR_SAFE = 1 , // validate only the size of the state section, this means
// that address is assumed to be safe if avoided keeping
// references to global/static states
CR_UNSAFE = 2 , // don't validate anything but that the size of the section
// fits, may not be identical though
CR_DISABLE = 3 // completely disable the auto state transfer
};
// cr_op is passed into the guest process to indicate the current operation
// happening so the process can manage its internal data if it needs.
enum cr_op {
CR_LOAD = 0 ,
CR_STEP = 1 ,
CR_UNLOAD = 2 ,
CR_CLOSE = 3 ,
};
enum cr_failure {
CR_NONE , // No error
CR_SEGFAULT , // SIGSEGV / EXCEPTION_ACCESS_VIOLATION
CR_ILLEGAL , // illegal instruction (SIGILL) / EXCEPTION_ILLEGAL_INSTRUCTION
CR_ABORT , // abort (SIGBRT)
CR_MISALIGN , // bus error (SIGBUS) / EXCEPTION_DATATYPE_MISALIGNMENT
CR_BOUNDS , // EXCEPTION_ARRAY_BOUNDS_EXCEEDED
CR_STACKOVERFLOW , // EXCEPTION_STACK_OVERFLOW
CR_STATE_INVALIDATED , // one or more global data section changed and does
// not safely match basically a failure of
// cr_plugin_validate_sections
CR_BAD_IMAGE , // The binary is not valid - compiler is still writing it
CR_INITIAL_FAILURE , // Plugin version 1 crashed, cannot rollback
CR_OTHER , // Unknown or other signal,
CR_USER = 0x100 ,
};
struct cr_plugin ;
typedef int ( * cr_plugin_main_func )( struct cr_plugin * ctx , enum cr_op operation );
// public interface for the plugin context, this has some user facing
// variables that may be used to manage reload feedback.
// - userdata may be used by the user to pass information between reloads
// - version is the reload counter (after loading the first instance it will
// be 1, not 0)
// - failure is the (platform specific) last error code for any crash that may
// happen to cause a rollback reload used by the crash protection system
struct cr_plugin {
void * p ;
void * userdata ;
unsigned int version ;
enum cr_failure failure ;
unsigned int next_version ;
unsigned int last_working_version ;
};
#ifndef CR_HOST
// Guest specific compiler defines/customizations
#if defined( _MSC_VER )
#pragma section(".state", read, write)
#define CR_STATE __declspec(allocate(".state"))
#endif // defined(_MSC_VER)
#if defined( CR_OSX )
#define CR_STATE __attribute__((used, section("__DATA,__state")))
#else
#if defined( __GNUC__ ) // clang & gcc
#define CR_STATE __attribute__((section(".state")))
#endif // defined(__GNUC__)
#endif
#else // #ifndef CR_HOST
// Overridable macros
#ifndef CR_LOG
# ifdef CR_DEBUG
# include
# define CR_LOG (...) fprintf(stdout, __VA_ARGS__)
# else
# define CR_LOG (...)
# endif
#endif
#ifndef CR_ERROR
# ifdef CR_DEBUG
# include
# define CR_ERROR (...) fprintf(stderr, __VA_ARGS__)
# else
# define CR_ERROR (...)
# endif
#endif
#ifndef CR_TRACE
# ifdef CR_DEBUG
# include
# define CR_TRACE fprintf(stdout, "CR_TRACE: %sn", __FUNCTION__);
# else
# define CR_TRACE
# endif
#endif
#ifndef CR_MAIN_FUNC
# define CR_MAIN_FUNC "cr_main"
#endif
#ifndef CR_ASSERT
# include
# define CR_ASSERT ( e ) assert(e)
#endif
#ifndef CR_REALLOC
# include
# define CR_REALLOC ( ptr , size ) ::realloc(ptr, size)
#endif
#ifndef CR_FREE
# include
# define CR_FREE ( ptr ) ::free(ptr)
#endif
#ifndef CR_MALLOC
# include
# define CR_MALLOC ( size ) ::malloc(size)
#endif
#if defined( _MSC_VER )
// we should probably push and pop this
# pragma warning(disable:4003) // not enough actual parameters for macro 'identifier'
#endif
#define CR_DO_EXPAND ( x ) x##1337
#define CR_EXPAND ( x ) CR_DO_EXPAND(x)
#if CR_EXPAND ( CR_HOST ) == 1337
#define CR_OP_MODE CR_UNSAFE
#else
#define CR_OP_MODE CR_HOST
#endif
#include
#include // duration for sleep
#include // memcpy
#include
#include // this_thread::sleep_for
#if defined( CR_WINDOWS )
#define CR_PATH_SEPARATOR '\'
#define CR_PATH_SEPARATOR_INVALID '/'
#else
#define CR_PATH_SEPARATOR '/'
#define CR_PATH_SEPARATOR_INVALID '\'
#endif
static void cr_split_path ( std :: string path , std :: string & parent_dir ,
std :: string & base_name , std :: string & ext ) {
std:: replace ( path . begin (), path . end (), CR_PATH_SEPARATOR_INVALID ,
CR_PATH_SEPARATOR );
auto sep_pos = path . rfind ( CR_PATH_SEPARATOR );
auto dot_pos = path . rfind ( '.' );
if ( sep_pos == std :: string :: npos ) {
parent_dir = "" ;
if ( dot_pos == std :: string :: npos ) {
ext = "" ;
base_name = path ;
} else {
ext = path . substr ( dot_pos );
base_name = path . substr ( 0 , dot_pos );
}
} else {
parent_dir = path . substr ( 0 , sep_pos + 1 );
if ( dot_pos == std :: string :: npos || sep_pos > dot_pos ) {
ext = "" ;
base_name = path . substr ( sep_pos + 1 );
} else {
ext = path . substr ( dot_pos );
base_name = path . substr ( sep_pos + 1 , dot_pos - sep_pos - 1 );
}
}
}
static std :: string cr_version_path ( const std :: string & basepath ,
unsigned version ,
const std :: string & temppath ) {
std:: string folder , fname , ext ;
cr_split_path ( basepath , folder , fname , ext );
std:: string ver = std :: to_string ( version );
#if defined( _MSC_VER )
// When patching PDB file path in library file we will drop path and leave only file name.
// Length of path is extra space for version number. Trim file name only if version number
// length exceeds pdb folder path length. This is not relevant on other platforms.
if ( ver . size () > folder . size ()) {
fname = fname . substr ( 0 , fname . size () - ( ver . size () - folder . size () - 1 ));
}
#endif
if (! temppath . empty ()) {
folder = temppath ;
}
return folder + fname + ver + ext ;
}
namespace cr_plugin_section_type {
enum e { state , bss , count };
}
namespace cr_plugin_section_version {
enum e { backup , current , count };
}
struct cr_plugin_section {
cr_plugin_section_type :: e type = {};
intptr_t base = 0 ;
char * ptr = 0 ;
int64_t size = 0 ;
void * data = nullptr ;
};
struct cr_plugin_segment {
char * ptr = 0 ;
int64_t size = 0 ;
};
// keep track of some internal state about the plugin, should not be messed
// with by user
struct cr_internal {
std :: string fullname = {};
std:: string temppath = {};
time_t timestamp = {};
void * handle = nullptr ;
cr_plugin_main_func main = nullptr ;
cr_plugin_segment seg = {};
cr_plugin_section data [ cr_plugin_section_type :: count ]
[ cr_plugin_section_version :: count ] = {};
cr_mode mode = CR_SAFEST ;
};
static bool cr_plugin_section_validate ( cr_plugin & ctx ,
cr_plugin_section_type :: e type ,
intptr_t vaddr , intptr_t ptr ,
int64_t size );
static void cr_plugin_sections_reload ( cr_plugin & ctx ,
cr_plugin_section_version :: e version );
static void cr_plugin_sections_store ( cr_plugin & ctx );
static void cr_plugin_sections_backup ( cr_plugin & ctx );
static void cr_plugin_reload ( cr_plugin & ctx );
static int cr_plugin_unload ( cr_plugin & ctx , bool rollback , bool close );
static bool cr_plugin_changed ( cr_plugin & ctx );
static bool cr_plugin_rollback ( cr_plugin & ctx );
static int cr_plugin_main ( cr_plugin & ctx , cr_op operation );
void cr_set_temporary_path ( cr_plugin & ctx , const std :: string & path ) {
auto pimpl = ( cr_internal * ) ctx . p ;
pimpl -> temppath = path ;
}
#if defined( CR_WINDOWS )
// clang-format off
#ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
#endif
#include
#include
// clang-format on
#if defined( _MSC_VER )
#pragma comment(lib, "dbghelp.lib")
#endif
using so_handle = HMODULE ;
#ifdef UNICODE
# define CR_WINDOWS_ConvertPath ( _newpath , _path ) std::wstring _newpath(cr_utf8_to_wstring(_path))
static std :: wstring cr_utf8_to_wstring ( const std :: string & str ) {
int wlen = MultiByteToWideChar ( CP_UTF8 , 0 , str . c_str (), -1 , 0 , 0 );
wchar_t wpath_small [ MAX_PATH ];
std:: unique_ptr < wchar_t [] > wpath_big ;
wchar_t * wpath = wpath_small ;
if ( wlen > _countof ( wpath_small )) {
wpath_big = std :: unique_ptr < wchar_t [] > ( new wchar_t [ wlen ]);
wpath = wpath_big . get ();
}
if ( MultiByteToWideChar ( CP_UTF8 , 0 , str . c_str (), -1 , wpath , wlen ) != wlen ) {
return L"" ;
}
return wpath ;
}
#else
# define CR_WINDOWS_ConvertPath ( _newpath , _path ) const std::string &_newpath = _path
#endif // UNICODE
static time_t cr_last_write_time ( const std :: string & path ) {
CR_WINDOWS_ConvertPath ( _path , path );
WIN32_FILE_ATTRIBUTE_DATA fad ;
if (! GetFileAttributesEx ( _path . c_str (), GetFileExInfoStandard , & fad )) {
return -1 ;
}
if ( fad . nFileSizeHigh == 0 && fad . nFileSizeLow == 0 ) {
return -1 ;
}
LARGE_INTEGER time ;
time . HighPart = fad . ftLastWriteTime . dwHighDateTime ;
time . LowPart = fad . ftLastWriteTime . dwLowDateTime ;
return static_cast < time_t > ( time . QuadPart / 10000000 - 11644473600LL );
}
static bool cr_exists ( const std :: string & path ) {
CR_WINDOWS_ConvertPath ( _path , path );
return GetFileAttributes ( _path . c_str ()) != INVALID_FILE_ATTRIBUTES ;
}
static bool cr_copy ( const std :: string & from , const std :: string & to ) {
CR_WINDOWS_ConvertPath ( _from , from );
CR_WINDOWS_ConvertPath ( _to , to );
return CopyFile ( _from . c_str (), _to . c_str (), FALSE) ? true : false;
}
static void cr_del ( const std :: string & path ) {
CR_WINDOWS_ConvertPath ( _path , path );
DeleteFile ( _path . c_str ());
}
// If using Microsoft Visual C/C++ compiler we need to do some workaround the
// fact that the compiled binary has a fullpath to the PDB hardcoded inside
// it. This causes a lot of headaches when trying compile while debugging as
// the referenced PDB will be locked by the debugger.
// To solve this problem, we patch the binary to rename the PDB to something
// we know will be unique to our in-flight instance, so when debugging it will
// lock this unique PDB and the compiler will be able to overwrite the
// original one.
#if defined( _MSC_VER )
#include
#include
#include
#include
static std :: string cr_replace_extension ( const std :: string & filepath ,
const std :: string & ext ) {
std:: string folder , filename , old_ext ;
cr_split_path ( filepath , folder , filename , old_ext );
return folder + filename + ext ;
}
template < class T >
static T struct_cast ( void * ptr , LONG offset = 0 ) {
return reinterpret_cast < T > ( reinterpret_cast < intptr_t > ( ptr ) + offset );
}
// RSDS Debug Information for PDB files
using DebugInfoSignature = DWORD ;
#define CR_RSDS_SIGNATURE 'SDSR'
struct cr_rsds_hdr {
DebugInfoSignature signature ;
GUID guid ;
long version ;
char filename [ 1 ];
};
static bool cr_pe_debugdir_rva ( PIMAGE_OPTIONAL_HEADER optionalHeader ,
DWORD & debugDirRva , DWORD & debugDirSize ) {
if ( optionalHeader -> Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC ) {
auto optionalHeader64 =
struct_cast < PIMAGE_OPTIONAL_HEADER64 > ( optionalHeader );
debugDirRva =
optionalHeader64 -> DataDirectory [ IMAGE_DIRECTORY_ENTRY_DEBUG ]
. VirtualAddress ;
debugDirSize =
optionalHeader64 -> DataDirectory [ IMAGE_DIRECTORY_ENTRY_DEBUG ]. Size ;
} else {
auto optionalHeader32 =
struct_cast < PIMAGE_OPTIONAL_HEADER32 > ( optionalHeader );
debugDirRva =
optionalHeader32 -> DataDirectory [ IMAGE_DIRECTO