diff --git a/include/cpp2util.h b/include/cpp2util.h index 0c40e1754..65a055550 100644 --- a/include/cpp2util.h +++ b/include/cpp2util.h @@ -283,6 +283,15 @@ #endif +#if defined(_WIN32) +#define CPPFRONTAPI __declspec(dllexport) +#else +#define CPPFRONTAPI __attribute__ ((visibility ("default"))) +#endif + +#define CPP2_C_API extern "C" CPPFRONTAPI + + namespace cpp2 { diff --git a/source/reflect.h b/source/reflect.h index f88c17704..db0d76a5f 100644 --- a/source/reflect.h +++ b/source/reflect.h @@ -44,11 +44,7 @@ class compiler_services_data; // Reflection and meta //=========================================================================== -#ifndef CPPFRONT_LOAD_METAFUNCTION_IMPL_HEADER -#define CPPFRONT_LOAD_METAFUNCTION_IMPL_HEADER "reflect_load_metafunction_never.h" -#endif - -#include CPPFRONT_LOAD_METAFUNCTION_IMPL_HEADER +#include "reflect_load_metafunction.h" #include "parse.h" diff --git a/source/reflect.h2 b/source/reflect.h2 index cb5075fc1..9157648ef 100644 --- a/source/reflect.h2 +++ b/source/reflect.h2 @@ -15,11 +15,7 @@ // Reflection and meta //=========================================================================== -#ifndef CPPFRONT_LOAD_METAFUNCTION_IMPL_HEADER -#define CPPFRONT_LOAD_METAFUNCTION_IMPL_HEADER "reflect_load_metafunction_never.h" -#endif - -#include CPPFRONT_LOAD_METAFUNCTION_IMPL_HEADER +#include "reflect_load_metafunction.h" #include "parse.h" diff --git a/source/reflect_load_metafunction.h b/source/reflect_load_metafunction.h new file mode 100644 index 000000000..26a09301b --- /dev/null +++ b/source/reflect_load_metafunction.h @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include +#include "cpp2util.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#else +#include +#endif // _WIN32 + +namespace cpp2::meta { + +class dll +{ +public: + dll(std::string const& path) + { +#ifdef _WIN32 + handle_ = static_cast(LoadLibraryA(path.c_str())); +#else + handle_ = static_cast(dlopen(path.c_str(), RTLD_NOW|RTLD_LOCAL)); +#endif // _WIN32 + // TODO(Herb/Johel): Decide a user-friendlier way of reporting this. + if(handle_ == nullptr) + std::cerr << "Error while loading DLL '" << path << "': " << get_last_error_() << '\n'; + } + + ~dll() noexcept + { + if(handle_ != nullptr) + { +#ifdef _WIN32 + FreeLibrary(static_cast(handle_)); +#else + dlclose(handle_); +#endif // _WIN32 + } + } + + // Uncopyable + dll(dll&) = delete; + dll(dll const&) = delete; + auto operator=(dll const&) -> dll& = delete; + // Unmovable + dll(dll&& from) = delete; + auto operator=(dll&& from) -> dll& = delete; + + auto is_open() noexcept -> bool { return handle_ != nullptr; } + + template + auto get_alias(std::string const& name) noexcept -> T* + { +#ifdef _WIN32 + auto symbol = GetProcAddress(static_cast(handle_), name.c_str()); +#else + auto symbol = dlsym(handle_, name.c_str()); + if(symbol == nullptr) + { + // Some platforms export with additional underscore, so try that. + auto const us_name = "_" + name; + symbol = dlsym(handle_, us_name.c_str()); + } +#endif // _WIN32 + // TODO(Herb/Johel): Decide a user-friendlier way of reporting this. + if(symbol == nullptr) + std::cerr << "Error while looking up DLL symbol '" << name << "': " << get_last_error_() << '\n'; + return function_cast_(symbol); + } +private: + void* handle_{nullptr}; + + template + static auto function_cast_(auto ptr) noexcept -> T { + using generic_function_ptr = void (*)(void); + return reinterpret_cast(reinterpret_cast(ptr)); + } + + static auto get_last_error_() noexcept -> std::string { +#ifdef _WIN32 + DWORD errorMessageID = GetLastError(); + if(errorMessageID == 0) + return {}; // No error message has been recorded + LPSTR messageBuffer = nullptr; + auto size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + errorMessageID, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + &messageBuffer, + 0, + nullptr + ); + std::string message(messageBuffer, static_cast(size)); + LocalFree(messageBuffer); + return message; +#else + return std::string{dlerror()}; +#endif // _WIN32 + } + +}; + + +// Load metafunction by opening DLL with OS APIs +// +// The ':'-separated library paths +// are read from the environment variable +// 'CPPFRONT_METAFUNCTION_LIBRARIES' +auto load_metafunction(std::string const& name) -> std::function +{ + // FIXME: On Windows, using this approach with the system apis not set to utf8, will + // break if a metafunction library contains unicode codepoints in its name, a proper + // way to handle this would be to use _wgetenv and use wchar_t strings for the dll opening + // function + auto cpp1_libraries_cstr = std::getenv("CPPFRONT_METAFUNCTION_LIBRARIES"); + if (!cpp1_libraries_cstr) { + return {}; + } + + auto cpp1_libraries = std::string_view{cpp1_libraries_cstr}; + auto cpp1_name = "cpp2_metafunction_" + name; + + while (!cpp1_libraries.empty()) + { + auto colon = cpp1_libraries.find(':'); + auto lib_path = cpp1_libraries.substr(0, colon); + cpp1_libraries.remove_prefix(lib_path.size() + unsigned(colon != lib_path.npos)); + + auto lib = std::make_shared(std::string(lib_path)); + if(!lib->is_open()) + continue; + + if (auto* fun = lib->get_alias(cpp1_name); fun != nullptr) + { + return [ + fun = fun, + lib = lib + ](type_declaration& t) + { + fun(static_cast(&t)); + }; + } + } + + return {}; +} + +} diff --git a/source/reflect_load_metafunction_boost_dll.h b/source/reflect_load_metafunction_boost_dll.h deleted file mode 100644 index 299671807..000000000 --- a/source/reflect_load_metafunction_boost_dll.h +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "cpp2util.h" - -namespace cpp2::meta { - -// Load metafunction using Boost.DLL -// -// The environment variable 'CPPFRONT_METAFUNCTION_LIBRARIES' -// is read and interpreted as ':'-separated library paths -// where a metafunction symbol is looked up at -auto load_metafunction(std::string const& name) -> std::function -{ - auto cpp1_libraries_cstr = std::getenv("CPPFRONT_METAFUNCTION_LIBRARIES"); - if (!cpp1_libraries_cstr) { - return {}; - } - - auto cpp1_libraries = std::string_view{cpp1_libraries_cstr}; - auto cpp1_name = "cpp2_metafunction_" + name; - - while (!cpp1_libraries.empty()) - { - auto colon = cpp1_libraries.find(':'); - auto lib_path = cpp1_libraries.substr(0, colon); - cpp1_libraries.remove_prefix(lib_path.size() + unsigned(colon != lib_path.npos)); - - auto lib = boost::dll::shared_library(lib_path, boost::dll::load_mode::append_decorations); - if (lib.has(cpp1_name)) - { - return [ - fun = lib.get_alias(cpp1_name), - lib = std::move(lib) - ](type_declaration& t) - { - fun(t); - }; - } - } - - return {}; -} - -} diff --git a/source/reflect_load_metafunction_never.h b/source/reflect_load_metafunction_never.h deleted file mode 100644 index c0d236d1b..000000000 --- a/source/reflect_load_metafunction_never.h +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include -#include "cpp2util.h" - -namespace cpp2::meta { - -// Protocol of cppfront for program-defined metafunctions -// -// Cppfront lowers a metafunction named 'f' as the `extern "C"` symbol 'cpp2_metafunction_f' -// -// The input to 'load_metafunction' is the id-expression in the meta-functions-list -// -// `load_metafunction' looks up the metafunction in an implementation-defined way -// -// The output of 'load_metafunction' -// is a function that forwards to the looked up metafunction, if found, -// and an empty function otherwise - -// Returns an empty function -auto load_metafunction(std::string const&) - -> std::function -{ - return {}; -} - -} diff --git a/source/to_cpp1.h b/source/to_cpp1.h index 0973f578b..50dad2d1d 100644 --- a/source/to_cpp1.h +++ b/source/to_cpp1.h @@ -6448,11 +6448,12 @@ class cppfront { auto identifier = print_to_string(*n.identifier); printer.print_extra( - "\nextern \"C\" constexpr auto cpp2_metafunction_" + "\nCPP2_C_API void cpp2_metafunction_" + identifier - + " = &" + + "(void* t) { " + identifier - + ";" + + "(*static_cast(t));" + + " }" ); } }