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, for example, we need to use C++ code from an Objective-C one and call C functions from Objective-C, 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 using some C++-objects in the block
Description
Below is the
code, where you try to use a variable of type std::ifstream
declared
outside the block inside the block.
__block std::ifstream file("/tmp/foo") ;
void (^block)() = ^{
file.rdbuf();
file.close();
file.open("/tmp/foo");
};
block();
If you
declare the variable with the qualifier __block
, you’ll get
the following error message (Apple LLVM 4.2(Clang)):
«main.mm:28:27: Call to implicitly-deleted copy constructor of 'std::ifstream' (aka
'basic_ifstream<char>')»
On the
latest clang (XCode5.0) you'll not get any error message. I also have submitted
a bug about it http://llvm.org/bugs/show_bug.cgi?id=17597.
The copy
constructor for std::ifstream
is not deleted in the latest version.
If without
this modifier, then the other:
«main.mm:31:9: Call to implicitly-deleted copy constructor of 'const std::ifstream' (aka
'constbasic_ifstream<char>')
main.mm:32:9: Member function 'close' not viable: 'this' argument has type 'const
std::ifstream' (aka 'constbasic_ifstream<char>'), but function is not
marked const»
GCC will
produce the similar error messages. However if you use the __block
in GCC an
internal compiler error occurs at first
«Internal compiler error: Segmentation fault: 11»
Solution
When you
deal with local variables of common Objective-C types, by default they are
passed by value into the block and cannot be changed inside it. If you try to
modify a variable inside a block you will get an error. If you put in front of
the variable __block
, then it
will be passed by reference and you can change its value within a block. __block
affects the
C++ objects in a different way.
If you use
the __block
with
C++-objects, the compiler tries to call the copy constructor for this object,
which is deleted (=delete
) in the class std::ifstream
. So from
this goes the first error.
If you do
not use __block
, the const
copy constructor should be called, which is also deleted for this class (hence
the second error). The third error is caused by the fact that you are trying to
call a non-const method close on a constant object std::ifstream
.
The solution here is to use an object through a
pointer. True for both Clang and GCC.
Problem 2. Passing an Objective-C object to a C
function under ARC
Description
Assume that
you want to pass some Objective-C object into the C function as a parameter of
type void*
, for example, into sqlite3_exec
.
NSMutableArray *rowsArray = [[NSMutableArray alloc] init];
sqlite3_exec(database, "select distinct category from persons", callback, rowsArray, NULL);
When ARC is
on you will get a compilation error
«main.m:36:48: Implicit conversion of Objective-C pointer type ' NSMutableArray *' to C pointer type 'void *' requires a bridged cast»
ARC will not
allow so easy 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):
sqlite3_exec(database, "select distinct category from persons", callback, (__bridge void*)rowsArray, NULL);
This is an
equivalent to the first code, but now, as you are in ARC mode, there is an
additional caution. The point is that the ARC can free rowsArray
, if it is
the last reference to it. Unfortunately, sqlite3_exec
does not
know anything about the objects, so will do not retain the argument. By the
time the function is called, the object can be removed already. The right
solution is the following:
sqlite3_exec(database, "select distinct category from persons", callback,
(__bridge_retained void*)rowsArray, NULL);
This will
call <span lang="EN-US">objc_retain</span><span lang="EN-US">()</span>
before passing the argument.
On the other
hand, the callback function should look something like this:
static int myCallback(void* ptr, int i, char** p1, char** p2) {
NSMutableArray* rowsArray = (__bridge_transfer NSMutableArray *)ptr;
ptr = NULL;
return 0;
}
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 scope. The line ptr = NULL
is optional, but it is a good
style. You have placed a reference that the ptr
contained,
under the control of ARC, so reset of the pointer is clear.
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, because only Clang supports ARC.
Problem 3. Creating Objective-C properties returning C++-objects in Objective-C classes
Description
Suppose, we have an Objective-C class:
@interface MyObjc : NSObject
{
}
@end
And imagine that we have some simple C++ class named MyCppClass. We would like to add some property to the Objective-C class that returns the object of type MyCppClass.
Solution
Now you can do such thing but only with C++ object variables, that have default constructors. Both Clang and GCC support such case.
Starting
with Mac OS X 10.4 and GCC 4 you can place C++ objects as instance member variables
in Objective-C classes assuming that the C++ object must be created with
default constructor. GCC compiler has a special compiler option that controls the process of calling the default constructor for this variables «Call
C++ Default Ctors / Dtors in Objective-C» (GCC_OBJC_CALL_CXX_CDTORS,
-fobjc-call-cxx-cdtors). Though
Clang has no special option for C++ constructors/destructors it generates correct
calls for C++ constructors.
For example, we can write the following code
class MyCppClass
{
MyCppClass();
};
@interface MyObjc : NSObject
{
MyCppClass cppVariable;
}
@property (nonatomic, assign) MyCppClass cppVariable;
@end
and at implementation file, @synthesize cppVariable;
as well.
Conclusion
It is clear that an interoperation of a 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.