Jump to content
RTollison

null terminated string array

Recommended Posts

i am needing to update a null terminated string array.

currently i have it in a string like this: one#0two#0three#0four#0#0#0#0#0#0#0#0#0#0....

i am needing to add five and six into the string but str := str + 'five' + #0 + 'six' + #0; doesn't actually work.

what would i need to do to get the string from  one#0two#0three#0four#0#0#0#0#0#0#0#0#0#0   to one#0two#0three#0four#0five#0six#0#0#0#0#0#0

Share this post


Link to post

That's a pretty unusual data representation. Double null-terminated strings are not uncommon on Windows at least. 

 

For what it is worth, you cannot call that null terminated, it is actually null separated.

 

Personally, I would probably work with this sort of structure by converting to a string list and working on that internally. Then, at the interface between your code and the code that handles null separated data, convert it back to null separated. I use these methods to split and join:

 

function Join(const Strings: array of string; Separator: Char): string;
var
  Index: Integer;
  Len: Integer;
  Pos: Integer;
begin
  if Length(Strings)=0 then begin
    Result := '';
    Exit;
  end;

  Len := 0;
  for Index := 0 to High(Strings) do begin
    Inc(Len, Length(Strings[Index]));
  end;
  SetLength(Result, Len + High(Strings));
  Pos := 1;
  for Index := 0 to High(Strings) do begin
    if Index>0 then begin
      Result[Pos] := Separator;
      Inc(Pos);
    end;
    Len := Length(Strings[Index]);
    Move(Pointer(Strings[Index])^, Result[Pos], Len*SizeOf(Char));
    Inc(Pos, Len);
  end;
end;

function Split(const Str: string; Separator: Char): TArray<string>;
var
  i, ItemIndex: Integer;
  len: Integer;
  SeparatorCount: Integer;
  Start: Integer;
begin
  len := Length(Str);
  if len=0 then begin
    Result := nil;
    Exit;
  end;

  SeparatorCount := 0;
  for i := 1 to len do begin
    if Str[i]=Separator then begin
      Inc(SeparatorCount);
    end;
  end;

  SetLength(Result, SeparatorCount+1);
  ItemIndex := 0;
  Start := 1;
  for i := 1 to len do begin
    if Str[i]=Separator then begin
      Result[ItemIndex] := Copy(Str, Start, i-Start);
      Inc(ItemIndex);
      Start := i+1;
    end;
  end;
  Result[ItemIndex] := Copy(Str, Start, len-Start+1);
end;

 

Of course, you can work directly with the null separated strings. That will involve a function to find the index of the character at the start of the n-th item. Just loop through the string looking for the separator (null in your case). And then you can use Insert and Delete to modify the string.

  • Sad 1

Share this post


Link to post

yeah its a windows string i am getting it from the registry REG_MULTI_SZ and i read it into the string in question then i need to update it and write it back to the registry. i was hoping that i didn't have to manually manipulate the string or anything like that. was hoping for as simple of a way to get the string updated and pushed back. Thanks.

Share this post


Link to post

I doubt that what your posted in your original post really is a REG_MULTI_SZ. That really is double null terminated rather than null separated.

Edited by David Heffernan

Share this post


Link to post

sha_str : string;

 

 

  sha_str := '';
  if RegOpenKeyEx(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010003', 0, KEY_READ,
    hOpenKey) = ERROR_SUCCESS then
  begin
   iSize := 0;
    if RegQueryValueEx(hOpenKey, 'Functions', nil, @iType, nil, @iSize)
      = ERROR_SUCCESS then
    begin
      SetLength(sha_str, iSize - 1);
      strSize := iSize;
    end;
    iType := REG_MULTI_SZ;       // Type of data that is going to be read
    if RegQueryValueEx(hOpenKey, 'Functions', nil, @iType, PByte(sha_str), @iSize)
      = ERROR_SUCCESS then
    begin

 

that is how i got to this point...

Share this post


Link to post

Once you get to the first double null then the rest of the data should be ignored. 

 

I'd remove everything after the first double null and then your original idea will work. You'll need to make sure you add back a double null terminator when you write it back. 

 

Are you really using ANSI text? 

Share this post


Link to post

