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