将以null为分隔符的PWideChar转换为字符串列表

3

通过调用 Windows API (GetUserPreferredUILanguages()),我会得到一个字符串列表,其中每个字符串都是使用 null 字符 #0 分隔的。 我需要将此转换为 Delphi 字符串列表。 我开始编写代码手动循环遍历列表,查找 #0 字符。

有更智能的方法吗?

GetUserPreferredUILanguages 返回的 PWideChar 示例:

('e','n','-','U','S',#0,'f','r','-','F','R',#0,#0,...)

根据我在文档中所读的内容,因为当我在我的电脑上调用该函数时,它只返回一种语言,即'en-US'#0#0。

这是我目前的代码:

procedure GetWinLanguages(aList: TStringList);
var lCount, lSize: ULong;
    lChars: array of WideChar;
    lIndex, lLastIndex: integer;
begin
  lSize := 1000;
  SetLength(lChars, lSize);
  GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, @lCount, @lChars[0], @lSize);

  // untested quick solution to convert from lChars to aList
  lIndex := 0;
  lLastIndex := -1;
  while (lIndex<=lSize) do
  begin
    while (lIndex<lSize) and (lChars[lIndex]<>#0) do
      inc(lIndex);
    if (lIndex-lLastIndex)>1 then
    begin
      // here: copy range lLastIndex to lIndex, convert to string and add to aList
      lLastIndex := lIndex;
      inc(lIndex);
    end else
      Break;
  end;
end;

PS. 我正在使用 Delphi Berlin 进行 FMX 项目,系统是 Windows 10。


给那位点踩的用户:请告诉我这个问题有什么问题。我很乐意改进它。 - Hans
2个回答

5
该API返回一个双null结尾字符串。以下程序演示了如何解析这样的字符串:
{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  Winapi.Windows;

procedure Main;
var
  NumLanguages, LanguagesBufferLen: ULONG;
  LanguagesBuffer: TArray<WideChar>;
  P: PWideChar;
  str: string;
begin
  LanguagesBufferLen := 0;
  Win32Check(GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, @NumLanguages, nil, @LanguagesBufferLen));
  SetLength(LanguagesBuffer, LanguagesBufferLen);
  Win32Check(GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, @NumLanguages, @LanguagesBuffer[0], @LanguagesBufferLen));
  P := @LanguagesBuffer[0];
  while P^<>#0 do begin
    str := P;
    Writeln(str);

    inc(P, Length(str)+1); // step over the string, and its null terminator
  end;
end;

begin
  try
    Main;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

很明显,从这段代码中提取一个将以 null 结尾的字符串解析为字符串列表的函数是很容易的。这样可以让您在其他地方重复使用代码。

谢谢。是的,我知道它返回什么。我称它为null-delimited PWideChar,从技术上讲就是这样。你的循环比我的简单得多 - 我喜欢那个。 - Hans
抱歉,我误读了你的代码。我以为你有一个 PWideChar 数组。 - David Heffernan
1
@DavidHeffernan:文档仅说明该函数报告“ERROR_INSUFFICIENT_BUFFER”,但未说明在输入为nil/0时该函数返回true还是false。实现此类双查询语义的函数通常使用的典型约定是,函数在任何错误(包括“ERROR_INSUFFICIENT_BUFFER”)时返回FALSE。如果该函数不是这种情况,则Microsoft没有遵循自己的约定,并且未充分记录行为。 - Remy Lebeau
那不是我读文档的方式。无论如何,函数的行为表明我的解释是正确的。 - David Heffernan
@DavidHeffernan 我意识到我的问题很容易被误读。我看到Remy对文本进行了一些改进,我也添加了一些文本以使其更清晰。 - Hans
显示剩余2条评论

2
API返回一个双重空终止字符串,每个子字符串都由#0字符分隔,然后列表以额外的#0字符终止。因此,您只需循环直到遇到最后一个#0字符即可。例如:
procedure GetWinLanguages(aList: TStringList);
var
  lCount, lSize: ULONG;
  lChars: array of Char;
  lStr: PChar;
begin
  lSize := 0;
  lChars := nil;

  repeat
    // unlike most Win32 APIs, GetUserPreferredUILanguages() returns TRUE when
    // pwszLanguagesBuffer is nil and pcchLanguagesBuffer is set to 0 (unless a
    // real error occurs!). This is not made clear in the documentation! The
    // function only returns FALSE with an ERROR_INSUFFICENT_BUFFER error code
    // when pwszLanguagesBuffer is not nil and pcchLanguagesBuffer is set too low...

    if not GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, @lCount, PChar(lChars), @lSize) then
    begin
      if GetLastError() <> ERROR_INSUFFICIENT_BUFFER then
        RaiseLastOSError;
    end
    else if lChars <> nil then
      Break;
    SetLength(lChars, lSize);
  until False;

  lStr := PChar(lChars);
  while (lStr^ <> #0) do
  begin
    aList.Add(lStr);
    Inc(lStr, StrLen(lStr)+1);
  end;
end;

这个行为在MSDN文档中没有明确说明。既然如此,我已经相应地修复了代码。 - Remy Lebeau
你为什么要循环? - David Heffernan
我通常在进行这种查询和动态数据分配时使用循环。由于正在查询系统数据,并且大小和数据是分别检索的,因此循环处理了在检索到实际数据之前大小可能发生变化的情况。是的,这是一个很小的机会窗口,但它仍然存在。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接