diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 321f87f..e712641 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ include(dylib) project(dylib VERSION 3.0.0 LANGUAGES CXX) if(NOT "${CMAKE_CXX_STANDARD}") - set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 11) endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 95a95de..7d960d5 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,30 +1,30 @@ -cmake_minimum_required(VERSION 3.14) - -project(dylib_example) - -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) - -# dylib fetch - -include(FetchContent) - -FetchContent_Declare( - dylib - GIT_REPOSITORY "https://github.com/martin-olivier/dylib" - GIT_TAG "v2.2.1" -) - -FetchContent_MakeAvailable(dylib) - -# build lib.cpp into a shared library - -add_library(dynamic_lib SHARED lib.cpp) - -# build main.cpp into an executable - -add_executable(dylib_example main.cpp) -target_link_libraries(dylib_example PRIVATE dylib) +cmake_minimum_required(VERSION 3.14) + +project(dylib_example) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) + +# dylib fetch + +include(FetchContent) + +FetchContent_Declare( + dylib + GIT_REPOSITORY "https://github.com/martin-olivier/dylib" + GIT_TAG "v2.2.1" +) + +FetchContent_MakeAvailable(dylib) + +# build lib.cpp into a shared library + +add_library(dynamic_lib SHARED lib.cpp) + +# build main.cpp into an executable + +add_executable(dylib_example main.cpp) +target_link_libraries(dylib_example PRIVATE dylib) diff --git a/example/README.md b/example/README.md index e4ff4f0..5abac6d 100644 --- a/example/README.md +++ b/example/README.md @@ -1,129 +1,129 @@ -# dylib example - -Here is an example about the usage of the `dylib` library in a project - -The functions and variables of our forthcoming dynamic library are located inside [lib.cpp](lib.cpp) - -```c++ -// lib.cpp - -#include - -#if defined(_WIN32) || defined(_WIN64) -#define LIB_EXPORT __declspec(dllexport) -#else -#define LIB_EXPORT -#endif - -extern "C" { - -LIB_EXPORT double pi_value = 3.14159; -LIB_EXPORT void *ptr = (void *)1; - -LIB_EXPORT double adder(double a, double b) { - return a + b; -} - -LIB_EXPORT void print_hello() { - std::cout << "Hello" << std::endl; -} - -} // extern "C" -``` - -The code that will load functions and global variables of our dynamic library at runtime is located inside [main.cpp](main.cpp) - -```c++ -// main.cpp - -#include -#include "dylib.hpp" - -int main() { - dylib lib("./", "dynamic_lib"); - - auto adder = lib.get_function("adder"); - std::cout << adder(5, 10) << std::endl; - - auto printer = lib.get_function("print_hello"); - printer(); - - double pi_value = lib.get_variable("pi_value"); - std::cout << pi_value << std::endl; - - void *ptr = lib.get_variable("ptr"); - if (ptr == (void *)1) std::cout << 1 << std::endl; - - return EXIT_SUCCESS; -} -``` - -Then, we want a build system that will: - -- Fetch `dylib` into the project -- Build [lib.cpp](lib.cpp) into a dynamic library -- Build [main.cpp](main.cpp) into an executable - -This build system is located inside [CMakeLists.txt](CMakeLists.txt) - -```cmake -# CMakeLists.txt - -cmake_minimum_required(VERSION 3.14) - -project(dylib_example) - -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) - -# dylib fetch - -include(FetchContent) - -FetchContent_Declare( - dylib - GIT_REPOSITORY "https://github.com/martin-olivier/dylib" - GIT_TAG "v2.2.1" -) - -FetchContent_MakeAvailable(dylib) - -# build lib.cpp into a shared library - -add_library(dynamic_lib SHARED lib.cpp) - -# build main.cpp into an executable - -add_executable(dylib_example main.cpp) -target_link_libraries(dylib_example PRIVATE dylib) -``` - -Let's build our code: -> Make sure to type the following commands inside the `example` folder - -```sh -cmake . -B build -cmake --build build -``` - -Let's run our code: - -```sh -# on unix, run the following command inside "build" folder -./dylib_example - -# on windows, run the following command inside "build/Debug" folder -./dylib_example.exe -``` - -You will have the following result: - -```sh -15 -Hello -3.14159 -1 -``` +# dylib example + +Here is an example about the usage of the `dylib` library in a project + +The functions and variables of our forthcoming dynamic library are located inside [lib.cpp](lib.cpp) + +```c++ +// lib.cpp + +#include + +#if defined(_WIN32) || defined(_WIN64) +#define LIB_EXPORT __declspec(dllexport) +#else +#define LIB_EXPORT +#endif + +extern "C" { + +LIB_EXPORT double pi_value = 3.14159; +LIB_EXPORT void *ptr = (void *)1; + +LIB_EXPORT double adder(double a, double b) { + return a + b; +} + +LIB_EXPORT void print_hello() { + std::cout << "Hello" << std::endl; +} + +} // extern "C" +``` + +The code that will load functions and global variables of our dynamic library at runtime is located inside [main.cpp](main.cpp) + +```c++ +// main.cpp + +#include +#include "dylib.hpp" + +int main() { + dylib lib("./", "dynamic_lib"); + + auto adder = lib.get_function("adder"); + std::cout << adder(5, 10) << std::endl; + + auto printer = lib.get_function("print_hello"); + printer(); + + double pi_value = lib.get_variable("pi_value"); + std::cout << pi_value << std::endl; + + void *ptr = lib.get_variable("ptr"); + if (ptr == (void *)1) std::cout << 1 << std::endl; + + return EXIT_SUCCESS; +} +``` + +Then, we want a build system that will: + +- Fetch `dylib` into the project +- Build [lib.cpp](lib.cpp) into a dynamic library +- Build [main.cpp](main.cpp) into an executable + +This build system is located inside [CMakeLists.txt](CMakeLists.txt) + +```cmake +# CMakeLists.txt + +cmake_minimum_required(VERSION 3.14) + +project(dylib_example) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) + +# dylib fetch + +include(FetchContent) + +FetchContent_Declare( + dylib + GIT_REPOSITORY "https://github.com/martin-olivier/dylib" + GIT_TAG "v2.2.1" +) + +FetchContent_MakeAvailable(dylib) + +# build lib.cpp into a shared library + +add_library(dynamic_lib SHARED lib.cpp) + +# build main.cpp into an executable + +add_executable(dylib_example main.cpp) +target_link_libraries(dylib_example PRIVATE dylib) +``` + +Let's build our code: +> Make sure to type the following commands inside the `example` folder + +```sh +cmake . -B build +cmake --build build +``` + +Let's run our code: + +```sh +# on unix, run the following command inside "build" folder +./dylib_example + +# on windows, run the following command inside "build/Debug" folder +./dylib_example.exe +``` + +You will have the following result: + +```sh +15 +Hello +3.14159 +1 +``` diff --git a/example/main.cpp b/example/main.cpp index 92912d8..f31b9dd 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -1,20 +1,20 @@ -#include -#include "dylib.hpp" - -int main() { - dylib lib("./", "dynamic_lib"); - - auto adder = lib.get_function("adder"); - std::cout << adder(5, 10) << std::endl; - - auto printer = lib.get_function("print_hello"); - printer(); - - double pi_value = lib.get_variable("pi_value"); - std::cout << pi_value << std::endl; - - void *ptr = lib.get_variable("ptr"); - if (ptr == (void *)1) std::cout << 1 << std::endl; - - return EXIT_SUCCESS; +#include +#include "dylib.hpp" + +int main() { + dylib lib("./", "dynamic_lib"); + + auto adder = lib.get_function("adder"); + std::cout << adder(5, 10) << std::endl; + + auto printer = lib.get_function("print_hello"); + printer(); + + double pi_value = lib.get_variable("pi_value"); + std::cout << pi_value << std::endl; + + void *ptr = lib.get_variable("ptr"); + if (ptr == (void *)1) std::cout << 1 << std::endl; + + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/include/dylib.hpp b/include/dylib.hpp index 7576088..4829155 100644 --- a/include/dylib.hpp +++ b/include/dylib.hpp @@ -22,6 +22,16 @@ #include #endif +#if (defined(_WIN32) || defined(_WIN64)) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#else +#include +#endif +#endif + #if (defined(_WIN32) || defined(_WIN64)) #define DYLIB_WIN_MAC_OTHER(win_def, mac_def, other_def) win_def #define DYLIB_WIN_OTHER(win_def, other_def) win_def @@ -201,11 +211,7 @@ class dylib { return get_variable(symbol_name.c_str()); } - struct symbols_params { - bool demangle; - }; - - std::vector symbols(symbols_params params = {.demangle = false}) const; + std::vector symbols(bool demangle = false) const; /** * @return the dynamic library handle @@ -214,7 +220,9 @@ class dylib { protected: native_handle_type m_handle{nullptr}; +#if !(defined(_WIN32) || defined(_WIN64)) int m_fd{-1}; +#endif }; #undef DYLIB_WIN_MAC_OTHER diff --git a/src/dylib.cpp b/src/dylib.cpp index 065d863..a4729af 100644 --- a/src/dylib.cpp +++ b/src/dylib.cpp @@ -17,12 +17,12 @@ #endif #else #include +#include +#include #endif #include -#include #include -#include #include "dylib.hpp" @@ -39,7 +39,22 @@ /* PRIVATE */ +std::string get_demangled_name(const char *symbol); + +#if (defined(_WIN32) || defined(_WIN64)) +std::vector get_symbols(HMODULE hModule, bool demangle); +#else std::vector get_symbols(int fd, bool demangle); +#endif + +void replace_occurrences(std::string &input, const std::string &keyword, const std::string &replacement) { + size_t pos = 0; + + while ((pos = input.find(keyword)) != std::string::npos) { + input.erase(pos, keyword.length()); + input.insert(pos, replacement); + } +} static dylib::native_handle_type open_lib(const char *path) noexcept { #if (defined(_WIN32) || defined(_WIN64)) @@ -103,17 +118,39 @@ dylib::dylib(const char *dir_path, const char *lib_name, bool decorations) { if (!m_handle) throw load_error("Could not load library '" + final_path + final_name + "'\n" + get_error_description()); +#if !(defined(_WIN32) || defined(_WIN64)) m_fd = open((final_path + final_name).c_str(), O_RDONLY); if (m_fd < 0) throw load_error("Could not load library"); +#endif } dylib::~dylib() { if (m_handle) close_lib(m_handle); +#if !(defined(_WIN32) || defined(_WIN64)) if (m_fd > -1) close(m_fd); +#endif +} + +#if !(defined(_WIN32) || defined(_WIN64)) +std::string format_symbol(std::string input) { + replace_occurrences(result, "std::__1::", "std::"); + replace_occurrences(result, "std::__cxx11::", "std::"); + + input.erase( + std::remove_if( + input.begin(), + input.end(), + ::isspace + ), input.end() + ); + + add_space_after_comma(input); + + return input; } std::string get_demangled_name(const char *symbol) { @@ -128,16 +165,18 @@ std::string get_demangled_name(const char *symbol) { throw std::bad_alloc(); res = abi::__cxa_demangle(symbol, buf, &size, &status); - - if (res) { - result = res; - buf = res; + if (!res) { + free(buf); + return ""; } - free(buf); + result = format_symbol(res); + + free(res); return result; } +#endif dylib::native_symbol_type dylib::get_symbol(const char *symbol_name) const { std::vector matching_symbols; @@ -151,7 +190,7 @@ dylib::native_symbol_type dylib::get_symbol(const char *symbol_name) const { auto symbol = locate_symbol(m_handle, symbol_name); if (symbol == nullptr) { - all_symbols = symbols({.demangle = false}); + all_symbols = symbols(); for (auto &sym : all_symbols) { auto demangled = get_demangled_name(sym.c_str()); @@ -198,9 +237,13 @@ dylib::native_handle_type dylib::native_handle() noexcept { return m_handle; } -std::vector dylib::symbols(symbols_params params) const { +std::vector dylib::symbols(bool demangle) const { try { - return get_symbols(m_fd, params.demangle); +#if !(defined(_WIN32) || defined(_WIN64)) + return get_symbols(m_fd, demangle); +#else + return get_symbols(m_handle, demangle); +#endif } catch (const std::string &e) { throw symbol_error(e); } diff --git a/src/win.cpp b/src/win.cpp new file mode 100644 index 0000000..1eddd2b --- /dev/null +++ b/src/win.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include + +#pragma comment(lib, "dbghelp.lib") + +#include +#include +#include +#include + +void replace_occurrences(std::string &input, const std::string &keyword, const std::string &replacement); + +void add_space_after_comma(std::string &input) { + std::string result; + + for (char c : input) { + if (c == ',') { + result += ", "; + } else { + result += c; + } + } + + input = result; +} + +std::string format_symbol(std::string input) { + replace_occurrences(input, "(class ", "("); + replace_occurrences(input, " get_symbols(HMODULE hModule, bool demangle) { + std::vector result; + + // Get the DOS header + PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule; + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) + throw std::string("Invalid DOS header"); + + // Get the NT headers + PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pDosHeader->e_lfanew); + if (pNTHeaders->Signature != IMAGE_NT_SIGNATURE) + throw std::string("Invalid NT headers"); + + // Get the export directory + DWORD exportDirRVA = pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (exportDirRVA == 0) + throw std::string("No export directory found"); + + PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + exportDirRVA); + + // Get the list of exported function names + DWORD* pNames = (DWORD*)((BYTE*)hModule + pExportDir->AddressOfNames); + DWORD* pFunctions = (DWORD*)((BYTE*)hModule + pExportDir->AddressOfFunctions); + WORD* pNameOrdinals = (WORD*)((BYTE*)hModule + pExportDir->AddressOfNameOrdinals); + + for (DWORD i = 0; i < pExportDir->NumberOfNames; ++i) { + const char* name = (const char*)((BYTE*)hModule + pNames[i]); + + if (!name) + continue; + + if (strcmp(name, "") == 0) + continue; + + if (demangle) { + std::string demangled = get_demangled_name(name); + if (!demangled.empty()) { + if (std::find(result.begin(), result.end(), name) == result.end()) + result.push_back(demangled); + continue; + } + } + + if (std::find(result.begin(), result.end(), name) != result.end()) + continue; + + result.push_back(name); + } + + return result; +} \ No newline at end of file diff --git a/tests/lib.cpp b/tests/lib.cpp index 88046eb..7d909d4 100644 --- a/tests/lib.cpp +++ b/tests/lib.cpp @@ -21,22 +21,22 @@ LIB_EXPORT void print_hello() { } // extern "C" -double do_you_find_me(double a, double b) { +LIB_EXPORT double do_you_find_me(double a, double b) { return a + b; } namespace toto { - double and_now(double a, double b) { + LIB_EXPORT double and_now(double a, double b) { return a + b; } - double and_now(double a, std::string b) { + LIB_EXPORT double and_now(double a, std::string b) { return a + b.size(); } } -double zaza = 12; +LIB_EXPORT double zaza = 12; namespace tata { - double zozo = 11; + LIB_EXPORT double zozo = 11; } \ No newline at end of file diff --git a/tests/tests.cpp b/tests/tests.cpp index e14b2ef..b12136a 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -2,11 +2,7 @@ #include #include "dylib.hpp" -#if (defined(_WIN32) || defined(_WIN64)) -#include -#else -#include -#endif +#include TEST(example, example_test) { testing::internal::CaptureStdout();