从 10.3 项目调用 c++ 函数 (bcb6 dll/lib) 函数

Calling c++ function (bcb6 dll/lib) function from 10.3 project

我在尝试调用定义为 _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 中都设置为 quad word

代码是这样的:

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 中使用 classic 编译器。

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

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

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

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

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

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

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