Skip to content

Commit fa1f4f5

Browse files
authored
Merge pull request #2 from fasiondog/feature/plugin
Feature/plugin
2 parents e3c4cae + 7d3cf6d commit fa1f4f5

File tree

11 files changed

+395
-8
lines changed

11 files changed

+395
-8
lines changed

cppcheck.cppcheck

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@
1313
<library>boost</library>
1414
<library>sqlite3</library>
1515
</libraries>
16+
<suppressions>
17+
<suppression>unusedFunction</suppression>
18+
</suppressions>
1619
<project-name>cppcheck</project-name>
1720
</project>

hikyuu/utilities/plugin/PluginBase.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2025 hikyuu.org
3+
*
4+
* Created on: 2025-03-18
5+
* Author: fasiondog
6+
*/
7+
8+
#pragma once
9+
#ifndef HKU_UTILS_PLUGIN_BASE_H_
10+
#define HKU_UTILS_PLUGIN_BASE_H_
11+
12+
#include <string>
13+
#include "hikyuu/utilities/config.h"
14+
#include "hikyuu/utilities/osdef.h"
15+
16+
namespace hku {
17+
18+
class PluginBase {
19+
public:
20+
PluginBase() = default;
21+
virtual ~PluginBase() = default;
22+
23+
/**
24+
* @brief 返回插件信息
25+
* @details
26+
* 插件信息为json格式,包含name(string)、version(int)、description(string)、author(string)字段
27+
* 如:{"name":"unknown","version": 1.0,"description":"","author":"unknown"}
28+
* @return std::string
29+
*/
30+
virtual std::string info() const noexcept = 0;
31+
};
32+
33+
} // namespace hku
34+
35+
#if HKU_OS_WINDOWS
36+
#define HKU_PLUGIN_DEFINE(plugin) \
37+
extern "C" __declspec(dllexport) hku::PluginBase* createPlugin() { \
38+
return new plugin(); \
39+
}
40+
#else
41+
#define HKU_PLUGIN_DEFINE(plugin) \
42+
extern "C" hku::PluginBase* createPlugin() { \
43+
return new plugin(); \
44+
}
45+
#endif
46+
47+
#endif /* HKU_UTILS_PLUGIN_BASE_H_ */
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2025 hikyuu.org
3+
*
4+
* Created on: 2025-04-06
5+
* Author: fasiondog
6+
*/
7+
8+
#pragma once
9+
10+
#include "PluginLoader.h"
11+
12+
namespace hku {
13+
14+
template <typename InterfaceT>
15+
class PluginClient : public InterfaceT {
16+
public:
17+
PluginClient() = delete;
18+
PluginClient(const std::string &path, const std::string &filename) {
19+
m_loader = std::make_unique<PluginLoader>(path);
20+
m_loader->load(filename);
21+
m_impl = m_loader->instance<InterfaceT>();
22+
}
23+
virtual ~PluginClient() = default;
24+
25+
PluginClient(const PluginClient &) = delete;
26+
PluginClient &operator=(const PluginClient &) = delete;
27+
28+
PluginClient(PluginClient &&rhs) : m_impl(rhs.m_impl), m_loader(std::move(rhs.m_loader)) {
29+
rhs.m_impl = nullptr;
30+
}
31+
32+
PluginClient &operator=(PluginClient &&rhs) {
33+
if (this != &rhs) {
34+
m_loader = std::move(rhs.m_loader);
35+
m_impl = rhs.m_impl;
36+
rhs.m_impl = nullptr;
37+
}
38+
}
39+
40+
std::string info() const noexcept override {
41+
return m_impl->info();
42+
}
43+
44+
InterfaceT *getPlugin() const {
45+
return m_impl;
46+
}
47+
48+
protected:
49+
InterfaceT *m_impl;
50+
51+
protected:
52+
std::unique_ptr<PluginLoader> m_loader;
53+
};
54+
55+
} // namespace hku
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2025 hikyuu.org
3+
*
4+
* Created on: 2025-03-18
5+
* Author: fasiondog
6+
*/
7+
8+
#include "hikyuu/utilities/osdef.h"
9+
#include "hikyuu/utilities/os.h"
10+
#include "PluginLoader.h"
11+
12+
#if HKU_OS_WINDOWS
13+
#include <windows.h>
14+
#else
15+
#include <dlfcn.h>
16+
#endif
17+
18+
namespace hku {
19+
20+
PluginLoader::PluginLoader() : PluginLoader(".") {}
21+
22+
PluginLoader::PluginLoader(const std::string& path) : m_path(path) {}
23+
24+
PluginLoader::~PluginLoader() {
25+
unload();
26+
}
27+
28+
std::string PluginLoader::getFileName(const std::string& pluginname) const noexcept {
29+
#if HKU_OS_WINDOWS
30+
return fmt::format("{}/{}.dll", m_path, pluginname);
31+
#elif HKU_OS_LINUX
32+
return fmt::format("{}/lib{}.so", m_path, pluginname);
33+
#elif HKU_OS_OSX
34+
return fmt::format("{}/lib{}.dylib", m_path, pluginname);
35+
#endif
36+
}
37+
38+
void* PluginLoader::getFunciton(const char* symbol) noexcept {
39+
#if HKU_OS_WINDOWS
40+
void* func = GetProcAddress(m_handle, symbol);
41+
#else
42+
void* func = dlsym(m_handle, symbol);
43+
#endif
44+
return func;
45+
}
46+
47+
bool PluginLoader::load(const std::string& pluginname) noexcept {
48+
std::string filename = getFileName(pluginname);
49+
HKU_WARN_IF_RETURN(!existFile(filename), false, "file({}) not exist!", filename);
50+
51+
#if HKU_OS_WINDOWS
52+
m_handle = LoadLibrary(HKU_PATH(filename).c_str());
53+
#else
54+
m_handle = dlopen(filename.c_str(), RTLD_LAZY);
55+
#endif
56+
HKU_WARN_IF_RETURN(!m_handle, false, "load plugin({}) failed!", filename);
57+
58+
typedef PluginBase* (*CreateFunction)();
59+
CreateFunction createFunction = reinterpret_cast<CreateFunction>(getFunciton("createPlugin"));
60+
HKU_WARN_IF_RETURN(!createFunction, false, "Failed to get plugin({}) handle!", filename);
61+
62+
m_plugin.reset(createFunction());
63+
if (!m_plugin) {
64+
HKU_ERROR("Failed to create plugin ({})!", filename);
65+
unload();
66+
m_plugin.reset();
67+
return false;
68+
}
69+
70+
return true;
71+
}
72+
73+
void PluginLoader::unload() noexcept {
74+
m_plugin.reset();
75+
if (m_handle) {
76+
#if HKU_OS_WINDOWS
77+
FreeLibrary(m_handle);
78+
#else
79+
dlclose(m_handle);
80+
#endif
81+
}
82+
}
83+
84+
} // namespace hku
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2025 hikyuu.org
3+
*
4+
* Created on: 2025-03-18
5+
* Author: fasiondog
6+
*/
7+
8+
#pragma once
9+
#ifndef HKU_UTILS_PLUGIN_LOADER_H_
10+
#define HKU_UTILS_PLUGIN_LOADER_H_
11+
12+
#include <mutex>
13+
#include <string>
14+
#include <memory>
15+
#include "hikyuu/utilities/config.h"
16+
#include "hikyuu/utilities/osdef.h"
17+
#include "hikyuu/utilities/Log.h"
18+
#include "PluginBase.h"
19+
20+
#if HKU_OS_WINDOWS
21+
#include <windows.h>
22+
#endif
23+
24+
#ifndef HKU_UTILS_API
25+
#define HKU_UTILS_API
26+
#endif
27+
28+
namespace hku {
29+
30+
class HKU_UTILS_API PluginLoader final {
31+
public:
32+
PluginLoader();
33+
explicit PluginLoader(const std::string& path);
34+
PluginLoader(const PluginLoader&) = delete;
35+
PluginLoader(PluginLoader&&) = delete;
36+
37+
~PluginLoader();
38+
39+
bool load(const std::string& pluginname) noexcept;
40+
41+
template <typename T>
42+
T* instance() const noexcept {
43+
HKU_IF_RETURN(!m_plugin, nullptr);
44+
return dynamic_cast<T*>(m_plugin.get());
45+
}
46+
47+
private:
48+
void unload() noexcept;
49+
void* getFunciton(const char* symbol) noexcept;
50+
std::string getFileName(const std::string& pluginname) const noexcept;
51+
52+
private:
53+
#if HKU_OS_WINDOWS
54+
HMODULE m_handle{nullptr};
55+
#else
56+
void* m_handle{nullptr};
57+
#endif
58+
std::string m_path;
59+
std::unique_ptr<PluginBase> m_plugin{nullptr};
60+
};
61+
62+
} // namespace hku
63+
64+
#endif /* HKU_UTILS_PLUGIN_LOADER_H_ */

test/plugin/TestPlugin.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) 2025 hikyuu.org
3+
*
4+
* Created on: 2025-03-19
5+
* Author: fasiondog
6+
*/
7+
8+
#include "TestPlugin.h"
9+
10+
namespace hku {
11+
12+
std::string TestPlugin::name() const {
13+
return "testplugin";
14+
}
15+
16+
std::string TestPlugin::info() const noexcept {
17+
return R"({"name":"unknown","version": 1.0,"description":"","author":"unknown"})";
18+
}
19+
20+
} // namespace hku
21+
22+
HKU_PLUGIN_DEFINE(hku::TestPlugin)

test/plugin/TestPlugin.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2025 hikyuu.org
3+
*
4+
* Created on: 2025-03-19
5+
* Author: fasiondog
6+
*/
7+
8+
#pragma once
9+
10+
#include <string>
11+
#include "hikyuu/utilities/plugin/PluginBase.h"
12+
#include "TestPluginInterface.h"
13+
14+
namespace hku {
15+
16+
class TestPlugin : public TestPluginInterface {
17+
public:
18+
TestPlugin() = default;
19+
virtual ~TestPlugin() = default;
20+
21+
virtual std::string name() const override;
22+
virtual std::string info() const noexcept override;
23+
};
24+
25+
} // namespace hku

