An elegant way to handle this string. (Unicode-PAnsiString issue)

Consider the following scenario:

type 
PStructureForSomeCDLL = ^TStructureForSomeCDLL;
TStructureForSomeCDLL = record 
  pName: PAnsiChar;
end

function FillStructureForDLL: PStructureForSomeDLL;
begin
  New(Result);
  // Result.pName := PAnsiChar(SomeObject.SomeString);  // Old D7 code working all right
  Result.pName := Utf8ToAnsi(UTF8Encode(SomeObject.SomeString));  // New problematic unicode version
end;

...code to pass FillStructureForDLL to DLL...

      

The problem in the unicode version is that the string conversion involved now returns a newline on the stack and is fixed at the end of the call to FillStructureForDLL, leaving the DLL with corrupted data. The old D7 code did not have intermediate conversion functions, and so there were no problems.

My current solution is a converter function as shown below which is too big a mistake IMO. Is there a more elegant way to achieve the same result?

var gKeepStrings: array of AnsiString;

{ Convert the given Unicode value S to ANSI and increase the ref. count 
  of it so that returned pointer stays valid }
function ConvertToPAnsiChar(const S: string): PAnsiChar;
var temp: AnsiString;
begin
  SetLength(gKeepStrings, Length(gKeepStrings) + 1);
  temp := Utf8ToAnsi(UTF8Encode(S));
  gKeepStrings[High(gKeepStrings)] := temp; // keeps the resulting pointer valid 
                                            // by incresing the ref. count of temp.
  Result := PAnsiChar(temp);
end;

      

+2


source to share


4 answers


One way might help solve the problem before it becomes a problem and I mean adapting the SomeObject class to support the ANSI encoded version of SomeString (ANSISomeString?) For you next to the original SomeString, keeping the two operations in the "setter" for the property SomeString (using the same UTF8> ANSI conversion you are already doing).

In non-Unicode compiler versions, make ANSISomeString is simply a "copy" of SomeString, which of course will not be a copy, but simply an additional reference to SomeString. In the Unicode version, it refers to a separate ANSI encoding with the same "lifetime" as the original SomeString.

procedure TSomeObjectClass.SetSomeString(const aValue: String);
begin
  fSomeString := aValue;

{$ifdef UNICODE}
  fANSISomeString := Utf8ToAnsi(UTF8Encode(aValue));
{$else}
  fANSISomeString := fSomeString;
{$endif}
end;

      



In your FillStructure function ... just change your code to refer to the ANSISomeString property - this is then completely independent of compilation for Unicode or not.

function FillStructureForDLL: PStructureForSomeDLL;
begin
  New(Result);
  result.pName := PANSIChar(SomeObject.ANSISomeString);
end;

      

+3


source


There are at least three ways to do this.

  • You can change the SomeObject class definition to use AnsiString instead of string ...
  • You could use a transform system to store the reference like in your example.
  • You can initialize result.pname

    with GetMem and copy the conversion result to result.pname^

    with Move

    . Just remember FreeMem it when you're done.


Unfortunately, none of these are ideal solutions. So take a look at the options and decide which one works best for you.

+2


source


Hopefully you already have some code in your application to properly get rid of all dynamically allocated records you are New()

in FillStructureForDLL()

. I find this code very dubious, but let's minify the code just to demonstrate the problem. In any case, the DLL you pass the record instance to doesn't care how big the memory is, it will still get a pointer to it. This way you can increase the record size to allow room for the Pascal string, which is now a temporary instance on the stack in the Unicode version:

type 
  PStructureForSomeCDLL = ^TStructureForSomeCDLL;
  TStructureForSomeCDLL = record 
    pName: PAnsiChar;
    // ... other parts of the record
    pNameBuffer: string;
  end;

      

And the function:

function FillStructureForDLL: PStructureForSomeDLL;
begin
  New(Result);
  // there may be a bug here, can't test on the Mac... idea should be clear
  Result.pNameBuffer := Utf8ToAnsi(UTF8Encode(SomeObject.SomeString));
  Result.pName := Result.pNameBuffer;
end;

      

BTW: You wouldn't even have this problem if the entry passed to the DLL was a stack variable in a procedure or function calling the DLL function. In this case, temporary string buffers will only be needed in the Unicode version if more than one needs to be passed PAnsiChar

(conversion calls otherwise reuse the temporary string). Think about changing your code accordingly.

Edit:

You write in the comment:

It would be the best solution to change the DLL structure.

Are you sure you cannot use this solution? The fact is that from the POV DLL, the structure does not change at all. Maybe I haven't cleared up, but the DLL doesn't care if the structure is passed to it exactly as it is declared. It will be passed a pointer to a structure, and that pointer must point to a block of memory that is at least as large as the structure and must have the same memory layout. However, it can be a block of memory that is larger than the original structure and contains additional data.

This is actually used in quite a few places in the Windows API. Have you ever wondered why there are structures in the Windows API that contain in the first place an ordinal value that gives the size of the structure? This is the key to the evolution of the API while maintaining backward compatibility. Whenever new information is required for the API function to work, it is simply added to the existing structure and a new version of the structure is declared. Note that the memory layout of older versions of the structure is retained. Older DLL clients can still call a new function that will use the struct sizing member to determine the version of the API.

In your case, there are no different versions of the structure in relation to the DLL. However, you can declare it larger than it actually is for your application while maintaining the memory layout of the real structure and adding additional data. The only time it doesn't work is when the last part of the structure was a different size record, like the Windows structure BITMAP

- with a fixed header and dynamic data. However, your record is like a fixed length.

+2


source


PChar (AnsiString (SomeObject.SomeString)) not working?

-1


source







All Articles