Passing & Receiving Structures

Just starting out? Need help? Post your questions and find answers here.
coder14
Enthusiast
Enthusiast
Posts: 327
Joined: Tue Jun 21, 2011 10:39 am

Passing & Receiving Structures

Post by coder14 »

Hi Everyone!
Is there any way we can pass values, or receive results, in structure form? For example:

Code: Select all

CGRectMake(cgRECT, x, y, width, height)  <<=== cgRECT is a unique RECT structure that is populated with the result

CGContextStrokeRect(cgContext, cgRECT) <<=== the cgRECT structure is passed to the function to be drawn
Clearly there are many better ways to enumerate and draw a rectangle, but this is just an example. The Mac OSX API is designed in this manner, and some functions will be impossible to use unless we can pass and receive structures.

Can anyone help please?

Thank you.
User avatar
Derren
Enthusiast
Enthusiast
Posts: 313
Joined: Sat Jul 23, 2011 1:13 am
Location: Germany

Re: Passing & Receiving Structures

Post by Derren »

Code: Select all

Structure myRect
    x.i
    y.i
    width.i
    height.i
EndStructure

Define cgRECT.myRect
CGRectMake(cgRECT, x, y, width, height)  ;<<=== cgRECT is a unique RECT structure that is populated with the result
CGContextStrokeRect(cgContext, cgRECT) ;<<=== the cgRECT structure is passed to the function to be drawn
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Passing & Receiving Structures

Post by wilbert »

There's a lot of things going on there.

- CGRectMake isn't a C function, it's a C macro so you can't import it using ImportC.
- Sometimes the C functions require a structure to be passed by reference, other times by value. PureBasic only knows how to pass them by reference. If it has to be passed by value, you have to work around the PureBasic limitation by passing the structure elements in sequence and also adapt your function declaration for that.
- PureBasic doesn't know how to handle it when the function returns a structure by value. Most likely you will need inline ASM to create a workaround for this.
User avatar
idle
Always Here
Always Here
Posts: 5094
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Passing & Receiving Structures

Post by idle »

can you show us the c headers?

Code: Select all

Structure rect
  x.i
  y.i
  width.i
  height.i
EndStructure   

;To create the macro 
;pass in a local Or Global scope rect that you've declared 
Macro mCgRectMake(cgrect,mx,my,mwidth,mheight) 
  cgrect\x = mx 
  cgrect\y = my 
  cgrect\width = mwidth
  cgrect\height = mheight 
EndMacro 

;or you can create one dynamically on the heap, you need to free it
Procedure CGRectMake(mx, my, mwidth, mheight) 
  Protected *cgrect.rect
  *cgrect = AllocateMemory(SizeOf(rect)) ; 
  *cgrect\x = mx 
  *cgrect\y = my 
  *cgrect\width = mwidth 
  *cgrect\height = mheight
  ProcedureReturn *cgrect 
EndProcedure   

;I'd assume the external c function would want a pointer rather than the structure 
Procedure CGContextStrokeRect(cgContext,cgRECT)
*mrect.rect = cgRect 
Debug *mRECT\x
Debug *mRECT\y
Debug *mRECT\width
Debug *mRECT\height
EndProcedure 

;global or protected 
Global myRect.rect 
mCgRectMake(myRect,10,10,200,200) 
;pass the address 
CGContextStrokeRect(cgContext,@myrect)

;heap allocated rect 
*myRect.rect = CGRectMake(10,10,200,200) 
;pass the pointer 
CGContextStrokeRect(cgContext,*myRect)
freememory(*myrect) 

Windows 11, Manjaro, Raspberry Pi OS
Image
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Passing & Receiving Structures

Post by wilbert »

A little background information, that hopefully helps to understand and work around the problems.

When a simple function like MyFunction(A, B, C) is called, it has to know what variables are passed.
Envision each variable as a little paper that is placed on a large stack of papers. They are placed in reverse order. When the function is called, the cpu adds a special paper on top with the return address so it knows where to return when the function exits with a return statement. So in this case, the stack contains
- ReturnAddress - A - B - C - ...

Is makes perfect sense that these little papers with the variables have to come off that stack again.
This is where calling conventions get important. When a C function is called, it expects the caller to get those papers out of the stack. When a PureBasic function is called, it expects the function that is called to get those papers out of the stack. That's why there is Import / ImportC, Procedure / ProcedureC so PureBasic knows who should take care of the cleaning up.

