我正在尝试使用c ++编程motorbee
当我运行代码时,我收到以下错误:
运行时检查失败#0 - ESP的值是 没有在函数调用中正确保存。 这通常是调用声明的函数的结果 一个调用约定,声明了一个函数指针 与不同的召集惯例。
这是我的代码。
#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);
}
答案 0 :(得分:9)
您的typedef
函数指针需要与您正在使用的库的calling convention匹配。例如,如果InitMotoBee
使用cdecl
,则typedef
将如下所示:
typedef bool (__cdecl *Type_InitMotoBee)(void)
SetMotors
函数接受参数,因此也需要正确设置调用约定(这可能是应用程序失败的地方)。
答案 1 :(得分:4)
错误消息告诉您ESP寄存器(堆栈指针)未正确维护&#34;。它没有应有的价值。
当您使用非托管语言(如C或C ++)进行函数调用时,函数的参数将被推送到堆栈 - 增加堆栈指针。当函数调用返回时,弹出参数 - 减少堆栈指针。
必须始终将堆栈指针恢复为函数调用之前的值。
调用约定精确指定应如何维护堆栈,以及调用者或被调用者是否负责从堆栈中弹出参数。
例如,在stdcall调用约定中,调用 ee 负责在函数返回之前恢复堆栈指针。在cdecl调用约定中,调用 er 负责。
显然混合调用约定很糟糕!如果呼叫 er 正在使用stdcall,则它希望呼叫 ee 来维护堆栈。如果调用 ee 正在使用cdecl,那么它希望调用 er 来维护堆栈。最终结果:没有人维护堆栈!或者相反的例子:每个人都在维护堆栈,这意味着它会被恢复两次并最终出错。
供参考,请查看this StackOverflow question。
Raymond Chen在这个问题上有一个很好的blog post。
这超出了本答案的范围,但如果您正在进行C#到C interop,那么了解调用约定是很重要的。
在Visual Studio中,C / C ++项目的默认调用约定是cdecl。
在.Net中,使用DllImport进行互操作调用的默认调用约定是stdcall。这也适用于代表。 (大多数本机Windows功能都使用stdcall。)
请考虑以下(不正确的)互操作。
[DllImport("MyDll", EntryPoint = "MyDll_Init"]
public static extern void Init();
它正在使用stdcall调用约定,因为它是.Net的默认值。如果您还没有更改MyDLL项目的Visual Studio项目设置,您很快就会发现这不起作用。 C / C ++ DLL项目的默认值为cdecl。
正确的互操作电话将是:
[DllImport("MyDll", EntryPoint = "MyDll_Init", CallingConvention = CallingConvention.Cdecl)]
public static extern void Init();
请注意显式的CallingConvention属性。 C#interop包装器将知道生成cdecl调用。
如果您确定您的呼叫约定是正确的,您可能仍会遇到运行时检查失败#0。
回想一下,函数参数在函数调用开始时被压入堆栈,然后在结束时再次弹出。为了确保正确维护堆栈,参数的大小必须在push和pop之间保持一致。
在本机代码中,编译器将为您处理此问题。你永远不需要考虑。当谈到C和C#之间的互操作时,你可能会被咬伤。
如果你在C#中有一个stdcall委托,就像这样:
public delegate void SampleTimeChangedCallback(SampleTime sampleTime);
对应于C函数指针,如下所示:
typedef void(__stdcall *SampleTimeChangedCallback)(SampleTime sampleTime);
一切都应该没问题。您在双方使用相同的调用约定(默认情况下,C#interop使用stdcall,我们在本机代码中明确设置__stdcall)。
但请看一下这些参数:SampleTime结构。它们都具有相同的名称,但一个是本机结构,另一个是C#结构。
本机结构看起来像这样:
struct SampleTime
{
__int64 displayTime;
__int64 playbackTime;
}
C#struct看起来像这样:
[StructLayout(LayoutKind.Explicit, Size = 32)]
public struct SampleTime
{
[FieldOffset(0)]
private long displayTime;
[FieldOffset(8)]
private long playbackTime;
}
查看C#结构的Size属性 - 它错了!两个8字节长度意味着16字节大小。也许有人删除了一些字段并且无法更新Size属性。
现在,当本机代码使用stdcall调用SampleTimeChangedCallback函数时,我们遇到了问题。
回想一下,在stdcall中,被调用者 - 即被调用的函数 - 负责恢复堆栈。
所以:调用者将参数推送到堆栈。在此示例中,这是在本机代码中发生的。参数的大小是编译器已知的,因此保证堆栈指针递增的值是正确的。
然后执行该功能 - 请记住,实际上这是一个c#委托。
由于我们正在使用stdcall,因此被调用者(c#delegate)负责恢复堆栈。但是在C#land中,我们对编译器撒了谎,告诉它SampleTime结构的大小是32字节,当它真的只有16时。
我们违反了One Definition Rule。
C#编译器别无选择,只能相信我们告诉它的内容,因此它将会恢复&#34;堆栈指针由32字节组成。
当我们返回呼叫站点(在本地)时,堆栈指针未正确恢复,所有投注均已关闭。
如果运气好的话,您将遇到#0的运行时检查。如果你不走运,程序可能不会马上崩溃。您可以确定的一件事是:您的程序不再执行您认为的代码。
答案 2 :(得分:0)
我最终将编译器选项从/ RTC1(实际上是/ RTC和/ RTCu)更改为/ RTCu。 http://support.microsoft.com/kb/822039
答案 3 :(得分:0)
我遇到了类似的问题,其中出现了相同的错误消息。
我用以下方式解决了这个问题。 在我的情况下出现问题,当我试图将成员函数作为回调传递给线程以执行异步调用。该类本身是由可执行项目调用的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..";
}
成员函数OGLModel::AsyncVertexDispose
的声明是通过在标题中使用virtual
来执行的。删除virtual
限定符后,ESP错误消息消失。
我没有有效的解释,但有些想法。我认为c ++如何处理内存中的成员函数调用(静态内存分配,动态内存分配)。您可以查看Difference between static memory allocation and dynamic memory allocation
答案 4 :(得分:0)
使用Visual Studio 2019 DLL出现类似的问题,该DLL内部使用Visual Studio 2017中编写的第3方库,该库使用Microsoft特定的__thiscall
调用约定。我需要在Delphi 7应用程序中调用回调。在早期版本的MSVC中,DLL使用__cdecl
调用约定,因此我的回调在Delphi中定义为:
TExternalProcCallbackDetectorError = procedure(dwError: DWORD); cdecl;
过去,这种类型的原型已与许多VS2003 DLL一起使用,没有任何问题。但是,当VS2019 C ++ DLL调用回调时,将调用Delphi代码...,然后引发Run-Time Check Failure #0
异常。灾难!
挠了一下头之后,我偶然发现了这个答案,尤其是@Rob的答案(感谢Rob!)。 Delphi不支持__thiscall
,但是将Delphi原型更改为以下内容可以解决此问题:
TExternalProcCallbackDetectorError = procedure(dwError: DWORD); stdcall;