Я кодировал несколько относительно простых коммуникационных протоколов, используя разделяемую память и разделяемые мьютексы. Но затем я хотел расширить поддержку для связи между двумя .dll, имеющими разное время выполнения. Совершенно очевидно, что если у вас есть некоторые std::vector<__int64>
и два DLL — один против 2010 года, другой против 2015 года — они не будут работать вежливо друг с другом. Тогда я подумал — почему я не могу сериализовать структуру в стиле ipc с одной стороны и десериализовать ее с другой — тогда время выполнения будет гладко работать друг с другом.
Короче говоря — я создал отдельный интерфейс для отправки следующей порции данных и для запроса следующей порции данных. Оба работают, пока происходит декодирование — то есть, если у вас есть вектор с 10 записями, каждая строка 1 Мб, а общая память — 10 Кб — тогда для передачи целых данных потребуется 1 * 10 * 1024/10 раз. Каждый следующий запрос сопровождается несколькими невыполненными вызовами функций — либо SendChunk, либо GetNextChunk в зависимости от направления передачи.
Теперь — я хотел, чтобы кодирование и декодирование происходили одновременно, но без какой-либо многопоточности, — тогда я пришел к решению использовать setjmp и longjmp. Я прилагаю часть кода ниже, просто чтобы вы поняли, что происходит во всем механизме.
#include "..."#include <setjmp.h> //setjmp
class Jumper: public IMessageSerializer
{
public:
char lbuf[ sizeof(IpcCommand) + 10 ];
jmp_buf jbuf1;
jmp_buf jbuf2;
bool bChunkSupplied;
Jumper() :
bChunkSupplied(false)
{
memset( lbuf, 0 , sizeof(lbuf) );
}
virtual bool GetNextChunk( bool bSend, int offset )
{
if( !bChunkSupplied )
{
bChunkSupplied = true;
return true;
}
int r = setjmp(jbuf1);
((_JUMP_BUFFER *)&jbuf1)->Frame = 0;
if( r == 0 )
longjmp(jbuf2, 1);
bChunkSupplied = true;
return true;
}
virtual bool SendChunk( bool bLast )
{
bChunkSupplied = false;
int r = setjmp(jbuf2);
((_JUMP_BUFFER *)&jbuf2)->Frame = 0;
if( r == 0 )
longjmp(jbuf1, 1);
return true;
}
bool FlushReply( bool bLast )
{
return true;
}
IpcCommand* getCmd( void )
{
return (IpcCommand*) lbuf;
}
int bufSize( void )
{
return 10;
}
}; //class Jumper
Jumper jumper;
void main(void)
{
EncDecCtx enc(&jumper, true, true);
EncDecCtx dec(&jumper, false, false);
CString s;
if( setjmp(jumper.jbuf1) == 0 )
{
alloca(16*1024);
enc.encodeString(L"My testing my very very long string.");
enc.FlushBuffer(true);
} else {
dec.decodeString(s);
}
wprintf(L"%s\r\n", s.GetBuffer() );
}
Здесь есть пара вопросов. После первого вызова setjmp я использую alloca (), которая выделяет память из стека, она будет автоматически освобождена при возврате. alloca может произойти только во время первого перехода, потому что любой вызов функции всегда использует стек вызовов (для сохранения адреса возврата) и может повредить стек «второго» потока.
Есть много статей, обсуждающих, насколько опасны setjmp и longjmp, но сейчас это как-то работающее решение. Размер стека (16 Кб) — это резервирование для последующих вызовов функций — decodeString и т. Д. — его можно настроить на большее, если не достаточно.
Попробовав этот код, я заметил, что код x86 работал нормально, но 64 — но не работал — у меня проблема, аналогичная описанной здесь:
Обнаружен недопустимый или невыровненный стек во время операции размотки
Как предложенная статья я добавил ((_JUMP_BUFFER *)&jbuf1)->Frame = 0;
вид сброса — и после этого 64-битный код начал работать. В настоящее время библиотека не использует какой-либо механизм исключения, и я не планирую использовать какой-либо (постараюсь поймать все, если необходимо, при вызовах функции encode * decode *.
Итак вопросы:
Это приемлемое решение для отключения раскрутки в коде? (((_JUMP_BUFFER *)&jbuf1)->Frame = 0;
) Что на самом деле означает раскрутка в контексте setjmp / longjmp?
Вы видите потенциальную проблему с данным фрагментом кода?
Задача ещё не решена.
Других решений пока нет …