Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Delphi "Strings" to/from MS VC C++


This question is answered. Helpful answers available: 0. Correct answers available: 1.


Permlink Replies: 10 - Last Post: Sep 6, 2015 11:37 PM Last Post By: Rudy Velthuis (...
Glen Adamson

Posts: 7
Registered: 10/30/03
Delphi "Strings" to/from MS VC C++  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 3, 2015 6:24 PM
I am writing an import unit to use a DLL compiled with VC 2008;

The C++ functions are declared as:
  __declspec(dllexport) int ObjGetName(HANDLE h, TCHAR *name, int max_chars)
  __declspec(dllexport) int ObjRename(HANDLE h, const TCHAR *name); 

In the C++ project properties “Character Set” has been set to “Use Multi-Byte Character Set”.
With reference to [https://msdn.microsoft.com/en-us/library/ey142t48.aspx] I think the “TCHAR” data format will result in a “char” string type and so “*name” should be a Delphi PAnsiChar which is defined as a null-terminated ANSI string with warnings about being unsafe.

I have declared the Delphi calls to the above C++ functions as:
  function ObjGetName(h: THandle; var name: PAnsiChar; max_chars: LongInt): LongInt; cdecl; external TheDLL name '?ObjGetName@@YAHPAXPADH@Z';
  function ObjRename(h: THandle; name: PAnsiChar): LongInt; cdecl;  external TheDLL name '?ObjRename@@YAHPAXPBD@Z';

To support the getting and sending of PAnsiChar string data I have drafted two function:
  function StrToPAnsiChar(s: string): PAnsiChar;
  begin
    Result := PAnsiChar(PAnsiString(s));
  end;
 
  function PAnsiCharToAnsiStr(pA: PAnsiChar; siz: integer): AnsiString;
  begin
    SetLength(Result, siz+1);
    move(pA, Result[1], siz);
  end;

The functions are not working. I am goooooooogled out, confused and need some help.

Can you help.

I thank you in advance;

Regards
Glen
Rudy Velthuis (...


Posts: 7,731
Registered: 9/22/99
Re: Delphi "Strings" to/from MS VC C++
Helpful
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 4, 2015 4:58 AM   in response to: Glen Adamson in response to: Glen Adamson
Glen Adamson wrote:

I am writing an import unit to use a DLL compiled with VC 2008;

The C++ functions are declared as:
  __declspec(dllexport) int ObjGetName(HANDLE h, TCHAR *name, int
max_chars)
  __declspec(dllexport) int ObjRename(HANDLE h, const
TCHAR *name);

In the C++ project properties “Character Set”
has been set to “Use Multi-Byte Character Set”. With reference to
[https://msdn.microsoft.com/en-us/library/ey142t48.aspx] I think the
“TCHAR” data format will result in a “char” string type and so
“*name” should be a Delphi PAnsiChar which is defined as a
null-terminated ANSI string with warnings about being unsafe.

I have declared the Delphi calls to the above C++ functions as:
  function ObjGetName(h: THandle; var name: PAnsiChar; max_chars:
LongInt): LongInt; cdecl; external TheDLL name</div>
 
Var is wrong here.
 
  function ObjGetName(h: THandle; name: PAnsiChar; max_chars: Integer):
Integer; cdecl; external TheDLL name '?ObjGetName@@YAHPAXPADH@Z';
 
Fortunately, this does not look like a method name, so that should work.
 
But you have a few more problems:
 
<div class="jive-quote">

function PAnsiCharToAnsiStr(pA: PAnsiChar;
siz: integer): AnsiString;
begin
SetLength(Result, siz+1);
move(pA, Result[1], siz);
end;
</div>
 
That should be:
 

SetLength(Result, siz+1);
Move(pa^, Result[1], siz);

if you do:
 

Move(pa, ...
{code}

then you are moving the pointer (and the data that follows it in
memory), not the data to which it points. So use pa^.

--
Rudy Velthuis http://www.rvelthuis.de

"Gentleman: Knows how to play the bagpipes, but doesn't."
Rudy Velthuis (...


Posts: 7,731
Registered: 9/22/99
Re: Delphi "Strings" to/from MS VC C++  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 4, 2015 5:10 AM   in response to: Rudy Velthuis (... in response to: Rudy Velthuis (...
Rudy Velthuis (TeamB) wrote:

function ObjGetName(h: THandle; name: PAnsiChar; max_chars:
Integer): Integer; cdecl; external TheDLL name
'?ObjGetName@@YAHPAXPADH@Z';

FWIW, the A that follows the @@Y in the mangled name means that the
calling convention is indeed cdecl.

http://www.kegel.com/mangle.html
--
Rudy Velthuis http://www.rvelthuis.de

"The dangerous patriot ... is a defender of militarism and its
ideals of war and glory."
-- Colonel James A. Donovan, Marine Corps
Glen Adamson

Posts: 7
Registered: 10/30/03
Re: Delphi "Strings" to/from MS VC C++  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 4, 2015 9:19 PM   in response to: Rudy Velthuis (... in response to: Rudy Velthuis (...
Thank you Rudy for your help.

I have followed your help and:
1. removed the “var” from the function ObjGetName and
2: changed move(pA, Result[1], siz); to move(pA^, Result[1], siz);

Following the above edits, some problems still exist:
1. When calling ObjGetName with the result of StrToPAnsiChar(‘’) the parameter PAnsiChar is equal nil causing an error. Calling with the result of StrToPAnsiChar(‘XXX’) where PAnsiChar <> nil, is OK.
2. The function StrToPAnsiChar appears to work OK, but a PAnsiChar only shows the first character in the string of many characters.
3. Function PAnsiCharToAnsiStr has the same problem, it does not show the complete string;

Some questions:

I assume that C++ “*name” is a pointer to a NULL terminated array of ASCII bytes. Is this correct?

If this is correct then is PAnsiChar the best Delphi equivalent?

Do you think a Delphi pointer to an array of byte would work for the C++ function call?

How can I get PAnsiChar to show the complete string?

I think my problem is to move a Delphi string variable into a structure that is acceptable to the C++ interface and then extract a string from the structure on return. Any thoughts or draft code about the structure required would be helpful.

Regards
Glen

Edited by: Glen Adamson on Sep 4, 2015 11:21 PM
Linden ROTH

Posts: 467
Registered: 11/3/11
Re: Delphi "Strings" to/from MS VC C++  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 5, 2015 1:54 AM   in response to: Glen Adamson in response to: Glen Adamson
Glen Adamson wrote:
2. The function StrToPAnsiChar appears to work OK, but a PAnsiChar only shows the first character in the string of many characters.
3. Function PAnsiCharToAnsiStr has the same problem, it does not show the complete string;

Which suggests Wide not Ansi

--
Linden
"Mango" was Cool but "Wasabi" was Hotter but remember it's all in the "source"
Rudy Velthuis (...


Posts: 7,731
Registered: 9/22/99
Re: Delphi "Strings" to/from MS VC C++ [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 6, 2015 11:37 PM   in response to: Glen Adamson in response to: Glen Adamson
Glen Adamson wrote:

Thank you Rudy for your help.

I have followed your help and:
1. removed the “var” from the function ObjGetName and
2: changed move(pA, Result[1], siz); to move(pA^, Result[1], siz);

Following the above edits, some problems still exist:
1. When calling ObjGetName with the result of StrToPAnsiChar(‘’)
the parameter PAnsiChar is equal nil causing an error. Calling with
the result of StrToPAnsiChar(‘XXX’) where PAnsiChar <> nil, is OK.
2. The function StrToPAnsiChar appears to work OK, but a PAnsiChar
only shows the first character in the string of many characters.
3. Function PAnsiCharToAnsiStr has the same problem, it does not show
the complete string;

Some questions:

I assume that C++ “*name” is a pointer to a NULL terminated array of
ASCII bytes. Is this correct?

Iy you mean char*, then yes, generally it is. It is simply a pointer to
an AnsiChar. If that is the first AnsiChar in such a string or not
should follow from the documentation.

IOW

char *name()

has a return type "char *", which is PAnsiChar.

If this is correct then is PAnsiChar the best Delphi equivalent?

Do you think a Delphi pointer to an array of byte would work for the
C++ function call?

Sure. C does not know how the bytes are declared on your side, i.e. as
bytes, single-byte characters, etc.

How can I get PAnsiChar to show the complete string?

MyString := AnsiString(PAnsiChar(Bla));

will turn your Bla into a string, no matter in which version.

--
Rudy Velthuis http://www.rvelthuis.de

Oliver's Law Of Location: No matter where you are, there you are.

Chris Rolliston

Posts: 332
Registered: 4/6/00
Re: Delphi "Strings" to/from MS VC C++
Helpful
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 5, 2015 2:32 AM   in response to: Glen Adamson in response to: Glen Adamson
function StrToPAnsiChar(s: string): PAnsiChar;
begin
Result := PAnsiChar(PAnsiString(s));
end;

A cast to PAnsiString is, literally, a cast to a pointer to AnsiString, and therefore invalid in this context. The intermediate cast should just be to AnsiString, which will perform an actually conversion (assuming D2009+):

function StrToPAnsiChar(const S: string): PAnsiChar; inline;
begin
  Result := PAnsiChar(AnsiString(S));
end;


function PAnsiCharToAnsiStr(pA: PAnsiChar; siz: integer): AnsiString;
begin
SetLength(Result, siz+1);
move(pA, Result[1], siz);
end;

Aside from the need to deference pA already mentioned if you're using Move, the siz+1 is unnecessary because Delphi strings are implicitly null-terminated:

function PAnsiCharToAnsiStr(pA: PAnsiChar; siz: integer): AnsiString;
begin
  if pA = nil then Result('');
  SetLength(Result, siz);
  Move(pA^, Result[1], siz);
end;


If you really do need to add 1, then I wouldn't give the helper function such a generic-sounding name. That said, the SetLength/Move pair can be combined into a single SetString one, in which case (assuming correctness of inputs) the nil check won't be needed, and the parameter won't need deferencing since SetString itself takes a (typed) pointer:

function PAnsiCharToAnsiStr(pA: PAnsiChar; siz: integer): AnsiString; inline;
begin
  SetString(Result, pA, siz);
end;


If you want to go to a string directly, then SetString supports that too:

function PAnsiCharToStr(pA: PAnsiChar; siz: integer): string; inline;
begin
  SetString(Result, pA, siz);
end;
Glen Adamson

Posts: 7
Registered: 10/30/03
Re: Delphi "Strings" to/from MS VC C++  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 5, 2015 5:14 AM   in response to: Chris Rolliston in response to: Chris Rolliston
Hi Chris,

A very good answer.

I dropped the “siz: integer” from PAnsiCharToStr and test. All worked as expected.

procedure TTest_API.DevIO;
var
 s1, s2: string;
 pAC: PAnsiChar;
 
  function StrToPAnsiChar(const S: string): PAnsiChar; inline;
  begin
    Result := PAnsiChar(AnsiString(S));
  end;
 
  function PAnsiCharToStr(pA: PAnsiChar): string; inline;
  begin
    SetString(Result, pA, length(pA));
  end;
 
begin
  s1  := 'try this';
  pAC := StrToPAnsiChar(s1);
  s2  := PAnsiCharToStr(pAC, 20);
end;


I then applied the helper to this C++ call (pass by value) and worked well:

name := StrToPAnsiChar('GlenNm');
function MarkCreate(name: PAnsiChar; lat: Double; lon: Double; icon_id: LongInt; var h: THandle): LongInt; cdecl; external MMAPI name '?MarkCreate@@YAHPBDNNHPAPAX@Z';


The second context (pass by reference) the “name “ parameter failed.

name := StrToPAnsiChar('');
function ObjGetName(h: THandle;     name: PAnsiChar; max_chars: Integer): Integer; cdecl; external MMAPI name '?ObjGetName@@YAHPAXPADH@Z';
str := PAnsiCharToStr(name);


I tried with a “var” added to the C++ call and then the call to PAnsiCharToStr(name) failed.
name := StrToPAnsiChar('');
function ObjGetName(h: THandle; var name: PAnsiChar; max_chars: Integer): Integer; cdecl; external MMAPI name '?ObjGetName@@YAHPAXPADH@Z';
str := PAnsiCharToStr(name);


I am not out of the woods yet.

Glen
Glen Adamson

Posts: 7
Registered: 10/30/03
Re: Delphi "Strings" to/from MS VC C++  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 5, 2015 5:19 PM   in response to: Glen Adamson in response to: Glen Adamson
I pursued the byte array concept and found ref: http://stackoverflow.com/questions/1398763/delphi-pchar-to-c-const-char

Two functions resulted:
type
  TGbytes = array of byte;
var
  nameB: PByteArray;
 
  function StrToPGbytes(ss: string; siz: integer): PByteArray;
  var
    a: TGbytes;
    n, cs: integer;
    s: AnsiString;
  begin
    s := ss;
    n := Length(s);
    cs := sizeof(s);
    SetLength(a, siz);
    move(s[1], a[0], 2*n);
    Result := PByteArray(a);
  end;
 
  function PGbytesToStr(pB: PByteArray; siz: integer): string;
  var
    k: integer;
    b: byte;
  begin
    Result := '';
    for k := 0 to siz - 1 do begin
      b := pB[k];
      Result := Result + chr(b);
      if b = 0 then begin
        SetLength(Result, k);
        exit;
        end;
      end;
  end;


When applied to the C++ calls all work well:

nameB := StrToPGbytes('GlenNm');
function MarkCreate(nameB: PByteArray; lat: Double; lon: Double; icon_id: LongInt; var h: THandle): LongInt; cdecl; external MMAPI name '?MarkCreate@@YAHPBDNNHPAPAX@Z';
 
nameB := StrToPAnsiChar('');
function ObjGetName(h: THandle; nameB: PByteArray; max_chars: Integer): Integer; cdecl; external MMAPI name '?ObjGetName@@YAHPAXPADH@Z';
str := PGbytesToStr(nameB);


The two function could likely be improved, any comments or example code is welcome.

Thank you all for your help.

Glen
Chris Rolliston

Posts: 332
Registered: 4/6/00
Re: Delphi "Strings" to/from MS VC C++  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 6, 2015 7:46 AM   in response to: Glen Adamson in response to: Glen Adamson
function PAnsiCharToStr(pA: PAnsiChar): string; inline;
begin
SetString(Result, pA, length(pA));
end;

Don't do that. Unlike when called against a Delphi string, the Length call here relies on the fact pA is null terminated, which is not obvious when you look at it at a glance. If pA is null terminated, then use a series of explicit casts, where the assumption of being null terminated will be more obvious:

function PAnsiCharToStr(pA: PAnsiChar): string; inline;
begin
  Result := string(AnsiString(pA));
end;


begin
s1 := 'try this';
pAC := StrToPAnsiChar(s1);
s2 := PAnsiCharToStr(pAC, 20);
end;

You can drop siz here because the Delphi input is implicitly null-terminated. This says nothing about your DLL input. Normally one would expect a C DLL to work with null-terminated character arrays, but if the export explicitly provides a length, then use it.

I am not out of the woods yet.

You need to determine exactly what the output from the DLL actually is. Delphi's C-style string support is fine - the fact you don't know what the DLL is doing is not.

I pursued the byte array concept

Utterly pointless I'm afraid.

Two functions resulted:
{code}type
TGbytes = array of byte;
var
nameB: PByteArray;

function StrToPGbytes(ss: string; siz: integer): PByteArray;
var
a: TGbytes;
n, cs: integer;
s: AnsiString;
begin
s := ss;
n := Length(s);
cs := sizeof(s);
SetLength(a, siz);
move(s[1], a[0], 2*n);
Result := PByteArray(a);
end;

There is no need to use this code. Again, your problem is not Delphi's support for C-style strings.
Glen Adamson

Posts: 7
Registered: 10/30/03
Re: Delphi "Strings" to/from MS VC C++  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 6, 2015 4:10 PM   in response to: Chris Rolliston in response to: Chris Rolliston
Hi Chis,

Thank you for your ongoing interest.

I agreed the problem is not Delphi but the interface to this DLL.

The byte array concept was promoted by the fact that C++ appeared to believe that any string was only one character long.

The reference to http://stackoverflow.com/questions/1398763/delphi-pchar-to-c-const-char explained why this could be so. I know very little about VS C++ so I can only take the explanation on face value.

The byte array functions have been cleaned up, the latest version are:
function StrToPGbytes(ss: string): PByteArray;
var
  a: TGbytes;
  n: integer;
  s: AnsiString;
begin
  s := ss;
  n := Length(s);
  SetLength(a, max(1, n));
  move(s[1], a[0], 2*n);
  Result := PByteArray(a);
  if Result = nil then
    beep;
end;
 
function PGbytesToStr(pB: PByteArray): string;
var
  k: integer;
  b: byte;
begin
  Result := '';
  if pB = nil then
    exit;
  k := 0;
  repeat
    b := pB[k];
    Result := Result + chr(b);
    inc(k);
    if b = 0 then begin
      SetLength(Result, k);
      exit;
      end;
  until false;
end;

These 2 functions are working with the DLL. None of the suggested type cast techniques have worked.

My guess is that the DLL was compiled with a version that produced a C++ “char” string constructed of a NULL terminated array of byte size ASCII characters.

To my knowledge there is no Delphi structure type like this today (the old PChar). If there is could you tell me about it.

Could you also please confirm that the dynamic array will not become a memory leak?

The two functions could most likely be improved, any comments or example code are welcome.

Glen
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02