diff --git a/CMakeLists.txt b/CMakeLists.txt index d4aa1bf..ea5fee9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,10 +23,18 @@ add_subdirectory(external/plugify) # set(GOLM_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/module.h" - "${CMAKE_CURRENT_SOURCE_DIR}/src/module.cpp") + "${CMAKE_CURRENT_SOURCE_DIR}/src/module.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/assembly.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/assembly.cpp") add_library(${PROJECT_NAME} SHARED ${GOLM_SOURCES}) -target_link_libraries(${PROJECT_NAME} PRIVATE plugify::plugify) +set(GOLM_LINK_LIBRARIES plugify::plugify) + +if(NOT COMPILER_SUPPORTS_FORMAT) + set(GOLM_LINK_LIBRARIES ${GOLM_LINK_LIBRARIES} fmt::fmt-header-only) +endif() + +target_link_libraries(${PROJECT_NAME} PRIVATE ${GOLM_LINK_LIBRARIES}) include(GenerateExportHeader) generate_export_header(${PROJECT_NAME} EXPORT_MACRO_NAME GOLM_EXPORT EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/exports/module_export.h) @@ -37,3 +45,10 @@ if(APPLE) elseif(UNIX) target_link_options(${PROJECT_NAME} PRIVATE "-Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/sym/version_script.lds") endif() + +target_compile_definitions(${PROJECT_NAME} PRIVATE + GOLM_PLATFORM_WINDOWS=$ + GOLM_PLATFORM_APPLE=$ + GOLM_PLATFORM_LINUX=$ + BINARY_MODULE_SUFFIX="${CMAKE_SHARED_LIBRARY_SUFFIX}" + BINARY_MODULE_PREFIX="${CMAKE_SHARED_LIBRARY_PREFIX}") diff --git a/src/assembly.cpp b/src/assembly.cpp new file mode 100644 index 0000000..53a726a --- /dev/null +++ b/src/assembly.cpp @@ -0,0 +1,66 @@ +#include "assembly.h" + +#if GOLM_PLATFORM_WINDOWS +#include +#elif GOLM_PLATFORM_LINUX +#include +#elif GOLM_PLATFORM_APPLE +#include +#else +#error "Platform is not supported!" +#endif + +namespace golm { + thread_local static std::string lastError; + + std::unique_ptr Assembly::LoadFromPath(const std::filesystem::path& assemblyPath) { +#if GOLM_PLATFORM_WINDOWS + void* handle = static_cast(LoadLibraryW(assemblyPath.c_str())); +#elif GOLM_PLATFORM_LINUX || GOLM_PLATFORM_APPLE + void* handle = dlopen(assemblyPath.string().c_str(), RTLD_LAZY); +#else + void* handle = nullptr; +#endif + if (handle) { + return std::unique_ptr(new Assembly(handle)); + } +#if GOLM_PLATFORM_WINDOWS + uint32_t errorCode = GetLastError(); + if (errorCode != 0) { + LPSTR messageBuffer = nullptr; + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + lastError = std::string(messageBuffer, size); + LocalFree(messageBuffer); + } +#elif GOLM_PLATFORM_LINUX || GOLM_PLATFORM_APPLE + lastError = dlerror(); +#endif + return nullptr; + } + + std::string Assembly::GetError() { + return lastError; + } + + Assembly::Assembly(void* handle) : _handle{handle} { + } + + Assembly::~Assembly() { +#if GOLM_PLATFORM_WINDOWS + FreeLibrary(static_cast(_handle)); +#elif GOLM_PLATFORM_LINUX || GOLM_PLATFORM_APPLE + dlclose(_handle); +#endif + } + + void* Assembly::GetFunction(const char* functionName) const { +#if GOLM_PLATFORM_WINDOWS + return reinterpret_cast(GetProcAddress(static_cast(_handle), functionName)); +#elif GOLM_PLATFORM_LINUX || GOLM_PLATFORM_APPLE + return dlsym(_handle, functionName); +#else + return nullptr; +#endif + } +} diff --git a/src/assembly.h b/src/assembly.h new file mode 100644 index 0000000..7da27cb --- /dev/null +++ b/src/assembly.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include + +namespace golm { + class Assembly { + public: + static std::unique_ptr LoadFromPath(const std::filesystem::path& assemblyPath); + static std::string GetError(); + + ~Assembly(); + + void* GetFunction(const char* functionName) const; + template requires(std::is_pointer_v<_Fn> && std::is_function_v>) + _Fn GetFunction(const char* functionName) const { + return reinterpret_cast<_Fn>(GetFunction(functionName)); + } + + private: + explicit Assembly(void* handle); + + private: + void* _handle{ nullptr }; + }; +} diff --git a/src/module.cpp b/src/module.cpp index 4b3aefc..82664f5 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -1,35 +1,121 @@ +#include "assembly.h" #include "module.h" +#include +#include +#include #include using namespace plugify; +namespace fs = std::filesystem; namespace golm { + GoLanguageModule::GoLanguageModule() = default; + InitResult GoLanguageModule::Initialize(std::weak_ptr provider, const IModule& module) { - // TODO: implement + if (!(_provider = provider.lock())) { + return ErrorData{ "Provider not exposed" }; + } return InitResultData{}; } void GoLanguageModule::Shutdown() { - // TODO: implement + _assemblyMap.clear(); + _provider.reset(); } LoadResult GoLanguageModule::OnPluginLoad(const IPlugin& plugin) { - // TODO: implement - return ErrorData{ "Loading not implemented" }; + const auto entryPoint = fs::path(plugin.GetDescriptor().entryPoint); + fs::path assemblyPath = plugin.GetBaseDir() / entryPoint.parent_path() / std::format(BINARY_MODULE_PREFIX "{}" BINARY_MODULE_SUFFIX, entryPoint.filename().string()); + + auto assembly = Assembly::LoadFromPath(assemblyPath); + if (!assembly) { + return ErrorData{ std::format("Failed to load assembly: {}", Assembly::GetError()) }; + } + + bool funcFail = false; + std::vector funcErrors; + + auto* const startFunc = assembly->GetFunction("Plugify_PluginStart"); + if (!startFunc) { + funcFail = true; + funcErrors.emplace_back("Plugify_PluginStart"); + } + + auto* const endFunc = assembly->GetFunction("Plugify_PluginEnd"); + if (!endFunc) { + funcFail = true; + funcErrors.emplace_back("Plugify_PluginEnd"); + } + + if (funcFail) { + std::string funcs(funcErrors[0]); + for (auto it = std::next(funcErrors.begin()); it != funcErrors.end(); ++it) { + std::format_to(std::back_inserter(funcs), ", {}", *it); + } + return ErrorData{ std::format("Not found {} function(s)", funcs) }; + } + + funcFail = false; + funcErrors.clear(); + + const auto& exportedMethods = plugin.GetDescriptor().exportedMethods; + std::vector methods; + methods.reserve(exportedMethods.size()); + + for (const auto& method : exportedMethods) { + if (auto* const func = assembly->GetFunction(method.funcName.c_str())) { + methods.emplace_back(method.name, func); + } + else { + funcFail = true; + funcErrors.emplace_back(method.name); + } + } + if (funcFail) { + std::string funcs(funcErrors[0]); + for (auto it = std::next(funcErrors.begin()); it != funcErrors.end(); ++it) { + std::format_to(std::back_inserter(funcs), ", {}", *it); + } + return ErrorData{ std::format("Not found {} method function(s)", funcs) }; + } + + const auto [_, result] = _assemblyMap.try_emplace(plugin.GetName(), std::move(assembly), startFunc, endFunc); + if (!result) { + return ErrorData{ std::format("Plugin name duplicate") }; + } + + return LoadResultData{ std::move(methods) }; } void GoLanguageModule::OnPluginStart(const IPlugin& plugin) { - // TODO: implement + if (const auto it = _assemblyMap.find(plugin.GetName()); it != _assemblyMap.end()) { + const auto& assemblyHolder = std::get(*it); + assemblyHolder.GetStartFunc()(); + } } void GoLanguageModule::OnPluginEnd(const IPlugin& plugin) { - // TODO: implement + if (const auto it = _assemblyMap.find(plugin.GetName()); it != _assemblyMap.end()) { + const auto& assemblyHolder = std::get(*it); + assemblyHolder.GetEndFunc()(); + } } void GoLanguageModule::OnMethodExport(const IPlugin& plugin) { // TODO: implement } + GoLanguageModule::AssemblyHolder::AssemblyHolder(std::unique_ptr assembly, StartFunc startFunc, EndFunc endFunc) : _assembly{ std::move(assembly) }, _startFunc{ startFunc }, _endFunc{ endFunc } { + } + + StartFunc GoLanguageModule::AssemblyHolder::GetStartFunc() const { + return _startFunc; + } + + EndFunc GoLanguageModule::AssemblyHolder::GetEndFunc() const { + return _endFunc; + } + GoLanguageModule g_golm; extern "C" diff --git a/src/module.h b/src/module.h index f0cfa5a..d1effb4 100644 --- a/src/module.h +++ b/src/module.h @@ -1,9 +1,17 @@ +#pragma once + #include +#include namespace golm { + using StartFunc = void (*)(); + using EndFunc = void (*)(); + + class Assembly; + class GoLanguageModule final : public plugify::ILanguageModule { public: - GoLanguageModule() = default; + GoLanguageModule(); // ILanguageModule plugify::InitResult Initialize(std::weak_ptr provider, const plugify::IModule& module) override; @@ -12,5 +20,22 @@ namespace golm { void OnPluginStart(const plugify::IPlugin& plugin) override; void OnPluginEnd(const plugify::IPlugin& plugin) override; void OnMethodExport(const plugify::IPlugin& plugin) override; + + private: + class AssemblyHolder { + public: + AssemblyHolder(std::unique_ptr assembly, StartFunc startFunc, EndFunc endFunc); + + StartFunc GetStartFunc() const; + EndFunc GetEndFunc() const; + + private: + std::unique_ptr _assembly; + StartFunc _startFunc{ nullptr }; + EndFunc _endFunc{ nullptr }; + }; + + std::shared_ptr _provider; + std::unordered_map _assemblyMap; }; } diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..7e9bb2a --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,6 @@ +* +!*.go +!*/ +!go.mod +!*.pplugin +!.gitignore diff --git a/test/example_golang_plugin/example_golang_plugin.pplugin b/test/example_golang_plugin/example_golang_plugin.pplugin new file mode 100644 index 0000000..49f4b23 --- /dev/null +++ b/test/example_golang_plugin/example_golang_plugin.pplugin @@ -0,0 +1,33 @@ +{ + "fileVersion": 1, + "version": 1, + "versionName": "1.0", + "friendlyName": "PluginGO", + "description": "An example of a plugin. This can be used as a starting point when creating your own plugin.", + "createdBy": "untrustedmodders", + "createdByURL": "https://github.com/untrustedmodders/", + "docsURL": "https://github.com/orgs/untrustedmodders/README.md", + "downloadURL": "https://github.com/orgs/untrustedmodders/example-repo.zip", + "updateURL": "https://github.com/untrustedmodders/plugify/issues", + "entryPoint": "bin/example_golang_plugin", + "supportedPlatforms": [], + "languageModule": { + "name": "golang" + }, + "dependencies": [], + "exportedMethods": [ + { + "name": "SayHello", + "funcName": "SayHello", + "paramTypes": [ + { + "type": "int32", + "name": "count" + } + ], + "retType": { + "type": "bool" + } + } + ] +} \ No newline at end of file diff --git a/test/example_golang_plugin/go.mod b/test/example_golang_plugin/go.mod new file mode 100644 index 0000000..549fb0d --- /dev/null +++ b/test/example_golang_plugin/go.mod @@ -0,0 +1,3 @@ +module plugify-plugin + +go 1.21.5 diff --git a/test/example_golang_plugin/main.go b/test/example_golang_plugin/main.go new file mode 100644 index 0000000..f1a1e2f --- /dev/null +++ b/test/example_golang_plugin/main.go @@ -0,0 +1,30 @@ +package main + +import "C" +import ( + "fmt" + "plugify-plugin/plugify" +) + +func init() { + plugify.OnPluginStart(func() { + fmt.Println("Go:OnPluginStart") + }) + + plugify.OnPluginEnd(func() { + fmt.Println("Go:OnPluginEnd") + }) +} + +//export SayHello +func SayHello(count int64) bool { + if count > 10 { + return false + } + + fmt.Printf("Hello %d times!\n", count) + + return true +} + +func main() {} diff --git a/test/example_golang_plugin/plugify/plugify.go b/test/example_golang_plugin/plugify/plugify.go new file mode 100644 index 0000000..d1e5dbc --- /dev/null +++ b/test/example_golang_plugin/plugify/plugify.go @@ -0,0 +1,34 @@ +package plugify + +import "C" + +type PluginStartCallback func() +type PluginEndCallback func() + +type Plugify struct { + fnPluginStartCallback PluginStartCallback + fnPluginEndCallback PluginEndCallback +} + +var plugify Plugify = Plugify{ + fnPluginStartCallback: func() {}, + fnPluginEndCallback: func() {}, +} + +//export Plugify_PluginStart +func Plugify_PluginStart() { + plugify.fnPluginStartCallback() +} + +func OnPluginStart(fn PluginStartCallback) { + plugify.fnPluginStartCallback = fn +} + +//export Plugify_PluginEnd +func Plugify_PluginEnd() { + plugify.fnPluginEndCallback() +} + +func OnPluginEnd(fn PluginEndCallback) { + plugify.fnPluginEndCallback = fn +}