3

Consider the following code snippet (in Delphi XE2):

function PrepData(StrVal: string; Base64Val: AnsiString): OleVariant;
begin
  Result := VarArrayCreate([0, 1], varVariant);
  Result[0] := StrVal;
  Result[1] := Base64Val;
end;

Base64Val is a binary value encoded as Base64 (so no null bytes). The (OleVariant) Result is automatically marshalled and sent between a client app and a DataSnap server.

When I capture the traffic with Wireshark, I see that both StrVal and Base64Val are transferred as Unicode strings. If I can, I would like to avoid the Unicode conversion for Base64Val. I've looked at all the Variant types and don't see anything other than varString that can transfer an array of characters.

I found this question that shows how to create a variant array of bytes. I'm thinking that I could use this technique instead of using an AnsiString. I'm curious though, is there another way to assign an array of non-Unicode character data to a Variant without a conversion to a Unicode string?

Community
  • 1
  • 1
James L.
  • 9,384
  • 5
  • 38
  • 77

2 Answers2

4

Delphi's implementation supports storing AnsiString and UnicodeString in a Variant, using custom variant type codes. These codes are varString and varUString.

But interop will typically use standard OLE variants and the OLE string, varOleStr, is 16 bit encoded. That would seem to be the reason for your observation.

You'll need to put the data in as an array of bytes if you do wish to avoid a conversion to 16 bit text. Doing so renders base64 encoding pointless. Stop base64 encoding the payload and send the binary in a byte array.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • "These codes are varString and varUString." - AFAIR that is for ShortString. for AnsiString there is varLString // PS: there's also yEnc encoding if binary stream is unfeasible for any reason and base64 is too heavy – Arioch 'The Nov 22 '13 at 08:45
  • @Arioch Nope, those are long strings – David Heffernan Nov 22 '13 at 08:52
  • Got it working. It sends binary data via a PVarArray, marshalled in the OleVariant, without using Base64. I'll post the answer separately, including a reference to David's other answer where he showed how to do most of the work. – James L. Nov 22 '13 at 09:05
2

Keeping with the example in the question, this is how I made it work (using code and comments from David's answer to another question as referenced in my question):

function PrepData(StrVal: string; Data: TBytes): OleVariant;
var
  SafeArray: PVarArray;
begin
  Result := VarArrayCreate([0, 1], varVariant);
  Result[0] := StrVal;
  Result[1] := VarArrayCreate([1, Length(Data)], varByte);
  SafeArray := VarArrayAsPSafeArray(Result[1]);
  Move(Pointer(Data)^, SafeArray.Data^, Length(Data));
end;

Then on the DataSnap server, I can extract the binary data from the OleVariant like this, assuming Value is Result[1] from the Variant Array in the OleVariant:

procedure GetBinaryData(Value: Variant; Result: TMemoryStream);
var
  SafeArray: PVarArray;
begin
  SafeArray := VarArrayAsPSafeArray(Value);
  Assert(SafeArray.ElementSize=1);
  Result.Clear;
  Result.WriteBuffer(SafeArray.Data^, SafeArray.Bounds[0].ElementCount);
end;
Community
  • 1
  • 1
James L.
  • 9,384
  • 5
  • 38
  • 77
  • There's a slight asymmetry here. The first section of code includes `SizeOf()` but the second does not. Neither is really needed since you know that the elements are bytes with a size of 1. But you should probably add some sanity checking to the second block of code because the compiler cannot guarantee the contents of the variant array. +1 and thanks for your earlier now deleted comment about explanations as to the why being valuable. I do try and I'm pleased that you noticed. :-) – David Heffernan Nov 22 '13 at 09:29
  • Thanks @David - I always appreciate the time you take to explain the **why**. I tried adding a `SizeOf()` as you suggest, but can't figure out how to access an element of `SafeArray.Data`. So I added `SizeOf(Byte)` to both (to be consistent). Although, that's not perfect either... – James L. Nov 22 '13 at 10:28
  • Would you mind if I edit your answer to show what I think you should do? That will be easier than trying to explain it. – David Heffernan Nov 22 '13 at 10:38
  • Your question was about AnsiString, your answer is about UnicodeString. Probably i miss something... – Arioch 'The Nov 22 '13 at 11:33
  • @Arioch'The It's actually just a byte array marshalling question – David Heffernan Nov 22 '13 at 13:44
  • @Arioch - Sorry for the confusion. The question was how to store an array of non-Unicode characters (actually a stream of bytes) in a Variant and avoid the Unicode conversion that occurs when you assign an AnsiString to a Variant. And then of course, how to get it out again... – James L. Nov 22 '13 at 20:05
  • @JamesL. you see... "characters" are not "bytes" but bytes+charset/codepage context. But if `array of bytes` suites you then that is fine – Arioch 'The Nov 25 '13 at 08:24
  • Just a little reminder. Use const StrVal: string; const Data: TBytes or Delphi magic will create two temporary variables and copy your string and bytedata one extra time for every execution.. – Atle S Apr 09 '15 at 07:09