通过 PAnsiChar (char *) 传递 AnsiString 是不是明确需要空终止符?

Posted

技术标签:

【中文标题】通过 PAnsiChar (char *) 传递 AnsiString 是不是明确需要空终止符?【英文标题】:Does passing AnsiString through PAnsiChar (char *) explicitly requires null terminator character or not?通过 PAnsiChar (char *) 传递 AnsiString 是否明确需要空终止符? 【发布时间】:2014-05-27 05:41:59 【问题描述】:

这里是新来的,并且是互操作和 C(++) 我确实搜索了这个问题的答案,但在 SO 或 www 上找不到它。这是我没有选择正确的单词来搜索答案,或者我错过了这个非常基本的问题的重点......

使用 Delphi dll 和“C(++) dll caller”之间的互操作,您是否明确声明 Delphi dll 中的空终止符以传递回 dll 调用者?

例如DLL调用者(C代码)

char ErrorMessage[10];
(*GetMessageFromDLLFunction)(&ErrorMessage[0], 10); // (char * ErrorMessage, int buffSize)

Dll 代码(Delphi):

GetMessageFromDLLFunction(errorMessage: PAnsiChar; buffSize: Integer)

// What if the message generated in this function exceeds 10 characters?
// do I return 9 characters and a null terminator?
tmpMessage := SetLength(tmpMessage, buffSize-1);
tmpMessage := tmpMessage + #0;
System.AnsiStrings.StrCopy(errorMessage,PAnsiChar(tmpMessage));

// or do I return 9 characters without an explicit null terminator?
tmpMessage := SetLength(tmpMessage, buffSize-1);
System.AnsiStrings.StrCopy(errorMessage,PAnsiChar(tmpMessage));

什么是最佳/正确做法?

【问题讨论】:

不是答案,但在这种情况下,您可能会对以下内容感兴趣:***.com/questions/1932883/… 【参考方案1】:

如果您返回的字符串有时以 null 结尾,有时则不是,那么您给调用者带来了沉重的复杂性负担。调用者有时必须添加空终止符。并且调用者面临着将字符串放在一个太短而不能包含空终止符的缓冲区中的尴尬情况。通常最好将复杂性降低到尽可能低的水平,所以我的感觉是,在所有条件相同的情况下,您应该始终返回一个以空值结尾的缓冲区。

您的代码目前没有尝试通知用户需要多大的缓冲区来存储完整的字符串。您可以继续这种方式,但我绝对建议您确保返回的值始终以空值结尾。看起来像这样:

function GetString(Buffer: PAnsiChar; Len: Integer): Integer;
var
  Value: AnsiString;
begin
  if Len < 1 then
  begin
    Result := STATUS_INVALID_PARAMETER;
    exit;
  end;

  Value := ... // get the string to be returned somehow
  StrLCopy(Buffer, PAnsiChar(Value), Len - 1);
  if Length(Value) < Len then
    Result := STATUS_OK
  else
    Result := STATUS_BUFFER_TOO_SHORT;
end;

或者,您可以修改接口,让调用者知道需要多大的缓冲区。可能看起来像这样:

function GetString(Buffer: PAnsiChar; var Len: Integer): Integer;
var
  Value: AnsiString;
begin
  if Len < 0 then
  begin
    Result := STATUS_INVALID_PARAMETER;
    exit;
  end;

  Value := ... // get the string to be returned somehow
  if Len > 0 then
    StrLCopy(Buffer, PAnsiChar(Value), Len - 1);
  if Length(Value) < Len then
    Result := STATUS_OK
  else
    Result := STATUS_BUFFER_TOO_SHORT;
  Len := Length(Value) + 1;
end;

【讨论】:

为什么要移动而不是 StrLCopy? 我不认为它“更干净”。对我来说,这似乎是过早的优化。 StrLCopy 是将文本复制到缓冲区的标准例程。移动是一个低级得多的例程,如果用户从 Ansi 移动到 Unicode,它会发送字节。使用低级 Move 是一种“优化”。 @0scar 其实我回答的重点不是示例代码,而是第一段中的文字。你问你的函数是否应该总是复制一个空终止符。我的建议非常强烈,它应该这样做。 @RudyVelthuis 好的,我认为StrLCopy 更干净。最新版本是您想到的。谢谢。【参考方案2】:

这完全由您决定。由于您将缓冲区长度作为输入传递,因此您可以采用任何一种方式。但是,如果您选择在输出中省略 null,您应该让函数返回实际写入缓冲区的字符数,以便调用者知道在哪里停止读取。如果完整的消息很重要,您甚至可以返回错误来让调用者知道缓冲区太小而无法接收完整的输出。

【讨论】:

在我的 Delphi 函数中,当消息被 Delphi dll 函数剪切时,我返回一个正整数结果。不返回实际长度。因此,为了在 C 中获得正确的终止字符串,我必须手动添加 #0 如果缓冲区太小或为零,我一般返回一个正整数,表示所需的缓冲区大小。我不填充缓冲区。如果缓冲区足够大,我返回 0 并用所需的文本填充缓冲区。这样,用户可以使用 nil 缓冲区查询函数以获得所需的大小,分配缓冲区,然后使用适当大小的缓冲区再次调用函数。 @RudyVelthuis 这通常很好,但它确实迫使用户动态分配缓冲区以获得一些输出。如果他们希望使用静态分配的缓冲区,那就很难了。换句话说,有时截断的文本就足够了。 @DavidHeffernan:我宁愿记录缓冲区必须至少具有一定的大小(例如,Windows API 中的 MAX_PATH 之类的东西),并确保所有结果都适合这样的缓冲。我想不出截断有意义的情况,但可能存在这种情况。事实是,这取决于具体情况,并且没有一个有效的秘诀。 @Rudy Truncation 可能在像GetWindowText 这样的 API 中有意义。您可能只需要检查前几个字符以查看它们是否包含特定的程序名称。当然没有单一的食谱。

以上是关于通过 PAnsiChar (char *) 传递 AnsiString 是不是明确需要空终止符?的主要内容,如果未能解决你的问题,请参考以下文章

在delphi中Pansichar是啥类型

Sting 与 WideString, PChar 与 PWideChar[草稿版]

字符串到PAnsiChar转换麻烦

在 Delphi 2009 中将字符串转换为 PAnsiChar

在 Delphi 2009 中将字符串转换为 PAnsiChar

Pchar string Pansichar ansistring