从Delphi调用特定的Win32 API - 为什么异常会在没有“asm pop ...”的情况下飞行?

时间:2022-10-28 23:05:43

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.

我正在使用Delphi为Ex​​cel制作XLL加载项,这涉及对xlcall32.dll的Excel4v函数进行大量调用。但是,我猜这里的Delphi专家很少使用这个特定的API,我希望在其他API中也可以观察到这个问题。

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

在C中,特别是在Microsoft Excel 2007 XLL SDK附带的xlcall.h文件中,Excel4v定义为:

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

In Delphi I'm using:

在Delphi我正在使用:

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

LPXLOPER是指向结构(在C中)或记录(在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:

我一直在做我在Delphi中声明C函数的功课(这篇优秀的文章是一个很好的帮助),我想我正在宣布Excel4v。但是,从Delphi代码调用该函数会导致异常(“访问违规......”是我一直看到的),除非后面跟着以下行:

asm pop sink; end;

Where "sink" is defined somewhere as an integer.

其中“sink”在某处定义为整数。

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:

我对组装没有任何线索......所以我没想办法用“asm pop sink; end;”来修复异常。但是“asm pop sink; end;”确实修复了异常。我第一次看到它在这篇有用的文章中用于使用Delphi制作XLL。这是最相关的报价:

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

“来自Delphi,带有加载项的大绊脚石是堆栈上返回地址之后的额外参数。每次调用Excel都可以免费使用。我从来没有发现它拥有什么,但只要你扔掉它,你的加载项工作正常。添加行asm pop变量,结束;在每次调用之后,变量可以是任何至少4个字节长的全局,局部或对象变量 - 整数就好了。要重复 - 这必须是在每次Excel4v通话后都包括在内。否则你正在制造定时炸弹。“

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?

基本上我想了解实际发生了什么,以及为什么。什么可能导致Win32函数返回“堆栈上的返回地址后的额外参数”,这实际上是什么意思?

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

并且有什么风险可以称为“asm pop sink; end;”每次调用Excel4v后......?它看起来工作正常,但是,由于我不明白发生了什么,感觉有点危险......

3 个解决方案

#1


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.

我不相信它是pascal vs stdcall - 它们是非常相似的调用约定,不应该在函数退出时导致不匹配的堆栈。

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.

这确实是一个非常好的语法,但它与上面的数组定义不同。数组参数是开放数组参数。它们可能看起来像任何数组,并且它们接受任何数组,但它们会获得一个额外的(隐藏)参数,该参数保存数组中的最高索引(高值)。因为这只是在Delphi中,而不是在C或C ++中,所以你会遇到一个真正的问题。 (另请参阅我关于开放数组的文章),因为参数的实际数量不匹配。

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.

您将获得传递给函数的额外“最高数组索引”参数。这是一个int,当函数退出时必须清理它,这样你就不会遇到损坏的堆栈并崩溃。本文指出如何将数组传递给C函数。

Something like:

type
 PLPXLOPER  = ^LPXLOPER;

And pass PLPXLOPER as the last parameter.

并将PLPXLOPER作为最后一个参数传递。

#2


Most Windows functions use __stdcall for their calling conventions.

大多数Windows函数使用__stdcall作为其调用约定。

#3


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

你的调用约定是错误的,特别是“stdcall”。 C声明被指定为“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.

Stdcall以从右到左的顺序传递参数,期望例程清理,并且不使用寄存器。 Pascal,OTOH按从左到右的顺序传递参数。因此,在任何一种情况下,事情都不会像另一半代码所期望的那样发生。

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

将您的Delphi声明更改为“pascal”而不是“stdcall”。

#1


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.

我不相信它是pascal vs stdcall - 它们是非常相似的调用约定,不应该在函数退出时导致不匹配的堆栈。

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.

这确实是一个非常好的语法,但它与上面的数组定义不同。数组参数是开放数组参数。它们可能看起来像任何数组,并且它们接受任何数组,但它们会获得一个额外的(隐藏)参数,该参数保存数组中的最高索引(高值)。因为这只是在Delphi中,而不是在C或C ++中,所以你会遇到一个真正的问题。 (另请参阅我关于开放数组的文章),因为参数的实际数量不匹配。

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.

您将获得传递给函数的额外“最高数组索引”参数。这是一个int,当函数退出时必须清理它,这样你就不会遇到损坏的堆栈并崩溃。本文指出如何将数组传递给C函数。

Something like:

type
 PLPXLOPER  = ^LPXLOPER;

And pass PLPXLOPER as the last parameter.

并将PLPXLOPER作为最后一个参数传递。

#2


Most Windows functions use __stdcall for their calling conventions.

大多数Windows函数使用__stdcall作为其调用约定。

#3


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

你的调用约定是错误的,特别是“stdcall”。 C声明被指定为“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.

Stdcall以从右到左的顺序传递参数,期望例程清理,并且不使用寄存器。 Pascal,OTOH按从左到右的顺序传递参数。因此,在任何一种情况下,事情都不会像另一半代码所期望的那样发生。

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

将您的Delphi声明更改为“pascal”而不是“stdcall”。