Perché Delphi DLLS può usare WidString senza usare ShareMem?
-
27-10-2019 - |
Domanda
La risposta di David a un'altra domanda Mostra una funzione DLL Delphi che restituisce un ampio. Non avrei mai pensato che fosse possibile senza l'uso di ShareMem
.
Il mio test dll:
function SomeFunction1: Widestring; stdcall;
begin
Result := 'Hello';
end;
function SomeFunction2(var OutVar: Widestring): BOOL; stdcall;
begin
OutVar := 'Hello';
Result := True;
end;
Il mio programma di chiamante:
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;
Funziona, e non capisco come. La convenzione che conosco è quella utilizzata dall'API di Windows, ad esempio Windows GetClassNameW
:
function GetClassNameW(hWnd: HWND; lpClassName: PWideChar; nMaxCount: Integer): Integer; stdcall;
Significa che il chiamante fornisce il buffer e la lunghezza massima. La DLL di Windows scrive su quel buffer con la limitazione della lunghezza. Il chiamante viene assegnato e dealluca la memoria.
Un'altra opzione è che la DLL alloca la memoria, ad esempio utilizzando LocalAlloc
, e il chiamante tratta la memoria chiamando LocalFree
.
In che modo funziona l'allocazione della memoria e il traffico con il mio esempio DLL? La "magia" accade perché il risultato è WideString
(BSTR
)? E perché le API di Windows non sono dichiarate con una conveniente convenzione? (Ci sono delle API Win32 conosciute che usano tale convenzione?)
MODIFICARE:
Ho testato la DLL con C#.
Chiamata SomeFunction1
provoca un AV (Attempted to read or write protected memory
).
SomeFunction2
funziona bene.
[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!
Ecco un seguito.
Soluzione
UN WideString
è lo stesso di a BSTR
, è solo il nome Delphi per questo. L'allocazione della memoria è gestita dall'allocatore COM condiviso, CoTaskMemAlloc
. Poiché tutte le parti usano lo stesso allocatore, puoi allocare in sicurezza in un modulo e deallocare in un altro.
Quindi, il motivo per cui non è necessario utilizzare Sharemem
è che il heap di Delphi non viene utilizzato. Invece viene utilizzato il heap com. E questo è condiviso tra tutti i moduli in un processo.
Se guardi l'implementazione di Delphi di Widestring vedrai le chiamate alle seguenti API: SysAllocStringLen
, SysFreeString
e SysReAllocStringLen
. Questi sono il sistema fornito BSTR
Funzioni API.
Molte delle API di Windows a cui si riferiscono pre-date l'invenzione di Com. Inoltre, ci sono vantaggi prestazionali nell'uso di un buffer a lunghezza fissa, assegnato dal chiamante. Vale a dire che può essere assegnato sullo stack anziché su un mucchio. Posso anche immaginare che i progettisti di Windows non vogliono forzare ogni processo a collegarsi OleAut32.dll
e pagare il prezzo di mantenimento del cumulo COM. Ricorda che quando la maggior parte dell'API di Windows è stata progettata, le caratteristiche delle prestazioni dell'hardware tipico erano molto diverse da ora.
Un altro possibile motivo per non usare BSTR
Più ampiamente è che l'API di Windows è mirata a C. e gestisce la vita di BSTR
Da C è molto più complicato che da lingue di livello superiore come C ++, C#, Delphi ecc.
C'è comunque una complicazione extra. Il Delphi Abi per WideString
I valori di restituzione non sono compatibili con gli strumenti Microsoft. Non dovresti usare WideString
come tipo di ritorno, restituilo invece tramite un out
parametro. Per maggiori dettagli vedi Perché non è possibile utilizzare un ampio valore come valore di ritorno per interrogare?