/*
Решение для перезагрузки в реальном времени только для одного файла для C, написанное на C++:
ПРИМЕЧАНИЕ. Единственный файл, который имеет значение в этом репозитории, — cr.h
Этот файл содержит документацию в уценке, лицензию, реализацию и общедоступный API. Все остальные файлы в этом репозитории являются вспомогательными и их можно смело игнорировать.
Вы можете скачать и установить cr с помощью менеджера зависимостей vcpkg:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install cr
Порт cr в vcpkg постоянно обновляется членами команды Microsoft и участниками сообщества. Если версия устарела, создайте проблему или запрос на извлечение в репозитории vcpkg.
Исполняемый файл (тонкого) хост-приложения будет использовать cr
для управления живой перезагрузкой реального приложения в форме динамически загружаемого двоичного файла, хост будет выглядеть примерно так:
#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 ;
}
А гость (реальное приложение) будет выглядеть так:
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
. Если исходный плагин выйдет из строя, хост должен определить следующий путь, и мы не будем перезагружать сломанный плагин. cr_plugin_load
в пользу cr_plugin_open
для согласованности с cr_plugin_close
. См. выпуск №49.CR_BAD_IMAGE
в случае, если двоичный файл все еще не готов, даже если его временная метка изменилась. Это могло произойти, если создание файла (компилятор или копирование) происходило медленно.CR_BAD_IMAGE
). Теперь версия уменьшается один раз в обработчике сбоев, а затем еще раз во время отката, а затем снова увеличивается. Откат из-за неполного образа не приведет к неправильному откату двух версий, он будет продолжаться с той же версией, повторяя загрузку до тех пор, пока образ не станет действительным (копия или компилятор не завершат запись в него). Это может повлиять на текущее использование cr
, если информация version
используется во время CR_UNLOAD
поскольку теперь это будет другое значение. Два простых примера можно найти в каталоге samples
.
Первое — это простое консольное приложение, демонстрирующее некоторые базовые статические состояния, работающие между экземплярами, и базовые тесты обработки сбоев. Печать на вывод используется, чтобы показать, что происходит.
Второй демонстрирует, как перезагрузить приложение opengl с помощью Dear ImGui. Некоторое состояние находится на стороне хоста, тогда как большая часть кода находится на стороне гостя.
В примерах и тестах используется система сборки fips. Для этого требуются Python и 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)
Это указатель на функцию динамической загружаемой двоичной точки входа.
Аргументы
ctx
указатель на контекст, который будет передан от host
к guest
и содержит ценную информацию о текущей загруженной версии, причине сбоя и пользовательских данных. Для получения дополнительной информации см. cr_plugin
.operation
какая операция выполняется, см. cr_op
.Возвращаться
CR_USER
. 0 или положительное значение, которое будет передано host
-процессу. bool cr_plugin_open(cr_plugin &ctx, const char *fullpath)
Загружает и инициализирует плагин.
Аргументы
ctx
контекст, который будет управлять внутренними данными плагина и пользовательскими данными.fullpath
Полный путь с именем файла к загружаемому двоичному файлу плагина или NULL
.Возвращаться
true
в случае успеха, false
в противном случае. void cr_set_temporary_path(cr_plugin& ctx, const std::string &path)
Устанавливает временный путь, по которому будут размещаться временные копии плагина. Должен вызываться сразу после cr_plugin_open()
. Если temporary
путь не указан, временные копии файла будут скопированы в тот же каталог, где находится исходный файл.
Аргументы
ctx
контекст, который будет управлять внутренними данными плагина и пользовательскими данными.path
полный путь к существующему каталогу, который будет использоваться для хранения временных копий плагина. int cr_plugin_update(cr_plugin &ctx, bool reloadCheck = true)
Эта функция вызовет функцию cr_main
плагина. Его следует вызывать так часто, как этого требует основная логика/приложение.
Аргументы
ctx
текущие данные контекста плагина.reloadCheck
необязательно: выполните проверку диска (stat()), чтобы узнать, нуждается ли динамическая библиотека в перезагрузке.Возвращаться
cr_main
. void cr_plugin_close(cr_plugin &ctx)
Очистка внутренних состояний, когда плагин больше не требуется.
Аргументы
ctx
текущие данные контекста плагина. cr_op
Перечисление, указывающее тип шага, выполняемого host
:
CR_LOAD
Выполняется загрузка, вызванная перезагрузкой, которую можно использовать для восстановления любого сохраненного внутреннего состояния.CR_STEP
Обновление приложения, это обычная и наиболее частая операция;CR_UNLOAD
Будет выполнена выгрузка для перезагрузки плагина, давая приложению один шанс сохранить любые необходимые данные;CR_CLOSE
Используется при закрытии плагина. Это работает как CR_UNLOAD
, но после этого не следует ожидать CR_LOAD
; cr_plugin
Структура контекста экземпляра плагина.
p
непрозрачный указатель для внутренних данных cr;userdata
могут использоваться пользователем для передачи информации между перезагрузками;version
для каждой успешной перезагрузки, начиная с 1 для первой загрузки. Версия изменится во время процесса обработки сбоя ;failure
используемый системой защиты от сбоев, будет содержать последний код ошибки сбоя, вызвавший откат. См. cr_failure
для получения дополнительной информации о возможных значениях; cr_failure
Если произойдет сбой в загружаемом двоичном файле, обработчик сбоя укажет причину сбоя одним из следующих способов:
CR_NONE
Нет ошибок;CR_SEGFAULT
Ошибка сегментации. SIGSEGV
в Linux/OSX или EXCEPTION_ACCESS_VIOLATION
в Windows;CR_ILLEGAL
В случае незаконного указания. SIGILL
в Linux/OSX или EXCEPTION_ILLEGAL_INSTRUCTION
в Windows;CR_ABORT
Прерывание, SIGBRT
в Linux/OSX, не используется в Windows;CR_MISALIGN
Ошибка шины, SIGBUS
в Linux/OSX или EXCEPTION_DATATYPE_MISALIGNMENT
в Windows;CR_BOUNDS
— EXCEPTION_ARRAY_BOUNDS_EXCEEDED
, только для Windows;CR_STACKOVERFLOW
— EXCEPTION_STACK_OVERFLOW
, только для Windows;CR_STATE_INVALIDATED
Статический сбой управления безопасностью CR_STATE
;CR_BAD_IMAGE
Плагин не является допустимым образом (т. е. компилятор все еще может его записать);CR_OTHER
Другой сигнал, только Linux;CR_USER
Ошибка пользователя (для отрицательных значений, возвращаемых cr_main
); CR_HOST
определить Это определение следует использовать перед включением cr.h
в host
. Если CR_HOST
не определен, cr.h
будет работать как файл заголовка общедоступного API, который будет использоваться в guest
реализации.
При желании CR_HOST
также может быть присвоено одно из следующих значений как способ настройки safety
режима работы для автоматического управления статическим состоянием ( CR_STATE
):
CR_SAFEST
будет проверять адрес и размер разделов данных состояния во время перезагрузок; если что-то изменится, загрузка будет отменена;CR_SAFE
Будет проверять только размер раздела состояния, это означает, что адрес статики может измениться (и лучше избегать хранения каких-либо указателей на статический материал);CR_UNSAFE
Не будет проверять ничего, кроме соответствия размера раздела, может быть не обязательно точным (растение допустимо, а уменьшение - нет), это поведение по умолчанию;CR_DISABLE
Полностью отключить автоматическое управление статическим состоянием; CR_STATE
Используется для пометки глобальной или локальной статической переменной, которая должна быть сохранена и восстановлена во время перезагрузки.
Использование
static bool CR_STATE bInitialized = false;
Вы можете определить эти макросы перед включением cr.h в хост (CR_HOST), чтобы настроить распределение памяти cr.h и другое поведение:
CR_MAIN_FUNC
: заменяет символ cr_main на имя функции, определяемое пользователем. по умолчанию: #define CR_MAIN_FUNC "cr_main"CR_ASSERT
: переопределить утверждение. по умолчанию: #define CA_ASSERT(e) Assert(e)CR_REALLOC
: переопределить realloc libc. по умолчанию: #define CR_REALLOC(ptr, size) ::realloc(ptr, size)CR_MALLOC
: переопределить malloc libc. по умолчанию: #define CR_MALLOC(размер) ::malloc(размер)CR_FREE
: переопределить свободу использования libc. по умолчанию: #define CR_FREE(ptr) ::free(ptr)CR_DEBUG
: выводит отладочные сообщения в CR_ERROR, CR_LOG и CR_TRACE.CR_ERROR
: записывает отладочные сообщения в поток stderr. по умолчанию (только CR_DEBUG): #define CR_ERROR(...) fprintf(stderr, VA_ARGS )CR_LOG
: регистрирует отладочные сообщения. по умолчанию (только CR_DEBUG): #define CR_LOG(...) fprintf(stdout, VA_ARGS )CR_TRACE
: печатает вызовы функций. по умолчанию (только CR_DEBUG): #define CR_TRACE(...) fprintf(stdout, "CR_TRACE: %sn", FUNCTION )О: О том, почему я сделал это, читайте здесь.
О: Убедитесь, что и ваш хост приложения, и ваша dll используют динамическую среду выполнения (/MD или /MDd), поскольку любые данные, размещенные в куче, должны быть освобождены с помощью одного и того же экземпляра распределителя, разделяя время выполнения между гостем и хосте, вы будете гарантировать, что используется тот же распределитель.
А: Да. Это должно работать без проблем в Windows. В Linux и OSX могут возникнуть проблемы с обработкой сбоев.
Если по какой-либо причине вам пришлось загрузить dll перед cr
, Visual Studio все равно может заблокировать PDB. Возможно, у вас возникла эта проблема, и решение здесь.
Во-первых, убедитесь, что ваша система сборки не мешает, все еще связываясь с вашей общей библиотекой. Есть так много вещей, которые могут пойти не так, и вам нужно быть уверенным, что только cr
будет иметь дело с вашей общей библиотекой. В Linux для получения дополнительной информации о том, как узнать, что происходит, проверьте эту проблему.
cr
— это перезагрузщик C
, и при работе с C он предполагает, что простые вещи в основном будут работать.
Проблема в том, как компоновщик решит переставить вещи в соответствии с количеством изменений, которые вы вносите в код. С инкрементными и локализованными изменениями у меня никогда не возникало проблем, в целом у меня почти не возникало проблем при написании обычного кода на C. Теперь, когда все становится более сложным и граничит с C++, это становится более рискованным. Если вам нужно делать сложные вещи, я предлагаю проверить RCCPP и прочитать этот PDF-файл и мою оригинальную запись в блоге о cr
здесь.
Имея всю эту информацию, вы сможете решить, что лучше подходит для вашего случая использования.
cr
За спонсорство порта cr
на MacOSX.
Дэнни Грейн
Рокас Купстис
Ной Райнхарт
Никлас Лундберг
Сепер Тагдисян
Роберт Габриэль Якабоски
@pixelherodev
Александр
Мы приветствуем ВСЕ вклады, здесь нет мелочей, даже исправление опечатки в одну букву приветствуется.
Единственное, что нам нужно, — это тщательно тестировать, поддерживать стиль кода и поддерживать актуальность документации.
Кроме того, мы принимаем и соглашаемся выпустить любой вклад под той же лицензией.
Лицензия MIT (MIT)
Авторские права (c) 2017 Дэнни Анджело Карминати Грейн
Настоящим разрешение бесплатно предоставляется любому лицу, получившему копию этого программного обеспечения и связанных с ним файлов документации («Программное обеспечение»), на использование Программного обеспечения без ограничений, включая, помимо прочего, права на использование, копирование, изменение, объединение. публиковать, распространять, сублицензировать и/или продавать копии Программного обеспечения, а также разрешать лицам, которым предоставлено Программное обеспечение, делать это при соблюдении следующих условий:
Вышеупомянутое уведомление об авторских правах и настоящее уведомление о разрешении должны быть включены во все копии или существенные части Программного обеспечения.
ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ, ГАРАНТИЯМИ ТОВАРНОЙ ЦЕННОСТИ, ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ И НЕНАРУШЕНИЯ ПРАВ. НИ ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ АВТОРЫ ИЛИ ОБЛАДАТЕЛИ АВТОРСКИХ ПРАВ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ЗА ЛЮБЫЕ ПРЕТЕНЗИИ, УБЫТКИ ИЛИ ДРУГУЮ ОТВЕТСТВЕННОСТЬ, БУДЬ В ДЕЙСТВИЯХ ПО КОНТРАКТУ, ПРАВОНАРУШЕНИЮ ИЛИ ДРУГИМ ОБРАЗОМ, ВОЗНИКАЮЩИЕ ОТ, ИЗ ИЛИ В СВЯЗИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ИЛИ ДРУГИМИ СДЕЛКАМИ, ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ.
* /
#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