RTollison 0 Posted December 1, 2020 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
David Heffernan 2353 Posted December 1, 2020 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. Share this post Link to post
RTollison 0 Posted December 1, 2020 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
David Heffernan 2353 Posted December 1, 2020 (edited) 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 December 1, 2020 by David Heffernan Share this post Link to post
RTollison 0 Posted December 1, 2020 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
David Heffernan 2353 Posted December 1, 2020 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
RTollison 0 Posted December 1, 2020 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
David Heffernan 2353 Posted December 1, 2020 Best to be clear on what you are doing here. The code seems to assume ANSI. Again are you using ANSI text? Share this post Link to post
RTollison 0 Posted December 1, 2020 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
David Heffernan 2353 Posted December 1, 2020 I'm sure you will be able to debug it. Good luck. 1 Share this post Link to post
RTollison 0 Posted December 1, 2020 haha i wouldn't take any bets on that. but thank you. Share this post Link to post
David Heffernan 2353 Posted December 1, 2020 I mean. I'd start by using TRegistry. And I'd write the code myself rather than hacking at scraps found from the web. 2 Share this post Link to post
Guest Posted December 1, 2020 (edited) 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 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 December 1, 2020 by Guest Share this post Link to post
Remy Lebeau 1436 Posted December 1, 2020 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
Guest Posted December 1, 2020 (edited) 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 December 1, 2020 by Guest Share this post Link to post
RTollison 0 Posted December 1, 2020 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
Guest Posted December 1, 2020 (edited) did try my sample? you can replace the strings "#0" by char(0) and add it in your stringlist items Edited December 1, 2020 by Guest Share this post Link to post
David Heffernan 2353 Posted December 1, 2020 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
Guest Posted December 1, 2020 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
RTollison 0 Posted December 1, 2020 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
David Heffernan 2353 Posted December 1, 2020 27 minutes ago, emailx45 said: You must always be correct! Sorry, my little knowledge! One day I'll get there... hug https://quality.embarcadero.com/browse/RSP-10041 https://quality.embarcadero.com/browse/RSP-11302 Here are a couple of examples, but there are more. Share this post Link to post
Guest Posted December 2, 2020 (edited) 4 hours ago, David Heffernan said: https://quality.embarcadero.com/browse/RSP-10041 https://quality.embarcadero.com/browse/RSP-11302 Here are a couple of examples, but there are more. 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 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 December 2, 2020 by Guest Share this post Link to post
David Heffernan 2353 Posted December 2, 2020 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