[Kotlin/Native][Interop] Provide pure c wrappers over cpp for skia interop

This commit is contained in:
Vladimir Ivanov
2019-08-19 11:44:20 +03:00
committed by Alexander Gorshenev
parent 61825e9aec
commit 5f582ad28a
31 changed files with 1525 additions and 66 deletions
+127
View File
@@ -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 компилятор и так сделает копию.
+8
View File
@@ -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;
};
*/
+6
View File
@@ -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"
+5
View File
@@ -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++
+11
View File
@@ -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()
}