Delphi如何正确处理带BOM的C++ DLL返回空终止字符串?
解决Delphi中处理C++ DLL返回带BOM的UTF-16空终止字符串问题
先拆解下你遇到的两个核心问题根源:
- 第一个版本用
TArray<Char>转PChar保留BOM:因为Delphi的Char就是WideChar(对应UTF-16LE),DLL返回的开头FEFF(UTF-16LE的BOM)会被当成普通字符串字符,PChar从第一个字符读到空终止符,自然就把BOM留在结果里了。 - 第二个版本用
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
相关产品推荐
相关产品推荐

