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.
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"