首页 文章

为什么WideString不能用作互操作的函数返回值?

提问于
浏览
41

我不止一次建议人们使用 WideString 类型的返回值进行互操作 .

这个想法是 WideStringBSTR相同 . 因为在共享COM堆上分配 BSTR ,所以在一个模块中分配并在另一个模块中解除分配是没有问题的 . 这是因为所有各方都同意使用相同的堆,即COM堆 .

但是,似乎 WideString 不能用作互操作的函数返回值 .

考虑以下Delphi DLL .

library WideStringTest;

uses
  ActiveX;

function TestWideString: WideString; stdcall;
begin
  Result := 'TestWideString';
end;

function TestBSTR: TBstr; stdcall;
begin
  Result := SysAllocString('TestBSTR');
end;

procedure TestWideStringOutParam(out str: WideString); stdcall;
begin
  str := 'TestWideStringOutParam';
end;

exports
  TestWideString, TestBSTR, TestWideStringOutParam;

begin
end.

和以下C代码:

typedef BSTR (__stdcall *Func)();
typedef void (__stdcall *OutParam)(BSTR &pstr);

HMODULE lib = LoadLibrary(DLLNAME);
Func TestWideString = (Func) GetProcAddress(lib, "TestWideString");
Func TestBSTR = (Func) GetProcAddress(lib, "TestBSTR");
OutParam TestWideStringOutParam = (OutParam) GetProcAddress(lib,
                   "TestWideStringOutParam");

BSTR str = TestBSTR();
wprintf(L"%s\n", str);
SysFreeString(str);
str = NULL;

TestWideStringOutParam(str);
wprintf(L"%s\n", str);
SysFreeString(str);
str = NULL;

str = TestWideString();//fails here
wprintf(L"%s\n", str);
SysFreeString(str);

TestWideString 的调用失败,出现此错误:

BSTRtest.exe中0x772015de处的未处理异常:0xC0000005:访问冲突读取位置0x00000000 .

同样,如果我们尝试使用p / invoke从C#调用它,我们就会失败:

[DllImport(@"path\to\my\dll")]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string TestWideString();

错误是:

ConsoleApplication10.exe中发生未处理的“System.Runtime.InteropServices.SEHException”类型的异常附加信息:外部组件引发了异常 .

通过p / invoke调用 TestWideString 按预期工作 .

因此,使用带有WideString参数的pass-by-reference并将它们映射到 BSTR 似乎工作得非常好 . 但不是函数返回值 . 我在Delphi 5,2010和XE2上测试了这一点,并在所有版本上观察到相同的行为 .

执行进入Delphi并几乎立即失败 . 对 Result 的赋值变为对 System._WStrAsg 的调用,其第一行读取:

CMP     [EAX],EDX

现在, EAX$00000000 ,并且自然存在访问冲突 .

有谁能解释一下?难道我做错了什么?我期望 WideString 函数值可行 BSTR s是不合理的吗?或者它只是一个Delphi缺陷?

2 回答

  • 21

    在常规的Delphi函数中,函数return实际上是一个通过引用传递的参数,即使在语法上它看起来和感觉就像一个'out'参数 . 您可以像这样测试它(这可能取决于版本):

    function DoNothing: IInterface;
    begin
      if Assigned(Result) then
        ShowMessage('result assigned before invocation')
      else
        ShowMessage('result NOT assigned before invocation');
    end;
    
    procedure TestParameterPassingMechanismOfFunctions;
    var
      X: IInterface;
    begin
      X := TInterfaceObject.Create;
      X := DoNothing; 
    end;
    

    要演示来电 TestParameterPassingMechanismOfFunctions()

    您的代码失败是因为Delphi和C对函数结果传递机制的调用约定的理解不匹配 . 在C中,函数返回的行为类似于语法建议: out 参数 . 但对于Delphi来说,这是一个 var 参数 .

    要修复,请尝试以下方法:

    function TestWideString: WideString; stdcall;
    begin
      Pointer(Result) := nil;
      Result := 'TestWideString';
    end;
    
  • 17

    在C#/ C中,您需要将结果定义为 out 参数,以保持 stdcall 调用约定的二进制代码兼容性:

    Returning Strings and Interface References From DLL Functions

    在stdcall调用约定中,函数的结果通过CPU的EAX寄存器传递 . 但是,Visual C和Delphi为这些例程生成不同的二进制代码 .

    Delphi代码保持不变:

    function TestWideString: WideString; stdcall;
    begin
      Result := 'TestWideString';
    end;
    

    C#代码:

    // declaration
    [DllImport(@"Test.dll")]        
    static extern void  TestWideString([MarshalAs(UnmanagedType.BStr)] out string Result);
    ...
    string s;
    TestWideString(out s); 
    MessageBox.Show(s);
    

相关问题