Beautiful C API
От: GhostCoders Россия  
Дата: 22.04.16 13:44
Оценка: 7 (3)
Добрый день!

Решил я запилить open source средство для автоматического враппинга С++ кода.
Компиляторов С\С++ много, у каждого свой ABI, соглашения о наименовании методов (name mangling) разные.
Даже размер std::string разный в Debug и Release.

Думаю, что получиться что-то вроде Swig в первом приближении, но не он.
Упор будет делаться на то, что генерируемые С-обвёртки будут читаемы для пользователей, а также то, что ими могут пользоваться С-программисты.

Ну и на стороне клиенты будет еще (опционально) генерироваться С++ header only style библиотека для возврата этого дела опять в С++.

Типа есть класс

class Printer
{
private:
    int some_member;

public:
    Printer() : some_member(0) {}
    void Show()
    {
        std::cout << "Member " << some_member << std::endl;
    }
};


И будут генерироваться С-обвертки:
extern "C" void* printer_create();
extern "C" void printer_destroy(void* printer);
extern "C" void printer_show(void* printer);


ну и С++ врапперы:
class Printer
{
private:
    void * printer_handle;
public:
     Printer()
     {
         printer_handle = printer_create();
     }
     ~Printer()
     {
         printer_destroy(printer_handler);
     }
     void Show()
     {
         printer_show(printer_handle);
     }
};


Ну, и вообще, наблюдал несколько разных библиотек, имеющих С API, написанное в духе С++,
то есть имеется метод имя_класса_create() имя_класса_destoy(), имя_класса_имя_метода(),
this обычно пробрасывается как первый аргумент void* или HANDLE. Да тот же WinAPI
Другие примеры ZeroMQ, и C++ биндинг к ней.

То есть средство должно быть как можно легковесней, не быть очередным COM.

У Swig есть С-генератор, но он вроде заброшен.
Плюс Swig парсит C++\C заголовки, я же планирую облегчить себе жизнь,
вводя описание API как внешние XML файлы в формализированном виде.

Кто что думает?

P.S.: На следующей недели обещаю принести ссылку на код.
Третий Рим должен пасть!
Re: Beautiful C API
От: -MyXa- Россия  
Дата: 22.04.16 14:00
Оценка:
Здравствуйте, GhostCoders, Вы писали:

GC>ну и С++ врапперы:

GC>
GC>class Printer
GC>{
GC>private:
GC>    void * printer_handle;
GC>public:
GC>     Printer()
GC>     {
GC>         printer_handle = printer_create();
GC>     }
GC>     ~Printer()
GC>     {
GC>         printer_destroy(printer_handler);
GC>     }
GC>     void Show()
GC>     {
GC>         printer_show(printer_handle);
GC>     }
GC>};
GC>


Дело нужное. Не забудь только оператор присваивания запретить.

И самое главное — любое летящее из внутреннего С++-а исключение должно обязательно ловиться С-шной обёрткой и снова бросаться в неизменном виде в С++-ной. И, кстати, что ты будешь делать в случае, если над С++-ом есть только С (без С++ над С) и этот внутренний С++ выбросит, скажем, boost::thread_interrupted. С-шный код как-то должен понять, что пора закругляться, но как?
Если не поможет, будем действовать током... 600 Вольт (C)
Re[2]: Beautiful C API
От: GhostCoders Россия  
Дата: 22.04.16 14:04
Оценка:
Здравствуйте, -MyXa-, Вы писали:

MX>Дело нужное. Не забудь только оператор присваивания запретить.

Детали реализации будем всем комьюнити оттачивать.

MX>И самое главное — любое летящее из внутреннего С++-а исключение должно обязательно ловиться С-шной обёрткой и снова бросаться в неизменном виде в С++-ной.

Об этом я думал.

MX> И, кстати, что ты будешь делать в случае, если над С++-ом есть только С (без С++ над С) и этот внутренний С++ выбросит, скажем, boost::thread_interrupted. С-шный код как-то должен понять, что пора закругляться, но как?

Над этим еще не размышлял.
Третий Рим должен пасть!
Отредактировано 22.04.2016 14:05 GhostCoders . Предыдущая версия .
Re: Beautiful C API
От: ononim  
Дата: 22.05.16 14:15
Оценка: 6 (1) :)
GC>То есть средство должно быть как можно легковесней, не быть очередным COM.
C++ это обычно еще и контейнеры std/ссылки на другие С++ объекты в параметрах + уже упомянутые исключения. Без всего этого С++ не торт, а со всем этим — придется реализовывать некий маршалинг, т.е. очередной ком.

