COM服务器发送空字符串,该字符串被转换为NULL指针。

3

我在C#中为COM服务器定义了以下接口:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("58C77969-0E7D-3778-9999-B7716E4E1111")]
public interface IMyInterface    
{
    string MyName { get; }
}

这个接口被引入并在Delphi XE5程序中实现。
导入的代码如下:
IMyInterface = interface(IUnknown)
  ['{58C77969-0E7D-3778-9999-B7716E4E1111}']
  function Get_MyName (out pRetVal: WideString): HResult; stdcall;
end;

实现如下所示:

type
  TMyImpl = class(TInterfacedObject, IMyInterface)
  public
    function Get_MyName (out pRetVal: WideString): HResult; stdcall;    
 end;

 function TMyImpl.Get_MyName (out pRetVal: WideString): HResult;
 var
  s: string;
 begin
   s:=''; // empty!
   pRetVal:=s;
   result:=S_OK;
 end;

当我使用C#像这样调用那个服务器时:
var server = new Server();
string s = server.MyName;

那么s是NULL,而不是预期的空字符串。

我该如何强制将空字符串作为空字符串传递到COM,而不是通过封送到NULL来替换它?


不是重复的问题,在我看来。另一个问题只是询问为什么它被视为空值。而这个问题则是询问如何强制使用空字符串。 - Ondrej Kelle
@TOndrej 嗯,考虑到提问者将其标记为重复问题,我认为这是可以的。同时,链接相关问题也是很好的做法。 - David Heffernan
我也没问题,把它们链接起来很好。 - Ondrej Kelle
那也是我的想法。不幸的是,我发现它已经超出了我的问题范围。 - coding Bott
2个回答

3
Delphi将空字符串实现为nil指针(参见System._NewUnicodeString)。您可以手动分配一个空的COM兼容字符串:
function TMyImpl.Get_MyName(out pRetVal: WideString): HResult;
var
  BStr: TBstr;
begin
  BStr := SysAllocString('');
  if Assigned(BStr) then
  begin
    Pointer(pRetVal) := BStr;
    Result := S_OK;
  end
  else
    Result := E_FAIL;
end;

或者你可以创建一个帮助函数:

function EmptyWideString: WideString;
begin
  Pointer(Result) := SysAllocString('');
end;

1
你不能销毁该字符串。所有权归调用者所有,除非进行了复制。也许已经复制了,那我说的就是无意义的。无论如何,在这里避免使用WideString肯定更容易。 - David Heffernan
@DavidHeffernan 正确,谢谢!移除了释放字符串的代码。但是 SysAllocString 可能会失败。 - Ondrej Kelle
我不认为在这里使用try/finally有太多意义。该函数的合同是不会引发异常的。将pRetVal:='';Result := E_FAIL;移动到一个else语句中并删除try/finally似乎更清晰一些。好的,我看到了你的编辑,我觉得那更干净一些。老实说,如果SysAllocString失败,那我们就完蛋了,但我理解你的观点。个人而言,我认为这是我愿意假定始终有效的事情。但我认识到其他观点也是有效的。 - David Heffernan
嗨,我测试了你的解决方案。如果字符串为空,则转换pRetVal:= Bstr;将再次导致空指针。WideString作为参数类型似乎是一个问题。但我无法更改它,因为接口是从C# tlb生成的。 - coding Bott
尝试类型转换:Pointer(pRetVal) := BStr; - Ondrej Kelle
那个指针转换起了作用,谢谢。 - coding Bott

2
尝试在Delphi端执行以下操作:
IMyInterface = interface(IUnknown)
  ['{58C77969-0E7D-3778-9999-B7716E4E1111}']
  function Get_MyName (out pRetVal: BSTR): HResult; stdcall;
end;

function TMyImpl.Get_MyName (out pRetVal: BSTR): HResult;
begin
  pRetVal := SysAllocString('');
  Result := S_OK;
end;

如果您希望处理 SysAllocString 失败的情况,那么您需要像这样编写代码:
function TMyImpl.Get_MyName (out pRetVal: BSTR): HResult;
begin
  pRetVal := SysAllocString('');
  Result := IfThen(Assigned(pRetVal), S_OK, E_FAIL);
end;

尽管我个人认为,在对SysAllocString('')进行错误检查时划定界限是合理的。

我的猜测是,Delphi将空的WideString解析为空指针而不是空的BSTR。在我看来这是一个缺陷。


你好,你使用TBStr的解决方案有效。但是声明是用C#编写的,并且使用tlbimp.exe将TLB导入到Delphi中。现在我需要一个想法,如何强制TLB导入使用tbstr而不是widestring,而无需每次生成时修改导入。 - coding Bott
我认为你需要对导入的接口进行后处理。 - David Heffernan
使用dotnet框架的RegAsm.exe csharp.dll /tlb,然后使用Delphi的tlibimp.exe -P+ -Ha- -Hr- -Hs- -DImports csharp.tlb,你可以在我的问题中看到结果。这是构建脚本的一部分,首先编译C#代码,然后进行导入,最后编译Delphi代码。 - coding Bott
确实。因此,对导入的接口进行后处理。或者像TOndrej向您展示的那样使用强制转换。我认为您的问题现在已经得到了回答,您正在进入如何处理答案的具体细节。这基本上不是这个问题的任务。 - David Heffernan

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