Ivan Romanov
Ivan Romanov

Reputation: 1228

Get Objective-C property from pure C

I develop Qt application with C++ code without mixing with Objective-C. Need to implement native window moving (to resolve flickering problems). Now I can move window with

struct NSPoint
{
    double x;
    double y;
} point;


point.x = (pos() + (mouseEvent->pos() - m_prevMousePos)).x();
point.y = (pos() + (mouseEvent->pos() - m_prevMousePos)).y();

id nsView = reinterpret_cast<id>(winId());
objc_msgSend(objc_msgSend((id)nsView, sel_registerName("window")), sel_registerName("setFrameTopLeftPoint:"), point);

This code works and allow me to avoid flickering problems. But Mac OS X and Qt coordinate systems are differnt. I want to get coordinate of top left window corner. For this I tried to use this code:

struct NSPoint
{
    double x;
    double y;
};

struct NSSize
{
    double width;
    double height;
};

struct NSRect
{
    NSPoint origin;
    NSSize size;
} rect;

id nsView = reinterpret_cast<id>(winId());
rect = *(NSRect*)objc_msgSend_stret)(objc_msgSend((id)nsView, sel_registerName("window")), sel_registerName("frame"));

But it leads to crush. Comments in objc/message.h say:

/* Struct-returning Messaging Primitives
 *
 * Use these functions to call methods that return structs on the stack. 
 * On some architectures, some structures are returned in registers. 
 * Consult your local function call ABI documentation for details.
 * 
 * These functions must be cast to an appropriate function pointer type 
 * before being called. 
 */

So here I stucked. My final question how to get NSWindow::frame property with Pure C?

Upvotes: 3

Views: 787

Answers (2)

Ivan Romanov
Ivan Romanov

Reputation: 1228

I found answer here How do I return a struct value from a runtime-defined class method under ARC?. objc_msgSend_stret intendet to return stuct. Need only to cast function to need return type.

So now I use such code

// Use native Mac OS X dragging to avoid flickering
struct NSPoint
{
    CGFloat x;
    CGFloat y;
};

struct NSSize
{
    CGFloat width;
    CGFloat height;
};

struct NSRect
{
    NSPoint origin;
    NSSize size;
} rect;

id nsView = reinterpret_cast<id>(winId());
rect = ((NSRect(*)(id, SEL))objc_msgSend_stret)(objc_msgSend(nsView, sel_registerName("window")), sel_registerName("frame"));

// Mac OS X uses not Qt coordinate system. Need convert y.
QPoint d = mouseEvent->pos() - m_prevMousePos;
rect.origin.x += d.x();
rect.origin.y -= d.y();

objc_msgSend(objc_msgSend(nsView, sel_registerName("window")), sel_registerName("setFrameOrigin:"), rect.origin);

Upvotes: 2

Rob Napier
Rob Napier

Reputation: 299605

First, this is dangerous:

struct NSPoint
{
    double x;
    double y;
} point;

This is only correct on 64-bit platforms. On 32-bit platforms, it's wrong. CGFloat exists for a reason.

Calling objc_msgSend directly is pretty dicey in any case, particularly if it's non-trivial (as yours is). Rather than jump through all these hoops, just add an ObjC wrapper that gives you what you want. ObjC is a superset of C, so it's pretty easy to expose a pure C interface. You can do this in C++ as well (which is probably even more convenient for you).

Just create a .h along the lines of:

void setWindowFrameTopLeftPoint(WinID winID, float x, float y);

You seem to have some kind of type already for "winID." You can just pass it in without having to reinterpret it. Passing floats rather than a struct means you don't have to worry about sizes so much. You'll just upconvert later.

Then implement it something like this, in a .m or .mm file:

void setWindowFrameTopLeftPoint(WinID winID, float x, float y) {
    [(id)winId setFrameTopLeftPoint:NSMakeRect(x,y)];
}

For the cost of writing a single line of ObjC, you keep everything clean and simple. On the assumption that you don't have all that many things that you need to call, this is very mechanical and simple to implement.

For your other function, it's the same thing, but you can unload the struct into some other struct with a size that's convenient (floats for instance rather than CGFloat if you prefer). I believe you can import CoreGraphics into C, since it's a C interface. That should give you CGPoint if it's convenient. But it may be easier just to convert to your own (Qt?) types directly.

Upvotes: 2

Related Questions