VC++ 异常处理在 x86 和 x64 上对于 IBPP / Firebird 客户端有所不同

VC++ Exception Handling differ on x86 and x64 for IBPP / Firebird client

本文关键字:IBPP Firebird 有所不同 客户端 异常处理 x86 x64 VC++      更新时间:2023-10-16

我在Visual Studio 2015/VC++上使用IBPP。IBPP是Firebird/interbase API的C ++包装器。 IBPP,Firebird Server 的C++客户端接口

这个软件包的一部分是一个小测试套件,你可以在这里下载: IBPP-2-5-3-1-SRC.zip

要从测试套件开始,您将找到一个简单的批处理文件来编译它

x:...\ibpp-2-5-3-1-src\tests\vs2005\simplest-build.bat

它与 vc++ 2015 的原生 x86 和 x64 工具链编译良好。

在编译之前,您需要编辑第 84 到 86 行

x:...\ibpp-2-5-3-1-src\tests\tests.cpp

const char* DbName = "x:/ibpptest/test.fdb";    // FDB extension (GDB is hacked by Windows Me/XP "System Restore")
const char* BkName = "x:/ibpptest/test.fbk";
const std::string ServerName = ""; //"localhost";   // Change to "" for local protocol / embedded

请记住创建目录x:ibpptest

此外,您需要下载文件,这些文件本身不可用,而是作为整个服务器存档的一部分。获取这两个文件: 32 位嵌入式 和 64 位嵌入式 .

为了简化起见,除了x:...ibpp-2-5-3-1-srctestsvs2005之外,还创建两个目录:

x:...ibpp-2-5-3-1-srctestsvs2015x86
x:...ibpp-2-5-3-1-srctestsvs2015x84

并将x:...ibpp-2-5-3-1-srctestsvs2005simplest-build.bat复制到其中。现在复制以下目录中的 fbclient 文件(32 位到 x86,64 位到 x64):

intl/*
udf/*
fbembed.dll
firebird.msg
ib_util.dll
icudt30.dll
icuin30.dll
icuuc30.dll
msvcp80.dll
msvcr80.dll

现在您可以编译并开始测试.exe。x86 二进制文件在测试 6 中生成一些错误,这没关系,因为您使用的是 fblient 文件的嵌入版本。x64 二进制文件将最终出现在 Windows 程序失败屏幕中。当测试套件激活异常时,在 Test3 中会发生这种情况:

try
{
#if defined(IBPP_WINDOWS) && defined(_DEBUG)
OutputDebugString(_("An exception will now get logged in the debugger: this is expected.n"));
#endif
st1->ExecuteImmediate(  "CREATE SYNTAX ERROR(X, Y) AS "
"SELECT ERRONEOUS FROM MUSTFAIL M" );
}
catch(IBPP::SQLException& e)
{
//~ std::cout<< e.what();
if (e.EngineCode() != 335544569)
{
_Success = false;
printf(_("The error code returned by the engine during an"
"voluntary statement syntax error is unexpected.n"));
}
}

在 x86 二进制文件中,此异常按预期捕获,但在 x64 二进制文件中不会。有没有人知道如何在 x64 二进制文件中实现类似的异常处理?

提前感谢任何帮助!

警告:我使用 Visual Studio 2017 环境运行最简单的构建.bat文件。

从我在下面提供的证据来看,Firebird 服务的 32 位和 64 位版本之间存在分支或其他编译差异,从而导致此问题。

解决方案:EngineCode() 成员函数在 64 位版本中不存在。您必须使用异常的 what() 成员函数,如 Test3() catch 块中注释掉的行所示。如果你想使用EngineCode信息,你必须从what()字符串中解析它,因为它包含了最初作为32位IBPP::SQLExceptionImpl类中的单个数据成员提供的所有信息。

try
{
#if defined(IBPP_WINDOWS) && defined(_DEBUG)
OutputDebugString(_("An exception will now get logged in the debugger: this is expected.n"));
#endif
st1->ExecuteImmediate(  "CREATE SYNTAX ERROR(X, Y) AS "
"SELECT ERRONEOUS FROM MUSTFAIL M" );
}
catch(IBPP::SQLException& e)
{
//~ std::cout<< e.what();
printf(e.what());
//if (e.EngineCode() != 335544569)
//{
//  _Success = false;
//  printf(_("The error code returned by the engine during an"
//      "voluntary statement syntax error is unexpected.n"));
//}
}

what() 调用的结果。

*** IBPP::SQLException ***
Context: Statement::ExecuteImmediate( CREATE SYNTAX ERROR(X, Y) AS SELECT ERRONEOUS FROM MUSTFAIL M )
Message: isc_dsql_execute_immediate failed
SQL Message : -104
can't format message 13:896 -- message file C:WINDOWSSYSTEM32firebird.msg not found
Engine Code    : 335544569
Engine Message :
Dynamic SQL Error
SQL error code = -104
Token unknown - line 1, column 8
SYNTAX

谜题:语句.cpp显示了IBPP::SQLExceptionImpl和其他...ExceptionImpl 用法,所以我不得不相信这个源代码是 32 位分支。如果这应该是 32 位和 64 位的共同分支,那么我看不出它是如何工作的。

有两个头文件定义异常类。我相信 _ibbp.h 用于编译 32 位客户端,ibbp.h 用于 64 位客户端。我通过更改 Test3() 中的 catch 子句得出了这些结论,以查看可能发生的异常。这。。。ExceptionImpl 类只能使用 32 位客户端 dll 进行编译,而不能使用 64 位 dll 进行编译。

从 _ibpp.h (32 位 fbclient dll 识别这些类,64 位 fbclient dll 不识别。

///////////////////////////////////////////////////////////////////////////////
//
//  Implementation of the "hidden" classes associated with their public
//  counterparts. Their private data and methods can freely change without
//  breaking the compatibility of the DLL. If they receive new public methods,
//  and those methods are reflected in the public class, then the compatibility
//  is broken.
//
///////////////////////////////////////////////////////////////////////////////
//
// Hidden implementation of Exception classes.
//
/*
std::exception
|
IBPP::Exception
/                 
/                   
IBPP::LogicException    ExceptionBase    IBPP::SQLException
|                 /   |          /
|   LogicExceptionImpl |   SQLExceptionImpl
|                      |
IBPP::WrongType            |
               |
IBPP::WrongTypeImpl
*/

