/*
用於 C 的單一文件頭即時重新載入解決方案,用 C++ 編寫:
注意:此儲存庫中唯一重要的檔案是cr.h
。
該文件包含 markdown 中的文件、許可證、實作和公共 api。此存儲庫中的所有其他文件都是支援文件,可以安全地忽略。
您可以使用 vcpkg 依賴項管理器下載並安裝 cr:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install cr
vcpkg 中的 cr 連接埠由 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_close
保持一致,已棄用cr_plugin_load
並改用cr_plugin_open
。請參閱問題 #49。CR_BAD_IMAGE
,以防二進位檔案即使其時間戳發生變更仍未準備好。如果產生檔案(編譯器或複製)很慢,則可能會發生這種情況。CR_BAD_IMAGE
)。現在,版本在崩潰處理程序中減少了一次,然後在回滾期間減少了一次,然後再次增加。由於圖像不完整而導致的回滾不會錯誤地回滾兩個版本,它將繼續在同一版本上重試加載,直到圖像有效(副本或編譯器完成寫入)。如果在CR_UNLOAD
期間使用version
訊息,這可能會影響cr
目前的使用,因為它現在將是不同的值。可以在samples
目錄中找到兩個簡單範例。
第一個是一個簡單的控制台應用程序,它演示了實例和基本崩潰處理測試之間工作的一些基本靜態狀態。列印到輸出用於顯示正在發生的情況。
第二個示範如何使用 Dear ImGui 即時重新載入 opengl 應用程式。有些狀態位於主機端,而大部分程式碼位於來賓端。
範例和測試使用 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
分段錯誤。 Linux/OSX 上的SIGSEGV
或 Windows 上的EXCEPTION_ACCESS_VIOLATION
;CR_ILLEGAL
如果有非法指令。 Linux/OSX 上的SIGILL
或 Windows 上的EXCEPTION_ILLEGAL_INSTRUCTION
;CR_ABORT
中止,Linux/OSX 上為SIGBRT
,Windows 上不使用;CR_MISALIGN
匯流排錯誤、Linux/OSX 上的SIGBUS
或 Windows 上的EXCEPTION_DATATYPE_MISALIGNMENT
;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
定義此定義應在host
中包含cr.h
之前使用,如果未定義CR_HOST
,則cr.h
將作為公共 API 頭檔在guest
實作中使用。
(可選) CR_HOST
也可以定義為以下值之一,作為配置自動靜態管理( CR_STATE
) safety
操作模式的一種方式:
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) 斷言(e)CR_REALLOC
:覆蓋 libc 的 realloc。預設值:#define CR_REALLOC(ptr, size) ::realloc(ptr, size)CR_MALLOC
:覆蓋 libc 的 malloc。預設值:#define CR_MALLOC(size) ::malloc(size)CR_FREE
:覆蓋 libc 的 free。預設值:#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 上可能會有崩潰處理問題
如果出於任何原因必須在cr
之前載入 dll,Visual Studio 可能仍會鎖定 PDB。您可能遇到這個問題,解決方案就在這裡。
首先,確保您的建置系統不會因仍連結到共享庫而受到干擾。有很多事情可能會出錯,您需要確保只有cr
才能處理您的共用程式庫。在 Linux 上,有關如何查找正在發生的情況的更多信息,請檢查此問題。
cr
是C
重新載入器,處理 C 時它假設簡單的事情大部分都會起作用。
問題是連結器將如何決定根據您在程式碼中所做的更改量來重新安排內容。對於增量和本地化更改,我從來沒有遇到任何問題,一般來說,透過編寫普通的 C 程式碼,我幾乎沒有遇到任何問題。現在,當事情開始變得更加複雜並接近 C++ 時,它的風險就變得更大。如果您需要做複雜的事情,我建議檢查 RCCPP 並閱讀此 PDF 和我關於cr
的原始部落格文章。
有了所有這些信息,您將能夠決定哪個更適合您的用例。
cr
贊助商贊助將cr
移植到 MacOSX。
丹尼·格萊因
羅卡斯·庫普斯蒂斯
諾亞·萊因哈特
尼克拉斯·倫德伯格
塞佩爾·塔格迪西安
羅伯特·加布里埃爾·賈卡博斯基
@pixelherodev
亞歷山大
我們歡迎所有的貢獻,沒有任何小事可以貢獻,甚至歡迎修正一個字母的拼字錯誤。
我們唯一需要的是徹底測試、維護程式碼風格並保持文件最新。
此外,接受並同意在同一許可證下發布任何貢獻。
麻省理工學院許可證 (MIT)
版權所有 (c) 2017 丹尼·安吉洛·卡米納蒂·格雷因
特此免費授予任何獲得本軟體和相關文件文件(「軟體」)副本的人不受限制地使用本軟體,包括但不限於使用、複製、修改、合併的權利、發布、分發、再授權和/或銷售軟體的副本,並允許向其提供軟體的人員這樣做,但須滿足以下條件:
上述版權聲明和本授權聲明應包含在本軟體的所有副本或主要部分中。
本軟體以「現況」提供,不提供任何明示或暗示的保證,包括但不限於適銷性、特定用途的適用性和不侵權的保證。 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE軟體.
* /
#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