Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 1 | # Mixing C++ and Objective-C |
| 2 | |
Avi Drissman | 2ce2256 | 2023-07-26 02:59:42 | [diff] [blame] | 3 | The Mac is in an unusual position of having most of its relevant UI APIs be in a |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 4 | different language than the one used for its core code. Navigating boundaries |
| 5 | between C++ and Objective-C can be tricky. |
| 6 | |
| 7 | ## Use Objective-C++ |
| 8 | |
| 9 | Using Objective-C++ works well for Mac-only implementation files. If a file is |
| 10 | named with a `.mm` extension, then it will be compiled as an Objective-C++ file. |
| 11 | Within such a file usage of Objective-C and C++ can be intermixed. |
| 12 | |
| 13 | If Objective-C++ works in the context needed, it is the preferred way to |
| 14 | accomplish mixing of C++ and Objective-C. |
| 15 | |
| 16 | ## Use the pimpl idiom |
| 17 | |
| 18 | The [pimpl idiom](https://en.wikipedia.org/wiki/Opaque_pointer#C++) is a |
| 19 | standard way to hide the implementation of a C++ class from its users, exposing |
| 20 | nothing but an implementation pointer in the header file. Usually it is used for |
Avi Drissman | 2ce2256 | 2023-07-26 02:59:42 | [diff] [blame] | 21 | compatibility (e.g. hiding implementation details), but it’s useful in Chromium |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 22 | for hiding the Objective-C implementation details in the `.mm` implementation |
| 23 | file and removing them from the `.h` file which might need to be included in a |
| 24 | different `.cc` implementation file and which thus cannot have any Objective-C |
| 25 | in it, even in a `private:` block. |
| 26 | |
| 27 | The standard boilerplate for doing this is named |
| 28 | [`ObjCStorage`](https://source.chromium.org/search?q=ObjCStorage&ss=chromium). |
| 29 | |
| 30 | In the header file, a nested struct is forward-declared for use by a |
| 31 | `std::unique_ptr`: |
| 32 | |
Avi Drissman | 663fce10 | 2023-04-19 14:58:03 | [diff] [blame] | 33 | ```cpp |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 34 | class UtilityObjectMac { |
| 35 | // ... |
| 36 | private: |
| 37 | struct ObjCStorage; |
| 38 | std::unique_ptr<ObjCStorage> objc_storage_; |
| 39 | }; |
| 40 | ``` |
| 41 | |
| 42 | and in the implementation `.mm` file, have that nested class host all the Obj-C |
| 43 | instance variables: |
| 44 | |
Avi Drissman | 663fce10 | 2023-04-19 14:58:03 | [diff] [blame] | 45 | ```cpp |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 46 | struct UtilityObjectMac::ObjCStorage { |
Avi Drissman | 0d6b43c | 2023-05-09 16:11:43 | [diff] [blame] | 47 | id appkit_token; |
| 48 | NSWindow* window; |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 49 | }; |
| 50 | |
| 51 | UtilityObjectMac::UtilityObjectMac() |
| 52 | : objc_storage_(std::make_unique<ObjCStorage>()) { |
Avi Drissman | 0d6b43c | 2023-05-09 16:11:43 | [diff] [blame] | 53 | objc_storage_->appkit_token = [NSFramework registerTheThing: //... |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 54 | // ... |
| 55 | ``` |
| 56 | |
| 57 | This moves all of the Objective-C code into an Objective-C++ file at the cost of |
| 58 | a secondary allocation and indirection on use. |
| 59 | |
Avi Drissman | 2ce2256 | 2023-07-26 02:59:42 | [diff] [blame] | 60 | ## Use `/base/apple/owned_objc.h` types |
| 61 | |
| 62 | It is, unfortunately, a common pattern in Chromium code to use macros and |
| 63 | typedefs to declare a platform-neutral name for a core platform UI type (e.g. |
| 64 | `ui/gfx/native_widget_types.h`’s `ui::NativeView`, |
| 65 | `ui/events/platform_event.h`’s `ui::PlatformEvent`) and then for pure C++ code |
| 66 | to pass those types around. For those cases, where the previous two approaches |
| 67 | can’t be used, the wrappers in `/base/apple/owned_objc.h` can be used. |
| 68 | |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 69 | ## Double-declaration (dangerous) |
| 70 | |
Avi Drissman | 2ce2256 | 2023-07-26 02:59:42 | [diff] [blame] | 71 | If none of the previous techniques will work, a double-declaration can be used. |
| 72 | An example can be seen in |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 73 | [native_widget_types.h](https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/native_widget_types.h): |
| 74 | |
Avi Drissman | 663fce10 | 2023-04-19 14:58:03 | [diff] [blame] | 75 | ```objectivec |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 76 | #ifdef __OBJC__ |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 77 | @class NSImage; |
| 78 | @class NSView; |
| 79 | @class NSWindow; |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 80 | #else |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 81 | class NSImage; |
| 82 | class NSView; |
| 83 | class NSWindow; |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 84 | #endif // __OBJC__ |
| 85 | ``` |
| 86 | |
| 87 | With this double-declaration, these types can be used from both C++ and |
| 88 | Objective-C. However, the price that is paid is that this is a (mostly) benign |
| 89 | violation of the [ODR](https://en.wikipedia.org/wiki/One_Definition_Rule) and |
| 90 | thus should be avoided if possible. |
| 91 | |
| 92 | Specifically, this can get dangerous with Objective-C ARC enabled, where a |
| 93 | pointer to a type declared this way will be treated by C++ as a raw pointer |
| 94 | while it will be treated by Objective-C as a smart pointer with retain/release |
| 95 | semantics. |
| 96 | |
Avi Drissman | 2ce2256 | 2023-07-26 02:59:42 | [diff] [blame] | 97 | Because of Chromium’s history as a non-ARC app, the approach of using |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 98 | double-declarations was found to be more acceptable of a tradeoff than it is |
| 99 | nowadays, so there is a lot of double-declaration. Revising code to remove |
Avi Drissman | 2ce2256 | 2023-07-26 02:59:42 | [diff] [blame] | 100 | double-declaration improves the code; please do so when it makes sense. There is |
| 101 | a [bug](https://crbug.com/1433041) tracking this effort of eventual removal. |
Avi Drissman | e3fb6dd | 2023-04-12 22:51:47 | [diff] [blame] | 102 | |
| 103 | Do not include `<objc/objc.h>`. It has all the pitfalls of double-declaration |
| 104 | for the `id` type (note that even though it defines `id` as `struct |
| 105 | objc_object*`, the Objective-C compiler does not see them as equivalent), but |
| 106 | has the additional pitfall of defining away the ARC ownership annotations if not |
| 107 | compiling with Objective-C ARC. The inclusion of it is therefore banned, as it |
| 108 | causes conflicts if included in header files, and while C++ implementation files |
| 109 | should not be involving themselves with Objective-C types anyway, Objective-C |
| 110 | implementation files implicitly have it included through their inclusion of |
| 111 | framework headers. |