/*
Une solution de rechargement en direct d'un seul en-tête de fichier pour C, écrite en C++ :
REMARQUE : le seul fichier important dans ce référentiel est cr.h
.
Ce fichier contient la documentation en markdown, la licence, l'implémentation et l'API publique. Tous les autres fichiers de ce référentiel sont des fichiers de support et peuvent être ignorés en toute sécurité.
Vous pouvez télécharger et installer cr à l'aide du gestionnaire de dépendances vcpkg :
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install cr
Le port cr dans vcpkg est tenu à jour par les membres de l'équipe Microsoft et les contributeurs de la communauté. Si la version est obsolète, veuillez créer un problème ou une pull request sur le référentiel vcpkg.
Un (mince) exécutable d'application hôte utilisera cr
pour gérer le rechargement en direct de l'application réelle sous la forme d'un binaire chargeable dynamique, un hôte serait quelque chose comme :
#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 ;
}
Tandis que l'invité (application réelle), ressemblerait à :
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
. Si le plugin initial plante, l'hôte doit déterminer le chemin suivant et nous ne rechargerons pas le plugin cassé. cr_plugin_load
obsolète au profit de cr_plugin_open
pour des raisons de cohérence avec cr_plugin_close
. Voir le numéro 49.CR_BAD_IMAGE
au cas où le fichier binaire n'est toujours pas prêt même si son horodatage a changé. Cela pouvait se produire si la génération du fichier (compilateur ou copie) était lente.CR_BAD_IMAGE
). Désormais, la version est décrémentée une fois dans le gestionnaire de crash, puis une autre fois pendant la restauration, puis à nouveau modifiée. Une restauration due à une image incomplète ne restaurera pas incorrectement deux versions, elle continuera avec la même version en réessayant le chargement jusqu'à ce que l'image soit valide (la copie ou le compilateur a fini d'y écrire). Cela peut avoir un impact sur les utilisations actuelles de cr
si les informations version
sont utilisées pendant CR_UNLOAD
car elles auront désormais une valeur différente. Deux exemples simples peuvent être trouvés dans le répertoire samples
.
La première est une application console simple qui démontre certains états statiques de base fonctionnant entre les instances et des tests de gestion de crash de base. L'impression vers la sortie est utilisée pour montrer ce qui se passe.
Le second montre comment recharger en direct une application opengl à l'aide de Dear ImGui. Certains états résident du côté hôte tandis que la majeure partie du code se trouve du côté invité.
Les exemples et tests utilisent le système de construction fips. Cela nécessite Python et 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)
Il s'agit du pointeur de fonction vers la fonction de point d'entrée binaire chargeable dynamique.
Arguments
ctx
vers un contexte qui sera transmis de host
à l' guest
contenant des informations précieuses sur la version actuellement chargée, la raison de l'échec et les données utilisateur. Pour plus d'informations, voir cr_plugin
.operation
quelle opération est en cours d'exécution, voir cr_op
.Retour
CR_USER
. 0 ou une valeur positive qui sera transmise au processus host
. bool cr_plugin_open(cr_plugin &ctx, const char *fullpath)
Charge et initialise le plugin.
Arguments
ctx
un contexte qui gérera les données internes du plugin et les données utilisateur.fullpath
chemin complet avec le nom de fichier vers le binaire chargeable pour le plugin ou NULL
.Retour
true
en cas de succès, false
sinon. void cr_set_temporary_path(cr_plugin& ctx, const std::string &path)
Définit le chemin temporaire vers lequel les copies temporaires du plugin seront placées. Doit être appelé immédiatement après cr_plugin_open()
. Si le chemin temporary
n'est pas défini, les copies temporaires du fichier seront copiées dans le même répertoire où se trouve le fichier d'origine.
Arguments
ctx
un contexte qui gérera les données internes du plugin et les données utilisateur.path
un chemin complet vers un répertoire existant qui sera utilisé pour stocker les copies temporaires du plugin. int cr_plugin_update(cr_plugin &ctx, bool reloadCheck = true)
Cette fonction appellera la fonction cr_main
du plugin. Il doit être appelé aussi souvent que les besoins fondamentaux de la logique/application.
Arguments
ctx
les données de contexte actuelles du plugin.reloadCheck
facultatif : effectuez une vérification du disque (stat()) pour voir si la bibliothèque dynamique a besoin d'un rechargement.Retour
cr_main
. void cr_plugin_close(cr_plugin &ctx)
Nettoyer les états internes une fois que le plugin n'est plus nécessaire.
Arguments
ctx
les données de contexte actuelles du plugin. cr_op
Enum indiquant le type d'étape exécutée par l' host
:
CR_LOAD
Un chargement provoqué par un rechargement est en cours d'exécution, peut être utilisé pour restaurer tout état interne enregistré.CR_STEP
Une mise à jour d'une application, c'est l'opération normale et la plus fréquente ;CR_UNLOAD
Un déchargement pour recharger le plugin sera exécuté, donnant à l'application une chance de stocker toutes les données requises ;CR_CLOSE
Utilisé lors de la fermeture du plugin, cela fonctionne comme CR_UNLOAD
mais aucun CR_LOAD
ne doit être attendu par la suite ; cr_plugin
La structure contextuelle de l'instance du plugin.
p
pointeur opaque pour les données cr internes ;userdata
peuvent être utilisées par l'utilisateur pour transmettre des informations entre les rechargements ;version
pour chaque rechargement réussi, commençant à 1 pour le premier chargement. La version changera lors d'un processus de gestion de crash ;failure
utilisé par le système de protection contre les pannes, contiendra le dernier code d'erreur d'échec qui a provoqué une restauration. Voir cr_failure
pour plus d'informations sur les valeurs possibles ; cr_failure
Si un crash dans le binaire chargeable se produit, le gestionnaire de crash indiquera la raison du crash avec l'une des suivantes :
CR_NONE
Aucune erreur ;CR_SEGFAULT
Défaut de segmentation. SIGSEGV
sous Linux/OSX ou EXCEPTION_ACCESS_VIOLATION
sous Windows ;CR_ILLEGAL
En cas d'instruction illégale. SIGILL
sous Linux/OSX ou EXCEPTION_ILLEGAL_INSTRUCTION
sous Windows ;CR_ABORT
Abandon, SIGBRT
sous Linux/OSX, non utilisé sous Windows ;CR_MISALIGN
, SIGBUS
sous Linux/OSX ou EXCEPTION_DATATYPE_MISALIGNMENT
sous Windows ;CR_BOUNDS
Est EXCEPTION_ARRAY_BOUNDS_EXCEEDED
, Windows uniquement ;CR_STACKOVERFLOW
Est EXCEPTION_STACK_OVERFLOW
, Windows uniquement ;CR_STATE_INVALIDATED
Défaillance de sécurité de gestion CR_STATE
statique ;CR_BAD_IMAGE
Le plugin n'est pas une image valide (c'est-à-dire que le compilateur peut encore l'écrire) ;CR_OTHER
Autre signal, Linux uniquement ;CR_USER
Erreur utilisateur (pour les valeurs négatives renvoyées par cr_main
); CR_HOST
définir Cette définition doit être utilisée avant d'inclure le cr.h
dans l' host
. Si CR_HOST
n'est pas défini, cr.h
fonctionnera comme un fichier d'en-tête d'API public à utiliser dans l'implémentation guest
.
En option, CR_HOST
peut également être défini sur l'une des valeurs suivantes afin de configurer le mode de fonctionnement safety
pour la gestion automatique de l'état statique ( CR_STATE
) :
CR_SAFEST
validera l'adresse et la taille des sections de données d'état lors des rechargements, si quelque chose change, la charge sera annulée ;CR_SAFE
validera uniquement la taille de la section d'état, cela signifie que l'adresse des éléments statiques peut changer (et il est préférable d'éviter de maintenir un pointeur vers des éléments statiques) ;CR_UNSAFE
ne validera rien mais que la taille de la section s'adapte, peut ne pas être nécessairement exacte (la croissance est acceptable mais la réduction ne l'est pas), c'est le comportement par défaut ;CR_DISABLE
Désactive complètement la gestion automatique de l'état statique ; CR_STATE
Utilisé pour baliser une variable statique globale ou locale à sauvegarder et à restaurer lors d'un rechargement.
Usage
static bool CR_STATE bInitialized = false;
Vous pouvez définir ces macros avant d'inclure cr.h dans l'hôte (CR_HOST) pour personnaliser les allocations de mémoire cr.h et d'autres comportements :
CR_MAIN_FUNC
: change le symbole 'cr_main' en nom de fonction défini par l'utilisateur. par défaut : #define CR_MAIN_FUNC "cr_main"CR_ASSERT
: remplacer l'assertion. par défaut : #define CA_ASSERT(e) assert(e)CR_REALLOC
: remplace la réallocation de la libc. par défaut : #define CR_REALLOC(ptr, taille) ::realloc(ptr, taille)CR_MALLOC
: remplace le malloc de la libc. par défaut : #define CR_MALLOC(size) ::malloc(size)CR_FREE
: remplace la version gratuite de la libc. par défaut : #define CR_FREE(ptr) ::free(ptr)CR_DEBUG
: génère des messages de débogage dans CR_ERROR, CR_LOG et CR_TRACECR_ERROR
: enregistre les messages de débogage sur stderr. par défaut (CR_DEBUG uniquement) : #define CR_ERROR(...) fprintf(stderr, VA_ARGS )CR_LOG
: enregistre les messages de débogage. par défaut (CR_DEBUG uniquement) : #define CR_LOG(...) fprintf(stdout, VA_ARGS )CR_TRACE
: imprime les appels de fonction. par défaut (CR_DEBUG uniquement) : #define CR_TRACE(...) fprintf(stdout, "CR_TRACE: %sn", FUNCTION )R : Découvrez pourquoi j’ai fait cela ici.
R : Assurez-vous que votre hôte d'application et votre DLL utilisent le temps d'exécution dynamique (/MD ou /MDd), car toutes les données allouées dans le tas doivent être libérées avec la même instance d'allocateur, en partageant le temps d'exécution entre l'invité et hôte, vous garantirez que le même allocateur est utilisé.
R : Oui. Cela devrait fonctionner sans problème sous Windows. Sous Linux et OSX, il peut y avoir des problèmes de gestion des plantages
Si vous deviez charger la DLL avant cr
pour une raison quelconque, Visual Studio peut toujours verrouiller la PDB. Vous rencontrez peut-être ce problème et la solution est ici.
Tout d’abord, assurez-vous que votre système de build n’interfère pas en créant toujours des liens vers votre bibliothèque partagée. Il y a tellement de choses qui peuvent mal tourner et vous devez être sûr que seul cr
s'occupera de votre bibliothèque partagée. Sous Linux, pour plus d'informations sur la façon de trouver ce qui se passe, consultez ce problème.
cr
est un rechargeur C
et en traitant avec C, cela suppose que les choses simples fonctionneront pour la plupart.
Le problème est de savoir comment l'éditeur de liens décidera de réorganiser les choses en fonction du nombre de modifications que vous apportez dans le code. Pour les modifications incrémentielles et localisées, je n'ai jamais eu de problèmes, en général, je n'ai pratiquement eu aucun problème en écrivant du code C normal. Maintenant, quand les choses commencent à devenir plus complexes et à la limite du C++, cela devient plus risqué. Si vous avez besoin de faire des choses complexes, je vous suggère de consulter RCCPP et de lire ce PDF et mon article de blog original sur cr
ici.
Avec toutes ces informations, vous serez en mesure de décider laquelle convient le mieux à votre cas d'utilisation.
cr
Pour avoir sponsorisé le portage de cr
vers MacOSX.
Danny Grein
Rokas Kupstys
Noah Rinehart
Nicolas Lundberg
Sepehr Taghdisian
Robert Gabriel Jakabosky
@pixelherodev
Alexandre
Nous accueillons TOUTES les contributions, il n'y a pas de choses mineures avec lesquelles contribuer, même les corrections de fautes de frappe d'une lettre sont les bienvenues.
La seule chose dont nous avons besoin est de tester minutieusement, de maintenir le style de code et de maintenir la documentation à jour.
En outre, accepter et accepter de publier toute contribution sous la même licence.
La licence MIT (MIT)
Copyright (c) 2017 Danny Angelo Carminati Grein
L'autorisation est accordée par la présente, gratuitement, à toute personne obtenant une copie de ce logiciel et des fichiers de documentation associés (le « Logiciel »), d'utiliser le Logiciel sans restriction, y compris, sans limitation, les droits d'utilisation, de copie, de modification, de fusion. , publier, distribuer, accorder des sous-licences et/ou vendre des copies du Logiciel, et permettre aux personnes à qui le Logiciel est fourni de le faire, sous réserve des conditions suivantes :
L'avis de droit d'auteur ci-dessus et cet avis d'autorisation doivent être inclus dans toutes les copies ou parties substantielles du logiciel.
LE LOGICIEL EST FOURNI « EN L'ÉTAT », SANS GARANTIE D'AUCUNE SORTE, EXPRESSE OU IMPLICITE, Y COMPRIS MAIS SANS LIMITATION LES GARANTIES DE QUALITÉ MARCHANDE, D'ADAPTATION À UN USAGE PARTICULIER ET DE NON-VIOLATION. EN AUCUN CAS LES AUTEURS OU LES TITULAIRES DES DROITS D'AUTEUR NE SERONT RESPONSABLES DE TOUTE RÉCLAMATION, DOMMAGES OU AUTRE RESPONSABILITÉ, QUE CE SOIT DANS UNE ACTION CONTRACTUELLE, DÉLIT OU AUTRE, DÉCOULANT DE, DE OU EN RELATION AVEC LE LOGICIEL OU L'UTILISATION OU D'AUTRES TRANSACTIONS DANS LE LOGICIEL.
* /
#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