Всем доброго времени суток!
Недавно при прототипировании одной из частей разрабатываемого нами продукта возникла одна интересная задача: нужно было проверить склейку Python и C++. Связано это было с тем, что основной код был написан на плюсах, и необходимо было подключить внешнюю библиотеку Websockets, написанную на Python (на тот момент не было соответствующей библиотеки на C++). Схема взаимодействия при такой задаче достаточно простая. Из C++ вызывается функция подключения к серверу (на python), в качестве параметра передается его адрес. Соответственно, при получении сообщния Python передавает его обратно в метод C++.
При написании кода использовалась питоновская библиотека Websocket от Autobahn (http://www.tavendo.de/autobahn/clientlibraries.html), которую было необходимо вызывать из C++. Для этих целей в Python предусмотрен Python C-API (http://docs.python.org/extending/index.html), однако многие простые действия, например, вызов функций делается несколькими действиями. После небольшого гугления был найден ряд библиотек, позволяющих упростить подобные действия: Boost.Python (http://www.boost.org/doc/libs/1_39_0/libs/python/doc/index.html), SWIG(http://www.swig.org/), Py++, Pybindgen, Pyrex… В результате был выбран Boost.Python как наиболее популярное решение.
Для начала напишем простенький эхо клиент на Python, который будет раз в секунду посылать сам себе сообщение “Hello world”, принимать его и отдавать в C++. cppMethods будет объявлен в C++ коде, cppMethods.printMessage(msg) — как раз место склейки со стороны Python, непосредственно вызов функции C++, которая будет печатать полученное сообщение.
Вот код Python — echo-client.py:
from twisted.internet import reactor
from autobahn.websocket import WebSocketClientFactory, WebSocketClientProtocol, connectWS
import cppMethods
class EchoClientProtocol(WebSocketClientProtocol):
def sendHello(self):
self.sendMessage("Hello, world!")
def onOpen(self):
self.sendHello()
def onMessage(self, msg, binary):
cppMethods.printMessage(msg)
reactor.callLater(1, self.sendHello)
def Connect(addressStr):
factory = WebSocketClientFactory(addressStr)
factory.protocol = EchoClientProtocol
connectWS(factory)
reactor.run()
Теперь напишем на C++ код, в котором опишем нашу функцию, вызываемую из питона. Для использования Python C-API нужно проинклудить Python.h. Обратите внимание, что на этом этапе мы еще не используем Boost.Python, лишь собственно родной Python C-API.
PrintEmb.cpp:
#include <Python.h>
#include <iostream>
#include <string>
static PyObject *
printString(PyObject * self, PyObject* args)
{
std::cout << PyString_AsString(args) << std::endl;
return Py_BuildValue("");
}
static PyMethodDef EmbMethods[] = {
{"printMessage", printString},
{NULL, NULL}
};
В последнем объявлении мы описали, что при вызове функции printMessage из Python будет вызван C++ метод printString.
Ну и наконец свяжем все это вместе. Для проверки работы websockets, помимо эхо-клиента, была использована ссылка на html5labs.
WebSocketConnect.cpp:
#include <Python.h>
#include <boost/python.hpp>
#include <iostream>
#include <string>
#include "PrintEmb.cpp"
void WebSocketConnect()
{
using namespace boost::python;
Py_Initialize();
Py_InitModule("cppMethods", EmbMethods);
PyObject * ws = PyImport_ImportModule("echo_client");
std::string address = "ws://html5labs-interop.cloudapp.net:4502/chat";
call_method<void>(ws, "Connect", address);
Py_Finalize();
}
В этом месте мы все-таки воспользовались возможностями Boost, а именно функцией call_method, иначе бы нам понадобилось написать существенно больше кода.
Ну вот как-то так. Здесь мы поинициализировали EmbMethods для питона, назвав их cppMethods, а затем вызвали из Python метод Connect и передали в него строку “address”. В результате наше приложение раз в секунду печатает строку «Hello World» (которую посылает сам себе питон), а также любое сообщение, приходящее с сервера вебсокетов.
Вот таким образом можно связать Python и C++. Буду благодарен за комментарии по теме.
Автор: rigeborod