Introduction
Using C functions from Objective-C is very easy but going the other way isn’t so easy. Especially with ARC which can destroy the object out from under you because C code is outside of ARC. With ARC Objective-C objects are no longer allowed in C structs for this very reason.
Here is a situation I ran into where I needed to use Objective-C objects from within C. At work we have a library that was ported to iOS. The library needed to use a part of the OS that is only accessible by Objective-C API calls. Since this library is multi platform we have an OS independent API that we have OS specific implementations. So our C library needed to use Objective-C objects to implement the OS specific functionality required. Porting the library from C to Objective-C isn’t an option.
Wrapping Objective-C functions in C
The solution is to wrap Objective-C objects in C functions. The C functions will still be in a .m file and can still be compiled directly into a C app (or library). This just works on OS X (cross compiling for iOS or not) without needing to do anything special.
Simple Example
This wraps NSLog
in C functions so it can be called from C.
logger.h
#ifndef __LOGGER_H__
#define __LOGGER_H__
void do_objc_log(const char *msg);
void do_objc_log_fmt(const char *fmt, ...);
#endif /* __LOGGER_H__ */
logger.m
#include "logger.h"
#import <Foundation/Foundation.h>
void do_objc_log(const char *msg)
{
if (msg == NULL || *msg == '\0')
return;
NSLog(@"%@", [NSString stringWithUTF8String:msg]);
}
void do_objc_log_fmt(const char *fmt, ...)
{
va_list args;
if (fmt == NULL)
return;
va_start(args, fmt);
NSLogv([NSString stringWithUTF8String:fmt], args);
va_end(args);
}
This is a very simple example that only wraps a single function and not complex objects.
Wrapping Objective-C objects in C
Lets look at how we can create an object in C before we get into putting Objective-C objects in them. Basically you have struct that holds data and functions that take the struct to operate on the data.
In this implementation we’d need to put the Objective-C object in the struct.
However, ARC doesn’t allow this because it can’t track objects in C structs.
That’s not entirely true, we can technically do this by using
__unsafe_unretained
but this is a bad idea. As the attribute states this is
unsafe because the struct won’t retain the object. ARC will destroy the object
once it thinks it’s no longer in use. ARC doesn’t know that the object is
“owned” at this point by the struct.
The solution is to use CFTypeRef
as the type in the struct and cast the
object to and from this type using the following attributes:
__bridge_retained
: Cast and transfer ownership from Objective C to C. This
will increment the reference count keeping the object from being destroyed. ARC
knows the object is in use. This needs to be paired with __bridge_transfer
when
it’s no longer needed otherwise the object will never be destroyed.
__bridge
: Cast and doesn’t transfer ownership. Doesn’t change the reference
count. This lets you take the CFTypeRef
and use it as the Objective-C object it
is. Without this the object type isn’t known so you can’t call its functions.
__bridge_transfer
: Cast and transfer ownership from C to Objective C. This
will decrement the reference count allowing ARC to destroy the object when
necessary.
The __bridge_
* casts need to be used with care. They are changing the
reference count so if they’re not used correctly you can either have a memory
leak or worse an object that get’s destroyed while still being used.
If you look at the definition of
CFTypeRef
you’ll see it’s just a void pointer. You have to use the type an not a void
pointer otherwise you’ll get a compilation warning when using the __bridge
*
attributes in a cast.
You don’t necessarily need to put the CFTypeRef
‘ed object in a struct. You
could have you’re functions take the CFTypeRef
directly but I’d recommend
against that because you’ll lose the type safety that you get from C. Another
way to handle this is to typedef CFTypeRef
and use that in the C functions.
This makes use clearer. This is fine but I like the idea of using a struct
because you can have more data than just the object to essentially extend the
object.
Object Wrapping Example
Object
The object just takes an NSInteger
as a starting parameter and has two
functions to add and subtract from the stored value.
mather.h
#ifndef __MATHER_H__
#define __MATHER_H__
#import <Foundation/Foundation.h>
@interface Mather : NSObject
@property (readonly)NSInteger currentVal;
+ (id)mather:(NSInteger)start;
- (id)init:(NSInteger)start;
- (void)add:(NSInteger)val;
- (void)sub:(NSInteger)val;
@end
#endif /* __MATHER_H__ */
The object implementation.
mather.m
#import "mather.h"
@implementation Mather
+ (id)mather:(NSInteger)start
{
return [[Mather alloc] init:start];
}
- (id)init:(NSInteger)start
{
self = [super init];
if (self == nil)
return nil;
_currentVal = start;
return self;
}
- (void)add:(NSInteger)val
{
_currentVal += val;
}
- (void)sub:(NSInteger)val
{
_currentVal -= val;
}
@end
C Wrapper
The wrapper puts the Objective-C object into a C struct. There are create and destroy functions which handle creation and the ownership bridging. This C “object” also hides the implementation of the struct so it an be changed without breaking ABI compatibility. It also prevents accidental manipulation of the data.
The wrapper handles creating, taking, using, and destroying the Objective-C object. It also implements two functions that does not uses a retained object. Instead it creates and uses the object without retaining it. The object is created, used, the value is returned and since the object is no longer needed ARC handles destroying it.
wrapper.h
#ifndef __WRAPPER_H__
#define __WRAPPER_H__
struct wadder;
typedef struct wadder wadder_t;
wadder_t *wa_create(int start);
void wa_destroy(wadder_t *wa);
int wa_started(wadder_t *wa);
void wa_add(wadder_t *wa, int val);
void wa_sub(wadder_t *wa, int val);
int wa_val(wadder_t *wa);
int wa_adds(int val1, int val2);
int wa_subs(int val1, int val2);
#endif /* __WRAPPER_H__ */
wrapper.m
#include <stdlib.h>
#import "mather.h"
#include "wrapper.h"
struct wadder {
CFTypeRef ma;
int start;
};
wadder_t *wa_create(int start)
{
Mather *ma;
wadder_t *wa;
ma = [Mather mather:start];
if (ma == nil)
return NULL;
wa = malloc(sizeof(*wa));
wa->start = start;
wa->ma = (__bridge_retained CFTypeRef)ma;
return wa;
}
void wa_destroy(wadder_t *wa)
{
Mather *ma;
if (wa == NULL)
return;
ma = (__bridge_transfer Mather *)wa->ma;
ma = nil;
free(wa);
}
void wa_add(wadder_t *wa, int val)
{
Mather *ma;
if (wa == NULL)
return;
ma = (__bridge Mather *)wa->ma;
[ma add:val];
}
void wa_sub(wadder_t *wa, int val)
{
Mather *ma;
if (wa == NULL)
return;
ma = (__bridge Mather *)wa->ma;
[ma sub:val];
}
int wa_val(wadder_t *wa)
{
Mather *ma;
if (wa == NULL)
return 0;
ma = (__bridge Mather *)wa->ma;
return [ma currentVal];
}
int wa_adds(int val1, int val2)
{
Mather *ma;
ma = [Mather mather:val1];
[ma add:val2];
return [ma currentVal];
}
int wa_subs(int val1, int val2)
{
Mather *ma;
ma = [Mather mather:val1];
[ma sub:val2];
return [ma currentVal];
}
int wa_started(wadder_t *wa)
{
return wa->start;
}
Putting it All Together
The C file will use the C functions exposed by the header files.
main.c
#include <stdio.h>
#include "wrapper.h"
#include "logger.h"
int main(int argc, char **argv)
{
wadder_t *cadder;
do_objc_log("Starting example app");
cadder = wa_create(7);
do_objc_log_fmt("Start val: %d", wa_val(cadder));
wa_add(cadder, 7);
do_objc_log_fmt("Added 7: %d", wa_val(cadder));
wa_sub(cadder, 4);
do_objc_log_fmt("subtracted 4: %d", wa_val(cadder));
do_objc_log_fmt("Started: %d. Current: %d", wa_started(cadder), wa_val(cadder));
wa_destroy(cadder);
printf("%s %s %sn", "Random", "printf", "here");
do_objc_log_fmt("no object add 1+2: %d", wa_adds(1, 2));
do_objc_log_fmt("no object sub 1-2: %d", wa_subs(1, 2));
do_objc_log("Done");
return 0;
}
Output
$ ./bin/bridge
2015-12-18 18:06:56.657 bridge[49240:1478448] Starting example app
2015-12-18 18:06:56.659 bridge[49240:1478448] Start val: 7
2015-12-18 18:06:56.659 bridge[49240:1478448] Added 7: 14
2015-12-18 18:06:56.659 bridge[49240:1478448] subtracted 4: 10
2015-12-18 18:06:56.659 bridge[49240:1478448] Started: 7. Current: 10
Random printf here
2015-12-18 18:06:56.659 bridge[49240:1478448] no object add 1+2: 3
2015-12-18 18:06:56.659 bridge[49240:1478448] no object sub 1-2: -1
2015-12-18 18:06:56.659 bridge[49240:1478448] Done
Building Everything
CMakeLists.txt
cmake_minimum_required (VERSION 3.0)
project (bridge C)
# Put binary in a bin dir.
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
set (SOURCES
main.c
wrapper.m
mather.m
logger.m
)
# Apple frameworks and libs needed.
find_library (FOUNDATION Foundation)
find_library (OBJC objc)
add_executable (${PROJECT_NAME} ${SOURCES})
target_link_libraries (${PROJECT_NAME} ${FOUNDATION} ${OBJC})
# Enable ARC
set_target_properties (${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fobjc-arc")
Summary
To use Objective-C objects in C create a .m file with only C functions (and an
accompanying .h file). Use __bridge_retained
, __bridge
, __bridge_transfer
on the Objective-C object from within the C functions.