Convert hexadecimal strings from TBytes data in Delphi - delphi

I'm looking for a simple way of taking lines of hexadecimal data from a TStringList (always "Windows-1252" text files) and chopping them into record blocks (every line can be different length).
In Delphi 7 I used:
procedure DecodeLineAddr(const aLine: AnsiString; var ByteCount: integer; var Address:Cardinal; var RecType: Integer);
begin
//123 4567 89 0
//:10 4640 00 0000 0600 0200 fa00 004f 7800 1e00 fb00 88
ByteCount:= StrToInt('$' + copy(aLine, 2, 2));
Address := StrToInt('$' + copy(aLine, 4, 4));
RecType := StrToInt('$' + copy(aLine, 8, 2));
end;
That is, just copy the chars from the correct positions in the initial "block info" in the line, then prepend a '$' so StrToInt would interpret the string as hex.
I process line-by-line - so it's easy enough to do something like:
aLineAsTBytes:= TEncoding.ASCII.GetBytes(aStringLst[ndx]);
Then pass aLineAsTBytes into DecodeLineAddr as TBytes instead of AnsiString.
It isn't clear to me of how I should decode the various bytes (or how to carve them up appropriately) in order to return the correct results with code that will work on desktop and mobile.
That is, if using aLine:TBytes (instead of AnsiString), what's the equivalent of:
ByteCount:= StrToInt('$' + copy(aLine, 2, 2));
(and is there a better/faster way of handling this?)
TIA.
EdB

