为什么WideString不能用作interop的函数返回值?
我有不止一次build议人们使用WideString
types的返回值来进行互操作。
- 访问Delphi DLL抛出ocasionalexception
- ASP.NET Web应用程序调用IIS Web服务器上的Delphi DLL,在返回PCharstring时locking
- 为什么Delphi DLL在不使用ShareMem的情况下使用WideString?
这个想法是WideString
和BSTR
是一样的。 因为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);