6

Have a simple one-off tasks which needs a progress bar. OpenSSL has a useful callback which one can use for that:

rsa=RSA_generate_key(bits,RSA_F4,progressCallback,NULL); 

with

static void callback(int p, int n, void *arg) { .. stuff 

However I want to call this from ObjectiveC without too much ado:

 MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; hud.mode = MBProgressHUDModeAnnularDeterminate; hud.labelText = @"Generating CSR"; [self genReq:^(int p,int n,void *arg) { hud.progress = --heuristic to guess where we are -- } completionCallback:^{ [MBProgressHUD hideHUDForView:self.view animated:YES]; }]; 

With Genrec: as an objC method:

-(void)genReq:(void (^)(int,int,void *arg))progressCallback completionCallback:(void (^)())completionCallback { ..... rsa=RSA_generate_key(bits,RSA_F4,progressCallback,NULL); assert(EVP_PKEY_assign_RSA(pkey,rsa)); rsa=NULL; .... completionCallback(); } 

Now completionCallback(); works splendidly and as expected. But I get a compiler warning/error which I cannot quell for the progress callback:

 Passing 'void (^__strong)(int, int, void *)' to parameter of incompatible type 'void (*)(int, int, void *)' 

So am curious -- what is the appropriate way to do this ?

Thanks,

Dw.

1 Answer 1

7

All code is just typed into this answer, test carefully before using!

Function pointers and blocks are not the same thing; the former is just a reference to code, the latter is a closure containing both code and an environment; they are not trivially interchangeable.

You can of course use function pointers in Objective-C, so that is your first option.

If you wish to use blocks then you need to find a way to wrap a block and pass it as a function reference...

The definition of RSA_generate_key is:

RSA *RSA_generate_key(int num, unsigned long e, void (*callback)(int,int,void *), void *cb_arg); 

The fourth argument can be anything and is passed as the third argument to the callback; this suggests we could pass the block along with a pointer to a C function which calls it:

typedef void (^BlockCallback)(int,int); static void callback(int p, int n, void *anon) { BlockCallback theBlock = (BlockCallback)anon; // cast the void * back to a block theBlock(p, n); // and call the block } - (void) genReq:(BlockCallback)progressCallback completionCallback:(void (^)())completionCallback { ..... // pass the C wrapper as the function pointer and the block as the callback argument rsa = RSA_generate_key(bits, RSA_F4, callback, (void *)progressCallback); assert(EVP_PKEY_assign_RSA(pkey,rsa)); rsa = NULL; .... completionCallback(); } 

And to invoke:

[self genReq:^(int p, int n) { hud.progress = --heuristic to guess where we are -- } completionCallback:^{ [MBProgressHUD hideHUDForView:self.view animated:YES]; } ]; 

Whether you need any bridge casts (for ARC) is left as an exercise!

Sign up to request clarification or add additional context in comments.

3 Comments

Most Lovely ! And __bridge is all that is needed for ARC. Shame that one needs to have the callback() in between - i.e. that one cannot pass the progressCallback as a (void (*)(int, int, void *)) from its void (^__strong)(int, int, void *) directly.
@Dirk-WillemvanGulik - the only reasonable way to support automatic passing of a block, i.e. closure (code pointer + environment), as a function pointer (just a code pointer) would be to dynamically generate code stubs; and that has various drawbacks. The common pattern in C is to specify a function pointer taking an extra value, usually of type void *, that can be used to pass a user-defined environment and thus manually construct a closure - and openssl follows that pattern and the solution above just uses it.
Clear. Thanks (again!). Have dug into NSStackBlock and makes total sense.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.