Giter Site home page Giter Site logo

dropbox / djinni Goto Github PK

View Code? Open in Web Editor NEW
2.9K 138.0 488.0 3.77 MB

A tool for generating cross-language type declarations and interface bindings.

License: Apache License 2.0

Makefile 0.31% C++ 31.56% Java 25.11% Objective-C 9.20% Python 0.45% Shell 1.20% Scala 16.56% Objective-C++ 13.31% Lex 0.36% CMake 1.61% Swift 0.04% Dockerfile 0.28%

djinni's Introduction

Djinni

Djinni is a tool for generating cross-language type declarations and interface bindings. It's designed to connect C++ with either Java or Objective-C. Python support is available in an experimental version on the python branch.

Djinni can be used to interface cross-platform C++ library code with platform-specific Java and Objective-C on Android and iOS. We announced Djinni at CppCon 2014. You can see the slides and video. For more info about Djinni and how others are using it, check out the community links at the end of this document.

Maintenance note: This repo is stable but no longer maintained by Dropbox. If you have questions or want to talk to other users of Djinni, you can join the Slack community via the link at the end of this document.

Main Features

  • Generates parallel C++, Java and Objective-C type definitions from a single interface description file.
  • Supports the intersection of the three core languages' primitive types, and user-defined enums, records, and interfaces.
  • Generates interface code allowing bidirectional calls between C++ and Java (with JNI) or Objective-C (with Objective-C++).
  • Can autogenerate comparator functions (equality, ordering) on data types.

Getting Started

Types

Djinni generates code based on interface definitions in an IDL file. An IDL file can contain three kinds of declarations: enums, records, and interfaces.

  • Enums become C++ enum classes, Java enums, or ObjC NS_ENUMs.
  • Flags become C++ enum classes with convenient bit-oriented operators, Java enums with EnumSet, or ObjC NS_OPTIONS.
  • Records are pure-data value objects.
  • Interfaces are objects with defined methods to call (in C++, passed by shared_ptr). Djinni produces code allowing an interface implemented in C++ to be transparently used from ObjC or Java, and vice versa.

IDL Files

Djinni's input is an interface description file. Here's an example:

# Multi-line comments can be added here. This comment will be propagated
# to each generated definition.
my_enum = enum {
    option1;
    option2;
    option3;
}

my_flags = flags {
  flag1;
  flag2;
  flag3;
  no_flags = none;
  all_flags = all;
}

my_record = record {
    id: i32;
    info: string;
    store: set<string>;
    hash: map<string, i32>;

    values: list<another_record>;

    # Comments can also be put here

    # Constants can be included
    const string_const: string = "Constants can be put here";
    const min_value: another_record = {
        key1 = 0,
        key2 = ""
    };
}

another_record = record {
    key1: i32;
    key2: string;
} deriving (eq, ord)

# This interface will be implemented in C++ and can be called from any language.
my_cpp_interface = interface +c {
    method_returning_nothing(value: i32);
    method_returning_some_type(key: string): another_record;
    static get_version(): i32;

    # Interfaces can also have constants
    const version: i32 = 1;
}

# This interface will be implemented in Java and ObjC and can be called from C++.
my_client_interface = interface +j +o {
    log_string(str: string): bool;
}

Djinni files can also include each other. Adding the line:

@import "relative/path/to/filename.djinni"

at the beginning of a file will simply include another file. Child file paths are relative to the location of the file that contains the @import. Two different djinni files cannot define the same type. @import behaves like #include with #pragma once in C++, or like ObjC's #import: if a file is included multiple times through different paths, then it will only be processed once.

Generate Code

When the Djinni file(s) are ready, from the command line or a bash script you can run:

src/run \
   --java-out JAVA_OUTPUT_FOLDER \
   --java-package com.example.jnigenpackage \
   --java-cpp-exception DbxException \ # Choose between a customized C++ exception in Java and java.lang.RuntimeException (the default).
   --ident-java-field mFooBar \ # Optional, this adds an "m" in front of Java field names
   \
   --cpp-out CPP_OUTPUT_FOLDER \
   \
   --jni-out JNI_OUTPUT_FOLDER \
   --ident-jni-class NativeFooBar \ # This adds a "Native" prefix to JNI class
   --ident-jni-file NativeFooBar \ # This adds a prefix to the JNI filenames otherwise the cpp and jni filenames are the same.
   \
   --objc-out OBJC_OUTPUT_FOLDER \
   --objc-type-prefix DB \ # Apple suggests Objective-C classes have a prefix for each defined type.
   \
   --objcpp-out OBJC_OUTPUT_FOLDER \
   \
   --idl MY_PROJECT.djinni

Some other options are also available, such as --cpp-namespace that put generated C++ code into the namespace specified. For a list of all options, run src/run --help

Sample generated code is in the example/generated-src/ and test-suite/generated-src/ directories of this distribution.

Note that if a language's output folder is not specified, that language will not be generated. For more information, run run --help to see all command line arguments available.

Use Generated Code in Your Project

Java / JNI / C++ Project

Includes & Build target

The following headers / code will be generated for each defined type:

Type C++ header C++ source Java JNI header JNI source
Enum/Flags my_enum.hpp MyEnum.java NativeMyEnum.hpp NativeMyEnum.cpp
Record my_record[_base].hpp my_record[_base].cpp (+) MyRecord[Base].java NativeMyRecord.hpp NativeMyRecord.cpp
Interface my_interface.hpp my_interface.cpp (+) MyInterface.java NativeMyInterface.hpp NativeMyInterface.cpp

(+) Generated only for types that contain constants.

Add all generated source files to your build target, as well as the contents of support-lib/java.

Our JNI approach

JNI stands for Java Native Interface, an extension of the Java language to allow interop with native (C/C++) code or libraries. Complete documentation on JNI is available at: http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html

For each type, built-in (list, string, etc.) or user-defined, Djinni produces a translator class with a toJava and fromJava function to translate back and forth.

Application code is responsible for the initial load of the JNI library. Add a static block somewhere in your code:

System.loadLibrary("YourLibraryName");
// The name is specified in Android.mk / build.gradle / Makefile, depending on your build system.

If you package your native library in a jar, you can also use com.dropbox.djinni.NativeLibLoader to help unpack and load your lib(s). See the Localhost README for details.

