Сначала была мечта… Что-то похожее на робототехническую систему, с двумя подвижными камерами, способностью отслеживать (направлять «взгляд» на) заданный объект и определять расстояние до объекта. И это был 2012 год. Но так как я больше программист нежели железячник, то все началось с реализации существующих в то время алгоритмов. Скоро пришло осознание, что алгоритмы и их реализация не есть цель. Цель – робототехническая система. Поэтому было принято решение воспользоваться существующими библиотеками обработки изображений. Но, к сожалению, на Object Pascal готовые библиотеки алгоритмов, которые были найдены в то время, не позволяли решать поставленные задачи. Очень понравилась OpenCV, тогда еще версия 2.4.2, но естественно без поддержки Object Pascal. Попытки других авторов трансляции заголовочных файлов OpenCV были скромными, просто как пример, концепция. Помню очень тогда взбесило, хотите Java – вот пожалуйста порт, инструменты для создания интерфейсных классов, незаметный для программиста проброс обращений в OpenCV и, самое главное, практически один в один с С++ вид программы на Java. Почти тоже самое с Python и некоторыми другими языками. Короче говоря, Дельфизм (аналог феминизма у нормальных людей) взял верх. Так как это была еще версия OpenCV 2.4.2, с С (cdecl) x32 функциями, то работа по трансляции заголовочных файлов оказалась достаточно простой… большой, но простой, очень объемной, но простой… рутинной, но простой… Просто добавь «cdecl; external core_lib;» и переставь местами имя параметра и его тип. Даже была попытка создать свой автоматизированный инструмент для трансляции. Доступные в то время готовые инструменты выдавали дичь, которую потом править было дольше, чем переводить вручную. Ну и тем более заголовочные файлы OpenCV это не "Hello world" – очень много приходилось принимать решений о том, как правильно перевести задумки авторов в код на Object Pascal. Например, в силу различий способов возврата Var параметров функцию cvGetSize пришлось реализовывать в виде ассемблерной вставки, которая меняет местами регистры. Некоторые вещи не доработаны, например, конвертация cvImage в TBitmap (функция cvImage2Bitmap) – реализованы не все возможные форматы изображений, но это не критично. В какой-то момент нас стало двое. После публикации статьи на Хабре к проекту подключился Михаил Григорьев (@Sleuthhound). В целом, весь объем API OpenCV вплоть до версии 2.4.13 был переведен. Потом включился «Форест Гамп» - добежали до этого места, а сможем ли побежать дальше. Появились примеры с OpenCV+OpenGL, OpenCV+SDL, OpenCV+FFMPEG. Для FFMPEG x32 даже была сделана собственная трансляция заголовочных файлов. Были найдены и переведены все существующие в интернете в то время примеры программ на С, использующих OpenCV. Добавлена возможность использовать все это в FPC. Разработаны компоненты для Delphi, которые похоже «не только лишь каждый» может установить. Автоматический инсталлятор компонент, не доведенный до релиза … А все почему? Да потому что вышел OpenCV 3.0. Ну зачем вы так… Классы, неявные преобразования… Да и вообще – x64. Object Pascal в то время так не мог. Дельфизм перешел в стадию радикального Дельфизма. Было предпринято множество попыток транслировать С++ классы на Object Pascal. Отправным пунктом послужила статья Using C++ objects in Delphi. В основе лежат две идеи основанные на разработки прокси dll. В первом случае вызовы Object Pascal просто транслировались в вызовы функций класса OpenCV, во втором – в прокси dll разрабатывались COM интерфейсы, ссылка на которые возвращалась в вызывающую программу. Остатки этих попыток можно наблюдать в каталоге source3. Оба подхода сопоставимы по получаемому результату. Основное, что изначально не понравилось в таких подходах: Требовалось «обернуть» все классы, что очень много и, в данном случае, очень сложно.  Напрочь терялась вся гибкость самой библиотеки OpenCV по неявному преобразованию типов и еще по многим другим параметрам. Были написаны несколько инструментов для автоматической трансляции OpenCV классов как для первого, так и для второго варианта. В одном из вариантов, для парсинга использовался llvm с собственной разработкой перевода заголовков llvm на Object Pascal. Была реализована попытка написания в Object Pascal аналога JNI из Java. В конечном итоге, из-за недостатка времени и необходимости периодически кушать и спать, попытка изобрести С++ в Object Pascal тогда не удалась… Ну да ладно, как говорят андроиды (не ОС) «Кто старое помянет, у того камера сломается…». Что хотелось от OpenCV на Object Pascal – это максимально гибкое использование возможностей библиотеки OpenCV, с явным и неявным преобразованием типов, скрытое обращение к самой OpenCV, использование стандартных конструкций Object Pascal и их скрытое от программиста преобразование в формат C++. Часть возможностей Object Pascal для реализации этих идей существовало еще в Delphi 2005 (если не ошибаюсь): class operator, Implicit, Explicit. Но этого было мало. В любом случае требовалось для класса в Object Pascal вызывать конструктор и деструктор, а для записи (record), например, Init и Done. Для интерфейсов – все-таки их надо было как-то создавать. Т.е. такого механизма как в C++, автоматического создания и уничтожения собственных структур пока не было. И вот свершилось - Custom Managed Records! Да еще с возможностью контроля присвоения - class operator Assign. Автоматическое создание с вызовом инициализатора и уничтожение структуры с вызовом «уничтожатора». Второе, что сдерживало работу – это разный способ передачи в x32 параметров в функции для Object Pascal и в классах С++. Приходилось исследовать каждую функцию (речь о классах еще в OpenCV 2.4.х). Проблема решилась с переходом OpenCV 3.0 на x64, с единым соглашением о вызовах в Object Pascal и С++ (но есть нюанс). Третье и может самое быть главное – обращение из Object Pascal к функциям класса из OpenCV. Как это выяснилось (достаточно давно) функции класса С++ можно импортировать по «декорированному» имени, а правильно их вызвать это уже дело техники. Кстати, дельфисты-китайцы сейчас очень часто этот способ используют. В совокупности, эти три фактора уже привели к появлению попыток использования OpenCV 4.5.2 в Delphi (например, DOpenCV), правда опять же как концепция, как пример. Так что, проснувшись одним утром, я явно услышал в щебете птиц, звуке закипающего чайника – «Пора!». Да и тут еще эти неожиданные нерабочие не выходные. Теперь по сути вопроса. 1. Получить из DLL все функции не проблема, например, в opencv_world454.dll их 7320 штук (в том числе 6930 функций и операторов классов и 390 функций). Раздекорировать – тоже не проблема. Понять, что это за функция класса: конструктор, деструктор, оператор – тоже не проблема, смотрим документацию, ну или разбираемся сами по раздекорированному имени (это сложнее). Например, конструктор cv::Mat(int rows, int cols, int type); имеет вид 2. Для импортированной функции пишем аналог на Object Pascal Например, для того же конструктора Обратите внимание, что первым параметром передается указатель на сам класс (регистр RAX), что является стандартным подходом во многих языках программирования. Ну если честно, то это указатель на память с данными класса. Передача параметров осуществляется согласно Microsoft x64 calling convention, ну почти. Одним из исключений является передача по значению не простых типов. Delphi, все не простые типы (например, записи) даже если они размером 1 байт передает как адрес, в тоже время передача по значению в классах С++ для данных размером меньше или равными 8 байт передаются в 64-битном регистре своим значением. Такое поведение наблюдается, например, для TRect<Int> (Object Pascal) и Rect=Rect2i=Rect_<int>(C++). Вообще, несмотря на соглашения о вызовах определяющих порядок использования регистров, в способе передачи данных из Object Pascal в функцию класса С++ есть несколько отличий, но, наверное, это тема отдельного разбирательства. В некоторых случаях импортировать по имени не получается (надо разбираться, оставим это в TODO) и приходится импортировать по индексу, что плохо. При переходе на следующую версию будут проблемы. 3.Когда-то, в умной книжке по C++, было сказано, что в C++ классы – это структуры (struct), подвергшиеся сильной радиационной и химической мутации. А действительно, что выдумывать что-то новое? Память под данные класса С++ выделяем на стадии компиляции, в месте его объявления вызываем конструктор по умолчанию (это если класс объявлен без параметров), когда не нужен – вызываем деструктор. Красота! И вот здесь и пригодились Custom Managed Records. Их поведение ну очень уж похоже на классы в C++. Размер записи TMat в точности равен размеру данных класса Mat, более того, если класс в OpenCV не содержит виртуальных функций и удается найти описание всех его полей, в том числе и protected, то мы получаем полную аналогию и доступ ко всем полям класса. Работа с С++ классами с виртуальными функциями, то есть их вызов через таблицу виртуальных функций тоже не проблема, но это опять же тема отдельного разговора. В случае, если лень искать поля класса, можно просто в теле record создать как это было сделано для std::vector<T> и оставлено на «TODO». Любой вектор C++ в памяти занимает 32 байта (хотя может и ошибаюсь). При создании записи (в месте объявления) вызывается При уничтожении Красота! Но есть нюанс. Иногда в С++ делается следующая штука Mat r = Mat(10, 3, CV_8UC3); т.е. сразу вызывается нужный конструктор. В Object Pascal конструктор будет вызываться два раза Var r:TMat := TMat.Mat(10, 3, CV_8UC3); при объявлении и при присвоении (еще и копирование Mat в class operator Assign), что замедляет работу. Много восторга принесло работающее в Object Pascal неявное преобразование типов, например, из TMat в TInputArray или цепочка TMat->TMatExpr->TMat->TInputArray. Дизассемблированный код Object Pascal стал очень похож на дизассемблированный код C++. Taк же спасают record helper. Если в С++ можно в заголовочных файлах наобъявлять структур и типов, а потом спокойно и где угодно их использовать, то в Object Pascal так нельзя. Выход из положения - дополнить функциональность в record helper где-нибудь в конце, после всех объявлений. 4. Неприятной особенностью некоторых функций и функций классов OpenCV является использование std:: классов. Если с std::string удалось справиться достаточно легко – создана opencv_delphi454.dll, которая экспортирует и дальше аналогично классам OpenCV, то вот с std::vector<T> пришлось повозиться. Сейчас реализован не оптимальный, корявый вариант.  Ну и, например, для создания Более элегантная реализация отложена в TODO. Если уже существуют решения – подскажите. Хотя в Object Pascal получилось красиво и это работает. Плохо только в  Пока оставлено в TODO. Сложилось впечатление, что OpenCV реализуется немножко сумасшедшими людьми в части организации кода. Иногда разбираешься как там все вызывается, преобразуется и думаешь: «Ну зачем так сложно-то?». В тоже время получившийся результат дает низкий уровень вхождения конечного пользователя. Подаешь на вход функции все, что угодно, а она это кушает и скрыто преобразовывает к нужному виду. В части создания интерфейса для Object Pascal, в целом, можно сказать, что получается. Не все, конечно, удастся реализовать один в один. Например, итераторы в std::vector – это еще та штучка, да и не хочется их вытаскивать только для того, чтобы реализовать один из вариантов конструктора std::vector. Однако примеры использования на C++ переписанные на Object Pascal очень похожи на оригинал, что позволит портировать большинство готовых конструкций и работающих программ. Скорее всего код будет еще неоднократно подвергаться рефакторингу в том числе и по способам взаимодействия с OpenCV. Кроме того, переведены доли процента объема OpenCV, а количество строк opencv_world.pas без включений уже перевалило за 4700. Конечно, там много комментариев, но часть из них станет кодом. Наверное, все-таки нужно будет разделять на отдельные модули исходя из деления modules в OpenCV. В конечном итоге будет разработана надстройка, позволяющая использовать родные для Object Pascal типы и структуры, например, TArray<T>, TList<T>. Хотя в последнем я сомневаюсь – слишком уж он громоздкий. Ну и, конечно, компоненты – нам тоже нужен низкий порог входа. Нужны советы по поводу организации opencv_delphi454.dll, так как я в С++ не Proficiency. Думаю, что знающим людям станет плохо, если они посмотрят код delphi_export.h. Ну и я не отказываюсь от помощи и советов по организации всего кода. Работа большая, интересная и чем-то похожа на охоту или рыбалку – азарт. И что же с мечтой? Скажу так - иногда путь к мечте интересней ее осуществления.