Now, when a structure is passed, usually a reference is passed so the function knows where to find the structure. In the case of some OS X functions, not the reference is passed but the structure itself by value. So if the structure contains two floats like a CGPoint structure and it is passed by value, two of those little papers are placed on the stack while there would only be one if it would be passed by reference.
So to work around this, you can simply pass the structure as if it were multiple parameters and treat the ImportC declaration likewise.

Then there's the part of the structure return. Again, usually it is returned by reference and that is no problem for PureBasic. In this case the function returns one value and that is a reference to the structure.
But there are OS X functions that return the structure itself; described like this on Wikipedia
Some compilers return simple data structures with the length of 2 registers or less in EAX:EDX, and larger structures and class objects requiring special treatment by the exception handler (e.g., a defined constructor, destructor, or assignment) are returned in memory. To pass "in memory", the caller allocates memory and passes a pointer to it as a hidden first parameter; the callee populates the memory and returns the pointer, popping the hidden pointer when returning.
To work around the issue, you can rewrite the ImportC statement - if the return structure is larger than 8 bytes - as if it has an extra initial parameter with a return structure reference but correct the stack pointer so the program won't crash.
For return structures that are not larger than 8 bytes, you need to use AEX:EDX (cpu registers).

So to make OS X functions like ...

CGAffineTransform CGAffineTransformTranslate ( CGAffineTransform t, CGFloat tx, CGFloat ty );
CGPoint CGPointApplyAffineTransform ( CGPoint point, CGAffineTransform t );

work in PureBasic, you have to do this ...

Code: Select all

EnableExplicit

ImportC ""
  CGAffineTransformTranslate (*returnStructure,   t_a.f, t_b.f, t_c.f, t_d.f, t_tx.f, t_ty.f,   tx.f, ty.f)
  CGPointApplyAffineTransform (p_x.f, p_y.f,   t_a.f, t_b.f, t_c.f, t_d.f, t_tx.f, t_ty.f)
EndImport

Define Tr.CGAffineTransform
Define TrResult.CGAffineTransform

Define P.CGPoint
Define PResult.CGPoint

Tr\a = 1
Tr\b = 0
Tr\c = 0
Tr\d = 1
Tr\tx = 20
Tr\ty = 40

P\x = 5
P\y = 20

; translate Tr by (20, 50) and correct stack pointer (return structure > 8 bytes)

CGAffineTransformTranslate(@TrResult, Tr\a, Tr\b, Tr\c, Tr\d, Tr\tx, Tr\ty, 20, 50)
!sub esp,4

Debug TrResult\ty

; apply transform Tr to point and get result from EAX:EDX (return structure <= 8 bytes)

CGPointApplyAffineTransform(P\x, P\y, Tr\a, Tr\b, Tr\c, Tr\d, Tr\tx, Tr\ty) 
!mov [v_PResult],eax
!mov [v_PResult + 4],edx

Debug PResult\x
Last edited by wilbert on Fri Oct 05, 2012 3:04 pm, edited 1 time in total.
coder14
Enthusiast
Enthusiast
Posts: 327
Joined: Tue Jun 21, 2011 10:39 am

Re: Passing & Receiving Structures

Post by coder14 »

@Derren: Thank you.

@idle: Thank you for your illustrative answer; it clears up a lot for me, and I'm sure for others as well.

@wilbert: Thank you for a very nice tutorial, and creative workarounds. Another one for the OSX API list.
void
Enthusiast
Enthusiast
Posts: 116
Joined: Sat Aug 27, 2011 9:50 pm
Location: Washington, USA

Re: Passing & Receiving Structures

Post by void »

To any other future forum searchers who might be looking at this problem on the x64 architecture, this might be of use:
http://www.altdevblogaday.com/2012/05/2 ... onvention/
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Passing & Receiving Structures

Post by wilbert »

void wrote:To any other future forum searchers who might be looking at this problem on the x64 architecture, this might be of use:
http://www.altdevblogaday.com/2012/05/2 ... onvention/
Calling conventions are complicated.
The link you gave only applies to windows.
On Linux and OS X, "System V AMD64 ABI" is used.
void
Enthusiast
Enthusiast
Posts: 116
Joined: Sat Aug 27, 2011 9:50 pm
Location: Washington, USA

