C++ 运行时检查失败 #0 - ESP 的值未在函数调用中正确保存
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10079625/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C++ Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call
提问by user1322682
I am trying to program motorbee using c++
我正在尝试使用 C++ 对 motorbee 进行编程
when I run the code I get the following error:
当我运行代码时,出现以下错误:
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
运行时检查失败 #0 - ESP 的值未在函数调用中正确保存。这通常是调用一个用一个调用约定声明的函数和一个用不同调用约定声明的函数指针的结果。
This is my code.
这是我的代码。
#include "stdafx.h"
#include <iostream>
#include "windows.h"
#include "mt.h"
using namespace std;
HINSTANCE BeeHandle= LoadLibrary("mtb.dll");
Type_InitMotoBee InitMotoBee;
Type_SetMotors SetMotors;
Type_Digital_IO Digital_IO;
void main ()
{
InitMotoBee = (Type_InitMotoBee)GetProcAddress( BeeHandle,"InitMotoBee");
SetMotors =(Type_SetMotors)GetProcAddress(BeeHandle,"SetMotors");
Digital_IO =(Type_Digital_IO)GetProcAddress(BeeHandle,"Digital_IO ");
InitMotoBee();
SetMotors(0, 50, 0, 0, 0, 0, 0, 0, 0);
}
回答by Joe
Your typedef
function pointer needs to match the calling conventionof the library you are using. For example, if InitMotoBee
uses cdecl
your typedef
will look like:
您的typedef
函数指针需要匹配您正在使用的库的调用约定。例如,如果InitMotoBee
使用cdecl
你的typedef
遗嘱:
typedef bool (__cdecl *Type_InitMotoBee)(void)
The SetMotors
function takes parameters so the calling convention will need to be set correctly for that as well (that is likely where the application is failing).
该SetMotors
函数采用参数,因此也需要为此正确设置调用约定(这很可能是应用程序失败的地方)。
回答by Rob
The error message is telling you that the ESP register (Stack Pointer) has not been properly "maintained". It doesn't have the value it should have.
错误消息告诉您 ESP 寄存器(堆栈指针)未正确“维护”。它没有应有的价值。
When you make a function call in an unmanaged language like C or C++ the arguments to the function are pushed on to the stack - increasing the stack pointer. When the function call returns, the arguments are popped back off - decreasing the stack pointer.
当您使用 C 或 C++ 等非托管语言进行函数调用时,函数的参数会被推送到堆栈中 - 增加堆栈指针。当函数调用返回时,参数被弹回 - 减少堆栈指针。
The stack pointer must always be restored to the same value it had before the function call.
堆栈指针必须始终恢复到它在函数调用之前的相同值。
Calling Conventions
调用约定
A calling convention specifies precisely how the stack should be maintained, and whether the caller or callee is responsible for popping the arguments off the stack.
调用约定精确地指定了堆栈应该如何维护,以及是调用者还是被调用者负责将参数从堆栈中弹出。
For example, in the stdcall calling convention the calleeis responsible for restoring the stack pointer before the function returns. In the cdecl calling convention, the calleris responsible.
例如,在 stdcall 调用约定中,调用ee负责在函数返回之前恢复堆栈指针。在cdecl调用约定,调用呃负责。
It should be obvious that mixing calling conventions is bad! If the calleris using stdcall, it's expecting the calleeto maintain the stack. If the calleeis using cdecl, it's expecting the callerto maintain the stack. End result: no one is maintaining the stack! Or the opposite example: everyone is maintaining the stack, meaning it gets restored twice and ends up wrong.
很明显,混合调用约定是不好的!如果呼叫ER使用STDCALL,它期待的呼叫EE保持堆栈。如果呼叫EE使用的cdecl,它期待的呼叫呃保持堆栈。最终结果:没有人维护堆栈!或者相反的例子:每个人都在维护堆栈,这意味着它被恢复两次并最终出错。
For reference, look at this StackOverflow question.
作为参考,请查看此 StackOverflow 问题。
Raymond Chen has a good blog poston the subject.
Raymond Chen在这个主题上有一篇很好的博客文章。
Which Calling Convention Should You Use?
您应该使用哪种调用约定?
That's beyond the scope of this answer, but if you're doing C# to C interop, it is important to know what calling conventions are in place.
这超出了本答案的范围,但如果您正在执行 C# 到 C 的互操作,那么了解调用约定是什么就很重要。
In Visual Studio, the default calling convention for a C/C++ project is cdecl.
在 Visual Studio 中,C/C++ 项目的默认调用约定是 cdecl。
In .Net the default calling convention for interop calls using DllImport is stdcall. This applies to delegates too. (Most native Windows functions use stdcall.)
在 .Net 中,使用 DllImport 的互操作调用的默认调用约定是 stdcall。这也适用于代表。(大多数本机 Windows 函数使用 stdcall。)
Consider the following (incorrect) interop call.
考虑以下(不正确的)互操作调用。
[DllImport("MyDll", EntryPoint = "MyDll_Init"]
public static extern void Init();
It is using the stdcall calling convention, because that is .Net's default. If you haven't changed the Visual Studio project settings for your MyDLL project, you'll soon find this doesn't work. The default for a C/C++ DLL project is cdecl.
它使用 stdcall 调用约定,因为这是 .Net 的默认设置。如果您没有更改 MyDLL 项目的 Visual Studio 项目设置,您很快就会发现这不起作用。C/C++ DLL 项目的默认值是 cdecl。
The correct interop call would be:
正确的互操作调用是:
[DllImport("MyDll", EntryPoint = "MyDll_Init", CallingConvention = CallingConvention.Cdecl)]
public static extern void Init();
Note the explicit CallingConvention attribute. The C# interop wrapper will know to generate a cdecl call.
请注意显式 CallingConvention 属性。C# 互操作包装器将知道生成 cdecl 调用。
What else can go wrong?
还有什么问题?
If you're sure your calling conventions are correct, you might still encounter run-time check failure #0.
如果您确定您的调用约定是正确的,您可能仍会遇到运行时检查失败 #0。
Marshalling Structs
编组结构
Recall that the function arguments are pushed on to the stack at the start of a function call, then popped off again at the end. In order to ensure that the stack is correctly maintained, the sizes of the arguments must be consistent between the push and pop.
回想一下,函数参数在函数调用开始时被压入堆栈,然后在结束时再次弹出。为了保证栈被正确维护,入栈和出栈的参数大小必须一致。
In native code, the compiler will deal with this for you. You never need to think about. When it comes to interop between C and C#, you might get bitten.
在本机代码中,编译器会为您处理这个问题。你永远不需要考虑。当谈到 C 和 C# 之间的互操作时,您可能会被咬。
If you have a stdcall delegate in C#, something like this:
如果您在 C# 中有一个 stdcall 委托,则如下所示:
public delegate void SampleTimeChangedCallback(SampleTime sampleTime);
which corresponds to a C function pointer, something like this:
它对应于 C 函数指针,如下所示:
typedef void(__stdcall *SampleTimeChangedCallback)(SampleTime sampleTime);
everything should be fine. You're using the same calling convention on both sides (C# interop uses stdcall by default, and we're explicitly setting __stdcall in the native code).
一切都应该没问题。您在双方使用相同的调用约定(C# 互操作默认使用 stdcall,我们在本机代码中显式设置 __stdcall)。
But look at those parameters: the SampleTime struct. They both have the same name, but one is a native struct, the other is a C# struct.
但是看看这些参数:SampleTime 结构。它们都具有相同的名称,但一个是本机结构,另一个是 C# 结构。
The native struct looks something like this:
本机结构看起来像这样:
struct SampleTime
{
__int64 displayTime;
__int64 playbackTime;
}
The C# struct looks like this:
C# 结构如下所示:
[StructLayout(LayoutKind.Explicit, Size = 32)]
public struct SampleTime
{
[FieldOffset(0)]
private long displayTime;
[FieldOffset(8)]
private long playbackTime;
}
Look at the Size attribute on the C# struct - it's wrong! Two 8-byte longs means a 16-byte size. Perhaps someone has removed some fields and failed to update the Size attribute.
看看 C# 结构体上的 Size 属性——这是错误的!两个 8 字节长表示 16 字节大小。也许有人删除了一些字段并且未能更新 Size 属性。
Now, when the native code calls the SampleTimeChangedCallback function, using stdcall, we run into a problem.
现在,当本机代码使用 stdcall 调用 SampleTimeChangedCallback 函数时,我们遇到了问题。
Recall that in stdcall, the callee- i.e. the function being called - is responsible for restoring the stack.
回想一下在 stdcall 中,被调用者——即被调用的函数——负责恢复堆栈。
So: the caller pushes parameters on to the stack. In this example, that's happening in native code. The size of the parameters is known by the compiler, so value by which the stack pointer is incremented is guaranteed to be correct.
所以:调用者将参数压入堆栈。在本例中,这发生在本机代码中。编译器知道参数的大小,因此保证堆栈指针递增的值是正确的。
The function then executes - remember that in reality this is a c# delegate.
然后执行该函数 - 请记住,实际上这是 ac# 委托。
Since we're using stdcall, the callee - the c# delegate - is responsible for restoring the stack. But in C# land we have lied to the compiler and told it that the size of the SampleTime structure is 32 bytes when it's really only 16.
由于我们使用的是 stdcall,被调用者——c# 委托——负责恢复堆栈。但是在 C# 领域,我们对编译器撒了谎,告诉它 SampleTime 结构的大小是 32 字节,而实际上只有 16 个字节。
We've violated the One Definition Rule.
我们违反了单一定义规则。
The C# compiler has no option but to believe what we tell it, so it will "restore" the stack pointer by 32bytes.
C# 编译器别无选择,只能相信我们所说的,因此它会将堆栈指针“恢复”32 字节。
When we return back to the callsite (in native land) the stack pointer has NOT been properly restored, and all bets are off.
当我们返回调用站点(在本地)时,堆栈指针尚未正确恢复,所有赌注都已关闭。
If you are lucky, you'll encounter a run-time check #0. If you're unlucky the program might not crash straight away. The one thing you can be sure of: your program is no longer executing the code you thought it was.
如果幸运的话,您会遇到运行时检查 #0。如果您不走运,程序可能不会立即崩溃。您可以确定的一件事是:您的程序不再执行您认为的代码。
回答by Diversity
I had a similar problem where the same error message appeared.
我遇到了类似的问题,出现了相同的错误消息。
I solved it in the following way. In my case the problem occured, when i tried to pass a member function as a callback to a thread in order to perform an asychron call. The class itself was part of a DLL (subcomponent) which was invoked by an executable project.
我通过以下方式解决了它。在我的情况下,当我尝试将成员函数作为回调传递给线程以执行异步调用时,出现了问题。类本身是由可执行项目调用的 DLL(子组件)的一部分。
OGLModel::~OGLModel() {
std::thread delVertexThread(&OGLModel::AsyncDisposeVertices, this, vertices);
delVertexThread.join();
}
void OGLModel::AsyncDisposeVertices(std::vector<OGLVertex> *vertices)
{
std::cout << "OGLModel garbage collection active..";
if (vertices != 0) {
std::vector<OGLVertex> *swap = new std::vector<OGLVertex>();
vertices->swap(*swap);
delete vertices;
}
std::cout << "OGLModel garbage collection finished..";
}
The declaration of the member function OGLModel::AsyncVertexDispose
was performed by using virtual
within the header. After removing the virtual
qualifier the ESP error message disappeared.
成员函数的声明OGLModel::AsyncVertexDispose
是通过virtual
在头文件中使用来执行的。删除virtual
限定符后,ESP 错误消息消失了。
I have no valid explanation for it but some idea. I think it has something to do how c++ handles its member function calls within memory (static memory allocation, dynamic memory allocation). You may have a look at Difference between static memory allocation and dynamic memory allocation
我对此没有有效的解释,但有一些想法。我认为这与 c++ 如何处理内存中的成员函数调用(静态内存分配、动态内存分配)有关。您可能会查看静态内存分配和动态内存分配之间的差异
回答by AlainD
Had similar problem using a Visual Studio 2019 DLL which internally used a 3rd-party library written in Visual Studio 2017 which used the Microsoft-specific __thiscall
calling convention. I needed to invoke a callback in a Delphi 7 application. In earlier versions of MSVC, DLLs used the __cdecl
calling convention, so my callback was defined in Delphi as:
使用 Visual Studio 2019 DLL 时遇到类似问题,该 DLL 在内部使用了在 Visual Studio 2017 中编写的第 3 方库,该库使用了 Microsoft 特定的__thiscall
调用约定。我需要在 Delphi 7 应用程序中调用回调。在 MSVC 的早期版本中,DLL 使用__cdecl
调用约定,因此我的回调在 Delphi 中定义为:
TExternalProcCallbackDetectorError = procedure(dwError: DWORD); cdecl;
This type of prototype had been used with numerous VS2003 DLLs in the past without any issue. But when the VS2019 C++ DLL invoked the callback, the Delphi code was called...and then the Run-Time Check Failure #0
exception was thrown. Disaster!
这种类型的原型过去曾与许多 VS2003 DLL 一起使用,没有任何问题。但是当VS2019 C++ DLL调用回调时,调用了Delphi代码……然后Run-Time Check Failure #0
抛出异常。灾难!
After scratching my head for some time, I stumbled on this answer, in particular @Rob's (thanks Rob!). Delphi did not support __thiscall
, but changing the Delphi prototype to the following resolved the issue:
在挠头一段时间后,我偶然发现了这个答案,特别是@Rob 的(感谢 Rob!)。Delphi 不支持__thiscall
,但将 Delphi 原型更改为以下解决了该问题:
TExternalProcCallbackDetectorError = procedure(dwError: DWORD); stdcall;
回答by BuilderBee
I ended up changing the compiler option from /RTC1 (which is effectively both /RTCs and /RTCu) to /RTCu. http://support.microsoft.com/kb/822039
我最终将编译器选项从 /RTC1(实际上是 /RTCs 和 /RTCu)更改为 /RTCu。 http://support.microsoft.com/kb/822039