GC>Ну, и вообще, наблюдал несколько разных библиотек, имеющих С API, написанное в духе С++, то есть имеется метод имя_класса_create() имя_класса_destoy(), имя_класса_имя_метода(),

Я както реализовывал одну библиотеку и решил соригинальничать. Сделал create, который возвращает указатель на структуру, содержащую указатели на thunk'и, вызывающие методы с this конкретно этого С++ объекта. В итоге в С код выглядел совсем как С++-ный
Some *thing = CreateSome();
thing->MakeUserHappy();
thing->Release();

CreateSome в свою очередь выделял кусок executable памяти, в которую генерил на асме стабчик для вызова конкретного метода конкретного плюсового объекта 'под капотом' Some.
Как много веселых ребят, и все делают велосипед...
Re[2]: Beautiful C API
От: GhostCoders Россия  
Дата: 23.05.16 08:03
Оценка:
Здравствуйте, ononim, Вы писали:

GC>>То есть средство должно быть как можно легковесней, не быть очередным COM.

O>C++ это обычно еще и контейнеры std/ссылки на другие С++ объекты в параметрах + уже упомянутые исключения. Без всего этого С++ не торт, а со всем этим — придется реализовывать некий маршалинг, т.е. очередной ком.
Согласен.
Вообще надеюсь, что тут можно будет отделаться интерфейсом для STL, написанным вручную и реализацией для него.
Будет идти в составе пакета.

GC>>Ну, и вообще, наблюдал несколько разных библиотек, имеющих С API, написанное в духе С++, то есть имеется метод имя_класса_create() имя_класса_destoy(), имя_класса_имя_метода(),

O>Я както реализовывал одну библиотеку и решил соригинальничать. Сделал create, который возвращает указатель на структуру, содержащую указатели на thunk'и, вызывающие методы с this конкретно этого С++ объекта. В итоге в С код выглядел совсем как С++-ный
O>
O>Some *thing = CreateSome();
O>thing->MakeUserHappy();
O>thing->Release();
O>

O>CreateSome в свою очередь выделял кусок executable памяти, в которую генерил на асме стабчик для вызова конкретного метода конкретного плюсового объекта 'под капотом' Some.
Прикольно. Но this ведь нужно было передавать, или как-то автоматом подхватывался?

P.S.: Сегодня вечером скину ссылку на GitHub проект, все еще в процессе, немного забросил его, но думаю возобновить.
Третий Рим должен пасть!
Re: Beautiful C API
От: GhostCoders Россия  
Дата: 24.05.16 19:57
Оценка:
Ссылка на GitHub.

Но там еще пилить и пилить.
Update: Для поднятия само-мотивации, дам ссылку в своей подписи.
Третий Рим должен пасть!
Отредактировано 24.05.2016 19:59 GhostCoders . Предыдущая версия .
Re: Beautiful C API
От: LuciferSaratov Россия  
Дата: 24.05.16 20:07
Оценка:
Здравствуйте, GhostCoders, Вы писали:

GC>Добрый день!


GC>Решил я запилить open source средство для автоматического враппинга С++ кода.

GC>Компиляторов С\С++ много, у каждого свой ABI, соглашения о наименовании методов (name mangling) разные.

штука нужная, я буквально неделю назад в полуавтоматическом режиме делал C-wrapper для библиотеки на C++.
как дела обстоят с колбэками? скажем, есть что-то вроде такого:


struct IEventListener {
    virtual void OnEventHappened( int errorCode ) = 0;
};

struct MyEventListener: public IEventListener {
    virtual void OnEventHappened( int errorCode ) {
        printf( "event happened\n" );
    }
};

struct EventSource {
    void Register( IEventListener *listener );
    void Unregister( IEventListener *listener );
};


как это все будет отображаться на plain-c ?
Re[2]: Beautiful C API
От: GhostCoders Россия  
Дата: 28.05.16 18:00
Оценка:
Здравствуйте, LuciferSaratov, Вы писали:

LS>штука нужная, я буквально неделю назад в полуавтоматическом режиме делал C-wrapper для библиотеки на C++.

LS>как дела обстоят с колбэками? скажем, есть что-то вроде такого:

LS>

LS>struct IEventListener {
LS>    virtual void OnEventHappened( int errorCode ) = 0;
LS>};

LS>struct MyEventListener: public IEventListener {
LS>    virtual void OnEventHappened( int errorCode ) {
LS>        printf( "event happened\n" );
LS>    }
LS>};

LS>struct EventSource {
LS>    void Register( IEventListener *listener );
LS>    void Unregister( IEventListener *listener );
LS>};

LS>


LS>как это все будет отображаться на plain-c ?


