[Kotlin/Native][Interop] Provide pure c wrappers over cpp for skia interop
This commit is contained in:
committed by
Alexander Gorshenev
parent
61825e9aec
commit
5f582ad28a
@@ -0,0 +1,127 @@
|
||||
Что я хотел, и что из этого вышло
|
||||
--------------------------------------
|
||||
|
||||
Я думал, что быстро прикостыляю базовый С++ к cinterop, используя plain C обёртки над CXXMethod и передавая адрес объекта (receiver) дополнительным (скрытым) параметром. Так сделан C++ runtime и так сделана куча биндингов в C++ из разных языков. Так что - должно получиться.
|
||||
|
||||
Вышло сплошное разочаровние. Если обобщить все встреченные беды - вывод такой: существующий cinterop не годится для этого от слова "совсем". Что обидно: если бы я не тратил время на борьбу с cinterop - я бы, наверное, за это время написал прототип с нуля. Делать глубокий рефакторинг в рамках тестового задания я счёл неправильным - я не имею возможности даже обсудить грабли, тем более - протестить то что получится. (на самом деле, бОльшую часть кода и инфраструктуры cinterop можно переиспользовать, но рефаторинг коснётся всех сценариев - так что надо всё тестить)
|
||||
|
||||
Что получилось в итоге. Ну я всё же прикостылял базовый C++, не всё и с разной степенью проработки (C++ бесконечен, see below for details). Можно запустить тесты. Можно попытаться затащить библиотеку или, например, свои собственные legacy наработки. Я пытаюсь игнорировать неподдержанные (пока) типы и конструкции, но не все учтены, и на реальном примере придется подхачить C++, чтобы пройти компиляцию. Но это не проблема: дописать поддержку недостающего - вопрос времени/эффортов, принципиальных проблем нет. (В сторону: интероп с собственным legacy кодом я вижу как, потенциально, основной business case. У меня много знакомых плюсеров, которые сидят на легаси огромного объёма и были бы счастливы патчить бизнес-логику на более динамичном и функциональном языке).
|
||||
|
||||
Но пока результат разочаровывает, и вот почему:
|
||||
|
||||
* Этим неудобно пользоваться из-за разницы между CValue<CxxClass> и сгенерённым котлиновским CxxClass. (Никто и не пользуется: даже Indexer.kt всюду использует CValue, а сгенерённые классы остались без дела)
|
||||
|
||||
* cinterop всё время пытается делать свои копии нативных структур: readValue, useContents, placeTo etc. Это не работает для нетривиальных объектов. Хуже того: это абсолютный тупик для move семантики, которая в современном C++ является основным трендом. Пример: сейчас модно писать API, в котором все std::string передаются by value, в расчете на move. При попытке оттащить string в какую-то "другую" память (даже с использованием правильных move/copy конструкторов) я получаю маленький объект, из которого торчит указатель в C++ heap, причём указатель будет всё время меняться, immutability ему противопоказана. Или std::shared_ptr ... в общем, понятно. И. наконец, эти копирования не добавляют ничего полезного в случае kotlin-native (или я не понял).
|
||||
|
||||
* Похоже, cinterop был заточен на то, что основной workflow такой: аллоцировать в котлине memory block, инициализировать поля вручную, дальше побитово копировать туда где нужно. У объектного языка другой flow: get handler (using create or new), keep it and use, destroy. Я прикостылял placement new, чтобы сконструировать объект там где его аллоцировал котлин, но смысла в этом ноль. И это не работает для Factory, у объекта может не быть публичного конструктора.
|
||||
|
||||
* Я не стал фиксить by value параметры и return, это будет глючить на нетривиальных объектах (в тестах таких нет). Фикс простой, но концептуальный и задевает весь существующий cinterop.
|
||||
|
||||
Что я предлагаю сделать:
|
||||
|
||||
* Отказаться от попыток менеджерить нативные объекты. Это невозможно для объектов и не имеет смысла для kotlin-native. Используем handler с нативным указателем внутри. Котлиновский класс сейчас так и генерится - отлично. Никаких CValue. Параметр by value - компилятор подставит звёздочку, *rawPtr. Возврат by value: я использую placement new и copy ctor, чтобы заставить C++ вернуть значение не на стеке, а в хипе. Собственно, это сделано. Но дальше надо в котлин отдать handler с указателем в хип, а не readValue. Это тривиально и это я тоже сделал, но это ломает совместимость с существующим кодом.
|
||||
* Упростить сгенерённый для котлина API, в смысле типов. Никаких CValue, ptr и прочего. Все параметры и возвращаемые типы - только котлиновский прокси класс, handler. Компилятор подставит нужное преобразование в значение, указатель или ссылку. Где-то здесь придётся покувыркаться с move и с temporary, но это feasible. Манглингом или типами показать разницу между const-nonconst параметрами (in/out). Разницы между by value и const ref вообще нет. В случае неоднозначности (конфликт имён из-за того, что не показываем в котлине разницу между by value и by ptr), и только в этом случае - дополнительно манглинг, или программист подскажет компилятору как-то, например напишет ptr.
|
||||
* Вместо бесполезного для деструкторов mamscope сделать cxxscope, чтобы автоматически вызывать зарегистрированные деструкторы. Я попробовал сделать, используя std::unique_ptr - но не закончил.
|
||||
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
0. Design style. Костыли. Во многих местах в реальной продуктовой задаче я бы предпочёл сначала сделать подготовительный рефакторинг, цикл review-commit-tests, потом поверх этого сделать фичу. Т.е. разделить коммит на два: изменение (без добавления) и добавление (без изменений). Для тестового задания я в основном выбирал вариант минимальных изменений, чтобы проще ревьюить за один раз. В результате - костыли и рост технического долга, но это осознанный выбор.
|
||||
|
||||
0. Сейчас могут сгенериться имена с конфликтами в двух популярных случаях: 1) const и non-const метод с одинаковой сигнатуройг 2) функции с похожей сигнатурой, одна получает параметр по указателю, другая по ссылке (оба биндятся как указатель). В первом случае: добавим mangling, во втором надо мапить LVReference в not nullable, и всё развяжется. Руки не дошли.
|
||||
|
||||
0. NativeIndex имеет плоскую структуру создаваемых сущностей. Хотелось иметь дерево, так проще поддержать вложенные классы и неймспейсы. Не успел. В результате вложенные структуры я сделал простым манглингом (quick and dirty). Это работает, но выглядит ужасно, надо переделать. В качестве варианта я рассматривал идею: оставить indexer плоским, но пусть он содержит только один уровень семантической вложенности (один уровень semantic parent в терминологии clang), и я буду строить дерево из множества маленьких одноуровневых индексеров.
|
||||
|
||||
0. Хотелось замапить namespace в package. Вроде просто, но уткнулся в тяжелый рефакторинг в генераторах: там единственность выходного файла и package прибита гвоздями. Один translation unit = один выходной файл. А тут translation unit должен создать целое дерево с перекрестными импортами.
|
||||
|
||||
0. Многие типы и конструкции не поддержаны, но каких-то принципиальных проблем в реализации я не вижу. Темплейты - почти мапятся в темплейты, а инстанцированный темплейт - это обычный класс. Exceptions надо просто ретранслировать. RValueRef кажется должен работать легко. Операторы по разному... их много и это надолго. Можно мапить в обычные функции. Но например operator-> я бы предложил не мудрить, а вместо этого написать удобный аналог unique_ptr и shared_ptr вручную, и тогда их почти не останется. Как мапить лямбду в блок и обратно, похоже без поддержки рантайма не обойдется. И т.д., фичи бесконечны...
|
||||
|
||||
0. Override С++ методов в котлине (callbacks) Можно сделать через обертки: генерим C++ класс-наследник, у которого каждый метод перевызывает соответствующий котлинвский метод как plain C. Это сработает, но сгенерим и скомпилируем много лишнего. Хорошо бы генерить эти обертки только on demand, т.е. когда компилятор видит, что котлин хотел бы перегрузить соответствующий метод. Но замечу, что в современном C++ для колбэка скорее будут использовать лямбду и std::function, чем наследование.
|
||||
|
||||
0. Надо замапить самые важные std типы: string, containers, smart pointers
|
||||
|
||||
0. Сейчас работа с C/C++ из котлина выглядит неестественно, многословная. Interop должен быть seamless. Без лишних interpretPointed, rawValue etc. Но это требует определенного пересмотра концепции (type matching). Уже сказал об этом выше.
|
||||
|
||||
0. Костыль visitChildren, или франкенштейн из двух параллельных indexDeclaration.
|
||||
Проблема в том, что IndexDeclaration(CXIdxDeclInfo) не отличает класс от темплейта (и функцию от темплейтной функции). Курсор внутри дерева indexDeclaration для темплейтов имеет kind = CXCursor_ClassDecl (или StructDecl) вместо ожидаемого CXCursor_ClassTemplate, и я не нашел способа вытащить "правильный" курсор. Но если разобрать дерево через visitChildren - то курсор правильный. Поэтому, если язык C++, то я разбираю классы через visitChildren (и отбрасываю темплейты, пока они не поддержаны). Я временно оставил такого франкенштейна, когда часть сущностей парсится через CXIdxDeclInfo (из clang_indexTranslationUnit), а часть через CXCursor (из visitChildren). Я думаю, что надо всё перенести в visitChildren, но у меня не было возможности протестить все сценарии в plain C и ObjC. Такой рефакторинг - не для тестового задания. Кстати, такой костыль уже был до меня, для парсинга ObjC, но я не стал втыкать свой код в него, а написал отдельный - потому что мне нужен был CXChildVisit_Recurse, а там был CXChildVisit_Continue и я опять боялся сломать ObjC, который не умею как следует протестить.
|
||||
|
||||
|
||||
Conceptual
|
||||
----------
|
||||
|
||||
0. Internal linkage.
|
||||
Сейчас сущности с internal linkage (static & anon namespace) не фильтруются, биндинг на них создаётся (все функции в .def в тестах написаны со словом static). Фактически, строим так, как будто C включён в котлиновский исходник. В таком случае, эти сущности должны биндиться как internal, причем каждый С-ник надо компилировать, как отдельный translation unit, иначе будут конфликты.
|
||||
- Это нормальный подход, если моё намерение - расширить (extend) нативную имплементацию на котлине.
|
||||
- Если моё намерение - затащить нативную библиотеку, то я компилирую все публичные хедеры как один translation unit, при этом лучше зафильтровать static.
|
||||
- Проблема некритична, потому что в публичных хедерах static и anon namespace не встречается. Зато часто в c/cpp имплементации. Лучше быть последовательным в этом вопросе.
|
||||
|
||||
0. Что делать с protected и private? Я пока выключил. Технически, сейчас котлиновский класс соответствует C++ ссылке на объект, а не derived class, т.е. должен иметь доступ только к public. IMHO: это неправильно, в такой логике невозможно реализовать C++ callbacks (оверрайдить виртуальные методы, в т.ч. приватные). See below.
|
||||
|
||||
0. Derive vs Use. Хотелось бы имитироватиь наследование от C++ класса. Это можно сделать в концепции kotlin object is a handler, через делегирование, но надо решить вопрос автоматического вызова деструктора на созданном объекте.
|
||||
|
||||
0. Конструкторы-деструкторы надо доработать. Вопрос концептуальный, требует обсуждения. И это довольно большая работа. Моё предложение: надо различать 3 сущности:
|
||||
- Явно аллоцированный объект с владением: alloc + init (т.е. placement new) и парный к нему destroy (это this->~MyClass()), деаллокация автоматическая или ручная
|
||||
- Holder, владеющий указатель на объект, созданный в нативном хипе по new MyClass() и парный к нему delete. Аналог std::unique_ptr. Семантически не отличается от предыдущего.
|
||||
- Non-owning holder без передачи ownership, т.е. невладеюший указатель. Освобождать не надо. Аналог C raw pointer.
|
||||
- Можно замапить std::shared_ptr на котлин тип с acquire/release парой.
|
||||
|
||||
|
||||
Design issues
|
||||
-------------
|
||||
|
||||
0. В контейнере members может лежать IncompleteField. Это имело бы смысл, если Incomplete - такой lazy тип, который позже можно, например, зарезолвить. Но это не предусмотрено - так что зачем добавлять dummy, надо было просто пропустить. C FunctionDecl ещё хуже: в registry складываем неподдерживаемые функции, их отличие только в том, что у них может быть UnsupportedType у параметра. В результате exception в stub builder'е. Я заменил такие невалидные объекты на null, их легче потом фильтровать. Но вообще-то их добавлять не следовало. Проблема в том, как сделано добавление: через getOrPut. Если я только в середине построения объекта понял, что валидный объект создать не получается - он всё равно уже добавлен в map, я уже внутри initializer'а. И это не эксепшн концептуально, потому что это нормальная часть контракта, не исключение. Здесь лучше подошла бы логика factory: пытаюсь создать объект и добавляю только если создался успешно.
|
||||
|
||||
0. Часто приватный метод класса является, по сути, чистой функцией и не работает со стейтом объекта - только с параметром. Такие функции надо вытаскивать из класса и делать чистой функцией, или экстеншном на типе параметра. Пример:. getArrayLength(type: ArrayType) сделана приватным методом StructStubBuilder'а, но никакого отношения к этому классу не имеет. Таких примеров много.
|
||||
|
||||
0. Спорное решение про throw Error("Native interop types constructors must not be called directly"). В чем смысл? Мой тип - handler, и это его конструктор. Точно так работает `std::make_unique` и `make_shared`, при этом я могу и конструктором воспользоваться: `auto x = unique_ptr<T>(new T())`. Другого способа сделать биндинг на класс, у которого только factory method - вообще нет. interpretPointed по сути то же самое, но непонятно, и зачем плодить сущности? Наконец, это runtime exception, а не синтаксическая ошибка, которую мог бы подсветить IDE - и это неудобно.
|
||||
В порядке эксперимента я отключил этот error, в результате и клиентский код, и генеренный стал проще и читабельнее. От чего защищаемся, ведь я все равно могу написать такой wrapper вручную, хранить как long то, что вернул фактори метод, а потом вызывать методы класса на нём? Пусть лучше это сделает генератор.
|
||||
|
||||
0. Parameter by value.
|
||||
На входе - C declaration:
|
||||
```
|
||||
struct CTest {};
|
||||
int paramByValue(struct CTest x);
|
||||
```
|
||||
На выходе
|
||||
```
|
||||
fun paramByValue(x: CValue<CTest>): Int {
|
||||
memScoped {
|
||||
return kniBridge0(x.getPointer(memScope).rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
int32_t c1_kniBridge0 (void* p0) {
|
||||
return (int32_t)paramByValue(*(struct CTest*)p0);
|
||||
}
|
||||
```
|
||||
Здесь копия сделана дважды: сначала CValue.getPointer, а потом при вызове paramByValue. Причем во втором случае C++ компилятор корректно использует конструктор копирования, а getPointer - увы...
|
||||
Нет, это еще не всё. Поскольку функция принимает CValue, а у меня в руках есть только `class CTest`, который `CStructVar`, то придется сделать третье копирование уже в клиентском коде: readValue.
|
||||
При возврате то же самое:
|
||||
```
|
||||
void c1_kniBridge1 (void* p0) {
|
||||
struct CTest kniStructResult = retByValue();
|
||||
memcpy(p0, &kniStructResult, sizeof(kniStructResult));
|
||||
}
|
||||
```
|
||||
Здесь уже два копирования. Достаточно было бы
|
||||
```
|
||||
void c1_kniBridge1 (void* p0) {
|
||||
*(struct CTest*) p0 = retByValue();
|
||||
}
|
||||
```
|
||||
(Я знаю, что это не сработает при наличии const fields, но это значит, что автор явно запретил копирование. Такую структуру на C невозможно создать в хипе, только на стеке - такой "тёмный угол" стандарта C. На C++ это решается с помощью placement new.)
|
||||
Но есть ещё и третье копирование в котлиновской обёртке:
|
||||
```
|
||||
return kniRetVal.readValue()
|
||||
```
|
||||
и это ещё не конец: kniRetVal это CVariable, так что readValue делает копию дважды. Четыре копирования на каждый вызов retByValue - только чтобы получить экземпляр структуры.
|
||||
|
||||
0. Я попытался постичь дзен этого пассажа:
|
||||
```
|
||||
* [CValuesRef] is designed to be used as Kotlin representation of pointer-typed parameters of C functions.
|
||||
* When passing [CPointer] as [CValuesRef] to the Kotlin binding method, the C function receives exactly this pointer.
|
||||
* Passing [CValues] has nearly the same semantics as passing by value: the C function receives
|
||||
* the pointer to the temporary copy of these values, and the caller can't observe the modifications to this copy.
|
||||
```
|
||||
Первые две фразы - ok. Третью я не смог расшифровать. `Passing [CValues]` - куда passing? Если C API принимает MyStruct* то это мутатор с input параметром и отдавать туда нужно указатель на исходный объект. Иначе в чём смысл - вызывать мутатор на временном объекте? Если параметр MyStruct const* то компилятор уже позаботился о нас (трюки типа mutable или const_cast не в счёт - тот кто делает это в публичном API - сам берет на себя ответственность). Если параметр по значению, то C компилятор и так сделает копию.
|
||||
@@ -0,0 +1,8 @@
|
||||
Build native:
|
||||
|
||||
g++ -std=c++14 -o src/native/features.o -c src/native/features.cpp
|
||||
|
||||
c++ interop:
|
||||
|
||||
../../dist/bin/cinterop -def features.def -compiler-options "-I." -o features
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
headers = src/native/features.h
|
||||
headerFilter = src/native/features.h
|
||||
package = test
|
||||
language = C++
|
||||
#compilerOpts = -std=c++14
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
//package test.cpp
|
||||
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.*
|
||||
import platform.posix.memcpy
|
||||
import test.*
|
||||
import kotlin.test.*
|
||||
|
||||
class FeatureTest {
|
||||
|
||||
@Test fun ctorDefault() {
|
||||
memScoped {
|
||||
val x = alloc<ns__CppTest>()
|
||||
ns__CppTest.__init__(x.ptr)
|
||||
assertEquals(42, x.iPub)
|
||||
assertEquals(42, x.foo(null))
|
||||
assertEquals(43, x.foo(x.ptr))
|
||||
// dtor is not called, leak is intentional for the purpose of UT
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun ctorWithParam() {
|
||||
memScoped {
|
||||
val x = alloc<ns__CppTest>()
|
||||
ns__CppTest.__init__(x.ptr, 1001, 0.0)
|
||||
assertEquals(1001, x.iPub)
|
||||
assertEquals(1001, x.foo(null))
|
||||
assertEquals(1002, x.foo(x.ptr))
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun copyCtor(y: ns__CppTest) {
|
||||
val x = nativeHeap.alloc<ns__CppTest>() {}
|
||||
ns__CppTest.__init__(x.ptr, y.ptr)
|
||||
|
||||
assertEquals(y.iPub, x.iPub)
|
||||
nativeHeap.free(x)
|
||||
}
|
||||
|
||||
@Test fun reinitWithCtorAndDtor(y: ns__CppTest) {
|
||||
val count = ns__CppTest.getCount()
|
||||
val x = nativeHeap.alloc<ns__CppTest>() {}
|
||||
ns__CppTest.__init__(x.ptr, y.ptr)
|
||||
assertEquals(ns__CppTest.getCount(), count + 1)
|
||||
assertEquals(y.iPub, x.iPub)
|
||||
|
||||
ns__CppTest.__destroy__(x.ptr)
|
||||
y.iPub = -11
|
||||
assertEquals(y.iPub, -11)
|
||||
ns__CppTest.__init__(x.ptr, y.ptr)
|
||||
assertEquals(x.iPub, -11)
|
||||
|
||||
ns__CppTest.__destroy__(x.ptr)
|
||||
assertEquals(ns__CppTest.getCount(), count)
|
||||
|
||||
nativeHeap.free(x)
|
||||
}
|
||||
|
||||
@Test fun publicField(x : ns__CppTest) {
|
||||
x.iPub = 21
|
||||
assertEquals(22, x.foo(x.ptr))
|
||||
}
|
||||
|
||||
@Test fun lvRefParameter() {
|
||||
memScoped {
|
||||
val x = alloc<ns__NoName>()
|
||||
var i = alloc<IntVar>()
|
||||
i.value = 758
|
||||
assertEquals(x.noNameMember(i.ptr), 759)
|
||||
assertEquals(i.value, 759)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test fun staticField() {
|
||||
val save = ns__CppTest.getCount()
|
||||
assertEquals(ns__CppTest.getCount(), ns__CppTest.counter)
|
||||
|
||||
ns__CppTest.counter = 654
|
||||
assertEquals(ns__CppTest.getCount(), 654)
|
||||
assertEquals(ns__CppTest.getCount(), ns__CppTest.counter)
|
||||
|
||||
ns__CppTest.counter = save
|
||||
assertEquals(ns__CppTest.getCount(), save)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun main() {
|
||||
|
||||
val testRun = FeatureTest()
|
||||
testRun.ctorDefault()
|
||||
testRun.ctorWithParam()
|
||||
|
||||
val a1 = interpretPointed<ns__CppTest>(ns__create().rawValue)
|
||||
testRun.publicField(a1)
|
||||
|
||||
testRun.staticField()
|
||||
|
||||
testRun.lvRefParameter()
|
||||
|
||||
a1.iPub = 112
|
||||
testRun.copyCtor(a1)
|
||||
testRun.reinitWithCtorAndDtor(a1)
|
||||
|
||||
|
||||
//******************************
|
||||
println("*** UT passed ***")
|
||||
//******************************
|
||||
|
||||
testStatic()
|
||||
|
||||
testCtor()
|
||||
// testCtor1()
|
||||
|
||||
// testCtor2()
|
||||
// testCtor3()
|
||||
test2()
|
||||
}
|
||||
|
||||
fun testStatic() {
|
||||
println("ns__CppTest.s_fun() returns ${ns__CppTest.s_fun()}")
|
||||
println("ns__CppTest.s_fun() returns ${ns__CppTest.s_fun()}")
|
||||
println("ns__CppTest.s_fun() returns ${ns__CppTest.s_fun()}")
|
||||
}
|
||||
|
||||
fun testCtor() {
|
||||
println("testCtor")
|
||||
val cxx = nativeHeap.alloc<ns__CppTest>() {
|
||||
memcpy(ptr, ns__create(), typeOf<ns__CppTest>().size.convert()) // use placement new here
|
||||
}
|
||||
cxx.foo(null)
|
||||
nativeHeap.free(cxx)
|
||||
}
|
||||
/*
|
||||
fun testCtor1() {
|
||||
println("testCtor1: interpretPointed<ns__CppTest>(ns__CppTest.__create__().rawValue)")
|
||||
val theStruct = interpretPointed<ns__CppTest>(ns__CppTest.__create__().rawValue)
|
||||
theStruct.iPub = 33
|
||||
theStruct.foo(theStruct.ptr)
|
||||
|
||||
println("testCtor1: ns__CppTest(ns__CppTest.__create__().rawValue)")
|
||||
val xs = ns__CppTest(ns__CppTest.__create__().rawValue)
|
||||
xs.foo(null)
|
||||
xs.foo(xs.ptr)
|
||||
|
||||
println("testCtor1: ns__CppTest(ns__CppTest.__create__(1001).rawValue)")
|
||||
val x2 = ns__CppTest(ns__CppTest.__create__(1001, 2.718).rawValue)
|
||||
x2.foo(null)
|
||||
x2.foo(x2.ptr)
|
||||
|
||||
}
|
||||
|
||||
fun testCtor2() {
|
||||
println("testCtor2: ns__CppTest(ns__create().rawValue)")
|
||||
val xs = ns__CppTest(ns__create().rawValue)
|
||||
xs.foo(null)
|
||||
xs.foo(xs.ptr)
|
||||
}
|
||||
|
||||
fun testCtor3() {
|
||||
println("testCtor3: MyStruct()")
|
||||
val xs = MyStruct()
|
||||
xs.foo()
|
||||
}
|
||||
*/
|
||||
|
||||
fun test2() {
|
||||
println("test2")
|
||||
val x = ns__bar(null)
|
||||
// val theS = interpretPointed<ns__CppTest>(ns__bar(null).rawValue)
|
||||
// theS.foo(null)
|
||||
|
||||
println("x.useContents {iPub} = ${x.useContents {iPub}}" )
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "features.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace ns {
|
||||
|
||||
int NoName::noNameMember(int& iRef) {
|
||||
cout << __PRETTY_FUNCTION__ << " invoked" << endl;
|
||||
return ++iRef;
|
||||
}
|
||||
|
||||
int CppTest::counter;
|
||||
|
||||
int CppTest::s_fun() {
|
||||
static int counter = 777;
|
||||
cout << __PRETTY_FUNCTION__ << " invoked" << endl;
|
||||
return counter++;
|
||||
}
|
||||
|
||||
int CppTest::foo(const CppTest* x) {
|
||||
int res = x == this;
|
||||
cout << "This is CppTest::foo: result is iPub + (int)(param == this): " << iPub + res << endl;
|
||||
return iPub + res;
|
||||
}
|
||||
|
||||
CppTest::CppTest() {
|
||||
cout << ++counter << "\t" << __PRETTY_FUNCTION__ << endl;
|
||||
}
|
||||
|
||||
CppTest::CppTest(const CppTest& c) {
|
||||
*this = c;
|
||||
cout << ++counter << "\t" << __PRETTY_FUNCTION__ << endl;
|
||||
}
|
||||
|
||||
CppTest::CppTest(int i, double j) : iPub(i) {
|
||||
cout << ++counter << "\t" << __PRETTY_FUNCTION__ << endl;
|
||||
}
|
||||
|
||||
|
||||
CppTest::~CppTest() {
|
||||
cout << --counter << "\t" << __PRETTY_FUNCTION__ << endl;
|
||||
}
|
||||
|
||||
|
||||
CppTest bar(CppTest* s) {
|
||||
if (s)
|
||||
return *s;
|
||||
else
|
||||
return * new CppTest();
|
||||
}
|
||||
|
||||
CppTest* create() {
|
||||
return new CppTest();
|
||||
}
|
||||
|
||||
} // ns
|
||||
|
||||
CppTest* create() {
|
||||
cout << __PRETTY_FUNCTION__ << " declared in global ns" << endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
::CppTest* ns2::create() {
|
||||
cout << __PRETTY_FUNCTION__ << " declared in ns2" << endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void test() {
|
||||
char buf[sizeof(ns::CppTest)];
|
||||
ns::CppTest* x = new((ns::CppTest*)buf) ns::CppTest();
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
|
||||
class MyClass {
|
||||
};
|
||||
|
||||
MyClass retByValue();
|
||||
|
||||
|
||||
|
||||
struct OuterCStruct {
|
||||
const int plainCField;
|
||||
struct {
|
||||
int innerCField;
|
||||
};
|
||||
|
||||
union {
|
||||
int innerUnionF1;
|
||||
double innerUnionF2;
|
||||
};
|
||||
|
||||
struct Inner {
|
||||
};
|
||||
struct Inner innerImpl;
|
||||
|
||||
#ifdef __cplusplus
|
||||
// Illegal C. ok in C++
|
||||
typedef struct {
|
||||
} NestedStructInnerT;
|
||||
NestedStructInnerT nestedStructT;
|
||||
#endif
|
||||
};
|
||||
|
||||
const int g_IntVal = 0;
|
||||
|
||||
|
||||
typedef int (*funT)(int);
|
||||
void fooFuncParam( funT );
|
||||
void barFuncParam( int (*)(int) );
|
||||
|
||||
|
||||
template <typename T> struct TmplStruct {
|
||||
public:
|
||||
void baz() const {}
|
||||
};
|
||||
|
||||
void funTmplParam(TmplStruct<int>& t);
|
||||
|
||||
template <class X> void tmplFunction();
|
||||
template <class X> void tmplFunction(X x);
|
||||
|
||||
//int funVariadic(const char* format, ...);
|
||||
|
||||
//#include <cstdarg>
|
||||
//void appendVAList(const char format[], va_list); // doesn't work
|
||||
|
||||
|
||||
int plainCFreeFunction();
|
||||
static inline void plainCInternalFunction(); // [Conceptual] should be mapped as internal fun or not eligible for binding at all
|
||||
|
||||
int g_Var;
|
||||
|
||||
struct PlainCStruct {
|
||||
int plainCField;
|
||||
struct {
|
||||
int innerCField;
|
||||
};
|
||||
};
|
||||
|
||||
typedef struct PlainCStruct PlainCStructT;
|
||||
|
||||
using PlainCStructAlias = PlainCStruct;
|
||||
|
||||
void wrappingFun() {
|
||||
class NestedInFunction {
|
||||
int varInLocalClass;
|
||||
} var; // local types shall be ignored
|
||||
}
|
||||
|
||||
struct UnknownT;
|
||||
UnknownT* cFunUnknownParams(const UnknownT*);
|
||||
|
||||
|
||||
struct ForwardT; // declaratuin
|
||||
ForwardT* cFunForwardParams(const ForwardT*); // decl
|
||||
|
||||
struct ForwardT {}; // definition
|
||||
ForwardT* cFunForwardParams(const ForwardT*) { // def
|
||||
return new ForwardT();
|
||||
}
|
||||
|
||||
namespace ns0 {
|
||||
class clDeclaredInNS;
|
||||
extern int varWithDefinition;
|
||||
int varWithDefFirst = 99;
|
||||
}
|
||||
|
||||
|
||||
namespace ns0_alias = ns0;
|
||||
class ns0_alias::clDeclaredInNS{};
|
||||
extern int ns0_alias::varWithDefFirst;
|
||||
|
||||
using clDeclaredInNSAlias = ns0_alias::clDeclaredInNS;
|
||||
|
||||
// int ns0_alias::varInNSAlias; // illegal declaration
|
||||
int ns0_alias::varWithDefinition = 21;
|
||||
|
||||
namespace ns {
|
||||
|
||||
int g_varInNS;
|
||||
|
||||
namespace {
|
||||
void funInInnerAnonNS();
|
||||
namespace nsInAnonNS {
|
||||
int memberOf_nsInAnonNS;
|
||||
}
|
||||
}
|
||||
|
||||
namespace NestedNS {
|
||||
int funInNestedNS();
|
||||
int g_varInNestedNS;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void funInInnerAnonNS();
|
||||
}
|
||||
|
||||
typedef class {
|
||||
public:
|
||||
int noNameMember(int& iRef);
|
||||
// int noNameMember(const int& iRef); need mangling
|
||||
static int noNameStaticFun(); // won't work
|
||||
int fieldInAnonClass;
|
||||
// static int s_fieldInAnonClass; not legal C++
|
||||
} NoName;
|
||||
|
||||
enum EnumInNS {one, two, three};
|
||||
|
||||
enum class EnumClass : char { one, two, three};
|
||||
|
||||
class CppTest {
|
||||
public:
|
||||
static int s_fun();
|
||||
|
||||
CppTest();
|
||||
|
||||
CppTest(const CppTest&);
|
||||
|
||||
explicit CppTest(int i, double j = 3.14);
|
||||
|
||||
~CppTest();
|
||||
|
||||
operator NoName() const;
|
||||
|
||||
int iPub = 42;
|
||||
|
||||
virtual int foo(const CppTest*);
|
||||
// virtual int foo(const CppTest*) const;
|
||||
|
||||
static int counter;
|
||||
|
||||
static NoName compStaticField;
|
||||
NoName compField;
|
||||
|
||||
static int getCount() { return counter; }
|
||||
template <class X> void fooTmplMember() const;
|
||||
|
||||
class Nested {
|
||||
public:
|
||||
int nestedFoo();
|
||||
};
|
||||
|
||||
enum NestedEnum {one, two, three};
|
||||
void funEnumParam(NestedEnum e);
|
||||
|
||||
private:
|
||||
CppTest* funPrivate() const;
|
||||
static int s_funPrivate();
|
||||
|
||||
private:
|
||||
int iPriv;
|
||||
};
|
||||
|
||||
CppTest funRetByValue(CppTest* s);
|
||||
|
||||
CppTest* create();
|
||||
|
||||
} // ns
|
||||
|
||||
namespace {
|
||||
ns::CppTest* fooInAnonNamespace();
|
||||
}
|
||||
|
||||
class CppTest;
|
||||
CppTest* create();
|
||||
|
||||
namespace ns2 {
|
||||
::CppTest* create();
|
||||
|
||||
} // ns2
|
||||
/*
|
||||
template <typename T> class TmplClass {
|
||||
public:
|
||||
void baz() const {}
|
||||
};
|
||||
|
||||
typedef class TmplStruct<int> IntTmplStruct;
|
||||
IntTmplStruct intTmplStruct;
|
||||
|
||||
struct Smth {
|
||||
IntTmplStruct intTmplStruct;
|
||||
};
|
||||
*/
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
g++ -std=c++14 main.cpp -I$HOME/work/cpptools/skia /Users/vdi/work/cpptools/skia/out/Static/libskia.a -framework CoreServices -framework CoreText -framework CoreGraphics
|
||||
|
||||
../../dist/bin/cinterop -def Skia.def -o Skia -compiler-options "-I/Volumes/vdi/work/cpptools/skia"
|
||||
|
||||
../../dist/bin/kotlinc src/skiaMain/kotlin/test_SkString.kt -o test_SkString -l Skia -linker-options "$HOME/work/cpptools/skia/out/Static/libskia.a" -linker-options "-framework CoreServices" -linker-options "-framework CoreText" -linker-options "-framework CoreGraphics"
|
||||
@@ -0,0 +1,5 @@
|
||||
headers = include/core/SkString.h include/core/SkTime.h
|
||||
headerFilter = include/core/SkTime.h include/core/SkString.h
|
||||
package = skia
|
||||
compilerOpts = -std=c++14
|
||||
compilerOpts = -x c++
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "include/core/SkTime.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
auto nsec = SkTime::GetNSecs();
|
||||
cout << nsec << endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import kotlinx.cinterop.*
|
||||
import SkTime.*
|
||||
|
||||
fun main() {
|
||||
println("Skia sample")
|
||||
println("${SkTime.GetNSecs()}")
|
||||
println("${SkTime.GetNSecs()}")
|
||||
println("${SkTime.GetNSecs()}")
|
||||
test_GetDateTime()
|
||||
}
|
||||
|
||||
fun test_GetDateTime() {
|
||||
memScoped {
|
||||
val dateTime = alloc<SkTime__DateTime>()
|
||||
SkTime.GetDateTime(dateTime.ptr)
|
||||
println("${dateTime.fYear}-${dateTime.fMonth}-${dateTime.fDay} ${dateTime.fHour}:${dateTime.fMinute}.${dateTime.fSecond}" )
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import kotlinx.cinterop.*
|
||||
import kotlin.test.*
|
||||
|
||||
import skia.*
|
||||
|
||||
fun hello() {
|
||||
memScoped {
|
||||
val cString = "Hello Skia".cstr.getPointer(memScope)
|
||||
val s = alloc<SkString>() {
|
||||
SkString.__init__(ptr, cString)
|
||||
}
|
||||
println(s.c_str()?.toKString())
|
||||
}
|
||||
}
|
||||
|
||||
fun SkString_create(from: String): SkString {
|
||||
val res = nativeHeap.alloc<SkString>() {}
|
||||
memScoped {
|
||||
val cString = from.cstr.getPointer(memScope)
|
||||
SkString.__init__(res.ptr, cString)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
fun SkString.clone(): SkString {
|
||||
return nativeHeap.alloc<SkString>() {
|
||||
SkString.__init__(ptr, this@clone.ptr)
|
||||
}
|
||||
}
|
||||
|
||||
fun SkString.delete(): Unit {
|
||||
SkString.__destroy__(this.ptr)
|
||||
nativeHeap.free(this)
|
||||
}
|
||||
|
||||
fun SkString.toKString() = this.c_str()!!.toKString()
|
||||
|
||||
|
||||
fun main() {
|
||||
|
||||
hello()
|
||||
|
||||
/*
|
||||
with (CppContext) {
|
||||
val go = SkString("Let's go fishing!")
|
||||
val goback = SkString(go)
|
||||
val pos = goback.find("fish")
|
||||
println(go)
|
||||
if (pos >= 0) {
|
||||
goback.insert(pos, "back!")
|
||||
goback.resize((pos + "back!".length)
|
||||
}
|
||||
println(goback)
|
||||
oback.swap(go)
|
||||
goback.resize(goback.size() - 1U)
|
||||
goback.append(" again!")
|
||||
println(goback)
|
||||
}
|
||||
*/
|
||||
val go = SkString_create("Let's go fishing!")
|
||||
println(go.toKString())
|
||||
|
||||
val goback = go.clone()
|
||||
memScoped {
|
||||
val pos = goback.find("fish".cstr.getPointer(memScope))
|
||||
if (pos >= 0) {
|
||||
goback.insert(pos.convert<ULong>(), "back!".cstr.getPointer(memScope))
|
||||
goback.resize((pos + "back!".length).convert<ULong>())
|
||||
}
|
||||
println(goback.toKString())
|
||||
|
||||
goback.swap(go.ptr)
|
||||
|
||||
goback.resize(goback.size() - 1U)
|
||||
goback.append(" again!".cstr.getPointer(memScope))
|
||||
println(goback.toKString())
|
||||
}
|
||||
go.delete()
|
||||
goback.delete()
|
||||
}
|
||||
Reference in New Issue
Block a user