1
votes

I have a Delphi 7 application that needs to call a SOAP API that is much too new for the available SOAP importers. I have satisfied myself that D7 can't call the SOAP API without too much effort to be worth while. But I also have Delphi XE2, and that can import the SOAP and call it quite happily. So I have written a simple dll wrapper in XE2 that exposes the necessary parts of the soap interface. I can call the dll from an XE program.

In Delphi7 I took the SOAP API import file from XE, stripped out the {$SCOPED_ENUMS ON} defines and the initialization section that calls unavailable SOAP wrappers, plus changed string to widestring throughout. That compiles. I'm using FastMM with ShareMM enabled to make string passing work and avoid making everything stdcall.

The reason I'm trying to do it this way is that if it works it will make the SOAP shim very easy to code and maintain, since 90% of the code is generated by the XE2 SOAP importer, and it will mean that when we move the D7 app to a modern Delphi the code will remain largely unchanged.

But when I run it, I get weird strings (and consequent access violations). I've got simple functions that don't use the SOAP code to make the problem more obvious.

Passing a widestring from Delphi7 exe into DelphiXE2 dll the string length is doubled (according to the Length() function), but there's no matching data conversion. So a widestring "123" in D7 becomes "1234...." in XE2, where the .... is whatever garbage happens to be on the stack. Viewed as byte arrays both have half zero bytes as expect.

Passing a widestring back from XE2 dll to D7 I get the mirror effect - the string length is halved and strings are simply truncated ("1234" becomes "12").

I'm pasting code in because I know you will ask for it.

In Delphi XE2 I'm exporting these functions:

// testing
function GetString(s:string):string; export;
function AddToString(s:string):string; export;

implementation

function GetString(s:string):string;
begin
  Result := '0987654321';
end;

function AddToString(s:string):string;
begin
  Result := s + '| ' + IntToStr(length(s)) + ' there is more';
end;

In Delphi 7:

function GetString(s:widestring):widestring; external 'SMSShim.dll';
function AddToString(s:widestring):widestring; external 'SMSShim.dll';

procedure TForm1.btnTestGetClick(Sender: TObject);
var
  s: widestring;
begin
  s := widestring('1234');
  Memo1.Lines.Add(' GetString: ' + GetString(s));
end;

procedure TForm1.btnTestAddClick(Sender: TObject);
var
  s: widestring;
begin
  s := widestring('1234567890');
  Memo1.Lines.Add(' AddToString: ' + AddToString('1234567890'));
end;

I can run from either side, using the D7 executable as the host app to debug the dll. Inspecting the parameters and return values in the debugger gives the results above.

Annoyingly, if I declare the imports in delphi7 as strings I get the correct length but invalid data. Declaring as shown I get valid data, wrong lengths, and access violations when I try to return.

Making it all stdcall doesn't change the behaviour.

The obvious solution is the just write simple wrapper functions that expose exactly the functionality I need right now. I can do that, but I'd prefer the above cunning way.

2
please do understand that string in XE2 is NOT equal to widestring in D7. Adapt your functions to use the WideString type in XE2 also, or use AnsiString wich maps to D7 String typewhosrdaddy
Delphi XE string is not WideString (BSTR COM type). In Delphi XE declare strings also as WideString and use stdcall. the ShareMem is not needed. look herekobik
Thanks for the answers-as-comments :) FWIW, I actually need sharemem because the strings are wrapped up in larger objects for the SOAP calls (that part of my questions is misleading, sorry). But as Rob suggests, I will probably be better off falling back to simple functions rather than trying to write future-compatible code calling SOAP-equivalent functions.Chris M

2 Answers

1
votes

The DLL in question exports functions that expect to receive UnicodeString parameters. (As you know, the string type became an alias for UnicodeString in Delphi 2009.) A Delphi 7 application cannot consume that DLL; the run-time library doesn't not know how to operate on that type because it didn't exist back in 2002 when Delphi 7 was published.

Although the character size for UnicodeString is compatible with WideString, they are not the same types. UnicodeString is structured like the new AnsiString, so it has a length field, a reference count, a character size, and a code page. WideString has a length field, but any other metadata it carries is undocumented. WideString is simply Delphi's way of exposing the COM BSTR type.

A general rule to live by is to never export DLL functions that couldn't be consumed by C.1 In particular, this means using only C-compatible types for any function parameters and return types, so string is out, but WideString is safe because of its BSTR roots.

Change the DLL to use WideString for its parameters instead of string.


1 Maintaining C compatibility also means using calling conventions that C supports. Delphi's default register calling convention is not supported in Microsoft C, so use cdecl or stdcall instead, just like you've seen in every Windows DLL you've ever used.

2
votes

There's not way to disable the UNICODE in Delphi XE2 (or any version greater than 2009) , however there are many resources that can help you to migrate your application.