Search code examples
c++unicoderad-studiodeclspec

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


I've encountered a very confusing phenomenon trying to call a function of a borland c++ builder 6 dll defined as _declspec(dllexport) in combination with a nested struct pointer parameter. The struct declarations contain AnsiString, std::string and std::vector members, and it did work when I was calling the dll from a bcb6 project. Unfortunately, when I try to call the same method from a Rad Studio 10.3 project, and I try to access members from the vector, I do encounter an access violation. Trying to figure out the root of the issue, I asked for the size of the vector: Before the call in 10.3: 1 After the call in bcb6: Large value > 80000

It seems that the struct is shifted, otherwise I can't explain the reason for this strange value. During the loop the access violation does occur:

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
  }
}

I did already check the c++ compiler alignment, which is set to quad word in both IDEs.

The code is like this:

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;
}

The method is defined like this:

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

The vector is filled this way:

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

Trying to access the vector elements of data->PersonData, like data->PersonData.Name within the loop (with the help of an iterator), I get an error message of this kind: Access violation at address BC3F2D3E. Read of address 00000000.

What does really puzzle me, is the fact, that it does when, I am debugging the same code in the RAD Studio (with the iterator, etc.), and it did also work in combination of bcb6<->bcb6. This has to be some compiler issue, but I have no concrete idea. I am using the classic compiler in 10.3.

I would really appreciate any advice, because I have no idea what the reason could be. Could it help to switch from struct to class?


Solution

  • You simply can't use non-trivial types across a DLL boundary, such as AnsiString, std::string, std::vector, etc. It is just not safe to do, in any version. Differences between DLL and EXE in terms of compilers used, configurations like alignments, memory management, etc all affect compatibility. Even when the EXE and DLL are compiled in the same compiler version (as in your BCB6 <-> BCB6 case), there can still be subtle differences that affect compatibility, for instance if the DLL is compiled statically so a common RTL instance is not shared with the EXE.

    In this case, since you are changing from one version of C++Builder to another, the STL library used in C++Builder 6 (STLPort) vs 10.3 (Dinkumware) are very different implementations which are not binary compatible with each other. And even the RTL's internal structure of AnsiString was changed in C++Builder 2009 (to accommodate support for UnicodeString and RawByteString), so even the DLL's version of AnsiString is not compatible with the EXE's version of AnsiString, either.

    Your BCB6-written DLL is simply not compatible with your 10.3-written EXE at all. The code shown will simply never work the way you want when crossing those compiler versions. But even recompiling the DLL in 10.3 is not guaranteed to solve all of the issues, as mentioned further above.

    You really need to redesign the DLL interface to not use any non-trivial types at all. The interface needs to be consistent and stable across all compilers and setups. structs are fine to use (if you use consistent padding and alignment), but stick with trivial types for members, like integers, fixed sized arrays, pointers to C-style character strings and dynamic arrays (which then gets into more cross-compiler memory management issues), etc.

    Or, you could re-implement the existing DLL to be a BPL Package instead, then you can safely use non-trivial types across the DLL boundary. However, you will then be locked in to a specific compiler version and specific RTL/STL implementations, thus requiring another recompile if you ever upgrade again in the future.

    If changing the existing DLL is not an option, you will have to wrap the existing DLL inside of a new BCB6-written DLL that does expose such an interface.