i am stumbling along on this one. i have the same thing in place for updating numbers and string but when i got to this one i am flying blind and just winging it. so after googling around i came across this example for reading but the writing was just as simple by setting the string manually to 'one'#0'two'#0'three'#0'four'#0'five'#0'six'#0; so i am trying figure out how to take what is there and if necessary to add to the string/whatever and then write it back to the registry.

Share this post


Link to post

here is what i currently have in place it all seems to run without errors/issues BUT when i run the regedit to verify the updated values I get a warning from the registry

Data of type REG_MULTI_SZ cannot contain empty strings.

Registry Editor will remove all empty strings found.

I don't see any extra lines but it pops the first time around and after it saves it then reopen it looks the same but no warning message.

 

reading of the registry and evaluating if two other values are needed or not.

    iType := REG_MULTI_SZ;       // Type of data that is going to be read
    if RegQueryValueEx(hOpenKey, 'Functions', nil, @iType, PByte(sha_str), @iSize)
      = ERROR_SUCCESS then
    begin
      Edit11.Text := stringreplace(copy(sha_str,0,Pos(#0#0,sha_str) + 1),#0,',',[rfReplaceAll]);
      strlist.DelimitedText := Edit11.Text;
      for i := strList.Count - 1 downto 0 do
        if strList = '' then
          strList.Delete(i);
      if Pos('RSA/SHA512',sha_str) = 0 then
      begin
        cxCheckBox6.Checked := True;
        strList.Append('RSA/SHA512');
      end;
      if Pos('ECDSA/SHA512',sha_str) = 0 then
      begin
        cxCheckBox6.Checked := True;
        strList.Append('ECDSA/SHA512');
      end;
      sha_str := '';
      sha_str := StringReplace(strList.DelimitedText,',',#0,[rfReplaceAll])+#0;
    end
    else
      cxCheckBox6.Checked := True;
    RegCloseKey(hOpenKey);
  end

 

then writing of updated value if needed

  begin
    SetLength(sha_str, Length(sha_str) - 1);
    tname := dir + 'SHA512_Default' + _dt + '.reg';
    RegistryExport(tname,'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010003');
    CleanUpExport(tname,'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010003', 'Functions');
    if RegCreateKeyEx(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010003', 0, nil,
      REG_OPTION_NON_VOLATILE, KEY_WRITE, nil, hOpenKey, pdI) = ERROR_SUCCESS then
    begin
      RegSetValueEx(hOpenKey, 'Functions', 0, REG_MULTI_SZ, Pointer(sha_str), Length(sha_str)*sizeof(Char));
    end;
  end;

 

Share this post


Link to post
4 hours ago, RTollison said:

i am needing to update a null terminated string array.

currently i have it in a string like this: one#0two#0three#0four#0#0#0#0#0#0#0#0#0#0....

i am needing to add five and six into the string but str := str + 'five' + #0 + 'six' + #0; doesn't actually work.

what would i need to do to get the string from  one#0two#0three#0four#0#0#0#0#0#0#0#0#0#0   to one#0two#0three#0four#0five#0six#0#0#0#0#0#0

maybe you can try my sample using "SPLIT" helper of strings + TString/TStringList to help your task!

  • RAD Studio 10.3.3 Arch (Rio)
  • VCL project test

 

image.thumb.png.301536b92ad9c0560d2c69f4aed587b4.png    image.thumb.png.54a10119f0edf4bc879f5b3c5557f900.png

 

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  System.StrUtils;

procedure TForm1.Button1Click(Sender: TObject);
var
  lText       : string;
  lTArrText   : TArray<string>;
  lNullCounter: integer;
begin
  lText := 'one#0two#0three#0four#0#0#0#0#0#0#0#0#0#0';
  //
  lNullCounter := lText.CountChar('#');
  //
  lTArrText := lText.Split(['#0'], TStringSplitOptions.ExcludeEmpty);
  //
  ListBox1.Items.AddStrings(lTArrText); // using ListBox for easy test! but, you can use any TString/TStringList
  //
  //
  ListBox1.Items.Add('five');
  ListBox1.Items.Add('six');
  //
  // add n "#0"  strings
  lText := StringReplace(ListBox1.Items.DelimitedText, ',', '#0', [rfReplaceAll]);
  //
  if (lNullCounter - ListBox1.Items.Count) > 0 then
    lText := lText + DupeString('#0', (lNullCounter - ListBox1.Items.Count));
  //
  ShowMessage(lText);
end;

 

huh

Edited by emailx45

Share this post


Link to post
1 hour ago, RTollison said:

sha_str : string;

Which version of Delphi are you using?  string is AnsiString in D2007 and earlier, but is UnicodeString in D2009 and later.

1 hour ago, RTollison said:

   iSize := 0;

    if RegQueryValueEx(hOpenKey, 'Functions', nil, @iType, nil, @iSize)
      = ERROR_SUCCESS then
    begin
      SetLength(sha_str, iSize - 1);
      strSize := iSize;
    end;

RegQueryValueEx() operates on BYTES not CHARACTERS.  The last parameter outputs the number of BYTES read.  This code is assuming that 1 Byte = 1 Char, which is true only when Char is AnsiChar but not when Char is WideChar.  When using a generic String to receive the raw bytes, you need to use (iSize div SizeOf(Char)) - 1 for the string's length (assuming the data is null terminated, which it SHOULD BE but is not GUARANTEED to be - use RegGetValue() instead to ensure proper null termination).

 

1 hour ago, RTollison said:

Edit11.Text := stringreplace(copy(sha_str,0,Pos(#0#0,sha_str) + 1),#0,',',[rfReplaceAll]);

If the data is terminated property, and you allocate and fill the String properly, then Pos(#0#0) will NEVER find the double-null terminator, since the 2nd #0 is not actually part of the string's length, it will have been cut off by the -1 on the call to SetLength().  Memory for the 2nd #0 will have been allocated, and the Registry will have populated that memory, but Pos() will ignore that memory since it is not part of the string's length.

 

Since you appear to be filling a TStringList, I would suggest something more like this:

 

var
  sha_str : string;
  hOpenKey: HKEY;
  iType, iSize: DWORD;
  strList: TStringList;
  P: PChar;
begin
  sha_str := '';
  if RegOpenKeyEx(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010003', 0, KEY_QUERY_VALUE, hOpenKey) = ERROR_SUCCESS then
  try
    iSize := 0;
    if (RegQueryValueEx(hOpenKey, 'Functions', nil, @iType, nil, @iSize) = ERROR_SUCCESS)
	   and (iType = REG_MULTI_SZ)
	   and (iSize >= SizeOf(Char)) then
    begin
      SetLength(sha_str, iSize div SizeOf(Char));
      if RegQueryValueEx(hOpenKey, 'Functions', nil, @iType, Pointer(sha_str), @iSize) = ERROR_SUCCESS then
      begin
        strList := TStringList.Create;
		try
          P := PChar(sha_str);
          while P^ <> #0 do
          begin
            strlist.Add(P);
            Inc(P, StrLen(P)+1);
		  end;
          Edit11.Text := strList.CommaText;
          if strlist.IndexOf('RSA/SHA512') = -1 then
          begin
            cxCheckBox6.Checked := True;
            strList.Append('RSA/SHA512');
          end;
          if strList.IndexOf('ECDSA/SHA512') = -1 then
          begin
            cxCheckBox6.Checked := True;
            strList.Append('ECDSA/SHA512');
          end;
          strList.Delimiter := #0;
          strList.StrictDelimiter := True;
          sha_str := strList.DelimitedText + #0;
        finally
          strList.Free;
        end;
      end;
    end
    else
      cxCheckBox6.Checked := True;
  finally
    RegCloseKey(hOpenKey);
  end;

  ...

  tname := dir + 'SHA512_Default' + _dt + '.reg';
  RegistryExport(tname,'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010003');
  CleanUpExport(tname,'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010003', 'Functions');
  if RegCreateKeyEx(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010003', 0, nil, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nil, hOpenKey, pdI) = ERROR_SUCCESS then
  begin
    RegSetValueEx(hOpenKey, 'Functions', 0, REG_MULTI_SZ, PChar(sha_str), (Length(sha_str) + 1) * SizeOf(Char));
    RegCloseKey(hOpenKey);
  end;
end;

 

Share this post


Link to post
4 hours ago, David Heffernan said:

Of course, you can work directly with the null separated strings. That will involve a function to find the index of the character at the start of the n-th item. Just loop through the string looking for the separator (null in your case). And then you can use Insert and Delete to modify the string.

Seems odd to replicate a function that already exists in the RTL.

  • unit System.SysUtils;
    • TStringHelper = record helper for string
    • class function Join(...)
  • lText:= lText.Join(',',[ListBox1.Items.DelimitedText] ); // in my sample above!

 

Edited by emailx45

Share this post


Link to post

i got it all separated out into the stringlist and what looks to be a clean string with null separated values when I am trying to write to the registry (RegSetValueEx) but its after that and i run regedit and open the functions value to look at the info that it throws a warning about empty strings.

 

 

Share this post


Link to post

did try my sample?

you can replace the strings "#0" by char(0) and add it in your stringlist items

Edited by emailx45

Share this post


Link to post
18 minutes ago, emailx45 said:

Seems odd to replicate a function that already exists in the RTL.

Good point. But I'm using XE7 and the string helper has many many bugs, including I believe in one of the split or join functions. Plus, my version minimise heap allocation. 

Share this post


Link to post
1 minute ago, David Heffernan said:

Good point. But I'm using XE7 and the string helper has many many bugs, including I believe in one of the split or join functions. Plus, my version minimise heap allocation. 

You must always be correct! Sorry, my little knowledge!
One day I'll get there...
hug

Share this post


Link to post

i am on 10.2 and i used Remy's suggestion for cleaning of the string/stringlist then now when i set the registry value it and check it with regedit it doesn't warn me about the empty values. So everything seems to working at this point and will be getting sent over to some QA people to try and break it.

Share this post


Link to post
4 hours ago, David Heffernan said:

Haven't they been fixed?

 

  • Nobody uses this params?   TStringSplitOptions = (None, ExcludeEmpty, ExcludeLastEmpty);  == SPLIT()!!!
    • default is "None", then all is valid
  • [';x'] = your x = remember? = 2
  •  
  • I think that the "hell" begin here:  System.SysUtils.pas, line  29894 and others calls to line 29814

 

image.thumb.png.35809187c2b831a235f1f77f873422ea.png     image.thumb.png.5fbb1d1ad672f2edc9267df65ffa5496.png

 

procedure TForm1.Button1Click(Sender: TObject);
var
  lTArrStr: TArray<string>;
begin
  lTArrStr := 'x;'.Split([';']); // 2 elements;
  lTArrStr := ''.Split([';']);   // 0 elements;
  lTArrStr := ';x'.Split([';']); // 2 elements;
  //
  lTArrStr := 'x;'.Split([';'], TStringSplitOptions.ExcludeEmpty); // 1 element;
  lTArrStr := ''.Split([';'], TStringSplitOptions.ExcludeEmpty);   // 0 element;
  lTArrStr := ';x'.Split([';'], TStringSplitOptions.ExcludeEmpty); // 1 element;
  //
  // if using:  '.' ',' ';' '/' etc... or other chars ---  needs a string contain 'a..z, A..Z'
  // lTArrStr := '0'.Split([';'] { with or without TStringSplitOptions.ExcludeEmpty } ); // dont compile!  E2018 Record, object or class required! == Maybe some inference here?
  //
  lText := '0';
  lTArrStr := lText.Split([';'] { with or without TStringSplitOptions.ExcludeEmpty } ); // compile!  then, is good pratice say what you want, else, stay to the time!
  //
  lTArrStr := '0z'.Split([';']);  // 1 element
  //
  lTArrStr := ''.Split([';']);  // 0 element  ... if (S <> '') or ((S = '') and (Options <> TStringSplitOptions.ExcludeEmpty)) then
  //
  showmessage(Length(lTArrStr).ToString)
end;

...

  • Join stay for another time, im tired out! my brain is burning!
Edited by emailx45

Share this post


Link to post
5 hours ago, emailx45 said:

Haven't they been fixed?

Maybe, but I'm still using XE7 which is why my own code doesn't use the RTL string helper. 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×