When a native library is called, JNI calls a special function called JNI_OnLoad. If you use Djinni for all JNI interface code, include support_lib/jni/djinni_main.cpp; if not, you'll need to add calls to your own JNI_OnLoad and JNI_OnUnload functions. See support-lib/jni/djinni_main.cpp for details.

Objective-C / C++ Project

Includes & Build Target

Generated files for Objective-C / C++ are as follows (assuming prefix is DB):

Type C++ header C++ source Objective-C files Objective-C++ files
Enum/Flags my_enum.hpp DBMyEnum.h
Record my_record[_base].hpp my_record[_base].cpp (+) DBMyRecord[Base].h DBMyRecord[Base]+Private.h
DBMyRecord[Base].mm (++) DBMyRecord[Base]+Private.mm
Interface my_interface.hpp my_interface.cpp (+) DBMyInterface.h DBMyInterface+Private.h
DBMyInterface+Private.mm

(+) Generated only for types that contain constants. (++) Generated only for types with derived operations and/or constants. These have .mm extensions to allow non-trivial constants.

Add all generated files to your build target, as well as the contents of support-lib/objc. Note that +Private files can only be used with ObjC++ source (other headers are pure ObjC) and are not required by Objective-C users of your interface.

Details of Generated Types

Enum

Enums are translated to C++ enum classes with underlying type int, ObjC NS_ENUMs with underlying type NSInteger, and Java enums.

Flags

Flags are translated to C++ enum classes with underlying type unsigned and a generated set of overloaded bitwise operators for convenience, ObjC NS_OPTIONS with underlying type NSUInteger, and Java EnumSet<>. Contrary to the above enums, the enumerants of flags represent single bits instead of integral values.

When specifying a flags type in your IDL file you can assign special semantics to options:

my_flags = flags {
  flag1;
  flag2;
  flag3;
  no_flags = none;
  all_flags = all;
}

In the above example the elements marked with none and all are given special meaning. In C++ and ObjC the no_flags option is generated with a value that has no bits set (i.e. 0), and all_flags is generated as a bitwise-or combination of all other values. In Java these special options are not generated as one can just use EnumSet.noneOf() and EnumSet.allOf().

Record

Records are data objects. In C++, records contain all their elements by value, including other records (so a record cannot contain itself).

Data types

The available data types for a record, argument, or return value are:

  • Boolean (bool)
  • Primitives (i8, i16, i32, i64, f32, f64).
  • Strings (string)
  • Binary (binary). This is implemented as std::vector<uint8_t> in C++, byte[] in Java, and NSData in Objective-C.
  • Date (date). This is chrono::system_clock::time_point in C++, Date in Java, and NSDate in Objective-C.
  • List (list<type>). This is vector<T> in C++, ArrayList in Java, and NSArray in Objective-C. Primitives in a list will be boxed in Java and Objective-C.
  • Set (set<type>). This is unordered_set<T> in C++, HashSet in Java, and NSSet in Objective-C. Primitives in a set will be boxed in Java and Objective-C.
  • Map (map<typeA, typeB>). This is unordered_map<K, V> in C++, HashMap in Java, and NSDictionary in Objective-C. Primitives in a map will be boxed in Java and Objective-C.
  • Enumerations / Flags
  • Optionals (optional<typeA>). This is std::experimental::optional<T> in C++11, object / boxed primitive reference in Java (which can be null), and object / NSNumber strong reference in Objective-C (which can be nil).
  • Other record types. This is generated with a by-value semantic, i.e. the copy method will deep-copy the contents.

Extensions

To support extra fields and/or methods, a record can be "extended" in any language. To extend a record in a language, you can add a +c (C++), +j (Java), or +o (ObjC) flag after the record tag. The generated type will have a Base suffix, and you should create a derived type without the suffix that extends the record type.

The derived type must be constructible in the same way as the Base type. Interfaces will always use the derived type.

Derived methods

For record types, Haskell-style "deriving" declarations are supported to generate some common methods. Djinni is capable of generating equality and order comparators, implemented as operator overloading in C++ and standard comparison functions in Java / Objective-C.

Things to note:

  • All fields in the record are compared in the order they appear in the record declaration. If you need to add a field later, make sure the order is correct.
  • Ordering comparison is not supported for collection types, optionals, and booleans.
  • To compare records containing other records, the inner record must derive at least the same types of comparators as the outer record.

Interface

Special Methods for C++ Only

+c interfaces (implementable only in C++) can have methods flagged with the special keywords const and static which have special effects in C++:

special_methods = interface +c { const accessor_method(); static factory_method(); }

  • const methods will be declared as const in C++, though this cannot be enforced on callers in other languages, which lack this feature.
  • static methods will become a static method of the C++ class, which can be called from other languages without an object. This is often useful for factory methods to act as a cross-language constructor.

Exception Handling

When an interface implemented in C++ throws a std::exception, it will be translated to a java.lang.RuntimeException in Java or an NSException in Objective-C. The what() message will be translated as well.

Constants

Constants can be defined within interfaces and records. In Java and C++ they are part of the generated class; and in Objective-C, constant names are globals with the name of the interface/record prefixed. Example:

record_with_const = record +c +j +o { const const_value: i32 = 8; }

will be RecordWithConst::CONST_VALUE in C++, RecordWithConst.CONST_VALUE in Java, and RecordWithConstConstValue in Objective-C.

Modularization and Library Support

When generating the interface for your project and wish to make it available to other users in all of C++/Objective-C/Java you can tell Djinni to generate a special YAML file as part of the code generation process. This file then contains all the information Djinni requires to include your types in a different project. Instructing Djinni to create these YAML files is controlled by the following arguments:

  • --yaml-out: The output folder for YAML files (Generator disabled if unspecified).
  • --yaml-out-file: If specified all types are merged into a single YAML file instead of generating one file per type (relative to --yaml-out).
  • --yaml-prefix: The prefix to add to type names stored in YAML files (default: "").

Such a YAML file looks as follows:

---
name: mylib_record1
typedef: 'record +c deriving(eq, ord)'
params: []
prefix: 'mylib'
cpp:
    typename: '::mylib::Record1'
    header: '"MyLib/Record1.hpp"'
    byValue: false
objc:
    typename: 'MLBRecord1'
    header: '"MLB/MLBRecord1.h"'
    boxed: 'MLBRecord1'
    pointer: true
    hash: '%s.hash'
objcpp:
    translator: '::mylib::djinni::objc::Record1'
    header: '"mylib/djinni/objc/Record1.hpp"'
