views:

526

answers:

3

I'm using Delphi to make an XLL add-in for Excel, which involves making a lot of calls to the Excel4v function of xlcall32.dll. However, as I'm guessing very few Delphi experts here have worked with that specific API, I'm hoping that the problem might have been observed in other APIs too.

In C, specifically in the xlcall.h file that comes with the Microsoft Excel 2007 XLL SDK, Excel4v is defined as:

int pascal Excel4v(int xlfn, LPXLOPER operRes, int count, LPXLOPER opers[]);

In Delphi I'm using:

function Excel4v(xlfn: Integer; operRes: LPXLOPER; count: Integer;
    opers: array of LPXLOPER): Integer; stdcall; external 'xlcall32.dll';

LPXLOPER is a pointer to a struct (in C) or record (in Delphi).

I've been doing my homework on declaring C functions in Delphi (this excellent article was a great help), and I think I'm declaring Excel4v properly. However, calls from Delphi code into that function cause exceptions ("access violation..." is what I keep seeing) unless they are followed by the following line:

asm pop sink; end;

Where "sink" is defined somewhere as an integer.

I have no clue about assembly... So there's no way would I have thought to try fixing the exceptions with "asm pop sink; end;". But "asm pop sink; end;" does indeed fix the exceptions. I first saw it used in this useful article on making XLLs using Delphi. Here's the most relevant quote:

"From Delphi the big stumbling block with add-ins is the extra parameter after the return address on the stack. This comes free with every call to Excel. I’ve never found out what it holds, but so long as you throw it away, your add-in will work fine. Add the line asm pop variable, end; after every call where variable can be any global, local or object variable that is at least 4 bytes long- integer is fine. To repeat- THIS MUST BE INCLUDED after every Excel4v call. Otherwise you are constructing a time-bomb."

Basically I want to understand what's actually happening, and why. What could be causing a Win32 function to return an "extra parameter after the return address on the stack", and what does that actually mean?

Might there be another way to fix this, e.g. with a different compiler option or a different way of declaring the function?

And is there anything risky about calling "asm pop sink; end;" after every call to Excel4v...? It seems to work fine, but, as I don't understand what's going on, it feels a little dangerous...

A: 

Most Windows functions use __stdcall for their calling conventions.

ChrisW
Thanks for the links ChrisW.
MB
+1  A: 

Your calling convention is wrong, specifically the "stdcall". The C declaration is specified as "pascal"

Stdcall passes parameters in right to left order, expects the routine to clean up, and does not use registers. Pascal, OTOH passes parameters in left to right order. Therefore, things are not happening the way the other half of the code expects in either case.

Change your Delphi declaration to also be "pascal" instead of "stdcall".

Ken White
I tried specifying it as pascal but I couldn't get any calls to it to work then. But the function definitely does work properly with stdcall, provided it's followed with the "asm pop..." line. The article at http://rvelthuis.de/articles/articles-convert.html mentions pascal having a counter-intuitive meaning though it's not all that specific.
MB
They used to be different calling conventions. Nowadays, "pascal" is a macro that means "stdcall" in the C headers.
Rob Kennedy
I've never seen that with any of the MS or Borland C compilers. I've seen _stdcall, __stdcall, and WINAPI used, but never seen "pascal" redefined. (Not saying you're wrong, Rob, just that I haven't seen it.) Regardless, if you're having to resort to an asm pop instruction, there's something wrong with the match of the calling conventions, and therefore my answer still stands. :-)
Ken White
I've seen PASCAL #define'd to __stdcall as well . . . but I'm not sure what it means with respect to delphi. My original thought was mismatched calling convention as well, but for a function like this the stack wouldn't be off by only 4 bytes between caller-save and callee-save conventions.
Michael
MSDN doesn't seem to have much to say about pascal other than that it's "obsolete" according to the link that ChrisW kindly pointed to: http://msdn.microsoft.com/en-us/library/wda6h6df(VS.80).aspxI hunted through the code in the XLL SDK and I couldn't find anything defining pascal as stdcall or anything else, so it's a bit of a mystery. But things are working fine now the array issue is fixed so I'm satisfied albeit still a bit confused (as I usually am with Win32...) :)
MB
+4  A: 

I don't believe it's pascal vs stdcall - they are very similar calling conventions and should not result in a mismatched stack on function exit.

From the referenced article,

This would indeed be a very nice syntax, but it is not the same as the above array definition. Array-of parameters are open array parameters. They may look like any array, and they do accept any array, but they get an extra (hidden) parameter, which holds the highest index in the array (the High value). Since this is only so in Delphi, and not in C or C++, you'd have a real problem. (See also my article on open arrays), since the real number of parameters wouldn't match.

You're getting the extra "highest array index" parameter being passed to the function. This is an int and has to be cleaned up when the function exits so that you don't wind up with a corrupted stack and crash. The article indicates how to pass arrays to C functions.

Something like:

type
 PLPXLOPER  = ^LPXLOPER;

And pass PLPXLOPER as the last parameter.

Michael
Thanks Michael, that sounds like the explanation to me :) I'll play about with it a bit to be sure and comment again when I am.
MB
Fantastic! As far as I can tell it's working perfectly.Define the function as function Excel4v(xlfn: Integer; operRes: LPXLOPER; count: Integer; opers: PLPXLOPER): Integer; stdcall; external 'xlcall32.dll';then to call it, make a Delphi array of LPXLOPER, and call Excel4v with @myArray[0] for PLPXLOPER. Works great, with stdcall, and no need for suspect-looking asm pop calls :)
MB