来自 ibpp.h (两个 fbclient dll 集都识别这些类)

/* IBPP never return any error codes. It throws exceptions.
* On database engine reported errors, an IBPP::SQLException is thrown.
* In all other cases, IBPP throws IBPP::LogicException.
* Also note that the runtime and the language might also throw exceptions
* while executing some IBPP methods. A failing new operator will throw
* std::bad_alloc, IBPP does nothing to alter the standard behaviour.
*
*                    std::exception
*                           |
*                   IBPP::Exception
*                 /                 
*    IBPP::LogicException    IBPP::SQLException
*             |
*      IBPP::WrongType
*/

.cpp在所有测试中,IBPP::SQLException的唯一捕获是在Test3()中。每隔一个捕获都使用IBPP::Exception。

所以这个问题只会在编译为 64 位时出现在Test3() 中,但我认为每当在 64 位实现中使用 IBPP::SQLException 时,它就会表现出来。

经过更多的研究,我找到了原因。我错了,认为 x86 和 x64 中的异常处理不同。IBPP库包含一个错误(自10年以来!),该错误仅在x64/windows szenario中发生,当firebird库向调用者报告(复杂的)错误条件时。

所以发生的事情是:

测试程序调用 IBPP libarary。IBPP库调用firebird API/库。火鸟库在长整型数组[20]中报告其调用结果,他们称之为"ISC_STATUS向量"。IBPP 库会检查此结果,并在出现错误时引发异常。测试程序捕获此类异常并将其报告给用户。

目前为止,一切都好。但问题是,IBPP 将 ISC_STATUS 定义为一个长整型数组[20],就像火鸟一样,直到 v2.0 之前也是如此——它只支持 x86 窗口。从 v2.1 开始,firebird 支持 x64 窗口,并将ISC_STATUS定义为一个intptr_t数组,这导致 Windows x64 LLP64 编译器上的"long long"和 LP64 Linux 编译器上的 long - 两者都是 64 位宽。在 ILP32 窗口和 linux x86 编译器上,intptr_t 是 32 位宽。 IBPP并没有缩小与Firebird的差距,而是长期保持其对ISC_STATUS的定义 - 这导致LLP64 Windows x64系统上的数据类型为32位宽(但在Linux上为64位,因为Linux上的gcc使用LP64系统,因此该错误仅在Windows上发生)。

因此,Firebird x64 API 向 64 位整数的"按引用参数"数组[20]报告多达 20 个状态整数。FBPP 库传递一个 32 位整数的数组[20]。当 firebird API 将值存储在数组的后半部分时,它会覆盖调用方的内存。在这种情况下,ISC_STATUS向量之后的下一个字节被 c++ 字符串对象占用。状态数组和字符串都是 IBS(碱基间状态)类的一部分。许多 IBPP 函数通常实例化此类的本地对象来管理 firebird API 结果和描述字符串的错误。当函数退出时,框架会清理此类本地对象并尝试释放字符串,但其内存中的元数据被 firebird API 覆盖。

因此,本地 IBS 对象的清理会导致未知的软件异常,从而驱动 IBPP 框架引发的异常。这个异常不是被测试程序捕获的,而是被窗口/沃森博士捕获的。

我将ISC_STATUS的定义从"长"固定为"intptr_t",并且所有工作都按预期进行。

谢谢大家的提示。