java:
    typename: 'com.example.mylib.Record1'
    boxed: 'com.example.mylib.Record1'
    reference: true
    generic: true
    hash: '%s.hashCode()'
jni:
    translator: '::mylib::djinni::jni::Record1'
    header: '"Duration-jni.hpp"'
    typename: jobject
    typeSignature: 'Lcom/example/mylib/Record1;'
---
name: mylib_interface1
typedef: 'interface +j +o'
    (...)
---
name: mylib_enum1
typedef: 'enum'
    (...)

Each document in the YAML file describes one extern type. A full documentation of all fields is available in example/example.yaml. You can also check the files test-suite/djinni/date.yaml and test-suite/djinni/duration.yaml for some real working examples of what you can do with it.

To use a library type in your project simply include it in your IDL file and refer to it using its name identifier:

@extern "mylib.yaml"

client_interface = interface +c {
  foo(): mylib_record1;
}

These files can be created by hand as long as you follow the required format. This allows you to support types not generated by Djinni. See test-suite/djinni/duration.yaml and the accompanying translators in test-suite/handwritten-src/cpp/Duration-objc.hpp and test-suite/handwritten-src/cpp/Duration-jni.hpp for an advanced example. Handwritten translators implement the following concept:

// For C++ <-> Objective-C
struct Record1
{
    using CppType = ::mylib::Record1;
    using ObjcType = MLBRecord1*;

    static CppType toCpp(ObjcType o) { return /* your magic here */; }
    static ObjcType fromCpp(CppType c) { return /* your magic here */; }

    // Option 1: use this if no boxing is required
    using Boxed = Record1;
    // Option 2: or this if you do need dedicated boxing behavior
    struct Boxed
    {
        using ObjcType = MLBRecord1Special*;
        static CppType toCpp(ObjcType o) { return /* your magic here */; }
        static ObjcType fromCpp(CppType c) { return /* your magic here */; }
    }
};
// For C++ <-> JNI
#include "djinni_support.hpp"
struct Record1
{
    using CppType = ::mylib::Record1;
    using JniType = jobject;

    static CppType toCpp(JniType j) { return /* your magic here */; }
    // The return type *must* be LocalRef<T> if T is not a primitive!
    static ::djinni::LocalRef<jobject> JniType fromCpp(CppType c) { return /* your magic here */; }

    using Boxed = Record1;
};

For interface classes the CppType alias is expected to be a std::shared_ptr<T>.

Be sure to put the translators into representative and distinct namespaces.

If your type is generic the translator takes the same number of template parameters. At usage each is instantiated with the translators of the respective type argument.

template<class A, class B>
struct Record1
{
    using CppType = ::mylib::Record1<typename A::CppType, typename B::CppType>;
    using ObjcType = MLBRecord1*;

    static CppType toCpp(ObjcType o)
    {
        // Use A::toCpp() and B::toCpp() if necessary
        return /* your magic here */;
    }
    static ObjcType fromCpp(CppType c)
    {
        // Use A::fromCpp() and B::fromCpp() if necessary
        return /* your magic here */;
    }

    using Boxed = Record1;
};

Miscellaneous

Record constructors / initializers

Djinni does not permit custom constructors for records or interfaces, since there would be no way to implement them in Java except by manually editing the autogenerated file. Instead, use extended records or static functions.

Identifier Format

Djinni supports overridable formats for most generated filenames and identifiers. The complete list can found by invoking Djinni with --help. The format is specified by formatting the word FooBar in the desired style:

  • FOO_BAR -> GENERATED_IDENT
  • mFooBar -> mGeneratedIdent
  • FooBar -> GeneratedIdent

Integer types

In Djinni, i8 through i64 are all used with fixed length. The C++ builtin int, long, etc and Objective-C NSInteger are not used because their length varies by architecture. Unsigned integers are not included because they are not available in Java.

Test Suite

Run make test to invoke the test suite, found in the test-suite subdirectory. It will build and run Java code on a local JVMy, plus Objective-C on an iOS simulator. The latter will only work on a Mac with Xcode.

Generate a standalone jar

The djinni_jar target of the main Makefile creates a standalone .jar. This uses the sbt assembly plugin under the hoods.

Simply call this target from the root directory:

make djinni_jar

This will produce a .jar file inside the src/target/scala_<SCALA_VERSION>/djinni-assembly-<VERSION>.jar.

You can move and use it as any other executable .jar.

Assuming the .jar is located at $DJINNI_JAR_DIR its version equals 0.1-SNAPSHOT:

# Example
java -jar $DJINNI_JAR_DIR/djinni-assembly-0.1-SNAPSHOT.jar \
    --java-out "$temp_out/java" \
    --java-package $java_package \
    --java-class-access-modifier "package" \
    --java-nullable-annotation "javax.annotation.CheckForNull" \
    --java-nonnull-annotation "javax.annotation.Nonnull" \
    --ident-java-field mFooBar \
    \
    --cpp-out "$temp_out/cpp" \
    --cpp-namespace textsort \
    --ident-cpp-enum-type foo_bar \
    \
    --jni-out "$temp_out/jni" \
    --ident-jni-class NativeFooBar \
    --ident-jni-file NativeFooBar \
    \
    --objc-out "$temp_out/objc" \
    --objcpp-out "$temp_out/objc" \
    --objc-type-prefix TXS \
    --objc-swift-bridging-header "TextSort-Bridging-Header" \
    \
    --idl "$in"

Note: The all target of the main Makefile includes the djinni_jar target.

Generate an iOS universal binary of the support library.

The ios-build-support-lib.sh helps you to build an universal static library for iOS platforms. It uses the platform file of the ios-cmake repository.

It basically creates one universal static library per IOS_PLATFORM variable and uses lipo to merge all the files in one.

There is basically two variables you would like to modify:

  • BUILD_APPLE_ARCHITECTURES: Specifies which IOS_PLATFORM to build. For more informations, take a look at https://github.com/leetal/ios-cmake.

  • ENABLE_BITCODE: enable/disable the bitcode generation.

Android Parcelable records

Djinni supports generating records that implements android.os.parcelable.

In order to do that, there are two steps needed:

  • deriving the records that should be parcelable with the keyword parcelable: deriving(parcelable)
  • run Djinni with the following flag --java-implement-android-os-parcelable true

