Introduction
This article overviews several complicated problems we can face with interoperation between C, Objective-C, C++ and Objective-C++ code. All the code samples were tested on both compilers from XCode 4.6.2 (Apple LLVM 4.2 (Clang) and LLVM GCC 4.2) and also on the latest XCode 5.0. Since the XCode 5.0 does not contain the GCC compiler anymore, so the examples were checked only on its Apple LLVM 5.0 (Clang). If the results are different for the latest Clang, I specify it explicitly.
Background
In many cases, we need to use C++ code from Objective-C one and call C functions from Objective-C, for example, so the article is about it. You also should know what an Objective-C++ is. It is a method for mixing Objective-C and C++ code in the same file and for their interoperation. You can definitely use it in XCode - name your files with the .mm extension.
Problem 1. Error when Assigning Blocks that Return C++-objects
Description
Let's say we have some simple Objective-C class hierarchy.
@interface Vegetable : NSObject
@end
@interface Carrot : Vegetable
@end
We can then write something like this:
Vegetable *(^getVegetable)();
Carrot *(^getCarrot)();
getVegetable = getCarrot;
This means that in respect of Objective-C classes, the blocks are covariant in the return value: a block that returns something more specific, can be considered as a "subclass" of the block that returns something more general. Here the block getCarrot
, which returns a Carrot
, can be safely assigned to the block getVegetable
. Indeed, it is always safe to take Carrot *
, when you expect Vegetable *
. And, it is logical that the reverse does not work: getCarrot = getVegetable;
does not compile, because when we really want a carrot, we do not really like to get a vegetable.
You can also write this:
void (^eatVegetable)(Vegetable *);
void (^eatCarrot)(Carrot *);
eatCarrot = eatVegetable;
This shows that blocks are contravariant in arguments: a block which can handle more general argument type may be used where we need a block that handles a more specific argument. If the block knows how to eat a vegetable, it will know how to eat a carrot too. Again, the converse is not true and will not compile: eatVegetable = eatCarrot;
It's all so in Objective-C. Now try to do something similar in C++ and Objective-C++.
class VegetableCpp {};
class CarrotCpp : public VegetableCpp {};
And then the following lines of code will not compile:
VegetableCpp *(^getVegetableCpp)();
CarrotCpp *(^getCarrotCpp)();
getVegetableCpp = getCarrotCpp;
void (^eatVegetableCpp)(VegetableCpp *);
void (^eatCarrotCpp)(CarrotCpp *);
eatCarrotCpp = eatVegetableCpp;
Clang produces the following errors:
«Main.mm: 28:17: Assigning to 'VegetableCpp * (^) ()' from incompatible type 'CarrotCpp * (^) ()'
main.mm: 32:17: Assigning to 'void (^) (CarrotCpp *)' from incompatible type 'void (^) (VegetableCpp *)' »
GCC produces the similar errors:
«Main.mm: 28: Cannot convert 'CarrotCpp * (^) ()' to 'VegetableCpp * (^) ()' in assignment
main.mm: 32: Cannot convert 'void (^) (VegetableCpp *)' to 'void (^) (CarrotCpp *)' in assignment
main.mm: 28:17: Assigning to 'VegetableCpp * (^) ()' from incompatible type 'CarrotCpp * (^) ()'
main.mm: 32:17: Assigning to 'void (^) (CarrotCpp *)' from incompatible type 'void (^) (VegetableCpp *)' »
Solution
This problem has no solution, and it is not expected to appear in the future. A bug on this topic on the compiler Clang was submitted here though. As I understood, this will not be implemented. This distinction is intentional, the reason is the difference between object models in Objective-C and C++. More specifically: having a pointer to the Objective-C object, one can convert it to a pointer to the object of the base class or a derived class without actually changing the value of the pointer: the address of the object remains the same.
Since C++ allows multiple and virtual inheritance, all this is not for C++ objects: if I have a pointer to a C++ class, and I convert it to a pointer to the object of the base class or a derived class, I may need to adjust the value of the pointer.
Consider this example:
class A { int _a; }
class B { int _b; }
class C : public A, public B { }
B *getC() {
C *cObj = new C;
return cObj;
}
For example, a new object of class C
in the method getC()
has been allocated at the address 0x10. The value of the pointer cObj
is 0x10. In the return
statement, that pointer to <code>C
should be adjusted so that it points to the part B
within C
. Since B
follows A
in the inheritance list of class C
, then (usually) in memory B
will follow A
, which means adding a 4-byte offset (== sizeof (A)) to the pointer, so the returned pointer is 0x14. Similarly, the conversion of B*
to C*
would lead to the subtraction of 4 bytes from the pointer. When we are dealing with virtual base classes, the idea is the same, but the offsets are unknown.
Now, consider what effect all this has on such an assignment:
C* (^getC)();
B* (^getB)();
getB = getC;
Block getC
returns a pointer to C
. To turn it into a block that returns a pointer to B
, we would have to adjust the pointer returned in each block call by adding 4 bytes. This is not the adjustment for the block, it is the adjustment of the pointer value, that the block returns. It would be possible to implement this by creating a new block that wraps the previous block and makes corrections, for example:
getB = B* (^thunk)() { return getC(); }
This is achievable for the compiler, which already provides a similar "trick" when you override the virtual function which returns the covariant type and needs adjustment. However, in the case of blocks it causes additional problems: the equality comparison operation ==
is allowed for blocks, therefore, to determine, "getB == getC
", we should be able to look through this wrapper, which would have been generated by assigning "getB = getC
" to compare the underlying pointers returned by the blocks. Again, this is achievable, but would require much more heavy runtime for the blocks that is able to create unique wrappers that can perform these adjustments of the return value (the same as for any contravariant parameters). While all of this is technically possible, the cost (in the size of runtime, complexity and execution time) outweighs the gains.
Going back to the Objective-C, I note that the model of single inheritance will never require any adjustments to the pointer to the object: there is only one address in the Objective-C object, and the type of static pointer is not important, so the covariance/contravariance will never require any wraps, and a block assignment is just a pointer assignment (+ _Block_copy
/ _Block_release
when using ARC).
Problem 2. Errors when Passing an Objective-C Object to a C Function in ARC Mode
Description
Assume that we want to pass some Objective-C object to the C function as a parameter of type void*
, for example, into pthread_create
.
pthread_t thread;
NSObject *obj = [[NSObject alloc]init];
pthread_create(&thread, NULL, startThread, obj);
When ARC is on, we will get a compilation error:
«main.m:36:48: Implicit conversion of Objective-C pointer type 'NSObject *' to C pointer type 'void *' requires a bridged cast»
ARC will not allow so easily to make such a conversion.
Solution
You need to use the bridged cast to move between the two memory models. The simplest form of such cast is (the XCode itself offers it, if you hover over the error in the code):
pthread_create(&thread, NULL, startThread, (__bridge void*)obj);
This is an equivalent to the first code, but now, as we are in ARC mode, there is an additional caution. The point is that the ARC will consider that can free obj
, if it is the last reference to it. Unfortunately, pthread_create
does not know anything about the objects, so will not retain the argument. By the time the thread is started, the object can be removed already. The right solution is the following:
pthread_create(&thread, NULL, startThread, (__bridge_retained void*)obj);
This will call objc_retain()
before passing the argument.
On the other hand, the thread function should look something like this:
void* startThread(void *arg)
{
id anObject = (__bridge_transfer id)arg;
arg = NULL;
}
This will put the object back under the control of ARC. This form of bridge cast expects that the object was already retained, and thus will free it (call release
) at the end of the block. The line arg = NULL
is optional, but it is a good style. You have placed a reference that the arg
contained, under the control of ARC, so reset of the pointer is obvious.
NOTE
Here is what the documentation for Automatic Reference Counting of Clang says:
«A bridged cast is a C-style cast annotated with one of three keywords:
- (
__bridge T
) op
casts the operand to the destination type T
. If T
is a retainable object pointer type, then op
must have a non-retainable pointer type. If T
is a non-retainable pointer type, then op
must have a retainable object pointer type. Otherwise the cast is ill-formed. There is no transfer of ownership, and ARC inserts no retain operations. - (
__bridge_retained T
) op
casts the operand, which must have retainable object pointer type, to the destination type, which must be a non-retainable pointer type. ARC retains the value, subject to the usual optimizations on local values, and the recipient is responsible for balancing that +1. - (
__bridge_transfer T
) op
casts the operand, which must have non-retainable pointer type, to the destination type, which must be a retainable object pointer type. ARC will release the value at the end of the enclosing full-expression, subject to the usual optimizations on local values.
These casts are required in order to transfer objects in and out of ARC control; see the rationale in the section on conversion of retainable object pointers.
Using a __bridge_retained
or __bridge_transfer
cast purely to convince ARC to emit an unbalanced retain or release, respectively, is poor form.»
Valid only for Clang.
Problem 3. How to Call the Inline C-function from Objective-C Code
Description
Let's say we have the files Foo.h and Foo.c.
File Foo.h
inline void foo();
File Foo.c
inline void foo()
{
printf("Hello, World\n");
}
A main.m file is as follows:
#import <Foundation/Foundation.h>
#include "Foo.h"
int main(int argc, const char * argv[])
{
foo();
NSLog(@"Hello, World!");
return 0;
}
Then we will get a linker error which states that the symbol foo is not found.
This problem has different solutions. C99 offers this approach: it requires from the programmer to have the module that contains a callable copy of the extern inline function. By default, in C99 inline functions without any storage specifier are considered extern. If all the declarations of inline function in a module do not have a storage specifier, then the function is considered as extern inline, and the body will not be generated for it. On the other hand, if one of the declarations for inline function in the module explicitly contains a keyword extern
, then exactly this module will generate a callable copy for the function. This leads to the following organization of the source files.
Solution
Insert the definition of extern inline function in the header file without using the keyword extern
. This header can be included in as many modules as you want to. In only one single module, you should include this header and declare the function prototype using extern
, to get the callable copies of the function (in this module it is not necessarily to repeat the keyword inline
).
The example will change as follows:
File Foo.h
inline void foo()
{
printf("Hello, World\n");
}
File Foo.c
extern inline void foo();
The file main.m remains the same.
And the problem is fixed. There is another solution - you can switch the C dialect option in the settings of the environment to GNU 89 without modifying the source files. True for both compilers. With the latest clang (XCode 5.0) this solution does not work. You need to place static
before the function. I have submitted a bug for this here, but I could not get any information on this issue except that we need to place static
.
Conclusion
It is clear that the interoperation of the code in different languages is not so simple, but unfortunately sometimes necessary. I would like to think that my article will be interesting, useful and will help many people avoid making similar mistakes.
History
- 05.12.2013: Initial version