Skip to content

[jnigen] Use varargs for dartonly method calls #1090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkgs/jni/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.8.0-wip"
version: "0.8.0"
js:
dependency: transitive
description:
Expand Down
4 changes: 4 additions & 0 deletions pkgs/jni/ffigen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ functions:
- 'GetJniContextPtr'
- 'setJniGetters'
- 'jni_log'
# Exclude functions with VarArgs, jnigen will generate them based on the
# exact arguments needed.
- 'globalEnv_NewObject'
- 'globalEnv_Call(Static|Nonvirtual|)[A-Z][a-z]+Method'
# Inline functions
# keep-sorted start
- 'acquire_lock'
Expand Down
3 changes: 3 additions & 0 deletions pkgs/jni/lib/src/jni.dart
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ extension ProtectedJniExtensions on Jni {
Jni._ensureInitialized();
Jni._bindings.deleteFinalizableHandle(finalizableHandle, object);
}

static Pointer<T> Function<T extends NativeType>(String) get lookup =>
Jni._dylib.lookup;
}

extension AdditionalEnvMethods on GlobalJniEnv {
Expand Down
331 changes: 246 additions & 85 deletions pkgs/jni/src/third_party/global_jni_env.c

Large diffs are not rendered by default.

184 changes: 153 additions & 31 deletions pkgs/jni/src/third_party/global_jni_env.h

Large diffs are not rendered by default.

75 changes: 56 additions & 19 deletions pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still want to generate global_jni_env.c which does:

call jni method -> check for exceptions -> convert to global ref -> return as JniResult. Now doing the same, but for functions with varargs.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack!

Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ const wrapperGetterDecl = '''
FFI_PLUGIN_EXPORT $wrapperName* GetGlobalEnv();
''';

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I can only add comments on modified lines.)

(And unrelated to this PR.)

Can we add to the preamble in generated files which script was run to generate the file?
Searching verbatim in the repository for a line from the preamble also works but is a a bit too dective style.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do in a following PR.

bool hasVarArgs(String name) {
return name == 'NewObject' ||
RegExp(r'^Call(Static|Nonvirtual|)[A-Z][a-z]+Method$').hasMatch(name);
}

/// Get C name of a type from its ffigen representation.
String getCType(Type type) {
if (type is PointerType) {
Expand All @@ -81,19 +86,24 @@ FunctionType getGlobalJniEnvFunctionType(FunctionType ft) {
}

// Returns declaration of function field in GlobalJniEnv struct
String getFunctionFieldDecl(
Member field,
) {
String getFunctionFieldDecl(Member field, {required bool isField}) {
final fieldType = field.type;
if (fieldType is PointerType && fieldType.child is NativeFunc) {
final nativeFunc = fieldType.child as NativeFunc;
final functionType = getGlobalJniEnvFunctionType(nativeFunc.type);
final resultWrapper = getResultWrapper(getCType(functionType.returnType));
final name = field.name;
final withVarArgs = hasVarArgs(name);
final params = functionType.parameters
.map((param) => '${getCType(param.type)} ${param.name}')
.join(', ');
return ('${resultWrapper.returnType} (*$name)($params);');
.map((param) => '${getCType(param.type)} ${param.name}')
.join(', ') +
(withVarArgs ? ', ...' : '');
final willExport = withVarArgs ? 'FFI_PLUGIN_EXPORT ' : '';
if (isField) {
return '${resultWrapper.returnType} (*$name)($params);';
}
return '$willExport${resultWrapper.returnType} '
'${getWrapperFuncName(field)}($params);';
} else {
return 'void* ${field.name};';
}
Expand Down Expand Up @@ -222,32 +232,50 @@ String? getWrapperFunc(Member field) {
final outerFunctionType = getGlobalJniEnvFunctionType(functionType);
final wrapperName = getWrapperFuncName(field);
final returnType = getCType(outerFunctionType.returnType);
final params = outerFunctionType.parameters
.map((param) => '${getCType(param.type)} ${param.name}')
.join(', ');
final withVarArgs = hasVarArgs(field.name);
final params = [
...outerFunctionType.parameters
.map((param) => '${getCType(param.type)} ${param.name}'),
if (withVarArgs) '...',
].join(', ');
var returnCapture = returnType == 'void' ? '' : '$returnType $resultVar =';
if (constBufferReturningFunctions.contains(field.name)) {
returnCapture = 'const $returnCapture';
}
final callParams = [
'jniEnv',
...(outerFunctionType.parameters.map((param) => param.name).toList())
...(outerFunctionType.parameters.map((param) => param.name).toList()),
if (withVarArgs) 'args',
].join(', ');
final resultWrapper = getResultWrapper(returnType);

var convertRef = '';
if (isJRefType(returnType) && !refFunctions.contains(field.name)) {
convertRef = ' $resultVar = to_global_ref($resultVar);\n';
}
final callee = field.name + (withVarArgs ? 'V' : '');
final varArgsInit = withVarArgs
? '''
va_list args;
va_start(args, methodID);
'''
: '';
final varArgsEnd = withVarArgs ? 'va_end(args);\n' : '';
final exceptionCheck = _noCheckException.contains(field.name)
? ''
: ' jthrowable $errorVar = check_exception();\n'
' if ($errorVar != NULL) {\n'
' return ${resultWrapper.onError};\n'
' }\n';
return '${resultWrapper.returnType} $wrapperName($params) {\n'
: '''
jthrowable $errorVar = check_exception();
if ($errorVar != NULL) {
return ${resultWrapper.onError};
}
''';
final willExport = withVarArgs ? 'FFI_PLUGIN_EXPORT ' : '';
return '$willExport'
'${resultWrapper.returnType} $wrapperName($params) {\n'
' attach_thread();\n'
' $returnCapture (*jniEnv)->${field.name}($callParams);\n'
'$varArgsInit'
' $returnCapture (*jniEnv)->$callee($callParams);\n'
'$varArgsEnd'
'$exceptionCheck'
'$convertRef'
' return ${resultWrapper.onResult};\n'
Expand All @@ -259,11 +287,20 @@ String? getWrapperFunc(Member field) {
void writeGlobalJniEnvWrapper(Library library) {
final jniEnvType = findCompound(library, envType);

final fieldDecls = jniEnvType.members.map(getFunctionFieldDecl).join('\n');
final fieldDecls = jniEnvType.members
.map((member) => getFunctionFieldDecl(member, isField: true))
.join('\n');
final varArgsFunctions = jniEnvType.members
.where((member) => hasVarArgs(member.name))
.map((member) => getFunctionFieldDecl(member, isField: false))
.join('\n');
final structDecl =
'typedef struct $wrapperName {\n$fieldDecls\n} $wrapperName;\n';
File.fromUri(Paths.globalJniEnvH).writeAsStringSync(
'$preamble$wrapperDeclIncludes$structDecl$wrapperGetterDecl');
File.fromUri(Paths.globalJniEnvH).writeAsStringSync('$preamble'
'$wrapperDeclIncludes'
'$structDecl'
'$wrapperGetterDecl'
'$varArgsFunctions\n');

final functionWrappers = StringBuffer();
final structInst = StringBuffer('$wrapperName globalJniEnv = {\n');
Expand Down
Loading
Loading