Implementing a C Callback with a Void Pointer Parameter in Swift
Swift is a promising programming language, but it is brand new, and when coding in Swift we are likely to have to use legacy code written in other languages, in particular C.
What if we need to interact with a C API that involves callbacks that take void pointer parameters? We may want to implement these callbacks in Swift and have the legacy C code call them. In this article we use Swift 2 features and look at two cases:
- Swift changes made to the data passed via the pointer do not need to be visible in C code.
- C code needs to see the changes made in Swift.
Sample C API
This tutorial uses the following trivial C API:
/**
* The struct to be passed to the callback via void*
*/
typedef struct
{
int32_t m_Int;
int64_t m_Long;
int16_t m_Array[3];
} APIStruct;
/**
* Function pointer type for the callback. Callback receives a void
* pointer, which is then treated as APIStruct * in the callback.
*/
typedef void (*my_cb_t)( void * );
/**
* C function using the callback. It creates an instance of APIStruct, prints
* it out, and gives it via void pointer to the callback identified by the 1st
* parameter. If the 2nd parameter is !=0, then the struct is also printed out
* after the callback returns.
*/
int CUseCallback( my_cb_t, int );
You can find the complete source code of this example on
github.
Implementing the Callback in Swift 2
Create an Xcode Project
First we create an Xcode project, using the OS X Application/Command Line Tool template and Swift as the language. The newly created project now contains a single file, main.swift. We then add two more files:- c_code.c: contains the C code implementing our trivial API. In real life this code would be in a library.
- bridging.h: the header for use with our C API. To keep things simple, we also use it as the bridging header, i.e. the header used by Swift to import C types. In the real world the bridging header would be separate and would include the header(s) that come with the library implementing the C API.
The project should now look something like this:
Invoke C API
Our C API can be called from Swift because Swift imports function prototypes and type definitions found in the bridging header that looks as follows:
#ifndef bridging_h
#define bridging_h
#pragma pack(1) // pack(2) & pack(4) would have the same effect
// NaiveCallback() works with no pragma or with pack(8)
#include <stdlib.h>
typedef struct
{
int32_t m_Int;
int64_t m_Long;
int16_t m_Array[3];
} APIStruct;
typedef void (*my_cb_t)( void * );
int CUseCallback( my_cb_t, int );
#endif /* bridging_h */
Please note the pragma at the top of the file. Packing has to do with how data is aligned within a structure. See, for example, GCC documentation. If the pragma were not there, then even the naive approach, see below, would have worked fine. However, it is quite likely that packing used in a real-world C library would be different from the one used by default in Xcode, and then we would run into problems with the naive approach.
Now we need to find out how the C definitions found in the bridging header are imported into Swift. To see how CUseCallback() is imported, just start typing the function name in main.swift, and you will see the following, thanks to Xcode's code completion:
Alternatively, one can click on the Quick Help Inspector button in Xcode, which is a small circle with a question mark in the Utilities area, and then place the cursor in the function name to see how it is imported:
Using one of these methods we find that our C API is imported into Swing as follows:
// APIStruct:
struct APIStruct
{
var m_Int : Int32
var m_Long : Int64
var m_Array : (Int16, Int16, Int16)
}
// Callback function pointer type:
typealias my_cb_t = (UnsafeMutablePointer<Void>) -> Void
// C function that uses the callback:
func CUseCallback(_: my_cb_t!, _: Int32) -> Int32
Please note that the array member of the C struct is imported as a tuple.
Naive Approach: Manually Implement a Swift Structure
Armed with this knowledge, we define a Swift structure, NaiveStruct, matching the imported APIStruct, define a callback that populates NaiveStruct from the void pointer, and also define a helper function that prints out a NaiveStruct:
/**
* This is a naive native Swift structure that tries to mimic the APIStruct
* from the C API. Depending on structure packing in C code, this may or
* may not work.
*/
struct NaiveStruct
{
var m_Int : Int32
var m_Long : Int64
var m_Array : (Int16, Int16, Int16)
}
/**
* Prints an instance of NaiveStruct. Only selected fields are printed.
*/
func printNaive( s : NaiveStruct )
{
print( "Printing NaiveStruct: " )
print( " m_Long: \(s.m_Long)" )
print( " m_Array: \(s.m_Array.0) \(s.m_Array.1) \(s.m_Array.2) " )
}
/**
* This is a naive implementation of the callback. Casts the void *,
* provided via an argument of type UnsafeMutablePointer<Void>, to
* UnsafeMutablePointer<NativeStruct>, takes its memory and naively
* creates an instance of NaiveStruct from it.
*
* This may or may not work depending on how APIStruct is packed in C
* code. If, e.g., #pragma pack(2) is used in C code, the content of
* NaiveStruct won't match that of the APIStruct provided by the C
* code via the void *.
*/
let NaiveCallback : my_cb_t = {( p : UnsafeMutablePointer<Void> )->Void in
print( "In NaiveCallback(), received a void pointer. " )
let _ns : NaiveStruct = (UnsafeMutablePointer<NaiveStruct>(p)).memory;
printNaive( _ns );
}
/**
* Call the C API giving it our NaiveCallback.
*/
CUseCallback( NaiveCallback, 0 )
The output from using this callback is:
Entered C code, printing newly created structure:
Printing structure in C
m_Long = 4567890123
m_Array = 123 456 789
In NaiveCallback(), received a void pointer.
Printing NaiveStruct:
m_Long: 128353117661036545
m_Array: 789 0 0
As can be easily seen, the NaiveStruct instance obtained in this way from the void pointer is corrupted. It would actually be correct if the pack pragma was not used. However, a real world C framework is likely to use a structure alignment that would cause problems with this approach.
Use Imported Struct to Treat the Pointer as an IN Parameter
Now let's use APIStruct imported into Swift via the bridging header:
/**
* Dumps an instance of the C API's structure, APIStruct, imported via
* the bridging header.
*/
func printAPI( s : APIStruct )
{
print( "Printing APIStruct: " )
print( " m_Long: \(s.m_Long)" )
print( " m_Array: \(s.m_Array.0) \(s.m_Array.1) \(s.m_Array.2) " )
}
/**
* A better callback implementation.
*
* This one casts the void pointer to UnsafeMutablePointer<APIStruct>,
* then uses its memory to construct an instance of APIStruct. Even if
* tight packing is used by the C code, the APIStruct will correctly
* reflect the data placed there by the C code. This is as long as
* the pack pragma is available via the bridging header.
*
* It is called OneWayCallback because whatever changes it makes to
* the APIStruct provided to it by C code won't be visible to the
* C code. This is because we modify a copy of the structure
* populated by the C code.
*/
let OneWayCallback : my_cb_t = {( p : UnsafeMutablePointer<Void> )->Void in
print( "In OneWayCallback(), received a void pointer. " );
var _apiS : APIStruct = (UnsafeMutablePointer<APIStruct>(p)).memory
printAPI ( _apiS );
print( "Setting m_Long in the structure to 98765432109 " )
_apiS.m_Long = 98765432109
}
/**
* Call the C API giving it the 1-way callback.
*/
CUseCallback( OneWayCallback, 1 )
Here the output:
Entered C code, printing newly created structure:
Printing structure in C
m_Long = 4567890123
m_Array = 123 456 789
In OneWayCallback(), received a void pointer.
Printing APIStruct:
m_Long: 4567890123
m_Array: 123 456 789
Setting m_Long in the structure to 98765432109
Now we are back in C code, see if the callback changed the structure...
Printing structure in C
m_Long = 4567890123
m_Array = 123 456 789
Now the data sent by the C code to the Swift callback via the void * parameter has been read correctly. However, changes made in the callback to the received APIStruct did not make it back to the C code because what was modified was a copy of the APIStruct passed via the void pointer.
Implement a Wrapper Swift Structure to Treat the Pointer as an INOUT Parameter
Here is a different approach, in which changes made to the data passed via the void pointer are visible to the C code invoking the callback, but the downside is that the data is valid in Swift only for as long as it is valid in the C framework. Another advantage of this approach is that the wrapper does not have to mimic the structure imported from C. The properties for accessing the passed APIStruct can be named differently, and other properties and methods can be added. It doesn't have to be a struct, either; it might just as well be a class.
/**
* A structure that wraps an UnsafeMutablePointer<APIStruct> and
* has getter/setter properties to access the data in the APIStruct
* pointed to by the wrapped pointer. Because the wrapped thing is
* not a copy of, but a pointer to the struct provided by the C code,
* any changes made via the computed properties will be available
* when the control returns to the C code that invoked the callback.
* The downside, however, is that the properties will work correctly
* only for the duration of the lifetime of the structure in the C
* code.
*
* In this case the struct mimics the APIStruct, but it doesn't have
* to. It could be a Swing class with plenty of added functionality.
*/
struct WrapperStruct
{
var m_Pointer : UnsafeMutablePointer<APIStruct>
init( _p : UnsafeMutablePointer<APIStruct> )
{
m_Pointer = _p
}
var m_Int : Int32 {
get { return m_Pointer.memory.m_Int }
set( val ) { m_Pointer.memory.m_Int = val }
}
var m_Long : Int64 {
get { return m_Pointer.memory.m_Long }
set( val ) { m_Pointer.memory.m_Long = val }
}
var m_Array : (Int16, Int16, Int16)
{
get { return (m_Pointer.memory.m_Array.0,
m_Pointer.memory.m_Array.1,
m_Pointer.memory.m_Array.2) }
set ( val ) {
m_Pointer.memory.m_Array.0 = val.0
m_Pointer.memory.m_Array.1 = val.1
m_Pointer.memory.m_Array.2 = val.2
}
}
}
/**
* Dumps selected fields from an instance of WrapperStruct.
*/
func printWrapper( s : WrapperStruct )
{
print( "Printing APIStruct: " )
print( " m_Long: \(s.m_Long)" )
print( " m_Array: \(s.m_Array.0) \(s.m_Array.1) \(s.m_Array.2) " )
}
/**
* A callback implementation that casts the argument to
* UnsafeMutablePointer<APIStrunct> and wraps that in a WrapperStruct.
* Please note that changes made via the WrapperStruct properties
* are visible to the C code!
*
* Notice that here we use a top-level Swift function instead of a closure
* literal. Swift 2.1 documentation states that top-level functions can be
* passed where a function pointer parameter is required. We could have used
* a literal as well.
*/
func TwoWayCallback( p : UnsafeMutablePointer<Void> )->Void
{
print( "In TwoWayCallback(), received a void pointer. " );
var _wS : WrapperStruct = WrapperStruct(_p: (UnsafeMutablePointer<APIStruct>(p)))
printWrapper( _wS )
print( "Setting m_Long in the structure to 98765432109 " )
_wS.m_Long = 98765432109
print( "Setting the array to 111, 222, 333" )
_wS.m_Array.0 = 111
_wS.m_Array.1 = 222
_wS.m_Array.2 = 333
}
/**
* Call C code with our more sophisticated 2-way callback.
*/
CUseCallback( TwoWayCallback, 1 )
The output from this one is:
Entered C code, printing newly created structure:
Printing structure in C
m_Long = 4567890123
m_Array = 123 456 789
In TwoWayCallback(), received a void pointer.
Printing APIStruct:
m_Long: 4567890123
m_Array: 123 456 789
Setting m_Long in the structure to 98765432109
Setting the array to 111, 222, 333
Now we are back in C code, see if the callback changed the structure...
Printing structure in C
m_Long = 98765432109
m_Array = 111 222 333