Пока, к сожалению, колбэки никак не поддерживаются.
Добавил в файлик TODO запись про поддержку колбэков.

Ну, а вообще идеи такие.

IEventListener — это класс, и он с точки зрения клиента будет представлен в виде С функции:
extern "C" void ieventlistener_oneventhappened(void* self, int errorCode);

Пока так, размер буковок пока не сохраняется, над этим надо подумать, но пока это не важно.

Если клиент будет вызывать реализацию IEventListener, которая реализована в библиотеке, то он будет пользоваться данной С-функцией.
Но, колбэк на то и колбэк, чтобы дать возможность библиотеке вызывать нашу реализацию IEventListener (реализованную на стороне клиента).

Пользователь вручную создает или автоматически генерирует такую С-функцию:

extern "C" void my_event_listener_on_event_happened(void* self, int errorCode)
{
    MyEventListener* self_pointer = static_cast<MyEventListener*>(self);
    self_pointer->OnEventHappened(errorCode);
    // ...
}


Возможно библиотека будет иметь насколько реализаций интерфейса IEventListener.
Но BCAPI должна каким-то образом узнать, что для интерфейса IEventListener возможна реализация на стороне клиента (могут быть колбэки).
Думаю, что для этого должен быть какой-то флаг в XML файле описания интерфейсов.

Если данный флаг включен, то BCAPI генерирует еще одну реализацию IEventListener:
class CustomCallbackForIEventListener : public IEventListener
{
    void* m_self;
    ONEVENTHAPPENDED_FP m_function;
public:
    virtual void SetSelfPointer(void* pointer)
    {
        m_self = pointer;
    }
    void SetOnEventHappenedPointer(ONEVENTHAPPENDED_FP pointer_to_function) // передается указатель не функцию типа ieventlistener_oneventhappened
    {
        m_function = pointer_to_function;
    }
    virtual void OnEventHappened( int errorCode )
    {
    m_function(m_self, errorCode); // вызываем функцию по указателю на нее, передаем ранее запомненный this на пользовательский объект
    }
};


Со стороны клиента нужно создать объект CustomCallbackForIEventListener (который будет обвёрнут обычным для BCAPI способом),
задать ему указатель на нужный объект колбака через SetSelf, задать указатель на функцию, и использовать объект CustomCallbackForIEventListener там, где планируется использовать IEventListener:

void f(EventSource *event_source)
{
    MyEventListener* my_event_listener = new MyEventListener();
    CustomCallbackForIEventListener * custom_callback = CreateCustomCallbackForIEventListener();
    custom_callback->SetSelf(&my_event_listener);
    custom_callback->SetOnEventHappenedPointer(my_event_listener_on_event_happened);
    event_source->Register(custom_callback);
}



Как-то так.
Замечания:
1) Фактически два раза вызов виртуальной функции при вызове колбэка OnEventHappened() — один раз виртуальная функция CustomCallbackForIEventListener::OnEventHappened(),
и затем вызов функции через указатель. Да еще третий раз в самой функции my_event_listener_on_event_happened() вызывается виртуальный метод.
Но на самом деле тут реализацию MyEventListener можно делать обычным классом (не полиморфным), а методы заинлайнить.
2) Интерфейс CustomCallbackForIEventListener нужно делать видимым для пользователя, чтобы он мог воспользоваться функциями SetSelfPointer и SetOnEventHappenedPointer
3) Если число методов большое (не только один метод OnEventHappened) — то это неудобно.
Нужно сделать так, чтобы генерировались классы, уже делающие всю работу на стороне клиента
4) Еще есть мысль, чтобы CustomCallbackForIEventListener тоже был неполиморфный, возможно с инлайн методами.
Третий Рим должен пасть!
Re: HelloWorld, первые примеры
От: GhostCoders Россия  
Дата: 30.05.16 17:52
Оценка:
Добрый вечер!

Сейчас в проекте Чаппи (beautuful capi, или просто capi, или просто чаппи) уже кое-что есть и что-то даже работает.

Проект использует Python 3 для собственно генерации.
Есть несколько примеров, написаны на С++ 2003, с использованием CMake (проверял на 3-м CMake, на 2.8 могут быть проблемы).

CMake примеры используют add_custom_command для запуска генерирующего Python скрипта:
add_custom_command(
  OUTPUT ${hello_world_SOURCE_DIR}/source/AutoGenWrap.cpp
  COMMAND ${PYTHON_EXECUTABLE} ${hello_world_SOURCE_DIR}/../../../source/Main.py -i ${hello_world_SOURCE_DIR}/hello_world.xml -p ${hello_world_SOURCE_DIR}/hello_world_params.xml -o ${hello_world_SOURCE_DIR}/include -w ${hello_world_SOURCE_DIR}/source/AutoGenWrap.cpp
  MAIN_DEPENDENCY ${hello_world_SOURCE_DIR}/hello_world.xml
  DEPENDS ${hello_world_SOURCE_DIR}/hello_world_params.xml
  WORKING_DIRECTORY ${hello_world_SOURCE_DIR}
)

