You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi如何正确处理带BOM的C++ DLL返回空终止字符串?

解决Delphi中处理C++ DLL返回带BOM的UTF-16空终止字符串问题

先拆解下你遇到的两个核心问题根源:

  1. 第一个版本用TArray<Char>转PChar保留BOM:因为Delphi的Char就是WideChar(对应UTF-16LE),DLL返回的开头FEFF(UTF-16LE的BOM)会被当成普通字符串字符,PChar从第一个字符读到空终止符,自然就把BOM留在结果里了。
  2. 第二个版本用TEncoding.Unicode.GetString出现大量末尾0:你预分配了缓冲区大小,转字符串时会把整个Buffer的字节都转成字符,包括空终止符后面的多余缓冲区内容,所以才会出现一堆无效的0字符。

方案一:直接操作指针(最优雅高效)

这个思路是把字节数组转成PWideChar,先检查并跳过BOM,再通过StrLen获取实际有效字符长度,最后用SetString直接构造正确字符串:

function TRFCNWParameter.GetAsString: string;
const
  DEFAULT_BUFFER_SIZE = 1024;
  UTF16LE_BOM: Word = $FEFF; // UTF-16LE的BOM对应的WideChar值
var
  ResultSize: cardinal;
  Buffer: TBytes;
  bufferCharLength: cardinal;
  lErrorInfo: RRFCNWErrorInfo;
  RC: TRfcReturnCode;
  WideBuf: PWideChar;
begin
  bufferCharLength := DEFAULT_BUFFER_SIZE;
  // 按WideChar的大小分配缓冲区
  SetLength(Buffer, bufferCharLength * SizeOf(WideChar));
  RC := RFCGetString(OwnerHandle, PChar(ParamName), @Buffer[0], bufferCharLength, ResultSize, @lErrorInfo);
  
  // 缓冲区不足时扩容重试
  if (RC <> RFC_OK) and (ResultSize >= bufferCharLength) then
  begin
    bufferCharLength := ResultSize + 1;
    SetLength(Buffer, bufferCharLength * SizeOf(WideChar));
    RC := RFCGetString(OwnerHandle, PChar(ParamName), @Buffer[0], bufferCharLength, ResultSize, @lErrorInfo);
  end;
  
  CheckForReturnedError(@lErrorInfo, 'Retrieve Parameter %s ' + ParamName);

  WideBuf := PWideChar(@Buffer[0]);
  
  // 检查并跳过BOM
  if (ResultSize >= 1) and (Word(WideBuf^) = UTF16LE_BOM) then
  begin
    Inc(WideBuf); // 跳过BOM字符
  end;
  
  // 用StrLen获取空终止符前的有效字符数,SetString直接构造字符串
  SetString(Result, WideBuf, StrLen(WideBuf));
end;

方案二:先截断字节再处理BOM

如果更习惯操作字节数组,可以先找到空终止符的位置,截取有效字节后转字符串,再移除BOM:

function TRFCNWParameter.GetAsString: string;
const
  DEFAULT_BUFFER_SIZE = 1024;
  UTF16LE_BOM: Word = $FEFF;
var
  ResultSize: cardinal;
  Buffer: TBytes;
  bufferCharLength: cardinal;
  lErrorInfo: RRFCNWErrorInfo;
  RC: TRfcReturnCode;
  NullBytePos: Integer;
  TempStr: string;
begin
  bufferCharLength := DEFAULT_BUFFER_SIZE;
  SetLength(Buffer, bufferCharLength * SizeOf(WideChar));
  RC := RFCGetString(OwnerHandle, PChar(ParamName), @Buffer[0], bufferCharLength, ResultSize, @lErrorInfo);
  
  if (RC <> RFC_OK) and (ResultSize >= bufferCharLength) then
  begin
    bufferCharLength := ResultSize + 1;
    SetLength(Buffer, bufferCharLength * SizeOf(WideChar));
    RC := RFCGetString(OwnerHandle, PChar(ParamName), @Buffer[0], bufferCharLength, ResultSize, @lErrorInfo);
  end;
  
  CheckForReturnedError(@lErrorInfo, 'Retrieve Parameter %s ' + ParamName);

  // 找到第一个双字节空终止符的位置(UTF-16的空字符是两个0字节)
  NullBytePos := 0;
  while (NullBytePos + 1 < Length(Buffer)) and (Buffer[NullBytePos] <> 0) and (Buffer[NullBytePos+1] <> 0) do
    Inc(NullBytePos, 2);
  
  // 截取有效字节转字符串
  TempStr := TEncoding.Unicode.GetString(Buffer, 0, NullBytePos);
  
  // 移除开头的BOM
  if (Length(TempStr) >= 1) and (Word(TempStr[1]) = UTF16LE_BOM) then
    Result := Copy(TempStr, 2, MaxInt)
  else
    Result := TempStr;
end;

关于你的疑问:IDE会自动处理BOM吗?

答案是不会。Delphi的string类型本质是UTF-16LE编码的无BOM字符串,调试器显示的#FEFF就是字符串的第一个字符,IDE不会自动忽略它——这也是你解析XML出问题的核心原因,XML解析器会把BOM当成无效的内容开头。

另外你提到的result := PChar(TEncoding.Unicode.GetString(buffer));能工作,是因为PChar会读到第一个空终止符就停止,自动截断了后面的多余0,但这种方式会先创建一个包含大量无效字符的中间字符串,既浪费内存又不够优雅,所以还是推荐上面两种直接处理有效内容的方案。

内容的提问来源于stack exchange,提问作者Matt Allwood

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:39:07