├── test ├── res.txt ├── res2.txt ├── resources │ └── res.txt ├── CMakeLists.txt ├── CMakeSettings.json └── src │ └── test_main.cpp ├── .gitignore ├── CMakeSettings.json ├── LICENSE ├── CMakeLists.txt ├── README.md ├── include └── ResourceManager │ └── ResourceHandle.h └── src └── embed_resource.cpp /test/res.txt: -------------------------------------------------------------------------------- 1 | this is: res -------------------------------------------------------------------------------- /test/res2.txt: -------------------------------------------------------------------------------- 1 | this is: res2 -------------------------------------------------------------------------------- /test/resources/res.txt: -------------------------------------------------------------------------------- 1 | this is: resources/res -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | build/ 3 | bld/ 4 | build-gcc/ 5 | build_qtcreator/ 6 | buildQt/ 7 | CMakeLists.txt.user* 8 | QtOpenGL.pro.user* -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Johnny Borov . Released under MIT License. 2 | 3 | cmake_minimum_required(VERSION 3.5 FATAL_ERROR) 4 | 5 | project(test_main) 6 | 7 | if(MSVC) 8 | add_compile_options(-WX -W4) 9 | elseif(MINGW) 10 | add_compile_options(-Werror -Wall -Wextra -Wno-ignored-qualifiers -pedantic) 11 | endif() 12 | 13 | add_subdirectory(".." "${CMAKE_BINARY_DIR}/ResourceManager") 14 | include_directories(${RESOURCE_MANAGER_INCLUDE_DIRS}) 15 | 16 | rm_embed_resources(RESOURCES "res.txt" "res2.txt" "resources/res.txt") 17 | 18 | #add_definitions(-DRM_NO_EXCEPTIONS) 19 | #set_source_files_properties(${RESOURCE_MANAGER_RESOURCES_CONFIG_FILE} PROPERTIES COMPILE_DEFINITIONS RM_NO_EXCEPTIONS) 20 | 21 | add_executable(${PROJECT_NAME} src/test_main.cpp ${RESOURCES}) -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\build\\MSVC-${name}", 9 | "installRoot": "${projectDir}\\build\\MSVC-INSTALL", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "-v", 12 | "ctestCommandArgs": "" 13 | }, 14 | { 15 | "name": "Release", 16 | "generator": "Ninja", 17 | "configurationType": "Release", 18 | "inheritEnvironments": [ "msvc_x64_x64" ], 19 | "buildRoot": "${projectDir}\\build\\MSVC-${name}", 20 | "installRoot": "${projectDir}\\build\\MSVC-INSTALL", 21 | "cmakeCommandArgs": "", 22 | "buildCommandArgs": "-v", 23 | "ctestCommandArgs": "" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /test/CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\build\\MSVC-${name}", 9 | "installRoot": "${projectDir}\\build\\MSVC-INSTALL", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "-v", 12 | "ctestCommandArgs": "" 13 | }, 14 | { 15 | "name": "Release", 16 | "generator": "Ninja", 17 | "configurationType": "Release", 18 | "inheritEnvironments": [ "msvc_x64_x64" ], 19 | "buildRoot": "${projectDir}\\build\\MSVC-${name}", 20 | "installRoot": "${projectDir}\\build\\MSVC-INSTALL", 21 | "cmakeCommandArgs": "", 22 | "buildCommandArgs": "-v", 23 | "ctestCommandArgs": "" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /test/src/test_main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Johnny Borov . Released under MIT License. 2 | 3 | #include 4 | #include "ResourceManager/ResourceHandle.h" 5 | 6 | void testInvalidResource() { 7 | ResourceHandle rhdi("i_dont_exist"); // -DRM_NO_EXCEPTIONS to disable throw on invalid recource 8 | std::cout << "rhdi is valid = " << rhdi.isValid() << '\n'; 9 | } 10 | 11 | int main() { 12 | try { 13 | ResourceHandle rh1("res.txt"); 14 | ResourceHandle rh2("res2.txt"); 15 | std::cout << "rh1 c_str = " << rh1.c_str() << '\n'; 16 | std::cout << "rh2 string = " << rh2.string() << '\n'; 17 | 18 | ResourceHandle rh3("resources/res.txt"); 19 | std::cout << "rh3 is valid = " << rh3.isValid() << '\n'; 20 | std::cout << "rh3 data = " << rh3.data() << '\n'; 21 | std::cout << "rh3 size = " << rh3.size() << '\n'; 22 | 23 | testInvalidResource(); 24 | } catch (const ResourceNotFound& e) { 25 | std::cout << e.what() << '\n'; 26 | } 27 | 28 | std::cin.get(); 29 | return 0; 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Johnny Borov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Johnny Borov . Released under MIT License. 2 | 3 | cmake_minimum_required(VERSION 3.5 FATAL_ERROR) 4 | project(ResourceManager) 5 | 6 | add_executable(embed_resource src/embed_resource.cpp) 7 | 8 | 9 | set(RESOURCE_MANAGER_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) 10 | 11 | # this file contains defenition for ResourceHandle constructor 12 | set(RESOURCE_MANAGER_RESOURCES_CONFIG_FILE "RM_generated_files/__resources__config.cpp" PARENT_SCOPE) 13 | 14 | function(rm_embed_resources output_resources_list) 15 | if(${ARGC} EQUAL 1) 16 | message(FATAL_ERROR "ResourceManager::rm_embed_resources: No resources provided") 17 | else() 18 | set(input_files_list) 19 | set(output_files_list) 20 | foreach(input_file_name ${ARGN}) 21 | file(RELATIVE_PATH relative_to_binary_input_file_name "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${input_file_name}") 22 | set(output_file_name "RM_generated_files/${input_file_name}.cpp") 23 | get_filename_component(output_file_dir ${output_file_name} DIRECTORY) 24 | 25 | # this runs only if input_file_name file changed since last time or output_file_name doesnt exist 26 | add_custom_command( 27 | OUTPUT "${output_file_name}" 28 | COMMAND ${CMAKE_COMMAND} -E make_directory "${output_file_dir}" 29 | COMMAND embed_resource "-data" "${input_file_name}" "${relative_to_binary_input_file_name}" "${output_file_name}" 30 | DEPENDS "${input_file_name}" 31 | VERBATIM) 32 | 33 | list(APPEND input_files_list "${input_file_name}") 34 | list(APPEND output_files_list "${output_file_name}") 35 | endforeach() 36 | 37 | # this runs only if one or more output_file_name file from the loop before changed since last time 38 | # or if RESOURCE_MANAGER_RESOURCES_CONFIG_FILE doesnt exist 39 | # there's no need to call cmake -E make_directory because its already created by this time 40 | add_custom_command( 41 | OUTPUT "${RESOURCE_MANAGER_RESOURCES_CONFIG_FILE}" 42 | COMMAND embed_resource "-config" "${input_files_list}" "${RESOURCE_MANAGER_RESOURCES_CONFIG_FILE}" 43 | DEPENDS ${output_files_list} 44 | VERBATIM) 45 | 46 | list(APPEND output_files_list "${RESOURCE_MANAGER_RESOURCES_CONFIG_FILE}") 47 | set(${output_resources_list} ${output_files_list} PARENT_SCOPE) 48 | endif() 49 | endfunction() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ResourceManager 2 | ResourceManager is a light cross-platform C++ CMake library to embed resources into executables or libraries. 3 | Light and cross-platform here means that it only depends on C++ standard library and CMake and doesn't use anything system specific. 4 | Any type of resource files is possible for embedding (text files or binary files like images or any other data). 5 | 6 | It's based on compiling binary representation of resource files into C++ global byte array and provides easy and convenient means 7 | to access this array from C++ code. 8 | 9 | # To use ResourceManager 10 | 1) Add it as a subdirectory into your project: 11 | ``` 12 | add_subdirectory("path to ResourceManager") 13 | ``` 14 | 2) Include its include directory (RESOURCE_MANAGER_INCLUDE_DIRS is defined after you add subdirectory): 15 | ``` 16 | include_directories(${RESOURCE_MANAGER_INCLUDE_DIRS}) 17 | ``` 18 | 3) Run rm_embed_resources command defined in subdirectory to generate source files containing resource files data: 19 | ``` 20 | rm_embed_resources(RESOURCES "res.txt" "src/main.cpp" "images/myimage.png") 21 | ``` 22 | 4) Use the now defined RESOURCES list-variable to add those source into your executable/library: 23 | ``` 24 | add_executable(MyProject src/main.cpp ${RESOURCES}) 25 | ``` 26 | 5) Include the header into your C++ code: 27 | ``` 28 | #include "ResourceManager/ResourceHandle.h" 29 | ``` 30 | 6) You can now use ResourceHandle class to access the resources binary data from C++: 31 | ``` 32 | ResourceHandle resource_handle("res.txt"); 33 | std::cout << "res.txt contents: " << resource_handle.c_str() << '\n'; 34 | std::cout << "res.txt length: " << resource_handle.length() << '\n'; 35 | ResourceHandle code_of_main_cpp("src/main.cpp"); 36 | std::cout << "source code of main.cpp:\n" << code_of_main_cpp.string() << '\n'; 37 | ResourceHandle my_image("images/myimage.png"); 38 | doSomethingWithBinaryDataOfMyImage(my_image.data(), my_image.size()); 39 | ``` 40 | 41 | # Additional information 42 | In the test forlder of this repository there is a simple cmake project which you can use as an example: 43 | [CMakeLists.txt](test/CMakeLists.txt) [test_main.cpp](test/src/test_main.cpp) 44 | 45 | More information about ResourceHandle class usage is avaliable in its header 46 | [ResourceHandle.h](include/ResourceManager/ResourceHandle.h) 47 | 48 | # Author 49 | Johnny Borov JohnnyBorov@gmail.com 50 | 51 | # Licence 52 | MIT License. See [LICENSE](LICENSE) file. 53 | -------------------------------------------------------------------------------- /include/ResourceManager/ResourceHandle.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Johnny Borov . Released under MIT License. 2 | 3 | #ifndef RM_RESOURCE_HANDLE_H 4 | #define RM_RESOURCE_HANDLE_H 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // This exception type is thrown if ResourceHandle constructor gets invalid resource name. 11 | // Compile with -DRM_NO_EXCEPTIONS to disable throwing on invalid resource name. 12 | class ResourceNotFound : public std::exception { 13 | public: 14 | ResourceNotFound(std::string resource_name) : m_message{"ResourceManager: Resource not found: " + resource_name} {} 15 | virtual const char* what() const noexcept { return m_message.c_str(); } 16 | 17 | private: 18 | const std::string m_message; 19 | }; 20 | 21 | 22 | // This class holds a pointer to the beginning of binary data for the requested resource 23 | // and the length/size (in bytes) of this data. (length and size are the same thing). 24 | // ------------------------------------------------------------------------------------------- 25 | // If constructor is called with an invalid resource name ResourceNotFound exception is thrown 26 | // Compile with -DRM_NO_EXCEPTIONS to disable throwing on invalid resource name. 27 | // ------------------------------------------------------------------------------------------- 28 | // If exceptions are disabled and constructor is called with an invalid resource name 29 | // then the pointer to the data equials nullptr and length/size equals 0. 30 | // In this case you can use isValid() to determine whether construction was successful or not. 31 | // =========================================================================================== 32 | // Avaliable functions: 33 | // -- const bool isValid() const noexcept: 34 | // # returns true if construction was successful, false otherwise. 35 | // # 36 | // -- const unsigned char* const data() const noexcept: 37 | // # returns the pointer to the beginning of binary data or nullptr if construction failed. 38 | // # The data is null-terminated in the end. 39 | // # 40 | // -- const char* const c_str() const noexcept: 41 | // # returns the pointer to the beginning of binary data or nullptr if construction failed. 42 | // # The data is null-terminated in the end. 43 | // # 44 | // -- std::string string() const: 45 | // # returns std::string based on the binary data with same length as the data. 46 | // # If there are zeroes in the middle of the data string will contain them anyway. 47 | // # 48 | // -- const size_t size() const noexcept: 49 | // # returns size of the data in bytes or 0 if construction failed. 50 | // # Null-terminator is not taken into account. 51 | // # 52 | // -- const size_t length() const noexcept: 53 | // # same as size(). 54 | // # 55 | // =========================================================================================== 56 | class ResourceHandle { 57 | public: 58 | ResourceHandle(std::string resource_name); 59 | 60 | const bool isValid() const noexcept { if (m_data_start) return true; else return false; } 61 | 62 | const size_t size() const noexcept { return m_data_len; } 63 | const size_t length() const noexcept { return m_data_len; } 64 | 65 | const unsigned char* const data() const noexcept { return m_data_start; } 66 | 67 | const char* const c_str() const noexcept { return reinterpret_cast(m_data_start); } 68 | std::string string() const { return std::string(reinterpret_cast(m_data_start), m_data_len); } 69 | 70 | private: 71 | const unsigned char* m_data_start; 72 | size_t m_data_len; 73 | }; 74 | 75 | #endif // RM_RESOURCE_HANDLE_H -------------------------------------------------------------------------------- /src/embed_resource.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Johnny Borov . Released under MIT License. 2 | 3 | #include 4 | #include 5 | 6 | void generateResourceDataSourceFile(char* resource_name, char* resource_file_name, char* output_file_name); 7 | void generateResourcesConfigSourceFile(char* resource_names_list, char* config_file_name); 8 | std::string modifyFileName(std::string file_name); 9 | 10 | 11 | int main(int argc, char* argv[]) { 12 | // working directory = CMAKE_BINARY_DIR when called from cmake custom command 13 | if (std::string(argv[1]) == "-data") { 14 | if (argc == 5) 15 | generateResourceDataSourceFile(argv[2], argv[3], argv[4]); 16 | else 17 | return -1; 18 | } else if (std::string(argv[1]) == "-config") { 19 | if (argc == 4) 20 | generateResourcesConfigSourceFile(argv[2], argv[3]); 21 | else 22 | return -1; 23 | } 24 | 25 | return 0; 26 | } 27 | 28 | 29 | // generate a cpp file (e.g. resources/res.txt.cpp) containing global extern array of const unsigned char 30 | // of binary data read from the requested input file (CMAKE_SOURCE_DIR/resources/res.txt) plus null-terminator 31 | // in the end of data and a global extern const size_t size of this data in bytes (without null-terminator) 32 | void generateResourceDataSourceFile(char* resource_name, char* resource_file_name, char* output_file_name) { 33 | std::string name{ resource_name }; // e.g. "resources/res.txt" 34 | std::string modified_name = modifyFileName(name); // becomes "resources__slash__res__dot__txt" 35 | 36 | std::ifstream ifs{ resource_file_name, std::ios::binary }; // e.g. reads from CMAKE_SOURCE_DIR/resources/res.txt 37 | std::ofstream ofs{ output_file_name }; // e.g. writes to resources/res.txt.cpp 38 | 39 | ofs << "#include \n"; 40 | ofs << "extern const unsigned char _resource_" << modified_name << "_data[] = {\n"; 41 | 42 | int line_count = 0; 43 | char c; 44 | while (ifs.get(c)) { 45 | ofs << "0x" << std::hex << (c & 0xff) << ", "; 46 | 47 | if (++line_count == 10) { 48 | ofs << '\n'; 49 | line_count = 0; 50 | } 51 | } 52 | 53 | ofs << "\'\\0\'"; // null-terminator in case data is going to be interprited as a c string 54 | ofs << "};\n"; 55 | // -1 excludes null-terminator 56 | ofs << "extern const size_t _resource_" << modified_name << "_len = sizeof(_resource_" << modified_name << "_data) - 1;\n"; 57 | } 58 | 59 | 60 | // generate a cpp file (e.g. __resources__config.cpp) that defines ResourceHandle constructor. 61 | // The defenition contains the hardcoded mapping of original resource names (e.g. "resources/res.txt") 62 | // to their corresponding global extern variables names. Thus it returns a handle which contains pointer to 63 | // the beginning of global extern array of const unsigned char and its size (see -data option description) 64 | void generateResourcesConfigSourceFile(char* resource_names_list, char* config_file_name) { 65 | std::ofstream ofs(config_file_name); // e.g. writes to __resources__config.cpp 66 | 67 | ofs << 68 | "#include \"ResourceManager/ResourceHandle.h\"\n" 69 | "\n" 70 | "ResourceHandle::ResourceHandle(std::string resource_name) {\n" 71 | " "; 72 | 73 | 74 | std::string resource_names{resource_names_list}; // e.g. "res.txt;res2.txt;resources/res.txt" 75 | size_t length = resource_names.length(); 76 | size_t start_pos = 0; 77 | do { 78 | size_t end_pos = resource_names.find_first_of(';', start_pos); 79 | if (end_pos == std::string::npos) 80 | end_pos = length; 81 | 82 | // e.g. "res.txt;res2.txt;resources/res.txt" -> "res.txt" -> "res__dot__txt" 83 | std::string name = resource_names.substr(start_pos, end_pos - start_pos); 84 | std::string modified_name = modifyFileName(name); 85 | 86 | ofs << 87 | "if (resource_name == \"" << name << "\") {\n" 88 | " extern const unsigned char _resource_" << modified_name << "_data[];\n" 89 | " extern const size_t _resource_" << modified_name << "_len;\n" 90 | " m_data_start = _resource_" << modified_name << "_data;\n" 91 | " m_data_len = _resource_" << modified_name << "_len;\n" 92 | " } else "; 93 | 94 | start_pos = end_pos + 1; // e.g. start next read from res2... position in "res.txt;res2.txt;resources/res.txt" 95 | } while (start_pos < length); 96 | 97 | 98 | ofs << 99 | "{\n" 100 | "#ifdef RM_NO_EXCEPTIONS\n" 101 | " m_data_start = nullptr;\n" 102 | " m_data_len = 0;\n" 103 | "#else\n" 104 | " throw ResourceNotFound{resource_name};\n" 105 | "#endif\n" 106 | " }\n" 107 | "}\n"; 108 | } 109 | 110 | 111 | // replace symbols that cant be used in c++ identeficator name 112 | // e.g. "resources/res.txt" -> "resources__slash__res__dot__txt" 113 | std::string modifyFileName(std::string file_name) { 114 | size_t search_from_pos = 0; 115 | size_t replace_from_pos; 116 | while ((replace_from_pos = file_name.find_first_of(".- /\\", search_from_pos)) != std::string::npos) { 117 | switch (file_name[replace_from_pos]) { 118 | case '.': 119 | file_name.replace(replace_from_pos, 1, "__dot__"); 120 | search_from_pos = replace_from_pos + 7; // shift len(__dot__) = 5 symbols 121 | break; 122 | case '-': 123 | file_name.replace(replace_from_pos, 1, "__dash__"); 124 | search_from_pos = replace_from_pos + 8; 125 | break; 126 | case ' ': 127 | file_name.replace(replace_from_pos, 1, "__space__"); 128 | search_from_pos = replace_from_pos + 9; 129 | break; 130 | case '/': 131 | file_name.replace(replace_from_pos, 1, "__slash__"); 132 | search_from_pos = replace_from_pos + 9; 133 | break; 134 | case '\\': 135 | file_name.replace(replace_from_pos, 1, "__bslash__"); 136 | search_from_pos = replace_from_pos + 10; 137 | break; 138 | } 139 | } 140 | 141 | return file_name; 142 | } --------------------------------------------------------------------------------