Community Links

  • Join the discussion with other developers at the Mobile C++ Slack Community
  • There are a set of tutorials for building a cross-platform app using Djinni.
  • mx3 is an example project demonstrating use of Djinni and other tools.
  • Slides and video from the CppCon 2014 talk where we introduced Djinni.
  • Slides and video from the CppCon 2015 talk about Djinni implementation techniques, and the addition of Python.
  • You can see a CppCon 2014 talk by app developers at Dropbox about their cross-platform experiences.

Authors

  • Kannan Goundan
  • Tony Grue
  • Derek He
  • Steven Kabbes
  • Jacob Potter
  • Iulia Tamas
  • Andrew Twyman

Contacts

djinni's People

Contributors

4brunu avatar alanjrogers avatar alexcohn avatar artwyman avatar choiip avatar csimmons0 avatar ddeville avatar forderud avatar gnichola avatar greatcall-kayek avatar izacus avatar j4cbo avatar jaetzold avatar jfirebaugh avatar jonmcclung avatar konovalov-aleks avatar marcinkaszynski avatar mjmacleod avatar mknejp avatar msjarrett avatar nachosoto avatar pwais avatar skabbes avatar sschuberth avatar steipete avatar stephenwspann avatar tiwoc avatar vmaks avatar wpurdy2 avatar yageek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

djinni's Issues

Circular definition problem in objective c

I'm trying to implement a delegate pattern:

device_discovery_delegate = interface +j +o {
device_added(device: device);
device_removed(device: device);
}

device = interface +c {
static add_delegate(delegate: device_discovery_delegate);
}

When doing this, the generated header Device.h imports DeviceDiscoveryDelegate.h which then references the protocol which is still not defined. I fixed this with this patch but I don't know if this is a good solution:

diff --git a/src/source/ObjcGenerator.scala b/src/source/ObjcGenerator.scala
index cd67a03..8d26b53 100644
--- a/src/source/ObjcGenerator.scala
+++ b/src/source/ObjcGenerator.scala
@@ -46,7 +46,7 @@ class ObjcGenerator(spec: Spec) extends Generator(spec) {
header.add("#import " + q(headerName(d.name)))
case DInterface =>
header.add("#import <Foundation/Foundation.h>")

  •        header.add("#import " + q(headerName(d.name)))
    
  •        header.add("@protocol " + idObjc.ty(d.name) + ";")
         val ext = d.body.asInstanceOf[Interface].ext
         if (ext.cpp) body.add("#import " + q(privateHeaderName(d.name + "_cpp_proxy")))
         if (ext.objc) body.add("#import " + q(privateHeaderName(d.name + "_objc_proxy")))
    

Cygwin uses incorrect paths

sbt fails to find sbt_launch.jar when djinni is run from cygwin

The failure in cygwin is due to incorrect paths. Java on windows requires windows-formatted paths, even when run from cygwin.

cygpath can be used to convert between the various formats.

Example build issue - missing local.properties

Error message on Mac:

NDK is not configured. Make sure there is a local.properties file with an ndk.dir entry in the directory

Following the instructions in README.md I get these errors:

$ make android
make: *** No rule to make target `android'.  Stop.

$ make ios
make: *** No rule to make target `ios'.  Stop.

$ make example_android
...

cd example/android/ && ./gradlew app:assembleDebug

FAILURE: Build failed with an exception.

* Where:
Build file 'djinni/example/android/app/build.gradle' line: 38

* What went wrong:
A problem occurred evaluating project ':app'.
> NDK is not configured. Make sure there is a local.properties file with an ndk.dir entry in the directory djinni/example/android.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Suggestions:

Since local.properties is excluded in gitignore, creating a local.properties.sample would be a great help.

Also updating the README.md with the steps required to download and install SDK/NDK would allow prospective users to quickly compile & test.

Make in djinni dir fails

Hello, just started working with djinni, tried to run make and it failed.

cd example/android/ && ./gradlew app:assembleDebug

FAILURE: Build failed with an exception.

* Where:
Build file '/Users/nt/dev/mac/lib/djinni/example/android/app/build.gradle' line: 17

* What went wrong:
A problem occurred evaluating project ':app'.
> Ambiguous method overloading for method java.io.File#<init>.
  Cannot resolve which method to invoke for [null, class java.lang.String] due to overlapping prototypes between:
    [class java.lang.String, class java.lang.String]
    [class java.io.File, class java.lang.String]
cd example/android/ && ./gradlew app:assembleDebug

FAILURE: Build failed with an exception.

* Where:
Build file '/Users/nt/dev/mac/lib/djinni/example/android/app/build.gradle' line: 17

* What went wrong:
A problem occurred evaluating project ':app'.
> Ambiguous method overloading for method java.io.File#<init>.
  Cannot resolve which method to invoke for [null, class java.lang.String] due to overlapping prototypes between:
    [class java.lang.String, class java.lang.String]
    [class java.io.File, class java.lang.String]

Here is output of make https://gist.github.com/nt9/6115ffb92885cf4b8cd9

Would be glad if someone could give an advice :)

C++/Java interop on Linux/MacOSX

Hi, I've tried to use djinni for calling C++ from Java on both Linux and MaxOSX but I get a java.lang.UnsatisfiedLinkError exception at runtime. Is it supposed to be possible to use the C++/Java interop functionality of djinni on Linux and MaxOSX or is the implementation specific to the Android build/runtime system?

f32

Hi,

