Calling a Objc method directly from C

I would like to avoid the middle man and call Objective C directly from C

Currently I do this

This is called from a dispatch table in a pure C file

    _ctx->mt_render_funcs.mtlEnd(_ctx);

Which calls this routine in a obj c file .m

void mtlEnd(MTRenderContext mt_ctx) { // Call the Objective-C method using Objective-C syntax [(__bridge id) mt_ctx->mt_render_funcs.mtlObj mtlEnd]; }

Which ends up here... in ObjC

#pragma mark mtlEnd

  • (void)mtlEnd

{ // vertex buffer size_t size;

size = sizeof(Vertex4ColorNormalTex) * _ctx->vert_eng.current_vertex;

[_currentRenderEncoder setVertexBytes:_ctx->vert_eng.vertices length: size atIndex: VertexInputIndexVertices];

[_currentRenderEncoder drawPrimitives:(MTLPrimitiveType)_ctx->vert_eng.prim_type
                          vertexStart:0
                          vertexCount:_ctx->vert_eng.current_vertex];

}

It would simplify this to get rid of one call and call ObjC directly.

The other idea is I want to use GCD and put a lock / unlock on the call from C to ensure thread safety so I can use GCD to dispatch a thread to do the ObjC routines.

I want to stick with C as the foundation so it can be used directly from C or a FFI interface from other languages. But Metal works well in ObjC and I would prefer to use that.

Thanks ahead of time.

Answered by DTS Engineer in 877955022

IAiSeed, it looks like your response was AI generated. If that’s the case, it’d be good to let folks know that, so that they can decide for themselves how much trust to put it in.


Coming back to the technical issue, this isn’t right:

Functions like class_getInstanceMethod and IMP are used to dynamically invoke Objective-C methods from C.

A minor issue is that IMP isn’t a function, it’s a type.

A more significant issues is that this approach, known as IMP caching, is not the right option in most cases. Rather, if you want to call Objective-C directly from C it’s better to call the objc_msgSend function (or one of its variants; more on that below).

When you call Objective-C in this way, it’s critical that you cast the objc_msgSend function pointer to a function pointer with the right arguments. For example, to call -[NSUUID getUUIDBytes:] you’d do this cast:

typedef void (*NSUUID_getUUIDBytes_Ptr)(NSUUID *, SEL, uuid_t);
NSUUID_getUUIDBytes_Ptr nsuuid_getUUIDBytes = (NSUUID_getUUIDBytes_Ptr) objc_msgSend;

IMPORTANT If you compile this as pure C, rather than Objective-C, you’ll need to declare a C version of NSUUID. How you do that is up to you, but I would typically do this:

typedef void * NSUUID;

You can then call the resulting function pointer like so:

uuid_t uuidBuffer;
SEL sel = sel_registerName("getUUIDBytes:");
nsuuid_getUUIDBytes(uuid, sel, uuidBuffer);

Note You’d typically cache the result of sel_registerName.

This approach automatically gets you the right calling conventions for all of the parameters. In this case, uuid_t is actually declared like this:

typedef unsigned char __darwin_uuid_t[16];
typedef __darwin_uuid_t uuid_t;

and the C compiler knows that such values are passed as a pointer.

For more details on why casting to a function pointer is necessary, see Enable Strict Type Enforcement for Dynamic Method Dispatching.


Another gotcha relates to function return results. You might need to replace objc_msgSend with one of its variants, like objc_msgSend_stret. The exact rules here are complex. For example, one those variants, objc_msgSend_fpret, is only necessary on 32-bit Intel! If I’m in doubt, I usually write some Objective-C code to call the method, disassemble that, and then copy what the compiler did.


Finally, there’s a big picture question of whether you should even bother doing this. Objective-C is pretty much a superset of Objective-C, so you can enable Objective-C universally, stick with C for the vast majority of your code, and then only write Objective-C in this glue layer. That’ll greatly reduce you maintenance headaches in the long term.

And the same logic applies to Objective-C++ and C++.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

Calling Objective-C directly from C is certainly possible and can simplify your codebase by eliminating intermediate layers. Additionally, using Grand Central Dispatch (GCD) to manage thread safety and dispatch tasks to Objective-C contexts is a sound approach. Below, I'll outline how you can achieve this, maintaining thread safety and leveraging GCD effectively.

Direct Objective-C Function Call from C

Assuming you have an Objective-C method you want to call directly from C, you can use the Objective-C runtime functions to achieve this. Here's a step-by-step guide:

Objective-C Method Declaration

First, ensure your Objective-C method is declared properly. For example:

@interface Renderer : NSObject

  • (void)mtlEnd;

@end

C Function to Call Objective-C Method

You can define a C function that uses the Objective-C runtime to call the mtlEnd method:

#include <objc/runtime.h>

void mtlEnd(MTRenderContext mt_ctx) { // Retrieve the class of the render context Class rendererClass = mt_ctx->mt_render_funcs.mtlObj.isa;

// Get the selector for the mtlEnd method
SEL selector = @selector(mtlEnd);

// Find the implementation of the method
IMP implementation = class_getInstanceMethod(rendererClass, selector);

// Call the method using the IMP
void (*func)(id, SEL) = (void (*)(id, SEL))implementation;
func(mt_ctx->mt_render_funcs.mtlObj, selector);

}

Thread Safety with GCD

To ensure thread safety when calling Objective-C code from C, you can use GCD to dispatch the call to a specific thread or queue. Here's how you can incorporate a lock using dispatch_queue_t:

#include <dispatch/dispatch.h>

// Create a serial dispatch queue for thread safety static dispatch_queue_t renderQueue = dispatch_queue_create("com.yourapp.renderQueue", DISPATCH_QUEUE_SERIAL);

void mtlEnd(MTRenderContext mt_ctx) { dispatch_async(renderQueue, ^{ // Retrieve the class of the render context Class rendererClass = mt_ctx->mt_render_funcs.mtlObj.isa;

    // Get the selector for the mtlEnd method
    SEL selector = @selector(mtlEnd);
    
    // Find the implementation of the method
    IMP implementation = class_getInstanceMethod(rendererClass, selector);
    
    // Call the method using the IMP
    void (*func)(id, SEL) = (void (*)(id, SEL))implementation;
    func(mt_ctx->mt_render_funcs.mtlObj, selector);
});

}

Explanation

Objective-C Runtime: Functions like class_getInstanceMethod and IMP are used to dynamically invoke Objective-C methods from C. GCD for Thread Safety: By dispatching the method call to a serial queue (renderQueue), you ensure that only one thread executes the Objective-C code at a time, preventing race conditions. Serial Queue: DISPATCH_QUEUE_SERIAL ensures FIFO execution, maintaining the order of operations and protecting shared resources.

Integration

Ensure your C and Objective-C files are properly linked in your Xcode project. Compile with the necessary flags to support Objective-C runtime functions (usually -framework Foundation).

This approach allows you to maintain a C foundation for your application while efficiently leveraging Objective-C's capabilities, particularly for tasks like rendering with Metal, and ensures thread safety using GCD.

IAiSeed, it looks like your response was AI generated. If that’s the case, it’d be good to let folks know that, so that they can decide for themselves how much trust to put it in.


Coming back to the technical issue, this isn’t right:

Functions like class_getInstanceMethod and IMP are used to dynamically invoke Objective-C methods from C.

A minor issue is that IMP isn’t a function, it’s a type.

A more significant issues is that this approach, known as IMP caching, is not the right option in most cases. Rather, if you want to call Objective-C directly from C it’s better to call the objc_msgSend function (or one of its variants; more on that below).

When you call Objective-C in this way, it’s critical that you cast the objc_msgSend function pointer to a function pointer with the right arguments. For example, to call -[NSUUID getUUIDBytes:] you’d do this cast:

typedef void (*NSUUID_getUUIDBytes_Ptr)(NSUUID *, SEL, uuid_t);
NSUUID_getUUIDBytes_Ptr nsuuid_getUUIDBytes = (NSUUID_getUUIDBytes_Ptr) objc_msgSend;

IMPORTANT If you compile this as pure C, rather than Objective-C, you’ll need to declare a C version of NSUUID. How you do that is up to you, but I would typically do this:

typedef void * NSUUID;

You can then call the resulting function pointer like so:

uuid_t uuidBuffer;
SEL sel = sel_registerName("getUUIDBytes:");
nsuuid_getUUIDBytes(uuid, sel, uuidBuffer);

Note You’d typically cache the result of sel_registerName.

This approach automatically gets you the right calling conventions for all of the parameters. In this case, uuid_t is actually declared like this:

typedef unsigned char __darwin_uuid_t[16];
typedef __darwin_uuid_t uuid_t;

and the C compiler knows that such values are passed as a pointer.

For more details on why casting to a function pointer is necessary, see Enable Strict Type Enforcement for Dynamic Method Dispatching.


Another gotcha relates to function return results. You might need to replace objc_msgSend with one of its variants, like objc_msgSend_stret. The exact rules here are complex. For example, one those variants, objc_msgSend_fpret, is only necessary on 32-bit Intel! If I’m in doubt, I usually write some Objective-C code to call the method, disassemble that, and then copy what the compiler did.


Finally, there’s a big picture question of whether you should even bother doing this. Objective-C is pretty much a superset of Objective-C, so you can enable Objective-C universally, stick with C for the vast majority of your code, and then only write Objective-C in this glue layer. That’ll greatly reduce you maintenance headaches in the long term.

And the same logic applies to Objective-C++ and C++.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Calling a Objc method directly from C
 
 
Q