What you're already doing will work but you'll need to make a few tweaks. Most Importantly, make your function into a "string" type instead of an "AnsiString" type, which means that you'll have to convert it.
Mobile strings are 0-based, so on mobile you'll need to subtract 1 from your indexes. Or you can use my ocopy() or zcopy() functions which both perform the same on all platforms. Use ocopy() if you're dealing with old windows code, it will treat your 0-based strings as 1-based strings essentially, making it easier to port.
const
{$IFNDEF MSWINDOWS}
STRZ = 1;
{$ELSE}
STRZ = 0;
function zcopy(sString: string; iStartZeroBased: nativeint; iLength: nativeint): string;
begin
result := '';
setlength(result, lesserof(iLength, length(sString)-iStartZerobased));
movemem32(#result[strz], #sString[(strz+iStartZeroBased)], length(result)*sizeof(char));
end;
function ocopy(sString: string; iStartOneBased: nativeint; iLength: nativeint): string;
begin
result := zcopy(sString, iStartOneBased-1, iLength);
end;
Next, take this code which isn't a totally complete solution, but will give you most of your ansi-string support on mobile (with some slight caveats around pointers). But basically you can essentially convert strings by simply assigning an ansistring to a string type or vice verse. I had to hack this away from a couple of dependencies so I don't guarantee that it will compile out of the box, but it should be pretty close.
unit iosbytestring;
interface
uses
sysutils, classes;
{$IFNDEF MSWINDOWS}
const STRZERO = 0;
{$ELSE}
const STRZERO = 1;
{$ENDIF}
type
Tiosansichar = packed record
private
b: byte;
class function AnsiFromChar(c: char): byte;static;
class function CharFromAnsi(b: byte): char;static;
public
function ToChar: char;
function ToOrd: byte;
class operator Implicit(const s: Tiosansichar): string;
class operator Implicit(const s: Tiosansichar): char;
class operator Implicit(const s: Tiosansichar): byte;
class operator Implicit(const s: Tiosansichar): pointer;
end;
Tiosbytestring = record
private
Fbytes: TBytes;
function GetChar(idx: nativeint): char;
procedure SetChar(idx: nativeint; const Value: char);
function GetAddrOf(idx: nativeint): pbyte;
function getbyte(idx: nativeint): byte;
procedure setbyte(idx: nativeint; const Value: byte);
public
property chars[idx: nativeint]: char read GetChar write SetChar;
property bytes[idx: nativeint]: byte read getbyte write setbyte;
property addrof[idx: nativeint]: pbyte read GetAddrOf;
class operator Implicit(const s: TIOSByteString): string;
class operator Implicit(const s: string): TIOSByteString;
class operator Add(const s1,s2: TIOSByteString): TIOSByteSTring;
class operator Add(const s1: string; const s2: TIOSByteString): TIOSByteSTring;
class operator Add(const s1: TIOSByteString; const s2: string): TIOSByteSTring;
procedure FromString(s: string);
function ToString: string;
procedure SetLength(i: nativeint);
end;
TIOSAnsiString = TIOSByteString;
{$IFNDEF MSWINDOWS}
ansistring = TIOSByteString;
utf8string = TIOSByteString;
widestring = string;
{$ENDIF}
implementation
{ iosbytestring }
class operator Tiosbytestring.Add(const s1: string;
const s2: TIOSByteString): TIOSByteSTring;
var
ss2,ss3: string;
begin
ss2 := s2.ToString;
ss3 := s1+ss2;
result.FromString(ss3);
end;
class operator Tiosbytestring.Add(const s1: TIOSByteString;
const s2: string): TIOSByteSTring;
var
ss1,ss3: string;
begin
ss1 := s1.ToString;
ss3 := ss1+s2;
result.FromString(ss3);
end;
procedure Tiosbytestring.FromString(s: string);
begin
Fbytes := TEncoding.ANSI.GetBytes(s);
end;
function Tiosbytestring.GetAddrOf(idx: nativeint): pbyte;
begin
result := #Fbytes[idx];
end;
function Tiosbytestring.getbyte(idx: nativeint): byte;
begin
result := Fbytes[idx-strzero];
end;
function Tiosbytestring.GetChar(idx: nativeint): char;
begin
result := Tiosansichar.CharFromAnsi(Fbytes[idx-strzero]);
end;
class operator Tiosbytestring.Implicit(const s: TIOSByteString): string;
begin
result := s.ToString;
end;
class operator Tiosbytestring.Implicit(const s: string): TIOSByteString;
begin
result.FromString(s);
end;
procedure Tiosbytestring.setbyte(idx: nativeint; const Value: byte);
begin
Fbytes[idx-strzero] := value;
end;
class operator Tiosbytestring.Add(const s1,
s2: TIOSByteString): TIOSByteSTring;
var
ss1,ss2,ss3: string;
begin
ss1 := s1.ToString;
ss2 := s2.ToString;
ss3 := ss1+ss2;
result.FromString(ss3);
end;
procedure Tiosbytestring.SetChar(idx: nativeint; const Value: char);
begin
Fbytes[idx-strzero] := Tiosansichar.AnsiFromChar(value);
end;
procedure Tiosbytestring.SetLength(i: nativeint);
begin
system.setlength(Fbytes,i);
end;
function Tiosbytestring.ToString: string;
begin
result := TEncoding.ANSI.GetString(Fbytes);
end;
{ Tiosansichar }
class function Tiosansichar.AnsiFromChar(c: char): byte;
var
s: string;
te: TEncoding;
b: TBytes;
begin
s := c;
b := TEncoding.ANSI.GetBytes(c);
result := b[0];
end;
class function Tiosansichar.CharFromAnsi(b: byte): char;
var
s: string;
bytes: TBytes;
begin
system.setlength(bytes, 1);
bytes[0] := b;
s := TEncoding.ANSI.GetString(bytes, 0, 1);
result := s[low(s)];
end;
class operator Tiosansichar.Implicit(const s: Tiosansichar): char;
begin
result := s.ToChar;
end;
class operator Tiosansichar.Implicit(const s: Tiosansichar): string;
begin
result := s.ToChar;
end;
class operator Tiosansichar.Implicit(const s: Tiosansichar): pointer;
begin
result := #s.b;
end;
class operator Tiosansichar.Implicit(const s: Tiosansichar): byte;
begin
result := s.b;
end;
function Tiosansichar.ToChar: char;
begin
result := CharFromAnsi(b);
end;
function Tiosansichar.ToOrd: byte;
begin
result := b;
end;
end.
So just add the above unit, add it to your uses clause, and magically, you'll have an ansistring type on your mobile platforms. Continue using the standard ansistring type on windows.
If all is well... this is how your code snippet might end up looking.
procedure DecodeLineAddr(const aLine: AnsiString; var ByteCount: integer; var Address:Cardinal; var RecType: Integer);
var
aLineWide: string;
begin
aLineWide = aLine;
//123 4567 89 0
//:10 4640 00 0000 0600 0200 fa00 004f 7800 1e00 fb00 88
ByteCount:= StrToInt('$' + ocopy(aLineWide, 2, 2));
Address := StrToInt('$' + ocopy(aLineWide, 4, 4));
RecType := StrToInt('$' + ocopy(aLineWide, 8, 2));
end;

Related

Storing up to 8 boolean values in a byte or up to 32 boolean values in an Integer

I have this need where I need to store either:
up to 8 Boolean values in a Byte
up to 32 Boolean values in a (U)Int32
up to 64 Boolean values in a (U)Int64
Is Byte more suitable than Char for 8-bits?
Do I use signed or unsigned for 32/64-bit?
Is there a Delphi-specific code sample to convert the Byte/Integer to/from an array of Booleans? And to set, say, the N-th item to true/false, such as:
SetItemBoolean(ItemNumber: Integer; Value: Boolean);
I found something to convert from a Char to an array of Booleans, I'm just wondering how to do it for Byte/Integer so I can support a bigger number of Boolean values.
https://ibeblog.com/2010/08/20/delphi-binary-data-storage/
Delphi offers TIntegerSet for this, which has the size of an Integer and thus can be cast on it.
var
Bits: TIntegerSet;
IntVal: Integer;
begin
if Value then
Include(Bits, ItemNumber)
else
Exclude(Bits, ItemNumber);
if ItemNumber in Bits then
{ Bit ItemNumber is set }
else
{ Bit ItemNumber is not set }
{ cast to Integer as needed }
IntVal := Integer(Bits);
{ or from Integer }
Bits := TIntegerSet(IntVal);
end;
For 8 bits in a byte you can declare a TByteSet in a similar way:
type
TByteSet = set of 0..7;
and cast it to or from a Byte variable.
This type implements arbitrarily sized bit sets.
type
TBitSet = record
private
FBitCount: Integer;
FSets: array of set of 0..255;
class function SetCount(BitCount: Integer): Integer; static;
procedure MakeUnique;
procedure GetSetIndexAndBitIndex(Bit: Integer; out SetIndex, BitIndex: Integer);
function GetIsEmpty: Boolean;
procedure SetBitCount(Value: Integer);
function GetSize: Integer;
public
class operator In(const Bit: Integer; const BitSet: TBitSet): Boolean;
class operator Equal(const bs1, bs2: TBitSet): Boolean;
class operator NotEqual(const bs1, bs2: TBitSet): Boolean;
class function SizeOfNativeSet(BitCount: Integer): Integer; static;
property BitCount: Integer read FBitCount write SetBitCount;
property Size: Integer read GetSize;
property IsEmpty: Boolean read GetIsEmpty;
procedure Clear;
procedure IncludeAll;
procedure Include(const Bit: Integer);
procedure Exclude(const Bit: Integer);
end;
{ TBitSet }
procedure TBitSet.MakeUnique;
begin
// this is used to implement copy-on-write so that the type behaves like a value
SetLength(FSets, Length(FSets));
end;
procedure TBitSet.GetSetIndexAndBitIndex(Bit: Integer; out SetIndex, BitIndex: Integer);
begin
Assert(InRange(Bit, 0, FBitCount-1));
SetIndex := Bit shr 8; // shr 8 = div 256
BitIndex := Bit and 255; // and 255 = mod 256
end;
function TBitSet.GetIsEmpty: Boolean;
var
i: Integer;
begin
for i := 0 to High(FSets) do begin
if FSets[i]<>[] then begin
Result := False;
Exit;
end;
end;
Result := True;
end;
procedure TBitSet.SetBitCount(Value: Integer);
var
Bit, SetIndex, BitIndex: Integer;
begin
if (Value<>FBitCount) or not Assigned(FSets) then begin
Assert(Value>=0);
FBitCount := Value;
SetLength(FSets, SetCount(Value));
if Value>0 then begin
(* Ensure that unused bits are cleared, necessary give the CompareMem call in Equal. This also
means that state does not persist when we decrease and then increase BitCount. For instance,
consider this code:
var
bs: TBitSet;
...
bs.BitCount := 2;
bs.Include(1);
bs.BitCount := 1;
bs.BitCount := 2;
Assert(not (1 in bs)); *)
GetSetIndexAndBitIndex(Value - 1, SetIndex, BitIndex);
for Bit := BitIndex + 1 to 255 do begin
System.Exclude(FSets[SetIndex], Bit);
end;
end;
end;
end;
function TBitSet.GetSize: Integer;
begin
Result := Length(FSets)*SizeOf(FSets[0]);
end;
class function TBitSet.SetCount(BitCount: Integer): Integer;
begin
Result := (BitCount + 255) shr 8; // shr 8 = div 256
end;
class function TBitSet.SizeOfNativeSet(BitCount: Integer): Integer;
begin
Result := (BitCount + 7) shr 3; // shr 3 = div 8
end;
class operator TBitSet.In(const Bit: Integer; const BitSet: TBitSet): Boolean;
var
SetIndex, BitIndex: Integer;
begin
BitSet.GetSetIndexAndBitIndex(Bit, SetIndex, BitIndex);
Result := BitIndex in BitSet.FSets[SetIndex];
end;
class operator TBitSet.Equal(const bs1, bs2: TBitSet): Boolean;
begin
Result := (bs1.FBitCount=bs2.FBitCount)
and CompareMem(Pointer(bs1.FSets), Pointer(bs2.FSets), bs1.Size);
end;
class operator TBitSet.NotEqual(const bs1, bs2: TBitSet): Boolean;
begin
Result := not (bs1=bs2);
end;
procedure TBitSet.Clear;
var
i: Integer;
begin
MakeUnique;
for i := 0 to High(FSets) do begin
FSets[i] := [];
end;
end;
procedure TBitSet.IncludeAll;
var
i: Integer;
begin
for i := 0 to BitCount-1 do begin
Include(i);
end;
end;
procedure TBitSet.Include(const Bit: Integer);
var
SetIndex, BitIndex: Integer;
begin
MakeUnique;
GetSetIndexAndBitIndex(Bit, SetIndex, BitIndex);
System.Include(FSets[SetIndex], BitIndex);
end;
procedure TBitSet.Exclude(const Bit: Integer);
var
SetIndex, BitIndex: Integer;
begin
MakeUnique;
GetSetIndexAndBitIndex(Bit, SetIndex, BitIndex);
System.Exclude(FSets[SetIndex], BitIndex);
end;
It's a simple binary logic. You can store data in any numeric type, but i recommend use Unsigned types. Here is example for BYTE type, but you can do write same for any(UInt16, UInt32, UInt64) just change type of AStorage param:
//for byte Index can be from 0 to 7
function GetByteBool(const AStorage : byte; AIndex : byte) : boolean;
begin
Result := (AStorage and (1 shl AIndex)) = (1 shl AIndex);
end;
procedure SetByteBool(var AStorage : byte; const AIndex : byte; const AValue : boolean);
begin
if AValue then begin
AStorage := AStorage or (1 shl AIndex);
end else begin
AStorage := AStorage xor (1 shl AIndex);
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
var b : byte := 17;
SetByteBool(b, 4, false);
if GetByteBool(b, 4) then
showmessage('true')
else
showmessage('false')
end;
In this case you will use just 1 BIT per 1 boolean value.

Delphi mapping for Wine function wine_nt_to_unix_file_name

How can I correctly call wine_nt_to_unix_file_name from WINE's ntdll.dll in Delphi (10.4)?
In the web I found the definition to be like this:
NTSTATUS wine_nt_to_unix_file_name(const UNICODE_STRING *nameW, ANSI_STRING *unix_name_ret, UINT disposition, BOOLEAN check_case)
Disposition changes the return result for non existent last path part and check_case is self explanatory.
I would like to use this function to display real unix paths of my application to the user when running in WINE. This should make it more easy for a medium user to find a folder to share data between native apps and the WINE environment.
What I tried:
type
TWineGetVersion = function: PAnsiChar; stdcall;
TWineNTToUnixFileName = procedure(pIn: Pointer; pOut: Pointer; aParam: integer; caseSens: Boolean); stdcall;
...
initialization
try
LHandle := LoadLibrary('ntdll.dll');
if LHandle > 32 then
begin
LWineGetVersion := GetProcAddress(LHandle, 'wine_get_version');
LWineNTToUnixFileName := GetProcAddress(LHandle, 'wine_nt_to_unix_file_name');
end;
except
LWineGetVersion := nil;
LWineNTToUnixFileName := nil;
end;
Retrieving the WINE version works great but I cannot get the path conversion up and running as I don't know how to handle the returned Pointer to ANSI_STRING what seems to be a Windows structure like this:
typedef struct _STRING {
USHORT Length;
USHORT MaximumLength;
PCHAR Buffer;
} STRING;
I tried to approach the problem this way:
MyBuffer: array [0 .. 2048] of AnsiChar;
LWineNTToUnixFileName(PChar(aWinPath), #MyBuffer, 0, true);
But the function is returning total garbage in the buffer when output byte by byte.
Update
Following the hint to the current Wine source and the hint with the structure I tried this version, unfortunately delivering garbage. The first parameter is a UNICODE STRING structure, the second a simple ansistring. The third parameter receives the length of the returned buffer.
type
TWineNTToUnixFileName = procedure(pIn: Pointer; pOut: Pointer; aLen: Pointer); stdcall;
TWineUnicodeString = packed record
Len: Word;
MaxLen: Word;
Buffer: PWideChar;
end;
function WinePath(const aWinPath: String): String;
var
inString: TWineUnicodeString;
MyBuffer: array [0 .. 2048] of AnsiChar;
aLen,i: integer;
begin
inString.Buffer := PChar(aWinPath);
inString.Len := length(aWinPath);
inString.MaxLen := inString.Len;
LWineNTToUnixFileName(#inString, #MyBuffer, #aLen);
result := '';
for i := 1 to 20 do
result := result + MyBuffer[i];
end;
Based on Zeds great answer i created this function that automatically tries the new API call if the old one fails
type
TWineAnsiString = packed record
Len: Word;
MaxLen: Word;
Buffer: PAnsiChar;
end;
PWineAnsiString = ^TWineAnsiString;
TWineUnicodeString = packed record
Len: Word;
MaxLen: Word;
Buffer: PWideChar;
end;
PWineUnicodeString = ^TWineUnicodeString;
var
wine_get_version: function: PAnsiChar; cdecl;
// Both are assigned to the function in ntdll.dll to be able to try both alternatives
wine_nt_to_unix_file_name: function(const nameW: PWineUnicodeString; unix_name_ret: PWineAnsiString; disposition: Cardinal): Cardinal; cdecl;
wine_nt_to_unix_file_name_1: function(const nameW: PWineUnicodeString; nameA: PAnsiChar; Sz: PCardinal; disposition: Cardinal): Cardinal; cdecl;
LHandle: THandle;
function WinePath(const aPathIn: String): String;
var
VSz: Cardinal;
VNameA: AnsiString;
VNameW: TWineUnicodeString;
VUnixNameRet: TWineAnsiString;
VStatus: Cardinal;
aPath: String;
newVersion: Boolean;
begin
if not assigned(wine_nt_to_unix_file_name) then
begin
Result := 'n/a';
exit;
end;
aPath := '\??\' + aPathIn;
Result := '?';
newVersion := false;
VNameW.Len := Length(aPath) * SizeOf(WideChar);
VNameW.MaxLen := VNameW.Len;
VNameW.Buffer := PWideChar(aPath);
VUnixNameRet.Len := 0;
VUnixNameRet.MaxLen := 0;
VUnixNameRet.Buffer := nil;
VStatus := wine_nt_to_unix_file_name(#VNameW, #VUnixNameRet, 0);
if VStatus <> 0 then
begin
VSz := 255;
SetLength(VNameA, VSz);
ZeroMemory(Pointer(VNameA), VSz);
VStatus := wine_nt_to_unix_file_name_1(#VNameW, Pointer(VNameA), #VSz, 0);
newVersion := true;
end;
if VStatus <> 0 then
begin
Result := 'Error ' + IntToStr(Status);
exit;
end;
if not newVersion then
begin
VSz := VUnixNameRet.Len;
SetString(VNameA, VUnixNameRet.Buffer, VSz);
// ToDo: RtlFreeAnsiString(#VUnixNameRet)
end
else
SetLength(VNameA, VSz);
Result := StringReplace(VNameA, '/dosdevices/c:/', '/drive_c/', [rfIgnoreCase]);
end;
Try this type for MyBuffer:
type
TWineString = packed record
Len : Word;
MaxLen : Word;
Buffer : PAnsiChar;
end;
Also you can't pass PChar as input string because it isn't a UNICODE_STRING as defined in wine:
typedef struct _UNICODE_STRING {
USHORT Length; /* bytes */
USHORT MaximumLength; /* bytes */
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
You should use this equivalent:
type
TWineUnicodeString = packed record
Len : Word;
MaxLen : Word;
Buffer : PWideChar;
end;
Update: This function has changed its API 6 months ago, so depending on wine version you should use one of two ways: define USE_WINE_STABLE if you are on stable wine v5.0 or undefine it if you use newer version:
program WineTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Winapi.Windows,
System.SysUtils;
{$DEFINE USE_WINE_STABLE}
type
{$IFDEF USE_WINE_STABLE}
TWineAnsiString = packed record
Len : Word;
MaxLen : Word;
Buffer : PAnsiChar;
end;
PWineAnsiString = ^TWineAnsiString;
{$ENDIF}
TWineUnicodeString = packed record
Len : Word;
MaxLen : Word;
Buffer : PWideChar;
end;
PWineUnicodeString = ^TWineUnicodeString;
var
wine_get_version: function: PAnsiChar; cdecl;
{$IFDEF USE_WINE_STABLE}
wine_nt_to_unix_file_name: function(const nameW: PWineUnicodeString;
unix_name_ret: PWineAnsiString; disposition: Cardinal): Cardinal; cdecl;
{$ELSE}
wine_nt_to_unix_file_name: function(const nameW: PWineUnicodeString;
nameA: PAnsiChar; Sz: PCardinal; disposition: Cardinal): Cardinal; cdecl;
{$ENDIF}
procedure TestWinePath(const APath: string);
var
VSz: Cardinal;
VNameA: AnsiString;
VNameW: TWineUnicodeString;
{$IFDEF USE_WINE_STABLE}
VUnixNameRet: TWineAnsiString;
{$ENDIF}
VStatus: Cardinal;
begin
VNameW.Len := Length(APath) * SizeOf(WideChar);
VNameW.MaxLen := VNameW.Len;
VNameW.Buffer := PWideChar(APath);
{$IFDEF USE_WINE_STABLE}
VUnixNameRet.Len := 0;
VUnixNameRet.MaxLen := 0;
VUnixNameRet.Buffer := nil;
VStatus := wine_nt_to_unix_file_name(#VNameW, #VUnixNameRet, 0);
{$ELSE}
VSz := 255;
SetLength(VNameA, VSz);
ZeroMemory(Pointer(VNameA), VSz);
VStatus := wine_nt_to_unix_file_name(#VNameW, Pointer(VNameA), #VSz, 0);
{$ENDIF}
Writeln('wine_nt_to_unix_file_name:');
Writeln('status = 0x', IntToHex(VStatus, 8));
if VStatus <> 0 then begin
Exit;
end;
{$IFDEF USE_WINE_STABLE}
VSz := VUnixNameRet.Len;
SetString(VNameA, VUnixNameRet.Buffer, VSz);
// ToDo: RtlFreeAnsiString(#VUnixNameRet)
{$ELSE}
SetLength(VNameA, VSz);
{$ENDIF}
Writeln('unix len = ', VSz);
Writeln('unix: ', VNameA);
Writeln('nt: ', APath);
end;
function LoadProc(const AHandle: THandle; const AName: string): Pointer;
begin
Result := GetProcAddress(AHandle, PChar(AName));
if Result = nil then begin
raise Exception.CreateFmt('Can''t load function: "%s"', [AName]);
end;
end;
var
LHandle: THandle;
LNtFileName: string;
begin
try
LNtFileName := ParamStr(1);
if LNtFileName = '' then begin
Writeln('Usage: ', ExtractFileName(ParamStr(0)), ' NtFileName');
Exit;
end;
LHandle := LoadLibrary('ntdll.dll');
if LHandle > 32 then begin
wine_get_version := LoadProc(LHandle, 'wine_get_version');
Writeln('wine version = ', wine_get_version() );
wine_nt_to_unix_file_name := LoadProc(LHandle, 'wine_nt_to_unix_file_name');
TestWinePath(LNtFileName);
end;
except
on E: Exception do begin
Writeln(E.ClassName, ': ', E.Message);
end;
end;
end.
Output (tested on Ubuntu 20.04):
$ wine WineTest.exe "\??\c:\windows\notepad.exe"
wine version = 5.0
wine_nt_to_unix_file_name:
status = 0x00000000
unix len = 49
unix: /home/zed/.wine/dosdevices/c:/windows/notepad.exe
nt: \??\c:\windows\notepad.exe

how to read/write record types to send them via udp?

I have a record type with name, login, external-ip, tags and a boolean. I want to send that information to other computer via UDP where I want to get it back into a variable of the same record type.
I already know how to send and receive simple strings with Indy's UDPClient/UDPServer.
But how to send a record data?
I also want to, if possible, pass this data to my encryption method codeSSL(s,k) and when received, pass to decodeSSL(s,k) but I will be very satisfied if you could answer my first question, which is more important.
On the sending side, you need to serialize your record data into a flat byte array, optionally encrypt those bytes, and then send them. On the receiving side, you would read the bytes, optionally decrypt them, and then serialize them back into a record. TIdUDPClient and TIdUDPServer has methods for reading/writing TIdBytes data, and the IdGlobal unit has functions for manipulating TIdBytes data.
For example:
Sender:
type
TMyRecord = record
Name: String;
Login: String;
ExternalIP: String;
Tags: String;
Flag: Boolean;
end;
procedure AppendStringToBuffer(var Bytes: TIdBytes; const S: String);
var
Tmp: TIdBytes;
Len: Byte;
begin
Tmp := ToBytes(S, enUTF8);
Len := Length(Tmp);
AppendByte(Bytes, Len);
AppendBytes(Bytes, Tmp);
end;
var
Rec: TMyRecord;
Buf: TIdBytes;
begin
Rec := ...;
AppendStringToBuffer(Buf, Rec.Name);
AppendStringToBuffer(Buf, Rec.Login);
AppendStringToBuffer(Buf, Rec.ExternalIP);
AppendStringToBuffer(Buf, Rec.Tags);
AppendByte(Buf, Ord(Rec.Flag));
// optionally encrypt the buffer...
MySocket.SendBuffer(TargetHost, TargetPort, Buf);
end;
Receiver:
type
TMyRecord = record
Name: String;
Login: String;
ExternalIP: String;
Tags: String;
Flag: Boolean;
end;
function ReadStringFromBuffer(const Bytes: TIdBytes; var Index: Integer): String;
var
Len: Integer;
begin
Len := Bytes[Index];
Inc(Index);
if Len > 0 then
begin
Result := BytesToString(Bytes, Index, Len, enUTF8);
Inc(Index, Len);
end else
Result := '';
end;
var
Rec: TMyRecord;
Buf: TIdBytes;
BufLen, Index: Integer;
SenderIP: String;
SenderPort: TIdPort;
begin
SetLength(Buf, 1025);
BufLen := MySocket.ReceiveBuffer(Buf, SenderIP, SenderPort);
if Buf <= 0 then Exit;
// optionally decrypt the buffer...
Index := 0;
Rec.Name := ReadStringFromBuffer(Buf, Index);
Rec.Login := ReadStringFromBuffer(Buf, Index);
Rec.ExternalIP := ReadStringFromBuffer(Buf, Index);
Rec.Tags := ReadStringFromBuffer(Buf, Index);
Rec.Flag := Buf[Index] <> $00;
...
end;
You should use a serialization library.
You can find some in this link, Delphi (win32) serialization libraries.
For a comparison benchmark between different serialization libraries, see New sample for JSON performance: mORMot vs SuperObject/XSuperObject/dwsJSON/DBXJSON.
This is an example using SuperObject for serializing/deserializing a record in a generic way.
program TestSerializer;
{$APPTYPE CONSOLE}
uses SuperObject;
type
Serializer = record
class function Serialize<T>(data: T): String; static;
class procedure Deserialize<T>(const jsonStr: String; var data: T); static;
end;
class procedure Serializer.Deserialize<T>(const jsonStr: String; var data: T);
var
ctx: TSuperRttiContext;
begin
ctx := TSuperRttiContext.Create;
try
data := ctx.AsType<T>(SO(jsonStr));
finally
ctx.Free;
end;
end;
class function Serializer.Serialize<T>(data: T): String;
var
ctx: TSuperRttiContext;
obj: ISuperObject;
begin
Result := '';
ctx := TSuperRttiContext.Create;
try
obj := ctx.AsJson<T>(data);
Result := obj.AsJson;
finally
ctx.Free;
end;
end;
type
TData = record
str: string;
int: Integer;
bool: Boolean;
flt: Double;
end;
var
data: TData;
jStr: String;
begin
data.str := 'Test';
data.int := 42;
data.bool := True;
data.flt := 3.14;
jStr := Serializer.Serialize<TData>(data);
WriteLn(jStr);
data.str := '';
Serializer.Deserialize<TData>(jStr,data);
ReadLn;
end.
If you are using Indy, there is a Send() method for sending string data.
You can find some examples of string encryption/decryption here: Delphi: simple string encryption.

CharInSet Compiler Warning in Delphi XE4

I have following statement in my Delphi 7 code.
TMyCharSet = set of char;
When I migrated that code to Delphi XE4, I am getting following compiler warning at above line.
W1050 WideChar reduced to byte char in set expressions. Consider using 'CharInSet' function in 'SysUtils' unit.
How should I redeclare TMyCharSet?
A set cannot contain items larger than a byte. Since Char in UniCode Delphi is a WideChar which is two bytes in size, a set type is an inappropriate container.
Here is an example of a generic set type based on a record, TSet<T>. This means that you don't have to think about creation and destruction of variables of this type. Use this type as a container for simple types. I tried to mimic most of the behavior of the set type.
Addition and subtraction of items can be done with + and - operators. Added the in operator as well.
Note: The record holds the data in a dynamic array. Assigning a variable to another will make both variables using the same dynamic array. A Copy-On-Write (COW) protection built-in will prevent a change in one variable to be reflected on the other one.
unit GenericSet;
interface
Uses
System.Generics.Defaults;
Type
TSet<T> = record
class operator Add(const aSet: TSet<T>; aValue: T) : TSet<T>; overload;
class operator Add(const aSet: TSet<T>; const aSetOfT: TArray<T>) : TSet<T>; overload;
class operator Add(const aSet1: TSet<T>; const aSet2: TSet<T>) : TSet<T>; overload;
class operator Subtract(const aSet: TSet<T>; aValue: T): TSet<T>; overload;
class operator Subtract(const aSet: TSet<T>; const aSetOfT: TArray<T>) : TSet<T>; overload;
class operator Subtract(const aSet1: TSet<T>; const aSet2: TSet<T>) : TSet<T>; overload;
class operator In(aValue: T; const aSet: TSet<T>): Boolean; overload;
class operator In(const aSetOf: TArray<T>; const aSet: TSet<T>): Boolean; overload;
class operator In(const aSet1: TSet<T>; const aSet2: TSet<T>): Boolean; overload;
private
FSetArray : TArray<T>;
function GetEmpty: Boolean;
public
procedure Add(aValue: T);
procedure AddSet(const setOfT: array of T); overload;
procedure AddSet(const aSet: TSet<T>); overload;
procedure Remove(aValue: T);
procedure RemoveSet(const setOfT: array of T); overload;
procedure RemoveSet(const aSet : TSet<T>); overload;
function Contains(aValue: T): Boolean; overload;
function Contains(const aSetOfT: array of T): Boolean; overload;
function Contains(const aSet : TSet<T>): Boolean; overload;
procedure Clear;
property Empty: Boolean read GetEmpty;
end;
implementation
procedure TSet<T>.Add(aValue: T);
begin
if not Contains(aValue) then begin
SetLength(FSetArray,Length(FSetArray)+1);
FSetArray[Length(FSetArray)-1] := aValue;
end;
end;
class operator TSet<T>.Add(const aSet: TSet<T>; aValue: T): TSet<T>;
begin
Result.AddSet(aSet.FSetArray);
Result.Add(aValue);
end;
class operator TSet<T>.Add(const aSet: TSet<T>; const aSetOfT: TArray<T>): TSet<T>;
begin
Result.AddSet(aSet.FSetArray);
Result.AddSet(aSetOfT);
end;
class operator TSet<T>.Add(const aSet1, aSet2: TSet<T>): TSet<T>;
begin
Result.AddSet(aSet1.FSetArray);
Result.AddSet(aSet2.FSetArray);
end;
procedure TSet<T>.AddSet(const setOfT: array of T);
var
i : Integer;
begin
for i := 0 to High(setOfT) do
Self.Add(setOfT[i]);
end;
procedure TSet<T>.AddSet(const aSet: TSet<T>);
begin
AddSet(aSet.FSetArray);
end;
procedure TSet<T>.RemoveSet(const setOfT: array of T);
var
i : Integer;
begin
for i := 0 to High(setOfT) do
Self.Remove(setOfT[i]);
end;
procedure TSet<T>.RemoveSet(const aSet: TSet<T>);
begin
RemoveSet(aSet.FSetArray);
end;
class operator TSet<T>.Subtract(const aSet1, aSet2: TSet<T>): TSet<T>;
begin
Result.AddSet(aSet1.FSetArray);
Result.RemoveSet(aSet2.FSetArray);
end;
class operator TSet<T>.Subtract(const aSet: TSet<T>;
const aSetOfT: TArray<T>): TSet<T>;
begin
Result.AddSet(aSet.FSetArray);
Result.RemoveSet(aSetOfT);
end;
class operator TSet<T>.Subtract(const aSet: TSet<T>; aValue: T): TSet<T>;
begin
Result.AddSet(aSet.FSetArray);
Result.RemoveSet(aValue);
end;
class operator TSet<T>.In(aValue: T; const aSet: TSet<T>): Boolean;
begin
Result := aSet.Contains(aValue);
end;
class operator TSet<T>.In(const aSetOf: TArray<T>; const aSet: TSet<T>): Boolean;
begin
Result := aSet.Contains(aSetOf);
end;
class operator TSet<T>.In(const aSet1: TSet<T>; const aSet2: TSet<T>): Boolean;
begin
Result := aSet2.Contains(aSet1.FSetArray);
end;
function TSet<T>.Contains(aValue: T): Boolean;
var
i : Integer;
c : IEqualityComparer<T>;
begin
c := TEqualityComparer<T>.Default;
Result := false;
for i := 0 to Length(FSetArray)-1 do
if c.Equals(FSetArray[i],aValue) then
Exit(True);
end;
function TSet<T>.GetEmpty: Boolean;
begin
Result := (Length(FSetArray) = 0);
end;
procedure TSet<T>.Clear;
begin
SetLength(FSetArray,0);
end;
function TSet<T>.Contains(const aSetOfT: array of T): Boolean;
var
i : Integer;
begin
Result := High(aSetOfT) >= 0;
for i := 0 to High(aSetOfT) do
begin
Result := Contains(ASetOfT[i]);
if not Result then
Exit(false);
end;
end;
function TSet<T>.Contains(const aSet: TSet<T>): Boolean;
begin
Result := Contains(aSet.FSetArray);
end;
procedure TSet<T>.Remove(aValue: T);
var
i : Integer;
c : IEqualityComparer<T>;
begin
c := TEqualityComparer<T>.Default;
for i := 0 to Length(FSetArray)-1 do
begin
if c.Equals(FSetArray[i],aValue) then
begin
SetLength(FSetArray,Length(FSetArray)); // Ensure unique dyn array
if (i < Length(FSetArray)-1) then
FSetArray[i] := FSetArray[Length(FSetArray)-1]; // Move last element
SetLength(FSetArray,Length(FSetArray)-1);
Break;
end;
end;
end;
end.
A sample test program:
program ProjectGenericSet;
{$APPTYPE CONSOLE}
uses
GenericSet in 'GenericSet.pas';
var
mySet,mySet1 : TSet<Char>;
begin
mySet.AddSet(['A','B','C']);
WriteLn(mySet.Contains('C'));
WriteLn(mySet.Contains('D')); // False
mySet := mySet + 'D';
WriteLn(mySet.Contains('D'));
WriteLn('D' in mySet);
mySet := mySet - 'D';
WriteLn(mySet.Contains('D')); // False
mySet := mySet + TArray<Char>.Create('D','E');
WriteLn(mySet.Contains('D'));
WriteLn(mySet.Contains(['A','D']));
mySet1 := mySet;
// Testing COW
mySet1.Remove('A');
WriteLn(mySet.Contains('A'));
mySet1:= mySet1 + mySet;
WriteLn(mySet1.Contains('A'));
mySet := mySet1;
mySet1.Clear;
WriteLn(mySet.Contains('A'));
ReadLn;
end.
You get the warning because XE4 uses WideChar for variable of Char type (and WideString for String), so Char takes 2 bytes instead of 1 byte now. Now it is possible to keep unicode characters in String/Char, but for same reason it is impossible to use set of char anymore (in Delphi it is fixed size, 32-bytes bits map and can keep up to 256 items so).
If you use only chars from range #0..#127 (only latin/regular symbols), then you can just replace Char -> AnsiChar (but when you will assign it from Char you will see another warning, you will have to use explicit type conversion to suppress it).
If you need national/unicode symbols, then there is no "ready to use" structure in Delphi, but you can use Tdictionary for this purpose:
type
TEmptyRecord = record end;
TSet<T> = class(TDictionary<T,TEmptyRecord>)
public
procedure Add(Value: T); reintroduce; inline;
procedure AddOrSetValue(Value: T); reintroduce; inline;
function Contains(Value: T):Boolean; reintroduce; inline;
end;
procedure TSet<T>.Add(Value: T);
var Dummy: TEmptyRecord;
begin
inherited AddOrSetValue(Value, Dummy);
end;
procedure TSet<T>.AddOrSetValue(Value: T);
var Dummy: TEmptyRecord;
begin
inherited AddOrSetValue(Value, Dummy);
end;
function TSet<T>.Contains(Value: T): Boolean;
begin
result := inherited ContainsKey(Value);
end;
Of course you will have initialize at as any other regular class.
But it will be still quite efficient (not so fast as "set of" of course, just because "set" is always limited by 256 items max size but highly optimized).
Alternatively you can create your own set class for unicode chars as map of bits, it will take 8kb of memory to keep all the bits and will be almost as fast as "set of".
See fourm suggestions from web:
if not (CharInSet(Key,['0'..'9',#8]) then key := #0;
From: http://www.activedelphi.com.br/forum/viewtopic.php?t=66035&sid=f5838cc7dc991f7b3340e4e2689b222a

Open any File in a Memo?

In Notepad you can Open any File and it will display the raw data inside.
I would like to do this in a TMemo but have struggled to find out how to do this.
I managed to find this code here.
I modified it to a function and changed it slightly for my purposes:
function OpenBinaryFile(var Data; Count: Cardinal): string;
var
Line: string[80];
i: Cardinal;
P: PAnsiChar;
nStr: string[4];
SL: TStringList;
const
posStart = 1;
binStart = 7;
ascStart = 57;
begin
P := #Data;
Line := '';
SL := TStringList.Create;
try
for i := 0 to Count - 1 do
begin
if (i mod 16) = 0 then
begin
if Length(Line) > 0 then
SL.Add(Trim(Line));
FillChar(Line, SizeOf(Line), ' ');
Line[0] := Chr(72);
end;
if P[i] >= ' ' then
Line[i mod 16 + ascStart] := P[i]
else
Line[i mod 16 + ascStart] := '.';
end;
SL.Add(Trim(Line));
Result := SL.Text;
finally
SL.Free;
end;
end;
It works, but it only displays in a fixed amount of characters per line, like this:
What do I need to change so it fills all the memo in the same way Notepad would?
Well, it's the if (i mod 16) = 0 test that is truncating the lines at 16 characters.
I believe that Notepad does the same as this code:
var
i: Integer;
s: AnsiString;
Stream: TFileStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead);
try
SetLength(s, Stream.Size);
if Stream.Size>0 then
Stream.ReadBuffer(s[1], Stream.Size);
finally
Stream.Free;
end;
for i := 1 to Length(s) do
if s[i]=#0 then
s[i] := ' ';
Memo1.Text := s;
end;
If you want to replace non-printable characters with '.' then you can easily do so by modifying the code above like this:
if s[i]<#32 then
s[i] := '.';
TStrings became TEncoding-aware in D2009. By default, TStrings.LoadFrom...() will use TEncoding.Default unless you tell it otherwise. I would suggest implementing a custom TEncoding derived class that reads/writes raw 8-bit data, eg:
type
TRawEncoding = class(TEncoding)
protected
function GetByteCount(Chars: PChar; CharCount: Integer): Integer; override;
function GetBytes(Chars: PChar; CharCount: Integer; Bytes: PByte; ByteCount: Integer): Integer; override;
function GetCharCount(Bytes: PByte; ByteCount: Integer): Integer; override;
function GetChars(Bytes: PByte; ByteCount: Integer; Chars: PChar; CharCount: Integer): Integer; override;
public
constructor Create;
function GetMaxByteCount(CharCount: Integer): Integer; override;
function GetMaxCharCount(ByteCount: Integer): Integer; override;
function GetPreamble: TBytes; override;
end;
.
constructor TRawEncoding.Create;
begin
FIsSingleByte := True;
FMaxCharSize := 1;
end;
function TRawEncoding.GetByteCount(Chars: PChar; CharCount: Integer): Integer;
begin
Result := CharCount;
end;
function TRawEncoding.GetBytes(Chars: PChar; CharCount: Integer; Bytes: PByte; ByteCount: Integer): Integer;
var
i : Integer;
begin
Result := Math.Min(CharCount, ByteCount);
for i := 1 to Result do begin
// replace illegal characters > $FF
if Word(Chars^) > $00FF then begin
Bytes^ := Byte(Ord('?'));
end else begin
Bytes^ := Byte(Chars^);
end;
//advance to next char
Inc(Chars);
Inc(Bytes);
end;
end;
function TRawEncoding.GetCharCount(Bytes: PByte; ByteCount: Integer): Integer;
begin
Result := ByteCount;
end;
function TRawEncoding.GetChars(Bytes: PByte; ByteCount: Integer; Chars: PChar; CharCount: Integer): Integer;
var
i : Integer;
begin
Result := Math.Min(CharCount, ByteCount);
for i := 1 to Result do begin
Word(Chars^) := Bytes^;
//advance to next char
Inc(Chars);
Inc(Bytes);
end;
end;
function TRawEncoding.GetMaxByteCount(CharCount: Integer): Integer;
begin
Result := CharCount;
end;
function TRawEncoding.GetMaxCharCount(ByteCount: Integer): Integer;
begin
Result := ByteCount;
end;
function TRawEncoding.GetPreamble: TBytes;
begin
SetLength(Result, 0);
end;
Then you can use it like this:
var
Enc: TEncoding;
begin
Enc := TRawEncoding.Create;
try
Memo1.Lines.LoadFromFile('filename', Enc);
finally
Enc.Free;
end;
end;

Resources