Commit 3a1c6421 authored by Jacob Potter's avatar Jacob Potter

Extension libraries for platform threading and custom exception translation.

parent b0e39230
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
/**
* Copyright 2015 Dropbox, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This is an example of how to replace Djinni's default JNI exception translation functions
* to provide custom behavior. For this example, we show how to convert specific Java exception
* types to custom C++ exception types.
*/
#include <exception>
#include "../../../support-lib/jni/djinni_support.hpp"
using namespace djinni;
/*
* Custom C++ exception I want to throw in response to a Java security exception.
* In production code, you'd want to put this in a header, so other C++ code can throw and
* catch it.
*/
class my_security_exception : public std::runtime_error {
public:
my_security_exception(const std::string & what_arg) : runtime_error(what_arg) {};
};
/*
* Helper type to prepare and cache all the Java type and method lookups we need.
*/
struct SecurityExceptionClassInfo {
const GlobalRef<jclass> clazz = djinni::jniFindClass("java/lang/SecurityException");
const jmethodID ctor = djinni::jniGetMethodID(clazz.get(), "<init>",
"(Ljava/lang/String;)V");
const jmethodID getMessage = djinni::jniGetMethodID(clazz.get(), "getMessage",
"()Ljava/lang/String;");
};
/*
* We're replacing pluggable functions inside the djinni namespace.
*/
namespace djinni {
/*
* Pluggable Djinni translation function for C++ -> Java. Called when a call
* from Java to C++ throws an exception. Called inside a catch block.
*
* The default implementation is defined with __attribute__((weak)) so this
* definition can replace it.
*/
void jniSetPendingFromCurrent(JNIEnv * env, const char * ctx) noexcept {
try {
throw;
} catch (const my_security_exception & e) {
const SecurityExceptionClassInfo & ci = djinni::JniClass<SecurityExceptionClassInfo>::get();
LocalRef<jstring> jmessage { env, jniStringFromUTF8(env, e.what()) };
LocalRef<jthrowable> jex { env, (jthrowable)env->NewObject(ci.clazz.get(), ci.ctor, jmessage.get()) };
env->Throw(jex);
return;
} catch (const std::exception & e) {
jniDefaultSetPendingFromCurrent(env, ctx);
}
}
/*
* Pluggable Djinni translation function for Java -> C++. Called when a call
* from C++ to Java throws an exception.
*
* The default implementation is defined with __attribute__((weak)) so this
* definition can replace it.
*/
void jniThrowCppFromJavaException(JNIEnv * env, jthrowable java_exception) {
const SecurityExceptionClassInfo & ci = djinni::JniClass<SecurityExceptionClassInfo>::get();
if (env->IsInstanceOf(java_exception, ci.clazz.get())) {
LocalRef<jstring> jmessage {
env, (jstring)env->CallObjectMethod(java_exception, ci.getMessage) };
throw my_security_exception(jniUTF8FromString(env, jmessage.get()));
}
throw jni_exception { env, java_exception };
}
} // namespace djinni
//
// Copyright 2015 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// Note: This is a sample implementation which interacts with Djinni-generated code.
// You may need to adjust names and packages to match the settings of code
// generation in your project.
//
package com.dropbox.djinni.extension_libs.platform_threads;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import android.os.Looper;
/**
* Cross-language platform thread implementation for Android Java.
* Create a subclass and override configureThread() to customize the created threads.
*/
public class AndroidPlatformThreads extends JavaPlatformThreads {
private static Looper sMainLooper = Looper.getMainLooper();
/**
* Creates an instance.
*/
public AndroidPlatformThreads() {}
/**
* Determines whether the calling thread is the main UI thread of the
* app. Some platforms do not have a notion of a main thread, in which
* case this method returns null.
*
* This implementation returns true or false based on the Android UI's
* notion of a main thread.
*/
@Override
@Nonnull
public Boolean isMainThread() {
return Looper.myLooper() == sMainLooper;
}
}
//
// Copyright 2015 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// Note: This is a sample implementation which interacts with Djinni-generated code.
// You may need to adjust names or include paths to match the settings of code
// generation in your project.
//
#include "platform_threads.hpp"
#include "thread_func.hpp"
#include <functional>
namespace djinni {
namespace extension_libs {
namespace platform_threads {
/**
* Pure C++ implementation of PlatformThreads, using std::thread.
* Note that thread names are ignored by this class.
*
* This implementation abstracts away the type of pointers to thread objects.
* By default, use CppPlatformThreads defined below. If you use non-nullable pointers,
* you may want to instantiate CppPlatformThreadsGeneric with a different type.
*/
template <typename ThreadFuncPtr>
class CppPlatformThreadsGeneric : public PlatformThreads {
public:
/** Implementation with default thread configuration. */
CppPlatformThreadsGeneric() : m_func_on_thread_start(nullptr) {};
/**
* Implementation which will call the given function on each new thread after
* it starts, before calling the normal ThreadFunc.
*/
CppPlatformThreadsGeneric(std::function<void()> func_on_thread_start)
: m_func_on_thread_start(make_shared<std::function<void()>>(std::move(func_on_thread_start))) {}
/** Creates and starts a new thread which will call the given function. */
virtual void create_thread(const std::string & /*name*/,
const ThreadFuncPtr & func) override final {
std::thread([run_func=std::move(func), start_func=m_func_on_thread_start]() {
if (start_func && *start_func) {
(*start_func)();
}
run_func->run();
}).detach();
}
/**
* Determines whether the calling thread is the main UI thread of the
* app. Some platforms do not have a notion of a main thread, in which
* case this method returns null.
*
* This implementation returns null since C++ doesn't have any default
* notion of a main/UI thread. Platform-specific subclasses may override.
*/
virtual std::experimental::optional<bool> is_main_thread() override final {
return nullopt;
}
private:
const shared_ptr<std::function<void()>> m_func_on_thread_start;
};
/**
* Instantiation of CppPlatformThreadsGeneric with standard pointers.
*/
using CppPlatformThreads = CppPlatformThreadsGeneric<std::shared_ptr<ThreadFunc>>;
/** Standard implementation of Djinni thread_func using std::function. */
class ThreadFuncImpl final : public ThreadFunc {
public:
/**
* Implicit constructor allowing std::function<void()> to be passed
* where ThreadFunc is expected.
*/
ThreadFuncImpl(std::function<void()> func) : m_func(std::move(func)) {}
/** Will be run on thread start. The thread will exit when this returns. */
virtual void run() override final {
m_func();
}
private:
const std::function<void()> m_func;
};
} } } // namespace djinni::extension_libs::platform_threads
# Platform service providing pluggable access to thread functionality
# which may vary by platform. Some language runtimes don't interact
# well with threads they didn't create, so this interface can be used
# as a factory for threads which can freely call across languages
# without issues.
platform_threads = interface +o +j +c {
# Creates and starts a new thread which will call the given function.
create_thread(name: string, func: thread_func);
# Determines whether the calling thread is the main UI thread of the
# app. Some platforms do not have a notion of a main thread, in which
# case this method returns null.
is_main_thread(): optional<bool>;
}
# Function to call on a thread created by platform interface.
thread_func = interface +c {
# Will be run on thread start. The thread will exit when this returns.
run();
}
//
// Copyright 2015 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// Note: This is a sample implementation which interacts with Djinni-generated code.
// You may need to adjust names and packages to match the settings of code
// generation in your project. You may also need to add a catch clause if you
// configure Djinni to declare checked exceptions on generated methods.
//
package com.dropbox.djinni.extension_libs.platform_threads;
import com.dropbox.djinni.generated.PlatformThreads;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Cross-language platform thread implementation using java.thread.Thread.
* Create a subclass and override configureThread() to customize the created threads.
*/
public class JavaPlatformThreads extends PlatformThreads {
/**
* Creates an instance.
*/
public JavaPlatformThreads() {}
/** Creates and starts a new thread which will call the given function. */
@Override
public void createThread(@Nonnull String name, @Nonnull ThreadFunc func) {
final ThreadFunc passFunc = func;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
passFunc.run();
}
}, name);
thread.setDaemon(true);
configureThread(thread);
thread.start();
}
/**
* Determines whether the calling thread is the main UI thread of the
* app. Some platforms do not have a notion of a main thread, in which
* case this method returns null.
*
* This implementation returns null since Java doesn't have any default
* notion of a main/UI thread. Platform-specific subclasses may override.
*/
@Override
@CheckForNull
public Boolean isMainThread() {
return null;
}
/**
* Called after each thread is created, but before it is started.
* The default implementation does nothing, resulting in a default thread configuration.
*/
protected void configureThread(Thread thread) {}
}
//
// Copyright 2015 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// Note: This is a sample implementation which interacts with Djinni-generated code.
// You may need to adjust names or include paths to match the settings of code
// generation in your project.
//
#include "DBPlatformThreads.h"
typedef void (^ __nullable DJIObjCPlatformThreadsConfigBlock)(NSThread * __nonnull thread);
typedef void (^ __nonnull DJIThreadFuncBlock)();
/**
* Cross-language platform thread implementation using NSThread.
*/
@interface DJIObjCPlatformThreads : NSObject<DBPlatformThreads>
- (nullable instancetype)init __attribute__((unavailable("Please use factory method instead.")));
/** Creates an object which produces threads with default configuration. */
+ (nullable DJIObjCPlatformThreads *)platformThreads;
/**
* Creates an object which calls the given block on each thread after it's created,
* but before it's started. A nil block will be ignored.
*/
+ (nullable DJIObjCPlatformThreads *)
platformThreadsWithConfigBlock:(DJIObjCPlatformThreadsConfigBlock)block;
/** Creates and starts a new thread which will call the given function. */
- (void)createThread:(nonnull NSString *)name
func:(nonnull DBThreadFunc *)func;
/** Creates and starts a new thread which will call the given block. */
- (void)createThread:(nonnull NSString *)name
block:(DJIThreadFuncBlock)block;
/**
* Determines whether the calling thread is the main UI thread of the
* app. Some platforms do not have a notion of a main thread, in which
* case this method returns null.
*
* This implementation returns YES or NO depending on NSThread's notion
* of the main thread.
*/
- (nonnull NSNumber *)isMainThread;
@end
//
// Copyright 2015 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "DJIObjCPlatformThreads.h"
#include "DBThreadFunc.h"
@interface DJIObjCPlatformThreadsInternalFunc : DBThreadFunc
- (instancetype)initWithBlock:(DJIThreadFuncBlock)block;
- (void)run;
@end
@implementation DJIObjCPlatformThreadsInternalFunc {
DJIThreadFuncBlock _runBlock;
}
- (instancetype)initWithBlock:(DJIThreadFuncBlock)block {
if (self = [super init]) {
_runBlock = [block copy];
}
return self;
}
- (void)run {
_runBlock();
}
@end
@implementation DJIObjCPlatformThreads {
DJIObjCPlatformThreadsConfigBlock _cfgBlock;
}
- (id) init __attribute__((unavailable)) {
return nil;
}
- (nullable instancetype)initWithConfigBlock:(DJIObjCPlatformThreadsConfigBlock)block {
if (self = [super init]) {
_cfgBlock = [block copy];
}
return self;
}
+ (nullable DJIObjCPlatformThreads *)platformThreads {
return [DJIObjCPlatformThreads platformThreadsWithConfigBlock:nil];
}
+ (nullable DJIObjCPlatformThreads *)
platformThreadsWithConfigBlock:(DJIObjCPlatformThreadsConfigBlock)block {
return [[DJIObjCPlatformThreads alloc] initWithConfigBlock:block];
}
- (void)createThread:(nonnull NSString *)name
func:(nonnull DBThreadFunc *)func {
NSThread * thread = [[NSThread alloc] initWithTarget:func selector:@selector(run) object:nil];
thread.name = name;
if (_cfgBlock) {
_cfgBlock(thread);
}
[thread start];
}
- (void)createThread:(nonnull NSString *)name
block:(DJIThreadFuncBlock)block {
[self createThread:name func:[[DJIObjCPlatformThreadsInternalFunc alloc] initWithBlock:block]];
}
- (nonnull NSNumber *)isMainThread {
return [NSNumber numberWithBool:[NSThread isMainThread]];
}
@end
{
"targets": [
{
"target_name": "thread_cpp",
"type": "static_library",
"sources": [
"cpp/thread_impl.hpp",
],
"include_dirs": [
"cpp",
],
"direct_dependent_settings": {
"include_dirs": [
"cpp",
],
},
},
{
"target_name": "thread_objc",
"type": "static_library",
"xcode_settings": {
"CLANG_ENABLE_OBJC_ARC": "YES",
},
"sources": [
"objc/DJIThreadFactoryImpl.h",
"objc/DJIThreadFactoryImpl.m",
],
"include_dirs": [
"objc",
],
"direct_dependent_settings": {
"include_dirs": [
"objc",
],
},
},
# Java sources are in java/ and android/
# Gyp doesn't include native support for Java.
],
}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/../extension-libs/extension-libs.iml" filepath="$PROJECT_DIR$/../extension-libs/extension-libs.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/src.iml" filepath="$PROJECT_DIR$/.idea/modules/src.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/src.iml" filepath="$PROJECT_DIR$/.idea/modules/src.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/src-build.iml" filepath="$PROJECT_DIR$/.idea/modules/src-build.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/src-build.iml" filepath="$PROJECT_DIR$/.idea/modules/src-build.iml" />
<module fileurl="file://$PROJECT_DIR$/../support-lib/support-lib.iml" filepath="$PROJECT_DIR$/../support-lib/support-lib.iml" /> <module fileurl="file://$PROJECT_DIR$/../support-lib/support-lib.iml" filepath="$PROJECT_DIR$/../support-lib/support-lib.iml" />
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment