Author Topic: Динамическое связывание средствами Си  (Read 2218 times)

0 Members and 1 Guest are viewing this topic.

Offline Алексей Гринь

  • Blogger
  • *
  • Posts: 24112
  • Gender: Male
Динамическое связывание средствами Си.

Динамическое связывание, реализованное через message-driven паттерн, широко используется в Obj-C, неявно присутствует при вызовах интерфейсных методов в C# и Java, является частью WinApi (одно из немногих адекватных решений Microsoft).
Статическое связывание, которое наблюдается в C++, не столько ограничивает в динамике, сколько ограничивает в рефакторинге - в свободе добавлять и удалять некие интерфейсные элементы "на лету" - а также провоцирует чересчур взаимозависимые монолитные архитектуры (да-да! :) ). Алсо приходится слишком много времени уделять на отшлифовку отношений между классами вместо моделирования задачи по существу (борьба с синтаксисом и семантикой).
Любая реализация высокоуровневых паттернов на Си будет требовать повышенного внимания (в отличие от С++, где можно положиться на авось - на компилятор), но мне это никогда не мешало :)
Итак, API у меня в целом такой:

Quote
#include "klist.h"
#include "kiter.h"

int main(KVOID)
{
    KLIST* list = klist();

    /* ... */
    /* Заполнение списка. */
    /* ... */

    KITER iter;
    kiter(&iter, (KOBJ*)list);

    /* Печатаем каждое число. */
    while(kiter_hasmore(&iter))
    {
        KANY v;
        kiter_next(&iter, &v);

        wprintf(L"%d ", kunboxi(&v));
    }

    return 0;
}

По порядку.

Quote
KLIST* list = klist();
Создаём тестовый список. Главное условие для существования любого объекта в моём фреймворке - это наличие m_refcnt в первых четырёх байтах (но оно в контексте темы неважно), а в последующих четырёх байтах - ссылка на функцию-обработчик. Эта функция обрабатывает сообщения, посылаемые объекту.

Имеет эта функция такую сигнатуру:
Quote
KVOID klist_proc(KOBJ* self, KANY* res, KMSG msg, KARGLIST args);
1-ый аргумент - ссылка на себя (любой объект приводим к KOBJ* через type-punning, ибо у каждого объекта первым полем стоит KOBJ m_base. Именно структура KOBJ располагает счётчиком ссылок и ссылкой на функцию-обработчик).
2-ой аргумент - хитрый контейнер для хранения значения любого типа (эмулирует параметризацию типов). Этот аргумент у нас используется для возврата значения.
3-ий аргумент - собственно само сообщение, 32-битное уникальное значение.
4-ый аргумент - на деле здесь декорируется стандартный сишный va_list.

При создании объекта обязательным пунктом стоит установка этой функции (по умолчанию вызовы делегируются в kobj_defproc, но это неважно).
Вот пример такой функции из класса KLIST:
По KOBJ_DEL можно видеть, что деструкторы у меня реализуется средствами этой же системы.
Макрос knextarg берёт следующий аргумент (на деле это стандартный va_arg).

Естественно, что вручную вызывать эту функцию как-то некузяво, поэтому у меня есть специальная хитрая функция-обёртка, экстенсивно использующая <stdarg.h>:
Quote
KVOID ksend(KOBJ* o, KANY* res, KMSG msg, ...)
{
    va_list ap;
    va_start(ap, msg);

    o->m_proc(o, res, msg, ap);

    va_end(ap);
}

Уникальные 32-битные значения для сообщений я генерирую просто: открываю интерпретатор python'а, пишу hash('MSG_NAME') и получаю уникальный хеш :)

Вот. Теперь объект может реагировать на неограниченное количество сообщений с произвольным количеством аргументов.

Далее-то самое интересное: интерфейсные врапперы. Забыл правильное название. В общем, они инкапсулируют работу с отсылкой сообщений (в явах и сишарпах это всякие StreamReader, TextWriter etc.)
Например, у меня есть класс KWRITER. В конструкторе он принимает любой объект, который понимает сообщения KSTREAM_CANWRITE, KSTREAM_OPENWRITE, KSTREAM_WRITESTR, KSTREAM_CLOSE и некоторые другие (можно сделать, чтобы он сперва динамически получал список поддерживаемых сообщений и проверял его, но мне в лом да это снижает производительность как мою, так и программы. Поэтому когда объект получает непонятное ему сообщение, просто завершаем программу с ошибкой).

Теперь я могу вызывать конструктор вот так:
Quote
KWRITER* wr = kwriter((KOBJ*)kfile(kstr(L"hello.txt")));

А могу вот так:
Quote
KWRITER* wr = kwriter(kstdout());

Далее нам уже совершенно безразлично, в какой тип потока мы делает запись. Мы просто пишем так:
Quote
kwriter_writestr(wr, kstr(L"some text"));

и оно автоматически всё делает за нас - выводит эту строку на консоль или в файл.

Функция kwriter_writestr на деле такая:
Quote
KVOID kwriter_writestr(KWRITER* me, KSTR* val)
{
    ksend(me->m_wrpd, 0, KSTREAM_WRITESTR, val);
}
Она просто посылает сообщение. А какому объекту и какого типа этот объект - нам без разницы, лишь бы понимал сообщение. Оно там само как-то решится :)

То же самое с приведённым в самом начале итератором. Его функции реализованы так:
Quote
KBOOL kiter_hasmore(KITER* me)
{
    KANY r;

    ksend(me->m_iterd, &r, KITER_HASMORE, me->r_cur, &me->m_spec);

    return kunboxb(&r);
}

KVOID kiter_next(KITER* me, KANY* res)
{
    ksend(me->m_iterd, res, KITER_NEXT, me->r_cur, &me->m_spec);

    /* Update index. */
    me->r_cur++;
}
То есть мы можем применять этот итератор к объекту абсолютно любого нового класса, лишь бы этот объект понимал сообщения KITER_HASMORE и KITER_NEXT (всего два!). Никаких перемудрённых деревьев наследования и шаблонов, ничего.

В кривое-ООП-ориентированных системах подобное ещё реализуется через т. н. mixins (более того, оно всё это у них считается мегакрутыми мегамодными фишками, которыми стоит похвастаться, лол)

И наких кривых цэпэпэв не надо.
肏! Τίς πέπορδε;

 

With Quick-Reply you can write a post when viewing a topic without loading a new page. You can still use bulletin board code and smileys as you would in a normal post.

Note: this post will not display until it's been approved by a moderator.
Name: Email:
Verification:
√49 Напишите ответ строчными буквами:
«Сто одёжек, все без застёжек» — что это?: