3

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?

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
Ivan Romanov
  • 1,138
  • 1
  • 9
  • 24
  • 1
    You write your application is QT which is C++, not C. Any reason you asked about C then? – too honest for this site Jan 30 '16 at 21:02
  • Actually it is not C++ related question. objc_msgSend is Pure C function. – Ivan Romanov Jan 30 '16 at 21:24
  • Please point me at where the C standard introduced `reinterpret_cast` and templates. I might have missed that. Seriously: You use that function apparently from C++, thus it is definitively a C++ context, not a C context. It does not matter in what language that is implemented, provided you have a correct C++-compatible declaration. – too honest for this site Jan 30 '16 at 21:49

2 Answers2

2

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.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • While I agree using appropriate types, I don't see why `double` would necessarily have a different encoding on 32 or 64 bit platforms. If both use IEC60559 floating point (as suggested by the standard), they have the same properties. – too honest for this site Jan 30 '16 at 21:53
  • 1
    Because Foundation's NSPoint is a different size on different platforms. So this struct will have the wrong size, and you will have undefined behavior when you try to pass it around. Size of the struct can also modify C calling conventions, so objc_msgsend_stret may not even be correct. The results may be in floating point registers. Picking the right version of msgSend is complex and highly platform specific. Leave it to the compiler. See http://www.sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html – Rob Napier Jan 30 '16 at 22:08
  • The key here isn't double. As you note, it will almost certainly be the same size. The problem is CGFloat which absolutely will not have the same size (that's why it exists). – Rob Napier Jan 30 '16 at 22:10
  • Thanks for clarifying. I just would not necessarily see that in context of the bitwidth of the platform. It is like `size_t` and other types. – too honest for this site Jan 30 '16 at 22:12
  • I included CoreGraphics. It's really works. Now I can use CGFloat. Your post have good explanation. But it's not answered on my question. It is possible but I don't want to introduce new source files in my project. – Ivan Romanov Jan 30 '16 at 22:14
  • Then you will have to carefully check the calling conventions to make sure which version of objc_msgSend to use. It depends on the platform (32 vs 64 intel are different). I'm not sure immediately which it would be. Maybe the _fp version since its returning just two floating points. It's very fragile to do it this way, not supported, and not very clearly documented (though it is obviously possible to work out the correct call). – Rob Napier Jan 30 '16 at 22:30
  • The link I posted explains in more detail and includes the link to the ABI docs and hints for how to attack it (write it in objc, compile it on each platform, see what version of msgSend it used). It's definitely the hard way, but doable. – Rob Napier Jan 30 '16 at 23:00
  • You also have to use specific types because the encoding of the *same* type can vary depending on if the function is called as a varargs function or as an explicitly typed function in some situations under certain ABIs. – bbum Jan 31 '16 at 01:45
2

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);
Community
  • 1
  • 1
Ivan Romanov
  • 1,138
  • 1
  • 9
  • 24