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.

È stato utile?

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?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top