at the CppCon talk you mentioned why there is only f64, something regarding inconsistent sizes across iOS architectures (it's been a while). However that only applies to CGFloat which is an alias to either float or double, depending on whether it runs on arm64 or not. The actual values of sizeof(float) and sizeof(double) always give 4 and 8, respectively. On Java the aliases jfloat and jdouble already do the right thing.

So I'm not entirely sure I understand why there is only f64.

Regards
Miro

Generalize exception translation

Right now, std::exception -> RuntimeException / NSException and that's it.

We want two things:

  • Define other exception mappings (C++ my_exception -> Java MyException)
  • Define exception types as records in the djinni file

Compiling on Linux with gcc 4.9

Hi, In order to compile on Linux with gcc 4.9 I had to add #include <cstring> to djinni_support.cpp. Otherwise it couldn't find thea declaration for strrchr.

Deriving objc header location when extending records

When a record is specified to be extended in ObjC (using the +o flag), the deriving header location is always ../DerivingHeaderName.h (see here). Now that header is obviously a piece of handwritten code. I could not make this work while keeping the generated and handwritten source files in separate directories.

Wouldn't it be better to add an option for specifying were handwritten source files are located? Or is there some other way around this?

Circular references in ObjC

Hey guys, I think #4 is resurfacing.

If I have

ClientListener = interface +j +o {
    onClientInitialized(client: Client);
}

Client = interface +c {
    static createClient(baseUrl: string, listener: ClientListener);
}

Then I get an ObjectiveC ClientListener.h file referencing Client.h and a Client.h file referencing ClientListener.h.

This was tested on ed6ef3c

Thanks!

c++ namespace was not generated when return interface object

for example, if run below dijinni definition with cpp-namespace option,
platform_obj = interface +o +j {
}
platform_api = interface +o +j {
Obj(): platform_obj;
}
the generated obj code will miss cpp-namespace prefix.

I am not familiar with scala, but I tried to change this file ObjcGenerator.scala with line 330 and 358:
val ret = m.ret.fold("void")(toCppType(, spec.cppNamespace))
val ret = m.ret.fold("void")(toCppType(
, spec.cppNamespace))

it seems work but I really dont know scala much, please help me to check if it's ok or not, thanks.

Getting started documentation with dependency information

I'm very interested in experimenting with djinni for bridging C++ libraries with Java on Windows. However, I'm struggling to get started since there does not seem to be a getting started guide or list of build tool dependencies documented anywhere. Also, the documentation and makefiles already present seem very geared towards "mobile" Android/iOS platforms, with little "desktop" information to be found.

Could it be possible to extend the existing documentation with a little bit of "getting started" information for different platforms such as e.g. Windows/Linux/Android/OS-x/iOS?

Thanks in advance,
Fredrik

Break circular dependencies with forward declarations

if you have the CIDL file

MyClassListener = interface +c {
    onMyClassInitialized(instance: MyClass);
}

MyClass = interface +c {
    static createMyClass(listener: MyClassListener);
}

then it will fail to compile because of a circular dependency. it would be rad to detect these and break them with forward declarations.

security exceptions in build

I am trying to compile Djinni, but am running into some Java security exceptions. If anybody has any suggestions on how to switch off these security settings, that would be great.

dhruba@dev223 ~/djinni]$ java -version
java version "1.7.0_06"
Java(TM) SE Runtime Environment (build 1.7.0_06-b24)
Java HotSpot(TM) 64-Bit Server VM (build 23.2-b09, mixed mode)

[dhruba@dev223 ~/djinni]$ uname -a
Linux 3.2.55-106_fbk22_00877_g6902630 #106 SMP Wed Apr 2 16:19:59 PDT 2014 x86_64 x86_64 x86_64 GNU/Linux

[dhruba@dev223 ~/djinni]$ src/run
Building Djinni...
java -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M -Djava.security.manager -Djava.security.policy=/home/dhruba/local/djinni/src/support/sbt.security.policy -jar /home/dhruba/local/djinni/src/support/sbt-launch.jar -Dsbt.override.build.repos=true -Dsbt.repository.config=/home/dhruba/local/djinni/src/support/sbt.resolvers.properties compile start-script
[info] Loading project definition from /data/users/dhruba/djinni/src/project
[info] Updating {file:/data/users/dhruba/djinni/src/project/}src-build...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[warn] ::::::::::::::::::::::::::::::::::::::::::::::
[warn] :: UNRESOLVED DEPENDENCIES ::
[warn] ::::::::::::::::::::::::::::::::::::::::::::::
[warn] :: com.typesafe.sbt#sbt-start-script;0.10.0: several problems occurred while resolving dependency: com.typesafe.sbt#sbt-start-script;0.10.0 {compile=[default(compile)]}:
[warn] java.security.AccessControlException: access denied ("java.net.SocketPermission" "xx:8080" "connect,resolve")
[warn] java.security.AccessControlException: access denied ("java.net.SocketPermission" "xx:8080" "connect,resolve")
[warn] java.security.AccessControlException: access denied ("java.net.SocketPermission" "xx:8080" "connect,resolve")
[warn] ::::::::::::::::::::::::::::::::::::::::::::::
[warn]
[warn] Note: Some unresolved dependencies have extra attributes. Check that these dependencies exist with the requested attributes.
[warn] com.typesafe.sbt:sbt-start-script:0.10.0 (sbtVersion=0.13, scalaVersion=2.10)
[warn]
sbt.ResolveException: unresolved dependency: com.typesafe.sbt#sbt-start-script;0.10.0: several problems occurred while resolving dependency: com.typesafe.sbt#sbt-start-script;0.10.0 {compile=[default(compile)]}:
java.security.AccessControlException: access denied ("java.net.SocketPermission" "xx.xx.xx.xx:8080" "connect,resolve")
java.security.AccessControlException: access denied ("java.net.SocketPermission" "xx.xx.xx.xx:8080" "connect,resolve")
java.security.AccessControlException: access denied ("java.net.SocketPermission" "xx.xx.xx.xx:8080" "connect,resolve")
at sbt.IvyActions$.sbt$IvyActions$$resolve(IvyActions.scala:217)
at sbt.IvyActions$$anonfun$update$1.apply(IvyActions.scala:126)
at sbt.IvyActions$$anonfun$update$1.apply(IvyActions.scala:125)
at sbt.IvySbt$Module$$anonfun$withModule$1.apply(Ivy.scala:115)
at sbt.IvySbt$Module$$anonfun$withModule$1.apply(Ivy.scala:115)
at sbt.IvySbt$$anonfun$withIvy$1.apply(Ivy.scala:103)
at sbt.IvySbt.sbt$IvySbt$$action$1(Ivy.scala:48)
at sbt.IvySbt$$anon$3.call(Ivy.scala:57)
at xsbt.boot.Locks$GlobalLock.withChannel$1(Locks.scala:98)
at xsbt.boot.Locks$GlobalLock.xsbt$boot$Locks$GlobalLock$$withChannelRetries$1(Locks.scala:81)
at xsbt.boot.Locks$GlobalLock$$anonfun$withFileLock$1.apply(Locks.scala:102)
at xsbt.boot.Using$.withResource(Using.scala:11)
at xsbt.boot.Using$.apply(Using.scala:10)
at xsbt.boot.Locks$GlobalLock.ignoringDeadlockAvoided(Locks.scala:62)
at xsbt.boot.Locks$GlobalLock.withLock(Locks.scala:52)
at xsbt.boot.Locks$.apply0(Locks.scala:31)
at xsbt.boot.Locks$.apply(Locks.scala:28)
at sbt.IvySbt.withDefaultLogger(Ivy.scala:57)
at sbt.IvySbt.withIvy(Ivy.scala:98)
at sbt.IvySbt.withIvy(Ivy.scala:94)
at sbt.IvySbt$Module.withModule(Ivy.scala:115)
at sbt.IvyActions$.update(IvyActions.scala:125)
at sbt.Classpaths$$anonfun$sbt$Classpaths$$work$1$1.apply(Defaults.scala:1223)
at sbt.Classpaths$$anonfun$sbt$Classpaths$$work$1$1.apply(Defaults.scala:1221)
at sbt.Classpaths$$anonfun$doWork$1$1$$anonfun$74.apply(Defaults.scala:1244)
at sbt.Classpaths$$anonfun$doWork$1$1$$anonfun$74.apply(Defaults.scala:1242)
at sbt.Tracked$$anonfun$lastOutput$1.apply(Tracked.scala:35)
at sbt.Classpaths$$anonfun$doWork$1$1.apply(Defaults.scala:1246)
at sbt.Classpaths$$anonfun$doWork$1$1.apply(Defaults.scala:1241)
at sbt.Tracked$$anonfun$inputChanged$1.apply(Tracked.scala:45)
at sbt.Classpaths$.cachedUpdate(Defaults.scala:1249)
at sbt.Classpaths$$anonfun$updateTask$1.apply(Defaults.scala:1214)
at sbt.Classpaths$$anonfun$updateTask$1.apply(Defaults.scala:1192)
at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:42)
at sbt.std.Transform$$anon$4.work(System.scala:64)
at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:237)
at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:237)
at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:18)
at sbt.Execute.work(Execute.scala:244)
at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:237)
at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:237)
at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:160)
at sbt.CompletionService$$anon$2.call(CompletionService.scala:30)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
error sbt.ResolveException: unresolved dependency: com.typesafe.sbt#sbt-start-script;0.10.0: several problems occurred while resolving dependency: com.typesafe.sbt#sbt-start-script;0.10.0 {compile=[default(compile)]}:
[error] java.security.AccessControlException: access denied ("java.net.SocketPermission" "xx.xx.xx.xx:8080" "connect,resolve")
[error] java.security.AccessControlException: access denied ("java.net.SocketPermission" "xx.xx.xx.xx:8080" "connect,resolve")
[error] java.security.AccessControlException: access denied ("java.net.SocketPermission" "xx.xx.xx.xx:8080" "connect,resolve")