include_directories(${hello_world_SOURCE_DIR}/source/)

add_library(hello_world SHARED
  ${hello_world_SOURCE_DIR}/source/AutoGenWrap.cpp
  ${hello_world_SOURCE_DIR}/source/PrinterImpl.h
  ${hello_world_SOURCE_DIR}/source/PrinterImpl.cpp
)


Для запуска всех примеров нужно сделать примерно следующее (находясь в корне репозитрия):
cd examples
mkdir build
cd build
cmake -G "Visual Studio 14 2015 Win64" ..


в папочке build появится солюшн для студии, открываем его и собираем.

Например, hello_word — пример библиотечки, hellow_world_client — пример программы, использующей эту библиотеку.

/examples/hello_world/library/hello_world.xml содержит описание API нашей библиотеки.
Формальная схема формата описания API — source/capi.xsd

Кратце так:
1) API состоит из пространств имен. Пространства имен могут быть вложенными неограниченное число раз.
2) На самом верхнем уровне могут быть несколько пространств имен.
3) В каждом пространстве имен могут находиться описания _классов_. Именно, классов, а не интерфейсов. Об этом чуть позже.
4) В каждом классе может быть несколько методов. У метода есть возвращаемое значение и набор аргументов.
5) Для каждого класса может быть несколько конструкторов.
6) В каждом програнстве имен могут быть еще standalone функции, как обычные так и фабрики, т.е. функции которые возвращают новые объекты классов.


По-умолчанию принята такая схема генерации хидеров:
1) Для каждого пространства имен создается папка
2) Структура папок вложенная, отражающая структуру вложенности пространст имен.
Например, у нас есть пространство имен Example::Geometry::Brep, то создается папка Example/Geometry/Brep
3) Каждый класс представлен своим хидером, например, класс Example::Geometry::Brep::Body будет находиться в
Example/Geometry/Brep/Body.h
4) Для каждого пространства имен создается "куммулятивный" хидер, который включает хидеры всех его классов и нижестоящих пространств имен.
Например, Example/Geometry/Brep.h будет включать в себе через #include файл Example/Geometry/Brep/Body.h,
Example/Geometry.h будет включать Example/Geometry/Brep.h,
а Example.h будет включать Example/Geometry.h.

Пользователю достаточно будет включить файл Example.h и все содержимое просранства имен Example будет включено.
Я также ввел различные флаги для управления алгоритмом создания таких папок.
Есть возможность генерации всех классов, пространств имен в один-единственный хидер (для простоты).

Кроме xml описания самого API есть xml с настройками генерации, формальная схема которого — source/capi-params.xsd

В папочке examples есть кое-какие примеры.
В папке test находятся тесты, сейчас там тестируются всевозможные режимы создание папок с хидерами (file_options).

Я не питонист, прощу оценить код, дать какие-нибудь полезные замечания, как по питон-коду, так и по С\С++ коду, так и вообще по проекту.
Третий Рим должен пасть!
Re: Динамическая загрузка С-функций
От: GhostCoders Россия  
Дата: 30.05.16 18:02
Оценка:
Думаю, что сабж выше пригодиться.

По-умолчанию, Чаппи, будет генерировать С-функции вида
extern "C" ...


Но для этого нужны .lib файлы — компаньоны .dll.
Проблема в том, что их форматы иногда не совместимы — COEF vs OMF.

Поэтому имеет смысл (опционально) сделать их динамически загружаемыми через указатели,
с использованием LoadLibrary\GetProcAddress\FreeLibrary для Windows, и dlopen\dlsym\dlclose для *nix
(будет располагаться в header-only библиотеке).

Но указатели нужно инициализировать явно, либо генерировать классы для этого, типа:


TFUNCTION_POINTER1 g_func1 = 0;
TFUNCTION_POINTER2 g_func2 = 0;

#ifdef _WIN32
#include <windows.h>
#else
#include <dl.h>
#endif


struct ModuleInitializer
{
    ModuleInitializer()
    {
#ifdef _WIN32
        m_module = LoaderLibrary("Module.dll");
        g_func1 = GetProcAddress(...);
        g_func2 = GetProcAddress(...);
        ...
 
#else
        m_module = dlopen("libModule.so", RTL_NOW);
        g_func1 = dlsym(...);
        g_func2 = dlsym(...);
        ...
#endif
    }