test/plugin/TestPluginInterface.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) 2025 hikyuu.org
3+
*
4+
* Created on: 2025-04-06
5+
* Author: fasiondog
6+
*/
7+
8+
#pragma once
9+
#include <hikyuu/utilities/plugin/PluginLoader.h>
10+
11+
namespace hku {
12+
13+
class TestPluginInterface : public PluginBase {
14+
public:
15+
TestPluginInterface() = default;
16+
virtual ~TestPluginInterface() = default;
17+
18+
virtual std::string name() const = 0;
19+
};
20+
21+
} // namespace hku

test/utilities/plugin/test_Plugin.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2025 hikyuu.org
3+
*
4+
* Created on: 2025-03-19
5+
* Author: fasiondog
6+
*/
7+
8+
#include "test_config.h"
9+
#include "hikyuu/utilities/os.h"
10+
#include "hikyuu/utilities/plugin/PluginClient.h"
11+
#include "../../plugin/TestPluginInterface.h"
12+
#include <cstdlib>
13+
14+
using namespace hku;
15+
16+
class TestPluginClient : public PluginClient<TestPluginInterface> {
17+
public:
18+
TestPluginClient(const std::string &path, const std::string &filename)
19+
: PluginClient<TestPluginInterface>(path, filename) {}
20+
21+
virtual std::string name() const override {
22+
HKU_CHECK(m_impl, "Plugin not loaded!");
23+
return m_impl->name();
24+
}
25+
};
26+
27+
TEST_CASE("test_plugin") {
28+
TestPluginClient plugin1(".", "testplugin");
29+
TestPluginClient plugin = std::move(plugin1);
30+
CHECK_EQ(plugin.name(), "testplugin");
31+
CHECK_THROWS(plugin1.name());
32+
HKU_INFO("{}", plugin.name());
33+
HKU_INFO("{}", plugin.info());
34+
}

0 commit comments

Comments
 (0)