flutter / website

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

Fix Step 2 for 'Binding to native iOS code using dart:ffi' page #3856

Open gaaclarke opened 4 years ago

gaaclarke commented 4 years ago

relevant page: https://docs.flutter.dev/platform-integration/ios/c-interop

1) Linking instructions: The page reads:

On iOS, you need to tell Xcode to statically link the file:

In Xcode, open Runner.xcworkspace.
Add the C/C++/Objective-C/Swift source files to the Xcode project.

You don't need to do that because the plugin is using CocoaPods, placing the cpp files in the correct directory will cause it to get linked.

2) Linker stripping If you follow the instruction you get an error at runtime because DynamicLibrary won't be able to find the "native_add" symbol. That is because it is getting stripped out at linking time, example:

aaclarke-macbookpro2:Debug-iphonesimulator aaclarke$ nm MessagingTiming/libMessagingTiming.a | grep native_add
MessagingTiming/libMessagingTiming.a(native_add.o):
0000000000000000 T _native_add
aaclarke-macbookpro2:Debug-iphonesimulator aaclarke$ nm Runner.app/Runner | grep native_add
aaclarke-macbookpro2:Debug-iphonesimulator aaclarke$ 
gaaclarke commented 4 years ago

According to ARM documentation __attribute__((unused)) is suppose to show up in a different place than we have. It doesn't seem to be respected on iOS from what i'm seeing.

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/BABCJJID.html

gaaclarke commented 4 years ago

I was only able to avoid the stripping of the symbol by doing the following:

@implementation MessagingTimingPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
  typedef int32_t (*native_add_t)(int32_t, int32_t);
  static volatile native_add_t native_add_var = &native_add;
  ...
}

As suggested by: https://stackoverflow.com/a/35326515

There has to be a better way =(

sfshaza2 commented 4 years ago

@gaaclarke, for which release of the SDK is this info valid? Thx

gaaclarke commented 4 years ago
aaclarke-macbookpro2:example aaclarke$ flutter doctor -v
[✓] Flutter (Channel unknown, v1.16.3-pre.24, on Mac OS X 10.14.6 18G2022, locale en-US)
    • Flutter version 1.16.3-pre.24 at /Users/aaclarke/dev/flutter
    • Framework revision 7f84dd0c60 (23 hours ago), 2020-03-25 12:56:51 -0700
    • Engine revision 2d42c74a34
    • Dart version 2.8.0 (build 2.8.0-dev.16.0 ba8baa46b4)

I suspect this is an issue for any version that supports FFI.

gaaclarke commented 4 years ago

I was able to fix the build by adding -force_load $(BUILT_PRODUCTS_DIR)/MessagingTiming/libMessagingTiming.a. I gotta find a way to automate adding this to users of the pod.

gaaclarke commented 4 years ago

I figured it out, we can add the linker flag to the podspec like so:

--- a/ios/MessagingTiming.podspec
+++ b/ios/MessagingTiming.podspec
@@ -20,4 +20,5 @@ A new flutter plugin project.

   # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.
   s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
+  s.user_target_xcconfig = { 'OTHER_LDFLAGS' => "-force_load $(BUILT_PRODUCTS_DIR)/#{s.name}/lib#{s.name}.a" }
 end
gaaclarke commented 4 years ago

@jmagman has pointed out that $(BUILT_PRODUCTS_DIR)/#{s.name}/lib#{s.name}.a won't work if the Podfile is set to use frameworks with use_frameworks!. The template doesn't have that but we might want to call it out.

jmagman commented 4 years ago

@jmagman has pointed out that $(BUILT_PRODUCTS_DIR)/#{s.name}/lib#{s.name}.a won't work if the Podfile is set to use frameworks with use_frameworks!. The template doesn't have that but we might want to call it out.

The generated app template for macOS and iOS Swift uses use_frameworks! and iOS Objective-C doesn't.

gaaclarke commented 4 years ago

I chatted with @jmagman a bit. There doesn't appear to be a one size fits all solution we can point people to.

If they are using static libraries, this path will work. If it is a static framework it will have to be tweaked a bit. If they are using dynamic frameworks, the linker flag isn't needed at all.

That logic can't be encoded into the podspec unfortunately. We should just have a note section explaining what to do if your symbol is getting stripped out.

sfshaza2 commented 4 years ago

Thanks so much for all of your research, @gaaclarke!

gaaclarke commented 4 years ago

The trick with the -force_load flag doesn't work on release builds because the symbol gets stripped out at the end.

fzyzcjy commented 4 years ago

Hi, actually, I cannot find the Runner.xcworkspace file in the plugin... How can I fix that? Thanks!

What I have done: execute flutter create --template=plugin mypackage. Then ls shows:

CHANGELOG.md     android          lib              test
LICENSE          example          pubspec.lock     mypackage.iml
README.md        ios              pubspec.yaml

On iOS, you need to tell Xcode to statically link the file:

In Xcode, open Runner.xcworkspace. Add the C/C++/Objective-C/Swift source files to the Xcode project.

jmagman commented 4 years ago

@fzyzcjy Plugins don't generate Xcode projects (other than the example app, which doesn't get shipped with your plugin). The apps that use your plugin will have an Xcode project. @gaaclarke's ios/YourPlugin.podspec comment is more relevant for you: https://github.com/flutter/website/issues/3856#issuecomment-604665614

fzyzcjy commented 4 years ago

@jmagman Thanks so much!