    ~ModuleInitializer()
    {
        g_func1 = 0;
        g_func2 = 0;
#ifdef _WIN32
        FreeLibrary(m_module);
#else
        dlclose(m_module);
#endif
    }

private:
#ifdef _WIN32
    HMODULE m_module;
#else
    void* m_module;
#endif
};


Сейчас пока реализована возможность с использованием implib библиотек (нужны файлы компаньоны к .dll — .lib, хотя для *nix это вообще не нужно, как понимаю).

Небольшая проблемка в том, что в оборачиваемой библиотеке существует несколько пространств имен верхнего уровня.
И нет единого хидера верхнего уровня для всей библиотеки (за исключением случая когда один файл только и генерируется).

И тут придеться инициализировать каждый неймспейс верхнего уровня отдельно, или... что-то придумать..

Какие будет мысли?

(хотя, конечно, реализация динамической загрузки С-функций, думаю, не должны быть в приоритете над остальными фичами, как так исключения, коллбэки и некоторые другие)
Третий Рим должен пасть!
Re: Циклические ссылки
От: GhostCoders Россия  
Дата: 07.06.16 17:20
Оценка:
На данный момент генерируются враппер-классы, используемые по значению.

То есть для класса
class Printer
{
private:
    void * printer_handle;
public:
     Printer()
     {
         printer_handle = printer_create();
     }
     ~Printer()
     {
         printer_destroy(printer_handler);
     }
     void Show()
     {
         printer_show(printer_handle);
     }
};


использование будет в духе:

{
    Printer printer_instance;
    printer_instance.Show();
}


То есть доступ идет через точку.
И сам класс, по сути являющийся легким враппером, имеет семантику копирования.

Проблема возникает в случае если класс А использует класс Б и наоборот.
Обычно, в С++ используют forward declaration, реализацию выносят в отдельный .cpp файл (от .h заголовка),
и в заголовках используют указатель или ссылку.

Такие циклические связи между классами необходимо отразить и в моих врапперах.
Но! Forward declaration работает только для указателей или ссылок, а у меня значения!

Поэтому пришлось немного исхитриться.
Для простого аргумента используется:

    void SetB(const Circular::ClassB& value)
    {
        struct raw_pointer_holder { void* raw_pointer; };
        circular_classa_setb(mObject, reinterpret_cast<const raw_pointer_holder*>(&value)->raw_pointer);
    }

и естественно, используется forward declaration для ClassB.

А для возвращаемых значений вот что:
    Circular::ClassBFwdPtr GetB()
    {
        return Circular::ClassBFwdPtr(circular_classa_getb(mObject));
    }


где Circular::ClassBFwdPtr есть:
namespace Circular
{
    namespace beautiful_capi
    {
        template<typename WrappedObjType>
        class forward_pointer_holder
        {
            void* m_pointer;
            bool m_object_was_created;
        public:
            explicit forward_pointer_holder(void* pointer)
             : m_object_was_created(false), m_pointer(pointer)
            {
            }
            ~forward_pointer_holder()
            {
                if (m_object_was_created)
                {
                    reinterpret_cast<WrappedObjType*>(this)->~WrappedObjType();
                }
            }
            operator WrappedObjType()
            {
                return WrappedObjType(m_pointer);
            }
            WrappedObjType* operator->()
            {
                m_object_was_created = true;
                return new(this) WrappedObjType(m_pointer);
            }
            void* get_raw_pointer() const
            {
                return m_pointer;
            }
        };
    }
    
    class ClassA;
    typedef beautiful_capi::forward_pointer_holder<ClassA> ClassAFwdPtr;
    class ClassB;
    typedef beautiful_capi::forward_pointer_holder<ClassB> ClassBFwdPtr;
}


То есть для возращаемых значений их можно присвоить враппер классу (благодаря оператору приведения),
или сделать вызов на лету, с использованием ->
    Circular::ClassA a_object; ...
    a_object.GetB()->Show(); // вызов на лету
    Circular::ClassB b_object = a_object.GetB();
    b_object.Show(); // а тут используем точку (".") как обычно


но получается, в одном месте используется стрелка, в другом точка. Нехорошо.

А как вам такая практика:

class Printer
{
private:
    void * printer_handle;
public:
     Printer()
     {
         printer_handle = printer_create();
     }
     ~Printer()
     {
         printer_destroy(printer_handler);
     }
     Printer* operator->()
     {
         return this;
     }
     const Printer* operator->() const
     {
         return this;
     }
     void Show()
     {
         printer_show(printer_handle);
     }
};


То есть можно использовать стрелку, даже для объектов-значений врапперов:
{
    Printer printer_instance;
    printer_instance->Show();
}


Есть ли подводные камни?
Третий Рим должен пасть!
Отредактировано 07.06.2016 17:24 GhostCoders . Предыдущая версия .
Re: Добавлен пример boost_shared_ptr
От: GhostCoders Россия  
Дата: 15.06.16 17:08
Оценка:
Сейчас только что добавил новый пример boost_shared_ptr, показывающий как оборачивать boost::shared_ptr смарт-поинтеры.
У самого boost::shared_ptr используется семантика копирования, а уж внутри себя он считает ссылки сам.
Плюс добавил субмодули для используемого boostа.

Еще добавился атрибут pointer_access, который означает, что доступ к обворачиваему классу идет через -> а не через .
Обычно это нужно для обвертки смарт-пойнтеров и похожих вещей.
Третий Рим должен пасть!
Re: down_cast и одноименный пример
От: GhostCoders Россия  
Дата: 20.06.16 15:47
Оценка:
Добрый вечер!

Сегодня добавил фичу down_cast.
Это такой аналог dynamic_cast.
down_cast — это шаблонная ф-я, которая принимает два шаблонных параметра — целевой тип и входной тип.
Входной тип обычно выводится автоматически.

Тест примера down_cast:
#include <iostream>
#include <cstdlib>
#include "Example.h"

using Example::down_cast;

void show(Example::IShapePtr shape)
{
    shape->Show();
    if (down_cast<Example::IPolygonPtr>(shape)->IsNotNull())
    {
        std::cout << "IPolygon" << std::endl;
        Example::IPolygonPtr polygon = down_cast<Example::IPolygonPtr>(shape);
        std::cout << "number of points = " << polygon->GetPointsCount() << std::endl;
    }
    if (down_cast<Example::ITrianglePtr>(shape)->IsNotNull())
    {
        std::cout << "ITriangle" << std::endl;
        Example::ITrianglePtr triangle = down_cast<Example::ITrianglePtr>(shape);
        triangle->SetPoints(-1, 1, 5, 6, 10, 15);
    }
    if (down_cast<Example::ISquarePtr>(shape)->IsNotNull())
    {
        std::cout << "ISquare" << std::endl;
        Example::ISquarePtr square = down_cast<Example::ISquarePtr>(shape);
        square->SetSize(3.14);
    }
    if (down_cast<Example::ICirclePtr>(shape)->IsNotNull())
    {
        std::cout << "ICircle" << std::endl;
        Example::ICirclePtr circle = down_cast<Example::ICirclePtr>(shape);
        circle->SetRadius(7.77);
    }
}

int main()
{
#if defined(_WIN32) && defined(_DEBUG)
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
    Example::IShapePtr triangle = Example::CreateTriangle();
    Example::IShapePtr shape0(Example::CreateCircle());
    Example::IShapePtr shape1(Example::CreateSquare());

    std::cout << std::endl << "The first pass." << std::endl;
    show(triangle);
    show(shape0);
    show(shape1);

    std::cout << std::endl << "The second pass." << std::endl;
    show(triangle);
    show(shape0);
    show(shape1);

    std::cout << std::endl;
    return EXIT_SUCCESS;
}


P.S.: Убрать using down_cast не удалось пока что.
Третий Рим должен пасть!
Добавлена поддержка исключений
От: GhostCoders Россия  
Дата: 27.06.16 07:26
Оценка:
Добрый день!

Добавил поддержку исключений.
exception_handling_mode может быть no_handling, которое означает что исключения никак специальным образом не обрабатываются (как раньше работало).
И новый режим by_first_argument, который означает, что каждая С-функция теперь в качестве аргумента принимает указатель на структуру
struct beautiful_capi_exception_info_t
{
    int code;
    void* object_pointer;
};


где — code исключения (0 — не было исключений), а object_pointer — указатель на этот объект.

Конечно, при использовании голого С в качестве клиента это будет не очень красиво и удобно.
Это должен быть стиль проверки кодов возврата и обратки ошибки в случае ненулевого кода.

Но С++ обвёртки скрывают это, можно перехватывать исключения через базовый тип, например так:
/
*
 * Beautiful Capi generates beautiful C API wrappers for your C++ classes
 * Copyright (C) 2015 Petr Petrovich Petrov
 *
 * This file is part of Beautiful Capi.
 *
 * Beautiful Capi is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Beautiful Capi is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Beautiful Capi.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#include <iostream>
#include <cstdlib>
#include "Example.h"

int main()
{
    try
    {
        Example::Printer printer;
        Example::ScannerPtr scanner;

        scanner->PowerOn();
        std::cout << "Scanned text: " << scanner->ScanText() << std::endl;
        scanner->PowerOff();

        printer.PowerOn();
        printer.Show("from main()");
        std::cout << "Trying to show null pointer" << std::endl;
        printer.Show(0); // Здесь выбрасывает исключение типа Exception::NullArgument, которое занаследовано от Exception::BadArgument, которое наследовано от Exception::Generic
        printer.PowerOff();
    }
    /* Если закоментировать блок catch для NullArgument, то исключение можно словить как Exception:BadArgument, если и его закоментировать, то словится как Exception:Generic
    /*catch (Exception::NullArgument& null_argument_exception)
    {
        std::cout << "    *** Exception::NullArgument was thrown" << std::endl;
        std::cout << "    " << null_argument_exception.GetErrorText() << std::endl;
        std::cout << "    argument: " << null_argument_exception.GetArgumentName() << std::endl;
    } */ 
    catch (Exception::BadArgument& null_argument_exception)
    {
        std::cout << "    *** Exception::BadArgument was thrown" << std::endl;
        std::cout << "    " << null_argument_exception.GetErrorText() << std::endl;
    }
    catch (Exception::Generic& generic_exception)
    {
        std::cout << "    *** Exception::Generic was thrown" << std::endl;
        std::cout << "    " << generic_exception.GetErrorText() << std::endl;
    }
    catch (const std::exception& exception)
    {
        std::cout << "    *** std::exception was thrown" << std::endl;
        std::cout << "    " << exception.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "    *** Unknown exception was hrown" << std::endl;
    }

    return EXIT_SUCCESS;
}


P.S.: Для голого С еще нужно немного доработать.
Третий Рим должен пасть!
Re[2]: Beautiful C API
От: GhostCoders Россия  
Дата: 27.06.16 07:34
Оценка:
Здравствуйте, -MyXa-, Вы писали:

MX>Не забудь только оператор присваивания запретить.

Пока никак на это не реагировал. Но планирую не запретить, а переопределить.

MX>И самое главное — любое летящее из внутреннего С++-а исключение должно обязательно ловиться С-шной обёрткой и снова бросаться в неизменном виде в С++-ной.

Совсем в неизменном виде — не получается.
Исключение ловится С-шной обёрткой, но С++-ной выбрасывается соответствующий класс-обёртка (который виден на стороне клиента и через который осуществляется доступ к библиотечному классу).

MX>И, кстати, что ты будешь делать в случае, если над С++-ом есть только С (без С++ над С) и этот внутренний С++ выбросит, скажем, boost::thread_interrupted. С-шный код как-то должен понять, что пора закругляться, но как?


Пока идей нет
Третий Рим должен пасть!
Re[2]: Beautiful C API
От: MasterZiv СССР  
Дата: 12.08.16 06:54
Оценка: +1
Здравствуйте, -MyXa-, Вы писали:

MX>Дело нужное. Не забудь только оператор присваивания запретить.


Я лично вот сильно сомневаюсь.
Очень уже сильно разная парадигма программирования в голом С и в маршевом C++.
Библиотеки С можно использовать в С++, и можно писать для них врапера.
А вот библиотеки С++ в С использовать очень будет сложно, для этого надо их сначала
переписать в дуже "С с классами", а потом уже использовать.

Исключения, управление памятью, инициализация, -- всё это не даст нормально
писать на С над С++ными библиотеками, да и зачем ? Легче выучить С++ и писать на нём.


Например:
[ccode]
extern "C" void* printer_create();
extern "C" void printer_destroy(void* printer);
[/сcode]

Предполагается, что printer_create() будет
0) выделять память
1) инициализировать в ней новый объект соотв. класса.

Как она будет выделять память, где создавать объект ? Очевидно, в хипе (в динамической памяти), больше негде.
А в С++ объекты могут создаваться в 3 классах памяти: auto, global, dynamic.
Почему мы должны себя обделять такими возможностями, программируя на С с использованием этой будущей библиотеки?
Нет, не должны.

Имя printer_create(); состоит из двух частей: имя класса, и имя функции в классе.
Полное имя класса в С++ может быть очень большим, очень длинным — namespaces, inner classes .
И его мало того надо упаковать в С-шный идентификатор, но ещё и добавить потом имя метода, которое тут вполне безобидно короткое,
а для реального метода тоже может быть большим. А orerloading ?

Ну и вообще, проблема использования С++ кода из С надуманая и неинтересная -- проще взять С++ и на нём писать.
С и С++ компиляторы теперь ходят парами.

