Open antonwantstosleep opened 4 years ago
Я вообще два дня как вижу C++, и написал следующее в AddInNative.h:
public:
...
enum Methods
{
eMethGetLastError = 0,
eMethConnect = 1,
eMethDeclareQueue = 2,
eMethBasicPublish = 3,
eMethBasicConsume = 4,
eMethBasicConsumeMessage = 5,
eMethBasicCancel = 6,
eMethBasicAck = 7,
eMethDeleteQueue = 8,
eMethBindQueue = 9,
eMethBasicReject = 10,
eMethDeclareExchange = 11,
eMethDeleteExchange = 12,
eMethUnbindQueue = 13,
eMethSetPriority = 14,
eMethGetPriority = 15,
eMethStartConsumingMessages = 16, // aws: Запуск функции для постоянного чтения сообщений в петле
eMethStopConsumingMessages = 17, // aws: Остановка функции для постоянного чтения сообщений в петле
eMethLast = 18 // Always last
};
private:
....
const wchar_t* m_eventSourceName = L"PinkRabbitMQ"; // aws: Источник по умолчанию для ОбработкаВнешнегоСобытия
const wchar_t* m_eventMessageName = L"AMQP Message"; // aws: Событие по умолчанию для ОбработкаВнешнегоСобытия
bool asyncMode = false; // aws: Асинхронный режим
long bufferDepth{100}; // aws: Длина очереди событий
std::thread loopConsumeMessagesThread; // aws: Поток выполнения для функции постоянного чтения сообщений в петле
void loopConsumeMessages(); // aws: Функция для постоянного чтения сообщений в петле. void - ничего не возвращает.
bool sendEvent(const wchar_t* source, const wchar_t* message, const wchar_t* data);
};
В AddInNative.cpp:
static const wchar_t *g_MethodNames[] =
{
L"GetLastError",
L"Connect",
L"DeclareQueue",
L"BasicPublish",
L"BasicConsume",
L"BasicConsumeMessage",
L"BasicCancel",
L"BasicAck",
L"DeleteQueue",
L"BindQueue",
L"BasicReject",
L"DeclareExchange",
L"DeleteExchange",
L"UnbindQueue",
L"SetPriority",
L"GetPriority",
L"StartConsumingMessages", // aws: Запуск функции для постоянного чтения сообщений в петле
L"StopConsumingMessages", // aws: Остановка функции для постоянного чтения сообщений в петле
};
static const wchar_t *g_MethodNamesRu[] =
{
L"GetLastError",
L"Connect",
L"DeclareQueue",
L"BasicPublish",
L"BasicConsume",
L"BasicConsumeMessage",
L"BasicCancel",
L"BasicAck",
L"DeleteQueue",
L"BindQueue",
L"BasicReject",
L"DeclareExchange",
L"DeleteExchange",
L"UnbindQueue",
L"SetPriority",
L"GetPriority",
L"StartConsumingMessages", // aws: Запуск функции для постоянного чтения сообщений в петле
L"StopConsumingMessages", // aws: Остановка функции для постоянного чтения сообщений в петле
};
bool AddInNative::CallAsProc(const long lMethodNum, tVariant* paParams, const long lSizeArray)
{
...
// aws
case eMethStartConsumingMessages: // aws: Запуск функции для постоянного чтения сообщений в петле
{
// aws: Устанавливаем асинхронный режим
asyncMode = true;
// aws: Проверяем длину очереди сообщений и устанавливаем ее при необходимости
if (m_iConnect && m_iConnect->GetEventBufferDepth() < bufferDepth)
{
m_iConnect->SetEventBufferDepth(bufferDepth);
}
// aws: Запускаем отдельный поток с функцией, передаем в него указатель на область памяти
// с функцией и этот объект (класс)
loopConsumeMessagesThread = std::thread(&AddInNative::loopConsumeMessages, this);
// aws: Отсоединяем этот поток от основного (этого)
loopConsumeMessagesThread.detach();
return true;
}
case eMethStopConsumingMessages: // aws: Остановка функции для постоянного чтения сообщений в петле
{
// aws: Отключим асинхронный режим
asyncMode = false;
// aws: Отдельный поток сам это поймет и остановится
return true;
}
// aws
// aws:----------------------------------------------------------------------//
void AddInNative::loopConsumeMessages() {
std::string outdata;
std::uint64_t outMessageTag;
std::uint16_t timeout = 3000;
while (asyncMode)
{
bool hasMessage = client.basicConsumeMessage(
outdata,
outMessageTag,
timeout
);
if (client.getLastError().length() != 0)
{
asyncMode = false;
return;
}
if (hasMessage)
{
client.basicAck(outMessageTag);
bool eventSent = sendEvent(m_eventSourceName, m_eventMessageName, Utils::stringToWs(outdata).c_str());
if (!eventSent)
{
return;
}
}
}
}
// aws: ---------------------------------------------------------------------//
bool AddInNative::sendEvent(const wchar_t* source, const wchar_t* message, const wchar_t* data)
{
bool result = false;
if (m_iConnect)
{
WCHAR_T *wsSource = 0;
WCHAR_T *wsMessage = 0;
WCHAR_T *wsData = 0;
convToShortWchar(&wsSource, source);
convToShortWchar(&wsMessage, message);
convToShortWchar(&wsData, data);
result = m_iConnect->ExternalEvent(wsSource, wsMessage, wsData);
delete[] wsSource;
delete[] wsMessage;
delete[] wsData;
}
return result;
}
Прошу помощи:
Я пробовал скомпилировать на Linux для Linux так.
$ cmake ..
$ cmake --build . --config Release
# Cmake script for background building 64 bit release project library PinkRabbitMQLinux
CMAKE_MINIMUM_REQUIRED(VERSION 2.6 FATAL_ERROR)
PROJECT(PinkRabbitMQLinux)
set (ROOT_DIR ${CMAKE_SOURCE_DIR}) set (CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/src) set(AddInNative_SRC ${CMAKE_SOURCE_DIR}/src)
SET(AddInNative_SRC ${CMAKE_SOURCE_DIR}/AddInNative.cpp ${CMAKE_SOURCE_DIR}/AddInNative.h ${CMAKE_SOURCE_DIR}/dllmain.cpp ${CMAKE_SOURCE_DIR}/stdafx.cpp ${CMAKE_SOURCE_DIR}/stdafx.h )
include_directories(${CMAKE_SOURCE_DIR}/include) include_directories(${CMAKE_SOURCE_DIR}/libevent/include) # aws: Ругань. include_directories(${CMAKE_SOURCE_DIR}/amqp/include) # aws: Ругань.
SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${CMAKE_SOURCE_DIR}) SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
SET(AddInDef_SRC ${CMAKE_SOURCE_DIR}/AddInNative.def)
add_definitions(-DUNICODE -DWIN32 -DNOMINMAX)
add_library(${PROJECT_NAME} SHARED ${AddInNative_SRC} ${AddInDef_SRC})
set_target_properties( ${PROJECT_NAME} PROPERTIES CLEAN_DIRECT_OUTPUT 1 OUTPUT_NAME ${PROJECT_NAME}Lin64 # aws: Ошибка. )
5. Компонента собралась в libPinkRabbitMQLinuxLin64.so, но весит всего 131,5 кБ против вашей 3 Мб. Где-то что-то не подключилось / не слинковалось?
6. Вот выхлоп ldd.
user@pc:~/Projects/PinkRabbitMQ_test/PinkRabbitMQLinux/build$ ldd PinkRabbitMQLinuxLin64.so linux-vdso.so.1 (0x00007ffeecdf6000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f4b8ec72000) libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f4b8e8e9000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f4b8e54b000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f4b8e333000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f4b8e114000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4b8dd23000) /lib64/ld-linux-x86-64.so.2 (0x00007f4b8f214000)
user@pc:~/Projects/PinkRabbitMQ_test/PinkRabbitMQLinux/build$ ldd PinkRabbitMQLinuxLin64_new.so linux-vdso.so.1 (0x00007fffb3f4e000) libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f9368fa0000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f9368d88000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9368997000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f93685f9000) /lib64/ld-linux-x86-64.so.2 (0x00007f9369540000)
7. Я упаковал ее в архив рядом с манифестом, но при подключении в 1С не работает.
В каталоге Linux проекта есть sln файл для запуска из под VisualStudio. Его и нужно использовать для разработки и компиляции линуксовой версии. Это полностью подготовленный проект. В этом случае развернуть окружение разработки будет относительно просто. В ридми есть краткая инструкция. Разрабатывать из-под блокнота на линуксе скорее всего не получится, я уже пробовал.
Подход с бесконечным циклом некорректный. Внутренняя либа для работы с rabbitMQ - AMQPСPP поддерживает возможность "подписаться" на очередь. Эту возможность и следуюет использовать. https://github.com/CopernicaMarketingSoftware/AMQP-CPP См раздел. CONSUMING MESSAGES
channel.consume("my-queue") .onReceived(messageCb) // Подписываемся на очередь
Прежде чем писать код рекомендую разобраться отладкой. Она вполне возможна и работает. Есть даже консольное приложение https://github.com/BITERP/PinkRabbitMQ/blob/master/PinkRabbitMQLinux/main.cpp, и юнит тесты который автоматически выполняться при запуске консольног оприложения
В каталоге Linux проекта есть sln файл для запуска из под VisualStudio. Его и нужно использовать для разработки и компиляции линуксовой версии.
Это да, и у меня есть Windows 10 под Virtual box, но VS 2019 со всеми плюшками (я не знаю, что конкретно надо) весит под 10 Гб, места на диске нету :(
А Вы бы не могли как-нибудь сгенерировать средствами VS 2019 make файл для линукса? Извините, если глупый вопрос (возможно я до конца не понимаю, о чем прошу).
Если нет - ок, попробую найти место.
Подход с бесконечным циклом некорректный. Внутренняя либа для работы с rabbitMQ - AMQPСPP поддерживает возможность "подписаться" на очередь.
Я читал исходники этой либы, но что-то этого сразу не увидел. Сейчас вижу, спасибо. Попробую покурить, как это реализовать в виде метода компоненты. Нужно ведь еще, чтобы он вызывался в 1C "асинхронно", а-ля .НачатьВызов<ИмяМетодаКомпоненты>, верно?
Подскажите, в правильную ли сторону я двигаюсь, исходя из моей задачи? Или есть вариант проще?
Немного почитал. Подскажите, правильно ли я понимаю.
Когда мы вызываем из 1С ваш метод basicConsume, в конечном итоге вызывается метод RabbitMQClient::basicConsume, где:
Когда мы вызываем метод basicConsumeMessage, то мы:
Когда мы вызываем метод basicAck - мы просто "акаем" сообщение на сервере, и оно там исчезает.
Вы бы могли сразу в коллбеке .onMessage создать для клиента 1С ExternalEvent, но поскольку внешние события не обрабатываются в 1С на сервере (а вам возможно нужно было это делать именно там), вы реализовали внутреннюю очередь сообщений?
В https://github.com/CopernicaMarketingSoftware/AMQP-CPP/blob/master/include/amqpcpp/deferredget.h и здесь https://github.com/CopernicaMarketingSoftware/AMQP-CPP/issues/156 автор библиотеки пишет, что вроде как разницы между событиями .onMessage, .onReceived и .onSuccess нет.
Следовательно, для реализации фичи нам нужен еще один параметр для вашего метода basicConsume. Что-то типа bool enableExternalEvents:
Так?
Примерно вусе так. Только не стоит нагружать метод RabbitMQClient::basicConsume дополниетлньой логикой. ЛУчше сделать отдельный метод для инициализации чтения сообщений во внешние события
Никак не могу понять (я тупой + c++ слишком сложный).
Подвешивать коллбек на .onMessage нужно в файле RabbitMQClient.cpp (например, в методе RabbitMQClient::basicConsume ну или в ином, отдельном RabbitMQClient::basicConsumeToExternalEvents, который такой же, только другой).
А Метод RabbitMQClient::basicConsumeToExternalEvents я вызываю из метода AddInNative::basicConsumeToExternalEvents из файла AddInNative.cpp. В том же файле для удобства и красоты я написал метод AddInNative::sendEvent, который дергает метод ExternalEvent живущего в том же файле m_iConnect.
Как мне поработать с AddInNative::sendEvent в коллбеке на .onMessage?
Нужно передать весь класс AddInNative (ссылку &this ?) в параметре при вызове RabbitMQClient::basicConsumeToExternalEvents? И сделать #include "AddInNative.h" в RabbitMQClient.h?
Или передавать только m_iConnect (и делать include IAddInDefBaseEx)?
Или можно как-то одну функцию AddInNative::sendEvent туда передать? Да еще и с живым m_iConnect?
Спасибо, что тратите на меня время.
Ничего не понял. Мне проще видеть код и ревьюить готовый пулл-реквест. Так я смогу сказать что правильно, а что не очень. Коллбеки делайте по аналогии как в других методах. Есть настроена отладка достаточно просто методом тыка будет найти оптимальный вариант
@ripreal , было сложно, но я настроил отладку под Windows в VS2019. Теперь могу ставить точки останова и в 1С, и в VS. Но свою проблему я не решил.
Пока мы находимся в файле СAddInNative.cpp (я тестировал проект для Windows для простоты), у нас видно this (объект класса CAddInNative) и m_iConnect (я так понимаю, это указатель на переменную, где хранится объект класса IAddInDefBase).
Когда мы вызываем метод RabbitMQClient, то переходим в файл RabbitMQClient.cpp, где ничего "старого" уже нет - this хранит объект класса RabbitMQClient.
И как в этот момент получить доступ:
Это можно сделать. Для передачи указателя на m_iConnect в класс RabbitMQClient используются каллбеки. С ними придется повозиться https://stackoverflow.com/questions/2298242/callback-functions-in-c. Каллбеки могут быт ьнаписаны по-разному. Рекомендую использовать С++ 2011 стиля.
Например, для задач телефонии Asterisk AMI + RabbitMQ + компонента + 1С:
В 1С нельзя вызвать клиентский метод с сервера. Но компонента может работать на клиенте и на сервере.
В частности, на сервере можно:
На клиенте можно:
Идея для использования на клиенте:
Насколько я знаю, это будет работать только на клиенте - на сервере "внешние события не обрабатываются".