4
votes

I have a DLL compiled with D2007 that has functions that return AnsiStrings.

My application is compiled in D2009. When it calls the AnsiString functions, it gets back garbage.

I created a little test app/dll to experiment and discovered that if both app and dll are compiled with the same version of Delphi (either 2007 or 2009), there is no problem. But when one is compiled in 2009 and the other 2007, I get garbage.

I've tried including the latest version of FastMM in both projects, but even then the 2009 app cannot read AnsiStrings from the 2007 dll.

Any ideas of what is going wrong here? Is there a way to work around this?

6
Sorry, I should have posted some code. Just to be clear: The interface declares the return value as "AnsiString," not "string". As far as I can tell, this doesn't have anything to do with the Unicode string conversion. At least not directly.TrespassersW
If you should have posted some code, then go ahead and post some code. You're allowed to edit your own questions.Rob Kennedy

6 Answers

11
votes

The internal structure of AnsiStrings changed between Delphi 2007 and Delphi 2009. (Don't get upset; that possibility has been present since day 1.) A Delphi 2009 string maintains a number indicating what code page its data is in.

I recommend you do what every other DLL on Earth does and pass character buffers that the function can fill. The caller should pass a buffer pointer and a number indicating the size of the buffer. (Make sure you're clear about whether you're measuring the size in bytes or characters.) The DLL function fills the buffer, writing no more than the given size, counting the terminating null character.

If the caller doesn't know how many bytes the buffer should be, then you have two options:

  • Make the DLL behave specially when the input buffer pointer is null. In that case, have it return the required size so that the caller can allocate that much space and call the function a second time.

  • Have the DLL allocate space for itself, with a predetermined method available for the caller to free the buffer later. The DLL can either export a function for freeing buffers that it has allocated, or you can specify some mutually available API function for the caller to use, such as GlobalFree. Your DLL must use the corresponding allocation API, such as GlobalAlloc. (Don't use Delphi's built-in memory-allocation functions like GetMem or New; there's no guarantee that the caller's memory manager will know how to call Free or Dispose, even if it's written in the same language, even if it's written with the same Delphi version.)

Besides, it's selfish to write a DLL that can only be used by a single language. Write your DLLs in the same style as the Windows API, and you can't go wrong.

2
votes

OK, so haven't tried it, so a big fat disclaimer slapped on this one.

In the help viewer, look at the topic (Unicode in RAD Stufio) ms-help://embarcadero.rs2009/devcommon/unicodeinide_xml.html

Returning the Delphi 2007 string to Delphi 2009, you should get two problems.

First, the code page mentioned by Rob. You can set this by declaring another AnsiString and calling StringCodePage on the new AnsiString. Then assign that to the old AnsiString by calling SetCodePage. That should work, but if it doesn't there is hope still.

The second problem is the element size which will be something completely mad. It should be 1, so make it 1. The issue here is that there is no SetElementSize function to lean on.

Try this:

var
  ElemSizeAddr: PWord; // Need a two-byte type
  BrokenAnsiString: AnsiString; // The patient we are trying to cure
...
  ElemSizeAddr := Pointer(PAnsiChar(BrokenAnsiString) - 10);
  ElemSizeAddr^ := 1; // The size of the element

That should do it!

Now if the StringCodePage/SetCodePage thing didn't work, you can do the same as above, changing the line where we get the address to deduct 12, instead of 10.

It has hack scribbled all over it, that's why I love it.

You are going to need to port those DLLs eventually, but this makes the port more manageable.

One final word - depending on how you return the AnsiString (function result, output parameter, etc) you may need to first assign the string to a different AnsiString variable just to make sure there is no trouble with memory being overwritten.

0
votes

You'll likely just need to convert the DLL to 2009. According to Embarcadero, the conversion to 2009 is 'easy' and should take you no time at all.

0
votes

Your DLL should not be returning AnsiString values to begin with. The only way that would work correctly in the first place is if both DLL and EXE were compiled with the ShareMem unit, and even then only if they are compiled with the same Delphi version. D2007's memory manager is not compatible with D2009's memory manager (or any other cross-version use of memory managers), AFAIK.

0
votes

I agree with Rob and Remy here: common Dlls should return PAnsiChar instead of AnsiStrings.

If the DLL works OK compiled with D2009, why simply doesn't stop compiling it with D2007 and start compiling it with D2009 once and for all?

0
votes

Just as a quick solution here: if your actual data that you pass back from dll in the string does not exceed 255 chars, you can change both the in-dll and interface declerations to use ShortString, which will work regardless of 2007/2009 version. Since you're using AnsiString already on 2007 without a codepage identifier, unicode wont give you any trouble.

if you go this way, all you need to do is change the declarations like:

function MyStringReturningFunction : ShortString ; external 'MyLibrary.dll';

(and in dll: function MyStringReturningFunction : ShortString; respectively)

Same goes for input/output parameters of course:

procedure MyStringTakingAndReturningFunction(s1:ShortString; var s2:ShortString); external 'MyLibrary.dll';

Should be easier than changing a lot of code. But be careful, as I said, your data must not exceed 255 characters since that is the maximum size a ShortString can hold.