Rationale for @protocol in +c interfaces

Hi,

I am currently in the process of trying out Djinni for our projects here. One thing that I am curious about is why the Objective-C side is using @protocol for the base type, but additionally also relies on a public @interface.

Specifically, if I have MyClass I get a @ProtoColl names MyClass and an @interface called MyClassCppProxy. Now as long as one only uses instance methods with id it's nice and all. But this totally makes object construction very inconvenient using class methods.

Example:

@protocl MyClass
+ (id<MyClass>) create;
@end

At first glance one might think you can call [MyClass create] but while this is great with static Java/C++ member functions that's not how Objective-C protocols work. One has to use [MyClassCppProxy create] instead. This is very inconvenient. First, I have to tell users about classes that I would consider a private implementation detail, and second, it means I have to tell our (overly religious) Objective-C developers to use something with "cpp" in its name...

Besides the inconvenience, there might also be a correctness issue. Right now all generated +c interfaces on the Objective-C side take id as parameter and then internally cast it to SomethingCppProxy* to get to the C++ object. But the argument may not necessarily be an instance of SomethingCppProxy. While this will most likely just crash the program I'd rather have it prevented at compile time if possible.

Both these issues would be solved if Djinni only created @interface types for +c interfaces. But I guess there is some rationale behind the current design and I would be curious to know about it.

Regards,
Miro

Preexisting record types

Djinni really only needs to know how to convert record types between objc, java and c++. What if we extended the include format to take plugins like this?

@include "date.json"

djinni_type: "custom_date"
objc_type: "NSDate"
java_type: "java.util.Date"
cpp_include_path: "<chrono>"
cpp_type: "std::chrono::system_clock::time_point"
jni_type: "jobject"
// custom_date.hpp
namespace djinni {
  jobject custom_date_to_java(time_point);
  NSDate * custom_date_to_objc(time_point);
  time_point custom_date_to_cpp(jobject);
  time_point custom_date_to_cpp(NSDate *);
}

ADL prevents correct type lookup

When defining a interface +j with a record type, ADL seems to be selecting the wrong types.

record_thing = record {
    data_member: string;
}

interface_thing = interface +j +o {
  on_event(thing: record_thing);
}

Generated code with comments:

namespace djinni_generated_java_classes {

void InterfaceThing::JavaProxy::JavaProxy::on_event(const ::my_namespace::RecordThing & c_thing) {
    JNIEnv * const jniEnv = djinni::jniGetThreadEnv();
    djinni::JniLocalScope jscope(jniEnv, 10);
    djinni::LocalRef<jobject> j_thing(jniEnv, RecordThing::toJava(jniEnv, c_thing));
    // selects my_namespace::InterfaceThing NOT djinni_generated_java_classes::InterfaceThing
    const InterfaceThing & data = djinni::JniClass<InterfaceThing>::get();
    // data.method_onEvent is undefined
    jniEnv->CallVoidMethod(getGlobalRef(), data.method_onEvent, j_thing.get());
    djinni::jniExceptionCheck(jniEnv);
};

}

Specify new language

I want to add support for a new language but the generators are tangled up.

For example there is code referencing each language generator in the generic generator class.

I don't think this scalable are you going to make it easier to add new languages.

Djinni fails to run when there is a space in the current directory name

If you try to run djinni in a path with a space, it will fail:

var/lib/jenkins/jobs/foo bar/workspace/vendor/djinni/src/build: line 15: cd: /var/lib/jenkins/jobs/foo: No such file or directory
dirname: missing operand
Try dirname --help' for more information. dirname: missing operand Trydirname --help' for more information.

The fix is in src/build,:

