0

我遇到了一个非常令人困惑的现象,试图结合嵌套结构指针参数调用定义为 _declspec(dllexport) 的 borland c++ builder 6 dll 的函数。结构声明包含 AnsiString、std::string 和 std::vector 成员,当我从 bcb6 项目调用 dll 时,它确实有效。不幸的是,当我尝试从 Rad Studio 10.3 项目中调用相同的方法并尝试从向量访问成员时,我确实遇到了访问冲突。试图找出问题的根源,我询问了向量的大小: 在 10.3 中调用之前:1 在 bcb6 中调用之后:大值 > 80000

好像是struct被移位了,否则我无法解释这个奇怪值的原因。在循环期间,确实发生了访问冲突:

int methodCall(const char* val, const A3* data, const char* info)
{
  for(std::vector<A2*>::iterator it = data->PersonData.begin(); it !=    
      data->PersonData.end(); ++it) 
  {
     AnsiString test = it->Name; //access violation
  }
}

我确实已经检查了 c++ 编译器对齐,它在两个 IDE 中都设置为四字。

代码是这样的:

struct A1
{
  AnsiString Val1;
  AnsiString Val2;
  std::string S1;
}
struct A2
{
  AnsiString Name;
  std::string Street;
  A1 Details;
}
struct A3
{
  AnsiString Val1;
  AnsiString Val2;
  std::vector<A2*> PersonData;
}

该方法定义如下:

int __declspec(dllexport) __stdcall methodCall(const char* val, const A3* data, const char* info){}

向量是这样填充的:

A3* a3 = new A3;
A2* a2 = new A2;
a2->Name = "Test";
a3->PersonData.push_back(a2);

尝试访问 data->PersonData 的向量元素,例如循环中的 data->PersonData.Name(在迭代器的帮助下),我收​​到此类错误消息:地址 BC3F2D3E 的访问冲突。读取地址 00000000。

真正让我感到困惑的是,当我在 RAD Studio 中调试相同的代码(使用迭代器等)时,它确实如此,并且它也确实可以与 bcb6<->bcb6 结合使用。这必须是一些编译器问题,但我没有具体的想法。我在 10.3 中使用经典编译器。

我真的很感激任何建议,因为我不知道可能是什么原因。从结构切换到类有帮助吗?

4

1 回答 1

3

您根本不能跨 DLL 边界使用非平凡的类型,例如AnsiString, std::string,std::vector等。在任何版本中这样做都是不安全的。DLL 和 EXE 在使用的​​编译器、对齐、内存管理等配置方面的差异都会影响兼容性。即使 EXE 和 DLL 在相同的编译器版本中编译(如在 BCB6 <-> BCB6 案例中),仍然可能存在影响兼容性的细微差异,例如,如果 DLL 是静态编译的,那么一个常见的 RTL 实例不是与EXE共享。

在这种情况下,由于您要从一个版本的 C++Builder 更改为另一个版本,因此 C++Builder 6 (STLPort) 与 10.3 (Dinkumware) 中使用的 STL 库是非常不同的实现,彼此之间不兼容二进制。甚至 C++Builder 2009 中更改了 RTL 的内部结构AnsiString(以适应对UnicodeString和的支持RawByteString),因此即使是 DLL 的版本AnsiString也与 EXE 的版本不兼容AnsiString

您的 BCB6 编写的 DLL 与您的 10.3 编写的 EXE 根本不兼容。当跨越这些编译器版本时,显示的代码将永远不会按照您想要的方式工作。但即使在 10.3 中重新编译 DLL 也不能保证解决所有问题,如上所述。

您确实需要重新设计 DLL 接口,以完全不使用任何重要的类型。接口需要在所有编译器和设置中保持一致和稳定。structs 很好用(如果你使用一致的填充和对齐方式),但坚持使用简单的成员类型,如整数、固定大小的数组、指向 C 样式字符串和动态数组的指针(然后进入更多的交叉编译器内存管理问题)等。

或者,您可以将现有的 DLL 重新实现为 BPL 包,然后您可以安全地跨 DLL 边界使用非平凡类型。但是,您将被锁定到特定的编译器版本和特定的 RTL/STL 实现,因此如果您将来再次升级,则需要再次重新编译。

如果无法更改现有的 DLL,则必须将现有的 DLL 包装在一个新的 BCB6 编写的 DLL 中,该 DLL 确实公开了这样的接口。

于 2021-09-13T21:43:23.683 回答