Re: Passing & Receiving Structures

Post by void »

wilbert wrote:
void wrote:To any other future forum searchers who might be looking at this problem on the x64 architecture, this might be of use:
http://www.altdevblogaday.com/2012/05/2 ... onvention/
Calling conventions are complicated.
The link you gave only applies to windows.
On Linux and OS X, "System V AMD64 ABI" is used.
I know. I hadn't yet gotten around to researching the other OSes yet. I just thought it would be nice to share what I had so far, and this seemed a good place to put it.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Passing & Receiving Structures

Post by wilbert »

void wrote:I know. I hadn't yet gotten around to researching the other OSes yet. I just thought it would be nice to share what I had so far, and this seemed a good place to put it.
It's good you mentioned it since things are different on 64 bit compared to 32 bit :)
void
Enthusiast
Enthusiast
Posts: 116
Joined: Sat Aug 27, 2011 9:50 pm
Location: Washington, USA

Re: Passing & Receiving Structures

Post by void »

wilbert wrote:
void wrote:I know. I hadn't yet gotten around to researching the other OSes yet. I just thought it would be nice to share what I had so far, and this seemed a good place to put it.
It's good you mentioned it since things are different on 64 bit compared to 32 bit :)
One thing I'm unclear on:

For x86 above you decrement esp by 4, which is the size of the register.

I'm reading things in MSDN that say that the x64 stack pointer is maintained as 16 byte aligned. (is this windows only? or a general x64 convention?)

What implications does this have for the 'hidden parameter' usage? Do I need to decrement RSP by 8, by 16, or conditionally based on the number and size of the other arguments?
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Passing & Receiving Structures

Post by wilbert »

To be honest, I don't know how the stack is handled on x64 when a structure has to be returned.

The stack alignment means that the stack pointer has to be 16 byte aligned when a function is called.
I believe Windows still functions even if you don't follow this but on OS X the application will crash if the stack isn't 16 byte aligned at the time an OS function is called.
void
Enthusiast
Enthusiast
Posts: 116
Joined: Sat Aug 27, 2011 9:50 pm
Location: Washington, USA

Re: Passing & Receiving Structures

Post by void »

wilbert wrote:To be honest, I don't know how the stack is handled on x64 when a structure has to be returned.

The stack alignment means that the stack pointer has to be 16 byte aligned when a function is called.
I believe Windows still functions even if you don't follow this but on OS X the application will crash if the stack isn't 16 byte aligned at the time an OS function is called.
Unfortunate. I have everything I need in order to get the information to and from the functions but I'd really like to try to maintain stack integrity afterwards. I suppose my only choice is to try it and find out.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Passing & Receiving Structures

Post by wilbert »

void wrote:Unfortunate. I have everything I need in order to get the information to and from the functions but I'd really like to try to maintain stack integrity afterwards. I suppose my only choice is to try it and find out.
You probably could do something like this and see if the stack pointer has changed.

Code: Select all

Global RSP1.i
Global RSP2.i

!mov [v_RSP1], rsp

; make the call

!mov [v_RSP2], rsp
Debug RSP2 - RSP1
Maybe libffi could also be a solution
http://sourceware.org/libffi/
void
Enthusiast
Enthusiast
Posts: 116
Joined: Sat Aug 27, 2011 9:50 pm
Location: Washington, USA

Re: Passing & Receiving Structures

Post by void »

wilbert wrote:
void wrote:Unfortunate. I have everything I need in order to get the information to and from the functions but I'd really like to try to maintain stack integrity afterwards. I suppose my only choice is to try it and find out.
You probably could do something like this and see if the stack pointer has changed.

Code: Select all

Global RSP1.i
Global RSP2.i

!mov [v_RSP1], rsp

; make the call

!mov [v_RSP2], rsp
Debug RSP2 - RSP1
Yeah, I figured that'd be the way to verify what really happens. I just need to get around to getting a 64-bit version of the DLL for testing first. :D
wilbert wrote:Maybe libffi could also be a solution
http://sourceware.org/libffi/
I did not know this exists. This could be amazing for my language. Thank you.

For what I'm working on right now, FFI would probably be the more complicated/difficult approach, but I'm glad to learn about it. It will almost certainly be useful later.
Post Reply