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

我有不止一次build议人们使用WideStringtypes的返回值来进行互操作。

  • 访问Delphi DLL抛出ocasionalexception
  • ASP.NET Web应用程序调用IIS Web服务器上的Delphi DLL,在返回PCharstring时locking
  • 为什么Delphi DLL在不使用ShareMem的情况下使用WideString?

这个想法是WideStringBSTR是一样的。 因为BSTR被分配在共享的COM堆上,所以在一个模块中分配并且在不同的模块中释放是没有问题的。 这是因为各方都同意使用相同的堆,COM堆。

但是,似乎WideString不能用作interop的函数返回值。

考虑下面的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未处理的exception:0xC0000005:访问冲突读取位置0x00000000。

同样,如果我们试图用p / invoke从C#中调用它,我们就失败了:

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

错误是:

ConsoleApplication10.exe中出现未处理的types为“System.Runtime.InteropServices.SEHException”的exception

其他信息:外部组件已引发exception。

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

因此,使用WideStringparameter passing引用并将它们映射到BSTR看起来工作得很好。 但不适用于函数返回值。 我已经在Delphi 5,2010和XE2上testing了这一点,并观察所有版本的相同行为。

执行进入delphi并几乎立即失败。 对“ Result ”的分配变成对System._WStrAsg的调用,其第一行显示如下:

 CMP [EAX],EDX

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

任何人都可以解释吗? 我做错了什么? 我无法期待WideString函数值是可行的BSTR ? 还是只是一个delphi缺陷?

在常规的Delphi函数中,函数return实际上是一个通过引用传递的参数,尽pipe在语法上它看起来和感觉像是一个“out”参数。 你可以像这样testing(这可能取决于版本):

 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; 

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

从DLL函数返回string和接口引用

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);