Протокол связи и локальная петля с использованием setjmp/longjmp

Я закодировал относительно простой протокол связи с использованием общей памяти и общих мьютексов. Но затем я захотел расширить поддержку для связи между двумя .dll, имеющими разное время выполнения. Совершенно очевидно, что если у вас есть несколько std::vector<__int64> и две dll - одна vs2010, одна vs2015 - они не будут вежливо работать друг с другом. Затем я подумал - почему я не могу сериализовать структуру в стиле ipc с одной стороны и десериализовать ее с другой - тогда vs runtimes будут работать гладко друг с другом.

Короче говоря, я создал отдельный интерфейс для отправки следующего фрагмента данных и для запроса следующего фрагмента данных. Оба работают во время декодирования - это означает, что если у вас есть вектор с 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 ?

  • Видите ли вы какие-либо потенциальные проблемы с данным фрагментом кода?


person TarmoPikaro    schedule 20.09.2016    source источник
comment
Я не отлаживаю setjmp/longjmp код; Мне еще предстоит найти проблему, для которой это стоящий подход. Такого рода проблемы только подкрепляют эту оценку. Это очень большая причиненная самому себе боль.   -  person MSalters    schedule 20.09.2016
comment
Да, люди боятся глубоких знаний. Без него нужно создавать два потока, каналы межпроцессного взаимодействия, разделяемую память и так далее. Теоретически все выполнимо — вопрос в том, насколько разработчик хочет углубиться. В любом случае - позвольте мне попрактиковаться здесь - я хочу получить здесь не то, что вы думаете об этом коде, а практические знания по этому вопросу.   -  person TarmoPikaro    schedule 20.09.2016
comment
Здесь действуют разные философии кодирования, а не страх перед экспериментами.   -  person user4581301    schedule 21.09.2016
comment
Теперь я не очень хорошо знаком с setjmp() и longjmp() (в основном все, что я знаю о них, является причиной не их использовать), но если вам нужны сопрограммы, возможно ли использовать boost::coroutine, C ++17 coroutine TS или библиотеку COROUTINE? Или я что-то здесь не понимаю?   -  person Justin Time - Reinstate Monica    schedule 21.09.2016
comment
Хм... Проверил сопрограммы, выглядит интересной идеей, но не проще ли мой подход, чем сопрограммы? По сути, вам нужно отдельно закодировать эту сопрограмму и каким-то образом протестировать API сопрограммы - я предоставляю аналогичный механизм с меньшим количеством кода. Конечно, есть риск, что что-то не сработает, но подобное есть в сопрограммах?   -  person TarmoPikaro    schedule 21.09.2016
comment
В настоящее время библиотека не использует какой-либо механизм исключений - я надеюсь, вы не выделяете память, поскольку эти вызовы могут генерировать ошибки, и это не единственное место, где могут быть неявные исключения. Я также надеюсь, что вы не полагаетесь на деструкторы. На момент написания этой статьи этот вопрос отмечен тегами [c++] и [setjmp], что в основном является противоречием в терминах.   -  person eh9    schedule 25.10.2016
comment
Я не намерен бросать - если происходит выделение памяти, я добавлю к нему try-catch.   -  person TarmoPikaro    schedule 25.10.2016
comment
but isn't my approach a little bit simpler than coroutines видя проблемы, которые у вас есть сейчас, я бы, вероятно, не согласился. Я ничего не вижу в текущем коде, но вы должны проверить свои типы. Похоже, где-то вы неверно указали на прыжки. Имейте в виду, что указатели вдвое больше в 64-битной версии, поэтому вам следует проверить там. И поставьте уровень предупреждений на максимум и пройдитесь по ним. Возможно, они покажут вам ошибку.   -  person Hayt    schedule 28.10.2016
comment
Этот код действительно работает, а также в нем гораздо меньше кода по сравнению с сопрограммами. Так что я не ищу ошибки в этом коде, но интересуюсь, что означает раскрутка/обработка исключений в контексте setjmp/longjmp.   -  person TarmoPikaro    schedule 28.10.2016