"Converting" a function pointer to a block in objective-C
Asked Answered
S

3

11

I'm doing some Interop from Mono C# to Obj-C and ran into this problem. The C# code needs to pass a callback - which it does with a function pointer. I can get the function pointer from the Obj-C side and call it and everything works. But I now need to give that function pointer as a callback to third party API which works with blocks as a callback. I want the third party to call the C# function - so in a way i'm trying to either convert the function pointer to a block so the third party can run it, or make some sort of a bridge - create my own block that runs that function pointer and give it to the third party. I can't seem to find a way to do that - how would I generate a block with info of which function to run and then give it to the third party. Maybe there's another option for me?

Edit: Putting the function in a global variable might work but I want to be able to have a multitude of those as the third party API is asynchronous and I don't want it calling the wrong callback.

Code I tried :

typedef void (*DummyAction)(char * result);
typedef void (^DummyBlock)(char * result);

@interface FunctionToBlock : NSObject
{
    DummyAction function;
    DummyBlock block;
}

- (id) initWithFunction: (DummyAction) func;
- (DummyBlock) block;
@end

@implementation FunctionToBlock : NSObject
- (id) initWithFunction: (DummyAction) func {
    if (self = [super init]) {
        function = func;
        block = ^(char * result) {
            function(result);
        };
    }
    return self;
}

- (DummyBlock) block {
    return block;
}
@end

And then I run this with

void RegisterCallback( char * text, DummyAction callback)
{
    FunctionToBlock *funcToBlock = [[FunctionToBlock alloc] initWithFunction : callback];
    funcToBlock.block(text);
}

And it fails with BAD_ACCESS. Maybe i'm doing something wrong as i'm not very proficient with Obj-C yet. I can confirm that the callback is ok if run directly and that the block is being called but it fails on the function(result) line.

Sawtelle answered 17/12, 2012 at 11:50 Comment(0)
J
7

why not just have a simple function

typedef void (*DummyAction)(char * result);
typedef void (^DummyBlock)(char * result);

DummyBlock functionToBlock(DummyAction func) {
    return [[^(char * result) {
                 func(result);
             } copy] autorelease];
}
Jesu answered 18/12, 2012 at 0:27 Comment(1)
Well, you are right. For some reason I haven't thought of that. Works great and very clean. Thank you!Sawtelle
S
7

What about

void (*myFunc)(int x); // ... your function pointer

void (^myBlock)(int) = ^(int x) {
    myFunc(x);
};

Then myBlock is a block that captures the value of the function pointer and calls the function when the block is executed.


ADDED: My suggestion, based on your code, using a @property (and assuming that you compile with ARC):

FunctionToBlock.h:

typedef void (*DummyAction)(char * result);
typedef void (^DummyBlock)(char * result);

@interface FunctionToBlock : NSObject
{
    DummyAction function; // Not really needed.
}

- (id) initWithFunction: (DummyAction) func;
@property(copy, nonatomic) DummyBlock block;   // "copy" is important here!

@end

FunctionToBlock.m:

#import "FunctionToBlock.h"

@implementation FunctionToBlock : NSObject
@synthesize block = _block; // Can be ommitted if you use Xcode 4.4 or later.

- (id) initWithFunction: (DummyAction) func
{
    if (self = [super init]) {
        function = func; // Not really needed.
        self.block = ^(char * result) {
            func(result); // Use "func", not "self->function", to avoid retain cycle.
        };
    }
    return self;
}
Splat answered 17/12, 2012 at 12:24 Comment(7)
A global variable? That's not very good because then I can't several of those. I tried putting myBlock as a member in a class along with myFunc but when I try to run myFunc from myBlock it's probably not in its scope and it fails with BAD_ACCESS. I guess global variables would be a last resort but i'd prefer a non-global approachSawtelle
@Amitloaf: I did not want to suggest a global variable. myFunc can be any function pointer. The value is captured in the block when the block is assigned.Splat
I added some more info about how I tried implementing something like what you suggested. Would like your thoughts about how to solve it, thanks!Sawtelle
@Amitloaf: Blocks the "outlive" the scope in which they were created must be copied. Try block = [^(char * result) { function(result); } copy];, this seems to work. Alternatively, you can declare it as as @property(copy, nonatomic) DummyBlock block;Splat
It worked perfectly! Thank you! That what I was missing :) Although I couldn't get the @property alternative to compile, the normal copy one worked!Sawtelle
@Amitloaf: I have added some code to my answer that shows how to use a property, and some other comments.Splat
Ah, that's the thing - I don't compile with ARC :) Thanks for the code though! It's useful!Sawtelle
J
7

why not just have a simple function

typedef void (*DummyAction)(char * result);
typedef void (^DummyBlock)(char * result);

DummyBlock functionToBlock(DummyAction func) {
    return [[^(char * result) {
                 func(result);
             } copy] autorelease];
}
Jesu answered 18/12, 2012 at 0:27 Comment(1)
Well, you are right. For some reason I haven't thought of that. Works great and very clean. Thank you!Sawtelle
W
0

A block is under the hood a pointer to a local data structure. A block becomes invalid as soon as you leave the scope where it was declared. The scope is the if-statement within init; as soon as you leave that, the block is invalid.

You are breaking coding conventions here in a bad way. First, instance variables should start with an underscore, so that everyone sees what you are doing. Better to use properties without declaring instance variables at all. And every block property should be declared as "copy". If you do that, everything is fine.

Wicks answered 19/2, 2016 at 13:54 Comment(1)
First, instance variables should start with an underscore, so that everyone sees what you are doing. Be careful what you put after the _, inherited from C, note that _<upper case letter> is reserved in any scope, and __<anything> is reserved in any scope.Subacid

© 2022 - 2024 — McMap. All rights reserved.