А нужна будет обёртка -- легче написать руками заточив под нужную семантику.
Re[3]: Beautiful C API
От: -MyXa- Россия  
Дата: 12.08.16 14:04
Оценка:
Здравствуйте, MasterZiv, Вы писали:

MZ>Исключения, управление памятью, инициализация, -- всё это не даст нормально

MZ>писать на С над С++ными библиотеками, да и зачем ? Легче выучить С++ и писать на нём.

Ну, например, С++ный код в dll-ки заворачивать. Или в COM (это чисто теоретически, т.к. не думаю, что автор это планирует, но так-то у COM такие же правила, как и у С — не кидать исключений, возвращать код ошибки, вместо std::string — BSTR, и т.д.). Можно, конечно, классы экспортировать из dll как есть, но, думаю, это не все оценят положительно (особенно, если с того конца Васик или Питон).

MZ>Как она будет выделять память, где создавать объект ? Очевидно, в хипе (в динамической памяти), больше негде.


Она может запрашивать память у приложения. Многие С-шные библиотеки обращаются к malloc/free не напрямую, а через прослойку, которую можно настроить или во время компиляции библиотеки, или прямо во время исполнения.
Если не поможет, будем действовать током... 600 Вольт (C)
Re: Добавлена поддержка колбэков
От: GhostCoders Россия  
Дата: 27.08.16 22:57
Оценка:
На днях была добавлена поддержка колбэков.
И поддержка исключений в таких колбэках.

Все в новом примере examples/callback:

class CustomPrinterImplementation
{
    std::string mLastPrintedText;
public:
    CustomPrinterImplementation()
    {
        std::cout << "CustomPrinterImplementation ctor" << std::endl;
    }
    ~CustomPrinterImplementation()
    {
        std::cout << "CustomPrinterImplementation ctor" << std::endl;
    }
    void Print(const char* text) // Note that this method is non-virtual
    {
        if (!text)
        {
            // This exception will be correctly passed through the library boundary
            // and will be caught by the library code
            throw Exception::NullArgument();
        }
        mLastPrintedText = std::string(text);
        std::transform(mLastPrintedText.begin(), mLastPrintedText.end(), mLastPrintedText.begin(), toupper);
        std::cout << mLastPrintedText << std::endl;
    }
};

int main()
{
    Example::Person famous_person;
    famous_person.SetFirstName("Isaac");
    famous_person.SetSecondName("Newton");
    famous_person.SetAge(26);
    famous_person.SetMale(true);

    Example::PrinterPtr printing_device = Example::CreateDefaultPrinter();
    famous_person.Dump(printing_device);
    famous_person.Print(printing_device, "Hello World");

    CustomPrinterImplementation my_printer_implementation;
    printing_device = Example::create_callback_for_customprinter(my_printer_implementation);
    famous_person.Dump(printing_device);

    // CustomPrinterImplementation::Print() will throw exception (Exception::NullArgument)
    // and this exception will be caught by the library code
    famous_person.Print(printing_device, 0);

    return EXIT_SUCCESS;
}


Здесь сначала создается некая персона и стандартная реализация принтера (из библиотеки).
Персона дампится через этот принтер.

Далее создается "кастомный" принтер (реализация на стороне клиента).
И персона дампится с использованием этого принтера.

Еще есть метод Print у персоны. Она этот вызов перенапрявляет принтеру.
Это нужно для того, чтобы кастомному принтеру пришел nullptr в качестве строки и тот выкинул исключение (на стороне клиента).
И это исключение на стороне клиента корректно обрабатывается на стороне библиотеки.
DLL boundary проходит корректно.

Еще список TODO большой, но двигаюсь к первому релизу.
Потом буду просить использовать в каких-нибудь проектах для тестирования\использования.
Третий Рим должен пасть!
Re: Версия 0.3 выпущена
От: GhostCoders Россия  
Дата: 15.12.16 08:56
Оценка:
Проект развивается потихонечку.
Добавил поддержку генерации doxygen комментариев, также был произведен рефакторинг.
Добавил план развития проекта (на GitHub в разделе Issues).
Третий Рим должен пасть!
Re: Beautiful C API
От: vdimas Россия  
Дата: 01.04.17 00:43
Оценка: 2 (1)
Здравствуйте, GhostCoders, Вы писали:

GC>ну и С++ врапперы:

GC>
GC>


Зачем так, если можно так:
class Printer
{
public:
    void Show() {
        printer_show(this);
    }

    static void * operator new(size_t size) {
        assert(size == sizeof Printer);
        return printer_create();
    }
    
    static void operator delete(void * obj, size_t size) {
        assert(size == sizeof Printer);
        printer_destroy(obj);
    }
};
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.