diff --git a/src/build b/src/build
index b3ccdac..c2f7d75 100755
--- a/src/build
+++ b/src/build
@@ -12,7 +12,7 @@ while [ -h "$loc" ]; do
loc="dirname "$loc"/$link" # Relative link
fi
done
-base_dir=$(cd dirname "$loc" && pwd)
+base_dir=$(cd "$(dirname "$loc")" && pwd)

need_to_build=0
dependencies=("build.sbt" "project/plugins.sbt" "source")

function / block types

list_ish = interface +c {
  map(iterator_fn: function<i64>): list_ish;
}

This would generate a block based api for objc, and a Callable (or similar one for java).

This should be flexible enough to encapsulate both synchronous and async functions.

deriving parcelable

Related to #6 - there are also places where we'd like the Java versions of records to be Parcelable.

deriving json

Djinni should be able to autogenerate json parsing and serialization code for records/enums.

Command line flag or other means to output only the filenames that would be generated

Something like --only-print-filenames? That'd make it way easier to use Djinni in GYP actions.

A lot of the time you can just specify one of the directories or nothing at all under outputs in a GYP action and still have the right thing happen, but if you wan to e.g. add your autogenned Objective-C headers to mac_framework_headers for a framework target, you wind up having to do two passes if you're using something like glob.py: one to generate the files, and one for the build target to pick up the list of files to copy. (This issue can also be sidestepped if you check the autogenerated files into version control, but it's kind of lame for that to be the only way to make it work.)

trailing comments

When using djinni, it fails on trailing comments. Using comments not for documentation, but for commenting out methods / fields doesn't really work.

Works (because it still looks like a documentation comment)

blah = interface +c {
    # method_a();
    method_b();
}

Doesn't work but I would guess every single developer would expect it to

blah = interface +c {
    method_a();
    # method_b();
}

Failing to build sample example on Ubuntu and Windows (using Cygwin)

Failing with following error when trying to build using: make example_android

FAILURE: Build failed with an exception.

  • Where:
    Build file '/home/prashant/Work/Djinni/example/android/app/build.gradle' line: 17

  • What went wrong:
    A problem occurred evaluating project ':app'.

    Ambiguous method overloading for method java.io.File#.
    Cannot resolve which method to invoke for [null, class java.lang.String] due to overlapping prototypes between:
    [class java.lang.String, class java.lang.String]
    [class java.io.File, class java.lang.String]

  • Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

Please suggest

Wrap C++ -> ObjC call paths in autoreleasepool

ObjC depends on autorelease to clean up memory in a timely fashion - without an autoreleasepool, ObjC temporaries created (esp. on background threads) can stick around for a very long time.

Don't pass primitives by const&

Idiomatic c++ wouldn't pass any primitive integer types by const&. I'm happy to write this up if it aligns with the goals of the project. I propose passing the following types by value.

  • T if T is integral
  • optional<T> if T is integral
  • shared_ptr<T>

Java to C++ exception translation

Common workflow with djinni (I'm focused on Java only now): "Client code" (Java) --> C++ library shared code --> Platform specific interface if necessary (again Java, TextboxListenerImpl in the example).

If we call platform specific code from C++ and it throws an exception, then it is caught by jniExceptionCheck and passed back to C++ as jni_exception_pending(), but no matter what C++ code does (catches it or not), exception still exists in JVM and client code is forced to catch it (even if C++ code has already recovered).
Any reason for this? Can JNI remove exception from JVM with env->ExceptionClear() before throwing jni_exception_pending()? Then we will have to catch exception only once (in C++ code). "C++ code" does call platform specific code and it should know better how to recover in case of exception. If it is necessary we still can re-throw it to client code, but in some cases client code should not care about this.

Any thoughts?
Thank you.

Tutorial or article describing workflow

Hello, maybe it is not the best place to ask these kind of questions and this particular question is not the best too, but could you describe basic workflow for someone who is not so experienced in build systems and xplatform build process, maybe write something like short "how-to"? Thank you :)

unused-parameter warnings on record types with no non-const fields

If you create a record without any non-const members (say, one to describe an app or library version...), the generated Objective-C constructors for it will produce unused-parameter warnings, since their arguments are never referenced. Perhaps we can just emit a "#pragma unused (varName)"?

Is optional<shared_ptr<T>> necessary?

I get the intention behind specifying an optional<T> in the interface definition, stating that the passed in object reference may be nil/null in Obj-C or Java and this intention is clearly shown in the C++ side using optional<shared_ptr<T>> as argument or return type.

My problem with this is twofold:

  1. shared_ptr already has a well defined empty state. Wrapping the whole thing inside optional seems redundant.
  2. There is no indication in the Obj-C/Java interface telling a user where passing nil or null is OK or not, contrary to the C++ signature. The difference only manifests itself in the form of a runtime error if used incorrectly. Therefore whether a function accepts empty object references must be documented somewhere as one cannot infer it form the argument/return types.

My conclusion is getting rid of optional<shared_ptr<T>> and instead transform nil/null to/from empty shared_ptr<T> for interface types, regardless of whether they are defined as T or optional<T> in the Djinni file. However for plain T the generated code could handle nil/null/nullptr as errors and throw an appropriate exceptions to the offending code with an explanatory error message along the lines of "argument xxx may not be null", "return value must not be null" and so on.

Does that sound like a sensible thing to do?

JNI Exception check in HBinary.hpp calls forbidden JNI methods in critical section

When retrieving binary data from Java environment, fromJava method in HBinary.hpp calls jniExceptionCheck which calls further methods.

The exception check call is made between the time GetPrimitiveArrayCritical and ReleasePrimitiveArrayCritical is called, causing undefined behaviour forbidden by the JVM standard.

It also causes a hard crash on Android ART / CheckJNI with an exception:

JNI DETECTED ERROR IN APPLICATION: thread Thread[13,tid=18055,Runnable,Thread*=0xaf075000,peer=<redacted>] using JNI after critical get

Suggest removing the check call in that method since it's usually done by the callee anyway.

Enums should be hashable

When you try to use an enum as the key in a map, the compiler complains that there is no hash function for the enum

A buffer data type

The current way the "data" type works is only marginally useful for data streaming between languages due to all the copying involved.

