flutter / website

Flutter documentation web site
https://docs.flutter.dev
Other
2.82k stars 3.22k forks source link

Update description of Static vs Dynamic LIbraries on 'Binding to native code using dart:ffi' pages #5334

Open Timmmm opened 3 years ago

Timmmm commented 3 years ago

Page URLs

Page source

https://github.com/flutter/website/tree/master/src/docs/development/platform-integration/c-interop.md

Description of issue

The introduction isn't very clear about static vs dynamic linking. It explains what they are - fine, I expect most people reading it already know - but then it just jumps into dynamic linking on Android and static linking on iOS with no explanation as to why.

I presume that dynamic linking is forbidden on iOS and static linking doesn't work on Android? Or something like that? I would be good to explain that.

sfshaza2 commented 3 years ago

Thoughts, @mit-mit?

mit-mit commented 3 years ago

Yes, I agree that this page would benefit from some elaboration. cc @dcharkes

dcharkes commented 3 years ago

Similar issue https://github.com/flutter/flutter/issues/75537.

I presume that dynamic linking is forbidden on iOS and static linking doesn't work on Android? Or something like that? I would be good to explain that.

We have not explored the non-documented ways. They might be possible. If they are, PRs are welcome to update the documentation.

We should update the page to reflect this. @sfshaza2 @mit-mit any suggestions for wording?

sfshaza2 commented 3 years ago

@chinmaygarde, do you have any insight into what we could say about this distinction?

chinmaygarde commented 3 years ago

There is no magic in the way symbol resolution works for Dart FFI. The symbols need to be resolved from a library using dlsym. dlsym needs a library handle. This handle is obtained by using dlopen with a specified path to a dynamic library. If symbol is already present in the current global scope, RTLD_DEFAULT can be used instead.

In Dart code, DynamicLibrary.open() is used to obtain the handle from a path and DyanmicLibrary.process() for the RTLD_DEFAULT handle.

The specific guidance for iOS and Android differs because of the way the applications are packaged.

On iOS, there is an executable that needs to be built already and it is generally easier to link static libraries into this executable. Once in the executable, DynamicLibrary.process() will work fine. However, there is nothing preventing the user from resolving the symbols from a dynamic library. I created a sample project that demonstrates this https://github.com/chinmaygarde/flutter_dylinkios_sample. Notice that in the Build Phases, I explicitly specified that the library be linked. This way, the symbols from that library are available in the global scope and I can use DynamicLibrary.process. But, I didn't need to do that. I could have skipped that step and loaded the library myself at runtime using DynamicLibrary.open() with the path returned by [NSBundle exectuablePath] for the framework bundle. It would have achieved the same thing. Using static libraries on iOS avoids having to remember to link the library and make sure to embed it in the final bundle, or, figure out some way of finding where the dynamic library exists at runtime. But, as demonstrated, it is supported and works fine. Screen Shot 2021-03-15 at 12 15 58 PM

Now, on Android, there no executable generated (the thing that contains the main entrypoint). So, we have to rely on dynamic libraries only on Android.

Hope this helps clarify things.

sfshaza2 commented 3 years ago

Wow, @chinmaygarde, thanks for that detailed answer!

Timmmm commented 3 years ago

Ah that makes sense. Although isn't Flutter AoT compiled now on Android? Does that not give a binary you can statically link into? I guess maybe it's just too much of a pain to do, and it wouldn't work with hot reload which would kind of suck.

chinmaygarde commented 3 years ago

Right, it would be limited to specific modes. Also, the default Flutter workflow for Android doesn't require a native toolchain. So everyone would have to download the NDK or some other toolchain and have that versioned and updated. The assumption is that if you are writing native code, you already have a build system and toolchain in place for it that can generate a dynamic library.