/*
Una solución de recarga en vivo de un solo encabezado de archivo para C, escrita en C++:
NOTA: El único archivo que importa en este repositorio es cr.h
Este archivo contiene la documentación en markdown, la licencia, la implementación y la api pública. Todos los demás archivos de este repositorio son archivos de soporte y pueden ignorarse de forma segura.
Puede descargar e instalar cr usando el administrador de dependencias vcpkg:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install cr
Los miembros del equipo de Microsoft y los contribuyentes de la comunidad mantienen actualizado el puerto cr en vcpkg. Si la versión no está actualizada, cree un problema o una solicitud de extracción en el repositorio de vcpkg.
Un ejecutable de aplicación de host (delgado) utilizará cr
para gestionar la recarga en vivo de la aplicación real en forma de binario cargable dinámico, un host sería algo como:
#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 ;
}
Mientras que el invitado (aplicación real), quedaría como:
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 el complemento inicial falla, el host debe determinar la siguiente ruta y no volveremos a cargar el complemento dañado. cr_plugin_load
a favor de cr_plugin_open
para mantener la coherencia con cr_plugin_close
. Consulte el número 49.CR_BAD_IMAGE
en caso de que el archivo binario aún no esté listo incluso si su marca de tiempo cambió. Esto podría suceder si la generación del archivo (compilador o copia) fue lenta.CR_BAD_IMAGE
). Ahora la versión disminuye una vez en el controlador de fallos y luego otra vez durante la reversión y luego se actualiza nuevamente. Una reversión debido a una imagen incompleta no revertirá incorrectamente dos versiones, continuará en la misma versión reintentando la carga hasta que la imagen sea válida (la copia o el compilador termine de escribir en ella). Esto puede afectar los usos actuales de cr
si la información version
se usa durante CR_UNLOAD
, ya que ahora tendrá un valor diferente. Se pueden encontrar dos ejemplos simples en el directorio samples
.
La primera es una aplicación de consola simple que demuestra algunos estados estáticos básicos que funcionan entre instancias y pruebas básicas de manejo de fallas. Imprimir a salida se utiliza para mostrar lo que está sucediendo.
El segundo demuestra cómo recargar en vivo una aplicación opengl usando Dear ImGui. Algunos estados viven en el lado anfitrión, mientras que la mayor parte del código está en el lado invitado.
Las muestras y pruebas utilizan el sistema de compilación fips. Requiere Python y 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)
Este es el puntero de función a la función de punto de entrada binario cargable dinámico.
Argumentos
ctx
a un contexto que se pasará del host
al guest
y que contiene información valiosa sobre la versión cargada actualmente, el motivo del error y los datos del usuario. Para obtener más información, consulte cr_plugin
.operation
qué operación se está ejecutando, consulte cr_op
.Devolver
CR_USER
. 0 o un valor positivo que se pasará al proceso host
. bool cr_plugin_open(cr_plugin &ctx, const char *fullpath)
Carga e inicializa el complemento.
Argumentos
ctx
un contexto que administrará los datos internos del complemento y los datos del usuario.fullpath
Ruta completa con el nombre del archivo al binario cargable para el complemento o NULL
.Devolver
true
en caso de éxito, false
en caso contrario. void cr_set_temporary_path(cr_plugin& ctx, const std::string &path)
Establece la ruta temporal en la que se colocarán las copias temporales del complemento. Debe llamarse inmediatamente después de cr_plugin_open()
. Si no se establece una ruta temporary
, las copias temporales del archivo se copiarán en el mismo directorio donde se encuentra el archivo original.
Argumentos
ctx
un contexto que administrará los datos internos del complemento y los datos del usuario.path
una ruta completa a un directorio existente que se utilizará para almacenar copias temporales de complementos. int cr_plugin_update(cr_plugin &ctx, bool reloadCheck = true)
Esta función llamará a la función cr_main
del complemento. Debe llamarse con tanta frecuencia como lo necesite la lógica/aplicación central.
Argumentos
ctx
los datos de contexto del complemento actual.reloadCheck
opcional: haga una verificación del disco (stat()) para ver si la biblioteca dinámica necesita una recarga.Devolver
cr_main
. void cr_plugin_close(cr_plugin &ctx)
Limpia los estados internos una vez que el complemento ya no sea necesario.
Argumentos
ctx
los datos de contexto del complemento actual. cr_op
Enumera que indica el tipo de paso que está ejecutando el host
:
CR_LOAD
Se está ejecutando una carga causada por la recarga, se puede utilizar para restaurar cualquier estado interno guardado.CR_STEP
Una actualización de la aplicación, esta es la operación normal y más frecuente;CR_UNLOAD
Se ejecutará una descarga para recargar el complemento, dándole a la aplicación una oportunidad de almacenar los datos necesarios;CR_CLOSE
Se usa al cerrar el complemento. Esto funciona como CR_UNLOAD
pero no se debe esperar CR_LOAD
después; cr_plugin
La estructura de contexto de la instancia del complemento.
p
internos;userdata
de usuario para pasar información entre recargas;version
para cada recarga sucesiva, comenzando en 1 para la primera carga. La versión cambiará durante un proceso de manejo de fallas ;failure
utilizada por el sistema de protección contra fallas, contendrá el último código de error que causó una reversión. Consulte cr_failure
para obtener más información sobre los valores posibles; cr_failure
Si ocurre una falla en el binario cargable, el controlador de fallas indicará el motivo de la falla con uno de estos:
CR_NONE
Sin errores;CR_SEGFAULT
Fallo de segmentación. SIGSEGV
en Linux/OSX o EXCEPTION_ACCESS_VIOLATION
en Windows;CR_ILLEGAL
En caso de instrucción ilegal. SIGILL
en Linux/OSX o EXCEPTION_ILLEGAL_INSTRUCTION
en Windows;CR_ABORT
Abortar, SIGBRT
en Linux/OSX, no utilizado en Windows;CR_MISALIGN
Error de bus, SIGBUS
en Linux/OSX o EXCEPTION_DATATYPE_MISALIGNMENT
en Windows;CR_BOUNDS
es EXCEPTION_ARRAY_BOUNDS_EXCEEDED
, solo Windows;CR_STACKOVERFLOW
es EXCEPTION_STACK_OVERFLOW
, solo para Windows;CR_STATE_INVALIDATED
Fallo estático de seguridad de gestión CR_STATE
;CR_BAD_IMAGE
El complemento no es una imagen válida (es decir, es posible que el compilador aún la escriba);CR_OTHER
Otra señal, solo Linux;CR_USER
Error de usuario (para valores negativos devueltos por cr_main
); CR_HOST
define Esta definición debe usarse antes de incluir cr.h
en el host
; si CR_HOST
no está definido, cr.h
funcionará como un archivo de encabezado de API público para usarse en la implementación guest
.
Opcionalmente, CR_HOST
también se puede definir con uno de los siguientes valores como una forma de configurar el modo de operación safety
para la gestión automática del estado estático ( CR_STATE
):
CR_SAFEST
Validará la dirección y el tamaño de las secciones de datos de estado durante las recargas; si algo cambia, la carga se revertirá;CR_SAFE
Validará sólo el tamaño de la sección de estado, esto significa que la dirección de las estáticas puede cambiar (y es mejor evitar mantener ningún puntero a cosas estáticas);CR_UNSAFE
No validará nada excepto que el tamaño de la sección se ajuste, puede que no sea necesariamente exacto (crecer es aceptable pero reducir no), este es el comportamiento predeterminado;CR_DISABLE
Desactiva completamente la gestión automática del estado estático; CR_STATE
Se utiliza para etiquetar una variable estática global o local para guardarla y restaurarla durante una recarga.
Uso
static bool CR_STATE bInitialized = false;
Puede definir estas macros antes de incluir cr.h en el host (CR_HOST) para personalizar las asignaciones de memoria de cr.h y otros comportamientos:
CR_MAIN_FUNC
: cambia el símbolo 'cr_main' al nombre de función definido por el usuario. predeterminado: #define CR_MAIN_FUNC "cr_main"CR_ASSERT
: anula la afirmación. predeterminado: #define CA_ASSERT(e) afirmar(e)CR_REALLOC
: anula la realloc de libc. predeterminado: #define CR_REALLOC(ptr, tamaño) ::realloc(ptr, tamaño)CR_MALLOC
: anula el malloc de libc. predeterminado: #define CR_MALLOC(tamaño) ::malloc(tamaño)CR_FREE
: anula la libertad de libc. predeterminado: #definir CR_FREE(ptr) ::gratis(ptr)CR_DEBUG
: genera mensajes de depuración en CR_ERROR, CR_LOG y CR_TRACECR_ERROR
: registra mensajes de depuración en stderr. predeterminado (solo CR_DEBUG): #define CR_ERROR(...) fprintf(stderr, VA_ARGS )CR_LOG
: registra mensajes de depuración. predeterminado (solo CR_DEBUG): #define CR_LOG(...) fprintf(stdout, VA_ARGS )CR_TRACE
: imprime llamadas a funciones. predeterminado (solo CR_DEBUG): #define CR_TRACE(...) fprintf(stdout, "CR_TRACE: %sn", FUNCTION )R: Lea sobre por qué hice esto aquí.
R: Asegúrese de que tanto el host de su aplicación como su dll estén usando el tiempo de ejecución dinámico (/MD o /MDd), ya que cualquier dato asignado en el montón debe liberarse con la misma instancia del asignador, compartiendo el tiempo de ejecución entre el invitado y host, garantizará que se esté utilizando el mismo asignador.
R: Sí. Esto debería funcionar sin problemas en Windows. En Linux y OSX puede haber problemas con el manejo de fallas
Si tuvo que cargar el dll antes de cr
por algún motivo, es posible que Visual Studio aún mantenga un bloqueo en el PDB. Es posible que tengas este problema y la solución está aquí.
Primero, asegúrese de que su sistema de compilación no interfiera al seguir vinculándose a su biblioteca compartida. Hay tantas cosas que pueden salir mal y debe asegurarse de que solo cr
se ocupe de su biblioteca compartida. En Linux, para obtener más información sobre cómo encontrar lo que está sucediendo, consulte este problema.
cr
es un cargador C
y, al tratar con C, se supone que las cosas simples funcionarán en su mayoría.
El problema es cómo el vinculador decidirá reorganizar las cosas de acuerdo con la cantidad de cambios que realice en el código. Para los cambios incrementales y localizados nunca tuve ningún problema; en general, casi no tuve ningún problema al escribir código C normal. Ahora, cuando las cosas empiezan a volverse más complejas y rozando C++, se vuelve más arriesgado. Si necesita hacer cosas complejas, le sugiero consultar RCCPP y leer este PDF y mi publicación de blog original sobre cr
aquí.
Con toda esta información podrás decidir cuál es mejor para tu caso de uso.
cr
Por patrocinar el puerto de cr
a MacOSX.
Danny Grein
Rokas Kupstys
Noah Rinhart
Niklas Lundberg
Sepehr Taghdisian
Robert Gabriel Jakabosky
@pixelherodev
Alejandro
Damos la bienvenida a TODAS las contribuciones, no hay cosas menores con las que contribuir, incluso las correcciones de errores tipográficos de una letra son bienvenidas.
Lo único que requerimos es realizar pruebas exhaustivas, mantener el estilo del código y mantener la documentación actualizada.
Asimismo, aceptando y comprometiéndose a liberar cualquier aportación bajo la misma licencia.
La licencia MIT (MIT)
Copyright (c) 2017 Danny Angelo Carminati Grein
Por el presente se otorga permiso, sin cargo, a cualquier persona que obtenga una copia de este software y los archivos de documentación asociados (el "Software"), para operar con el Software sin restricciones, incluidos, entre otros, los derechos de uso, copia, modificación, fusión. , publicar, distribuir, sublicenciar y/o vender copias del Software, y permitir que las personas a quienes se les proporciona el Software lo hagan, sujeto a las siguientes condiciones:
El aviso de derechos de autor anterior y este aviso de permiso se incluirán en todas las copias o partes sustanciales del Software.
EL SOFTWARE SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A LAS GARANTÍAS DE COMERCIABILIDAD, IDONEIDAD PARA UN PROPÓSITO PARTICULAR Y NO INFRACCIÓN. EN NINGÚN CASO LOS AUTORES O TITULARES DE DERECHOS DE AUTOR SERÁN RESPONSABLES DE NINGÚN RECLAMO, DAÑO U OTRA RESPONSABILIDAD, YA SEA EN UNA ACCIÓN CONTRACTUAL, AGRAVIO O DE OTRA MANERA, QUE SURJA DE, FUERA DE O EN RELACIÓN CON EL SOFTWARE O EL USO U OTRAS NEGOCIOS EN EL 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