I was thinking about introducing a "buffer" data type that represents memory shared between both sides of the fence without allocating and copying stuff around in every call. Both Objective-C and Java have facilities to access "unmanaged" regions of memory.

  • From C++ to
    • Java: a direct java.nio.ByteBuffer which can be created in JNI with NewDirectByteBuffer() and does not copy the content.
    • Objective-C: NSData dataWithBytesNoCopy:length:freeWhenDone: same as above (**)
  • From Java to C++: Pass in a java.nio.ByteBuffer and use GetDirectBufferAddress() and GetDirectBufferCapacity() to transform into something like std::experimental::array_view<uint8_t>.
  • From Objective-C to Java: Pass in a NSMutableData and use .length and .mutableBytes to construct the array view.

The whole point of this exercise is to avoid copying the buffer content and is intended for long-lived buffers that are shared and written to/read from on both sides to exchange bulk data. There of course must be some sort of agreement in the interface protocol about who creates the data and make sure it is not modified in a way that invalidates the memory region.

On a related note, maybe the "data" datatype should also switch to something like std::experimental::array_view<const uint8_t> to avoid the copy at least in one direction where possible.

** The only drawback here is that NSData is read-only. If mutable access is necessary the buffer has to be created on the Objective-C side with NSMutableData.

+c interfaces returning +o +j

Hi,

the current code generation is broken for cases where a +c interface returns +j or +o interfaces.

Given:

extern = interface +j +o {...}
intern = interface +c { foo(): extern; }

On the JNI side this tries to call Extern::toJava(), which does not exist, and for Obj-C it tries to static_pointer_cast from Extern to ExternObjcProxy, which is mostly likely not the case and causes undefined behavior.

I think the easiest fix would be to disallow returning +j +o interfaces from +c. Supporting this properly would be a bit more involved I think.

Windows

Running djinni on windows fails due to generating target/start.bat but trying to exec target/start from run_assemble_built

For running on cygwin, it should generate a normal "start" and exec it. Or, if it's running from a windows command prompt it should "start /WAIT start.bat". The cygwin approach seems less problematic...

async return types

I would love an automatic way to generate async results for various platforms. This allows the idiomatic async pattern to be applied on each platform.

my_slow_class = interface + c {
  do_long_operation(id: i64): async string;
}

This might generate the following:

C++

class MySlowClass {
//...
public:
    void do_long_operation(int64_t id, const function<void(string)>& on_complete); 
//...
};

Objc

@protocol DjinniMySlowClass
- (void)doLongOperation:(int64_t)id withCompletionBlock:(void (^)(NSString *))block;
@end

And maybe something with AsyncTask<Params, Progress, Result> on android.

A Barrel of Refactoring

Hey folks,

over the past month or two Djinni was used quite extensively here and we ran into a few problems, resulting in a number of refactorings, both internal (not changing the output) and external (significantly changing the output). Besides, we need something similar to #52, which at the time posed quite a challenge considering how Djinni was structured. So in preparation to make #52 (and others like #54, #46, #45) much easier to realize something had to happen.

Before doing a pull request I thought it'd be better to share these changes and get your input. So here we go:

  • Split the ObjcGenerator into ObjcGenerator and ObjcppGenerator. This makes it easier to adjust only one of them and is also how the Java/JNI generators are separated. The philosophy here is that using only ObjcGeneratoris sufficient to create all the Obj-C files necessary in the same fashion as JavaGenerator only creates Java classes, but no JNI content.
  • Introduce "Marshal" types, responsible for naming types, files, creating parameter signatures, to/from C++ conversion, etc. Now every language has its own Marshal. The xxxGenerators are responsible for the overall file structure, whereas xxxMarshals do the actual language interop. So now JNIGenerator can make use of CppMarshal to create consistent type signatures etc. (This is a crucial part to make #52 remotely possible)
  • The Obj-C support library was restructured similar to how the JNI support library works, using template translation types. This wastly simplifies the work of ObjcppGenerator.
  • Before, Djinni was generating @protocol types for both +c and +o and provided a xxCppProxy in a +Private.h include file. This was a real thorn to every Obj-C developer exposed to Djinni generated types as it didn't really follow any existing conventions on that platform. Therefore came an implementation of #25. To recap: +c types now generate an @interface acting as proxy to C++ and +o types generate a @protocol to be implemented in Objc-C. Therefore +c types are represented by T* variables and +o by id<T>. There was similar outcry in the Java camp about +j not creating a Java interface instead of class but changing the JNI side is a bit more involved.
  • Since Obj-C and Obj-C++ are now considered two separate generators, there's a new set of --objcpp-x command line arguments:
    • --objcpp-out: The output folder for private Objective-C++ files (Generator disabled if unspecified).
    • --objcpp-include-prefix: The prefix for #import of Objective-C++ header files from Objective-C++ files.
    • --objcpp-include-cpp-prefix: The prefix for #include of the main C++ header files from Objective-C++ files.
    • --objcpp-include-objc-prefix: The prefix for #import of the Objective-C header files from Objective-C++ files (default: the same as --objcpp-include-prefix)
    • --objc-ext => --objcpp-ext: The filename extension for Objective-C++ files (default: "mm")
  • Many inconsistencies about formatting and signatures are gone as that is all handled by single entities (aka Marshals).
  • Since ObjcGenerator is setup to create self-containing Obj-C records it creates its own .m files containing the comparison functions, initializers, etc. separate from Obj-C++ translators. But because Obj-C only allows compile-time constants at file scope the file must have extension .mm to support constants of record types. Same applies to interfaces.
  • The comparison operators for C++ records are generated as free friend functions to make them symmetric and follow best practices.
  • JNI fromCpp() functions now all return LocalRef<jobject> instead of jobject and local reference cleanup is handled by it form there on.
  • Generated code didn't always fully qualify the ::djinni namespace for support library stuff

There may be other small fixes but those are the really important ones. It also includes fixes for issues #69, #26, #21, #10 and implicitly includes pull requests #67, #55, #42, #41, #39, #38 .

Do these changes break existing code? Hell yes, they do.
But at least the example and test-suite all pass.
I get that this kind of bulk change is a Bad Thing™, but never was there ever a point in the past 2 months where I could have said "yes, this is a small self-contained change that can be merged back now". There was (and still is) quite the amount of redundancy and intertwining. Also the reason why I'm doing this post first.

Anyway, the branch is here https://github.com/mknejp/djinni/tree/feature/codegen-refactor so check it out if interested and see how much code it breaks. You will definitely have to add the --objcpp-out option otherwise that part is going to be missing entirely. If it's fine I'll do the pull request and hop on to #52.

C# support?

C# support would allow for easy Windows Phone integration.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.