我的测试DLL:
function SomeFunction1: Widestring; stdcall; begin Result := 'Hello'; end; function SomeFunction2(var OutVar: Widestring): BOOL; stdcall; begin OutVar := 'Hello'; Result := True; end;
我的来电程序:
function SomeFunction1: WideString; stdcall; external 'Test.dll'; function SomeFunction2(var OutVar: Widestring): BOOL; stdcall; external 'Test.dll'; procedure TForm1.Button1Click(Sender: TObject); var W: WideString; begin ShowMessage(SomeFunction1); SomeFunction2(W); ShowMessage(W); end;
它的作品,我不明白如何。我知道的惯例是Windows API使用的惯例,例如Windows GetClassNameW:
function GetClassNameW(hWnd: HWND; lpClassName: PWideChar; nMaxCount: Integer): Integer; stdcall;
意思是调用者提供缓冲区和最大长度。 Windows DLL以长度限制写入该缓冲区。调用者分配并释放内存。
另一个选择是,DLL通过使用LocalAlloc分配内存,Caller通过调用LocalFree释放内存。
内存分配和解除分配如何与我的DLL示例一起工作?是否发生“魔术”,因为结果是WideString(BSTR)?为什么不通过这种方便的惯例声明Windows API? (有没有任何已知的Win32 API使用这样的惯例?)
编辑:
我用C#测试过DLL。
调用SomeFunction1会导致AV(尝试读取或写入受保护的内存)。
SomeFunction2工作正常。
[DllImport(@"Test.dll")] [return: MarshalAs(UnmanagedType.BStr)] static extern string SomeFunction1(); [DllImport(@"Test.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool SomeFunction2([MarshalAs(UnmanagedType.BStr)] out string res); ... string s; SomeFunction2(out s); MessageBox.Show(s); // works ok MessageBox.Show(SomeFunction1()); // fails with AV!
这是followup。
解决方法
所以,你不需要使用Sharemem的原因是Delphi堆没有被使用。而是使用COM堆。这是一个进程中所有模块之间共享的。
如果您查看Delphi实现的WideString,您将看到以下API的调用:SysAllocStringLen
,SysFreeString
和SysReAllocStringLen
.这些是BSTR
API functions提供的系统。
您参考的许多Windows API早于COM的发明。此外,使用由调用者分配的固定长度缓冲区具有性能优势。也就是说它可以分配在堆栈而不是堆上。我也可以想象,Windows设计师不想强制每个进程必须链接到OleAut32.dll并支付维护COM堆的代价。请记住,当大多数Windows API被设计时,典型硬件的性能特征与现在非常不同。
没有更广泛使用BSTR的另一个可能的原因是Windows API是针对C的。从C开始,管理BSTR的生命周期要比C,C#,Delphi等更高级的语言更加棘手。