Я работал над этой проблемой в течение нескольких месяцев, и теперь хотел действительно придумать правильное решение, которое будет обрабатывать случай создания новых пользовательских классов (и экземпляров этих классов) с помощью функций-членов / свойства во время выполнения в проекте C ++ 11.
До сих пор я использовал SWIG (ранее с Python, теперь с Lua, исследуя Squirrel). Как и все библиотеки связывания / встраивания C ++, с которыми я сталкивался до сих пор (Luna *, luabinder, luabind, OOLua, Sqrat / Sqext, Squall), все ожидают, что ваши классы будут предопределены в C ++ до выполнения кода, потому что они либо полагаются на препроцессор директивы или шаблоны.
Поэтому мой вопрос заключается в том, существуют ли какие-либо библиотеки, которые используют более процедурный подход к переносу языка, или есть какие-нибудь хорошие учебники / примеры для чего-то вроде Lua или Squirrel, которые можно было бы порекомендовать для обработки создания пользовательских классов с пользовательскими членами и функциями? Некоторое направление будет с благодарностью.
Даже очень хороший пример, показывающий, как создать пользовательский класс с функцией и свойством в Lua, Squirrel, через соответствующие API-интерфейсы C ++ без использования макросов / шаблонов / динамически генерируемого кода, был бы чрезвычайно полезен.
РЕДАКТИРОВАТЬ: я дошел до создания Instance
класс, который содержит std::vector
членов пары ключ / значение и член, идентифицирующий тип, чтобы можно было искать функции. Тем не менее, существует очень мало документации по созданию простых классов в Lua / Squirrel без использования статического кода.
РЕДАКТИРОВАТЬ 2: Я хотел бы решение, которое работает на любой платформе и без необходимости динамического связывания.
Создание класса, производного от некоторого существующего класса C ++, — единственный (известный мне) способ ввести новый класс в работающую программу C ++. Если не считать динамической компиляции фактического исходного кода C ++ и загрузки полученной библиотеки, нет никакого способа физически добавить новый класс. Следующая лучшая вещь — это создать в C ++ прокси-объект, который обернет объект Python (Lua и т. Д.), И сделать этот объект Python (Lua) экземпляром класса, который расширяет существующий класс C ++, отраженный на стороне Python (Lua).
C++
+---------+ mirrors +--------------+
| class X | ...............................> | class X |
+---------+ | mirrored to |
| | Python |
| inherits +--------------+
v inherits |
+-----------------+ v
| class X_Wrapper | references +--------------+
| | python obj -------------------------> | class CX(X): |
+-----------------+ | def met() |
+--------------+
Вот пример расширения класса C ++ с помощью Python с использованием boost :: python в качестве моста.
Сторона C ++:
#include <boost/python.hpp>
#include <iostream>
using namespace boost::python;
// this is the interface we will implement in Python
struct World
{
virtual std::string greet() = 0;
virtual ~World() {}
};
// this is a helper class needed to access Python-overrided methods
struct WorldWrap : World, wrapper<World>
{
std::string greet()
{
return this->get_override("greet")();
}
};
// This function tests our class implemented in Python
std::string test(World* w)
{
std::cout << "Calling w->greet() on some World-derived object\n";
return w->greet();
}
// This is what the Python side will see
BOOST_PYTHON_MODULE(hello)
{
class_<WorldWrap, boost::noncopyable>("World")
.def("greet", pure_virtual(&World::greet));
def("test", test);
}
Сторона Python:
import helloclass HomeWorld(hello.World):
""" Implements a function defined in C++ as pure virtual """def greet(self):
return "howdy"
home = HomeWorld()
print (hello.test(home))
Рассмотрим следующий пример Lua Multimap.
Multimap = {};
function Multimap:__index(key)
if (key == 'keys') then
local ret = {}
for k,_ in pairs(self) do
ret[#ret+1] = k;
end
return ret;
else
return rawget(getmetatable(self), key)
end
end
function Multimap.Create()
local self = {};
setmetatable(self, Multimap);
return self;
end
function Multimap:Insert(key, value)
local list = self[key];
if (list == nil) then
list = {};
self[key] = list;
end
table.insert(list, value);
end
function Multimap:Remove(key, value)
local list = self[key];
assert(list ~= nil, "key not found");
for i = 1,#list do
if (list[i] == value) then
table.remove(list, i);
if (#list == 0) then
self[key] = nil;
end
return;
end
end
error("value not found");
end-- testing
m = Multimap.Create()
m:Insert(1,5)
m:Insert(2,6)
m:Insert(3,7)
m:Insert(1,8)
m:Remove(2,6)
print(pcall(function()
m:Remove(2,6) -- will produce assert exception
end))
print("keys left: ", table.concat(m.keys, ','))
Вы можете реализовать это в C ++ несколькими способами.
#include <Lua/src/lua.hpp>
int Multimap_Index(lua_State* L) {
lua_settop(L, 2); // force 2 arguments
const char *key_value = "key";
size_t key_len;
const char *key = lua_tolstring(L, 2, &key_len);
if (!strncmp(key, key_value, strlen(key_value))) {
int i = 0;
lua_newtable(L); // stack : self, key, ret = {}
int ret = lua_gettop(L);
lua_pushnil(L); // stack : self, key, ret, nil
while (lua_next(L, 1) != 0) { // stack : self, key, ret, k, v
lua_pop(L, 1); // stack : self, key, ret, k
lua_len(L, ret); // stack : self, key, ret, k, #ret
lua_pushvalue(L, -2); // stack : self, key, ret, k, #ret, k
lua_rawseti(L, ret, lua_tointeger(L, -2)+1); // ret[#ret+1] = k ; || stack : self, key, ret, k, #ret
lua_pop(L, 1); // stack : self, key, ret, k
}
// stack : self, key, ret
return 1;
}
else {
lua_getmetatable(L, 1); // stack : self, key, metatable(self)
lua_pushvalue(L, 2); // stack : self, key, metatable(self), key
lua_rawget(L, -2); // stack : self, key, metatable(self), rawget(metatable(self), key)
return 1;
}
}
int Multimap_Remove(lua_State* L) {
lua_settop(L, 3); // force 3 arguments: self, key, value
lua_checkstack(L, 12); // reserve 12 arguments on stack (just in case)
lua_pushvalue(L, 2); // stack: self, key, value, key
lua_gettable(L, 1); // stack: self, key, value, list = self[key]
if (lua_isnil(L, -1))
luaL_error(L, "key not found");
lua_len(L, -1); // stack: self, key, value, list, #list
int count = lua_tointeger(L, -1);
lua_pop(L, 1); // stack: self, key, value, list
for (int i = 1; i <= count; ++i) {
lua_rawgeti(L, -1, i); // stack: self, key, value, list, v = list[i]
if (lua_compare(L, 3, 5, LUA_OPEQ)) { // if (list[i] == value)
lua_getglobal(L, "table"); // stack : self, key, value, list, v, table
lua_getfield(L, -1, "remove"); // stack : self, key, value, list, v, table, table.remove
lua_pushvalue(L, 4);
lua_pushinteger(L, i); // stack : self, key, value, list, v, table, table.remove, list, i
lua_call(L, 2, 0); // table.remove(list, i); || stack : self, key, value, list, v, table
lua_pushnil(L);
if (lua_next(L, 4) == 0) { // if list is empty table
lua_pushvalue(L, 2);
lua_pushnil(L);
lua_settable(L, 1); // self[key] = nil
}
return 0;
}
}
luaL_error(L, "value not found");
}
int main() {
auto L = luaL_newstate();
luaL_openlibs(L);
lua_newtable(L);
int Multimap = lua_gettop(L); // Multimap = {}
lua_pushvalue(L, Multimap);
lua_setglobal(L, "Multimap"); // _G.Multimap = Multimap;
// option 1: create a C function for operation
// Multimap.__index = &Multimap_Index
lua_pushcfunction(L, Multimap_Index);
lua_setfield(L, Multimap, "__index");
// option 2: compile Lua code and use it
luaL_loadstring(L,
"local self = {};\n""setmetatable(self, Multimap);\n""return self;\n");
lua_setfield(L, Multimap, "Create"); // Multimap.Create = &Multimap_Create
luaL_loadstring(L,
"local self, key, value = ...;\n" // initialize local variables from parameters here
"local list = self[key];\n""if (list == nil) then\n"" list = {};\n"" self[key] = list;\n""end\n""table.insert(list, value);\n");
lua_setfield(L, Multimap, "Insert"); // Multimap.Create = &Multimap_Insert
lua_pushcfunction(L, Multimap_Remove);
lua_setfield(L, Multimap, "Remove"); // Multimap.Create = &Multimap_Remove
lua_getfield(L, Multimap, "Create");
lua_call(L, 0, 1);
int m = lua_gettop(L);
lua_getfield(L, m, "Insert"); // stack : m, m.insert
int Insert = lua_gettop(L);
// m.Insert(m, 1, 5)
lua_pushvalue(L, Insert);
lua_pushvalue(L, m);
lua_pushinteger(L, 1);
lua_pushinteger(L, 5);
lua_call(L, 3, 0);
// m.Insert(m, 2, 6)
lua_pushvalue(L, Insert);
lua_pushvalue(L, m);
lua_pushinteger(L, 2);
lua_pushinteger(L, 6);
lua_call(L, 3, 0);
// m.Insert(m, 3, 7)
lua_pushvalue(L, Insert);
lua_pushvalue(L, m);
lua_pushinteger(L, 3);
lua_pushinteger(L, 7);
lua_call(L, 3, 0);
// m.Insert(m, 1, 8)
lua_pushvalue(L, Insert);
lua_pushvalue(L, m);
lua_pushinteger(L, 1);
lua_pushinteger(L, 8);
lua_call(L, 3, 0);
// m.Remove(m, 2, 6)
lua_getfield(L, m, "Remove");
lua_pushvalue(L, m);
lua_pushinteger(L, 2);
lua_pushinteger(L, 6);
lua_call(L, 3, 0);
// m.Remove(m, 2, 6)
lua_getfield(L, m, "Remove");
lua_pushvalue(L, m);
lua_pushinteger(L, 2);
lua_pushinteger(L, 6);
lua_pcall(L, 3, 0, 0);
printf("%s\n", lua_tostring(L, -1));
lua_getglobal(L, "table");
lua_getfield(L, -1, "concat");
lua_getfield(L, m, "keys");
lua_pushstring(L, ",");
lua_call(L, 2, 1);
printf("keys left: %s\n", lua_tostring(L, -1));
lua_close(L);
return 0;
}
Отказ от ответственности: Я публикую этот вклад в качестве ответа, потому что у меня недостаточно очков репутации, чтобы добавить комментарий.
Комментарий: Если оставить в стороне проблему связывания с конкретным языком сценариев, кажется, что вы столкнулись с фундаментальным ограничением C++
язык: это не «динамический«(как указано в других комментариях). То есть язык не предоставляет никаких функций для расширения или изменения скомпилированной программы.
Может быть, все надежды не потеряны, хотя. Поиск в Интернете «динамической загрузки c ++» показывает, что некоторые системы (например, Linux и Windows) действительно реализуют динамическая загрузка механизм.
Вот ссылки на две (старые) статьи, которые говорят на эту тему.
Они кажутся интересными на первый взгляд. Я не уверен, что они все еще актуальны, хотя.
Это всего лишь выстрел в темноте.