2009-11-09 3 views
6

Devo creare una DLL che viene utilizzata da un'applicazione VB6. Questa DLL deve fornire diverse funzioni, alcune devono restituire stringhe.Come posso restituire un PChar da una funzione DLL a un'applicazione VB6 senza rischiare crash o perdite di memoria?

Questa è la dichiarazione VB6:

Declare Function MyProc Lib "mylib.dll" (ByVal Param As String) As String 

E questo l'implementazione stub Delphi in mylib.dll:

Cosa devo tornare qui? Chi libererà la memoria della stringa PChar returnd?

EDIT: sto chiedendo di Delphi 2005 (PChar = PAnsiChar)

risposta

6

È necessario creare un BSTR. Le stringhe VB6 sono in realtà BSTR. Chiama SysAllocString() sul lato Delphi e restituisci BSTR al lato VB6. Il lato VB6 dovrà chiamare SysFreeString() per liberare la stringa - lo farà automaticamente.

Se PChar corrisponde a una stringa ANSI (il tuo caso) devi convertirlo manualmente in Unicode - usa MultiByteToWideChar() per quello. Vedere this answer per come utilizzare meglio SysAllocStringLen() e MultiByteToWideChar() insieme.

+0

Ho fatto alcuni esperimenti prima di chiedere su SO. Ho usato una variabile di stringa globale (Ansi) e ho appena restituito PAnsiChar (MyGlobalVar) e ha funzionato. Perché ha funzionato quando in realtà devo restituire un BSTR (= due byte per carattere)? –

+2

AFAIK quando si tratta di chiamate API, VB6 esegue in modo implicito tutte le stringhe su stringhe ANSI, anche se VB6 funziona internamente con BSTR. –

+0

DR è corretto: se si utilizza ByVal nella dichiarazione DLL, VB6 converte implicitamente tutte le stringhe da e verso le stringhe ANSI durante la chiamata alle DLL. – MarkJ

1

Utilizzare l'API di Windows per allocare la memoria a cui punta il puntatore PChar in. Quindi, l'app VB può deallocare la memoria dopo l'uso, utilizzando anche l'API di Windows.

+3

Questo ti lega a un'implementazione particolare (leggi: strategia di allocazione della memoria). Fornire procedure di allocazione/dealloca accoppiate è più trasparente. –

1

Direi che chiunque distribuisca la memoria deve anche liberarlo in questo caso. Incontrerai problemi con altri scenari. Quindi il modo più sicuro e pulito sarebbe:

  1. La DLL alloca la memoria (perché sa quanto) e restituisce il PChar al chiamante
  2. Dopo che il chiamante viene fatto con esso, chiama FreePointer indietro al DLL
  3. DLL libera la memoria nel FreePointer esportato funzione di

la configurazione sarebbe come questo:

unit DLL; 

interface 

uses 
    SysUtils; 

function Execute(const Params: PChar): PChar; stdcall; 
procedure FreePointer(const P: PChar); stdcall; 

exports Execute; 
exports FreePointer; 

implementation 

function Execute(const Params: PChar): PChar; stdcall; 
var 
    Size: Cardinal; 
begin 
    Size := Calculate the size; 
    GetMem(Result, Size); 

    ...do something to fill the buffer 
end; 

procedure FreePointer(const P: PChar); stdcall; 
begin 
    FreeMem(P); 
end; 

end. 
+0

Per quanto ho capito, non funziona VB6/COM. Con BSTRs il chiamante è responsabile della liberazione di * all * stringhe, per quanto strano possa sembrare. Vedi questa domanda SO: http://stackoverflow.com/questions/872794/who-owns-returned-bstr –

+0

Oh, non lo so. Non conosco molto bene VB. Sì, è un po 'di confusione se me lo chiedi. – Runner

+0

Funzionerebbe, ma voglio essere in grado di utilizzare la funzione DLL come vorrei utilizzare qualsiasi altra funzione VB6. Una chiamata FreePointer aggiuntiva dopo ogni chiamata alla mia funzione avrebbe rovinato il mio codice. Temo anche che con questo le funzioni interne di gestione delle stringhe di VB6 potrebbero interferire. –

2

Combinare la risposta di Sharptooth e Lars D; non sono i widestrings già assegnati tramite Windows e BSTR?

+0

Avrei dovuto menzionare: Delphi 2005 –

+0

i widestrings sono afaik D4 +, quindi nessun problema lì. –

+0

Delphi non libererà automaticamente WideString?Ad ogni modo, sono andato con la risposta di Sharptooth, perché come notato in diverse risposte e commenti, VB6 confeziona una stringa ANSI in un BSTR, il che risulterebbe in caster brutti sul lato Delphi. –

2

Non ho familiarità con Dephi, ma qui ci sono le due opzioni principali quando si usano stringhe con una DLL non COM e VB6.

Opzione 1. Utilizzare stringhe "ANSI".

'DLL routine expecting to be passed pointers to ANSI strings ' 
'VB6 will allocate and deallocate the strings ' 
'Its vital that VB6 allocates sufficient space for the return string ' 
Declare Sub MyProc Lib "mylib.dll" (ByVal Param As String, _ 
    ByVal OutVal As String) 

Function DoMyProc(ByVal Param As String) As String 
    Dim sResult As String 
    sResult = Space$(255) ' create 255 bytes of space for the return string ' 
    Call MyProc(Param, sResult) 
    DoMyProc = sResult 
End Function 

Opzione due. Usa BSTR.

'DLL routine expecting to be passed two BSTRs. It will modify the second one. ' 
'VB6 "owns" both BSTRs and will deallocate them when it has finished with them. ' 
Declare Sub MyProc(ByVal lpParam As Long, ByVal lpOutVal As Long) 

Function DoMyProc(ByVal Param As String) As String 
    Dim sResult As String 
    Call MyProc(StrPtr(Param), StrPtr(sResult)) 
    DoMyProc = sResult 
End Function 

Vorrei anche suggerire guardando il Microsoft advice sulla scrittura DLL C di essere chiamato da VB. Originariamente rilasciato con VB5 ma ancora rilevante per VB6.

+0

Forse un riferimento migliore è la sezione * Hardcore Visual Basic * sul passaggio stringhe alle chiamate API (la tua Delphi DLL è come una chiamata API dal punto di vista VB6) http://vb.mvps.org/hardcore/html/stringsinsideout .htm – MarkJ

1

Non è possibile restituire un PChar come risultato di una funzione, ma è possibile passare un ulteriore parametro PChar e copiare la stringa che si desidera restituire a questo PChar.Si noti che VB deve allocare quella stringa alla dimensione richiesta prima di passarla alla dll. Anche in VB che il parametro deve essere dichiarato come param ByVal come stringa e deve essere passato con ByVal:

param = "aaaaaaaaaaaaaaaaaaaa" ' reserve 20 characters 
    call myproc(byval param) 

Il ByVal aggiuntivo nella chiamata farà la magia compilatore di conversione di una stringa VB per un PChar e ritorno.

(spero di ricordare che questo è corretto, è stato un po 'di tempo da quando sono stato costretto a usare VB.)

+0

È questa restrizione che non è possibile restituire PChar come risultato di una funzione, VB specifico? – Runner

+0

Non penso che sia vero. C'è molta documentazione MSDN su questo argomento, semplicemente non sembra capirlo. –

+0

Hai provato il mio approccio? Assegnare e negoziare la memoria all'interno della DLL. O il problema è effettivamente nel formato della memoria restituita (convenzione PChar o qualcosa di simile)? Il mio codice è stato testato in BDS 2006 e RAD 2010 e funziona perfettamente. – Runner

5

Se non si vuole rischiare di crash o le perdite di memoria, quindi Create la vostra API utilizzando l'API di Windows come modello. Lì, le funzioni API in genere non allocano la propria memoria. Invece, il chiamante passa un buffer e dice all'API quanto è grande il buffer. L'API riempie il buffer fino a quel limite. Vedere la funzione GetWindowText, ad esempio. Le funzioni non restituiscono i puntatori, a meno che non siano puntatori a cose che il chiamante ha già fornito. Invece, il chiamante fornisce tutto da solo, e la funzione usa semplicemente tutto ciò che viene dato. Non si vede quasi mai un parametro del buffer di uscita che non sia accompagnato da un altro parametro che indica la dimensione del buffer.

Un ulteriore miglioramento che è possibile apportare a tale tecnica è quello di consentire alla funzione di indicare a il chiamante quanto deve essere grande il buffer. Quando il puntatore di input è un puntatore nullo, la funzione può restituire quanti byte il chiamante deve fornire. Il chiamante chiamerà la funzione due volte.

Non è necessario derivare l'API da zero. Utilizza le API già funzionanti come esempi su come esporre il tuo.

+0

Buona risposta, +1. Come restituire le stringhe è un argomento che sembra essere discusso più e più volte, anche se con variazioni e con linguaggi diversi. Vorrei che ci fosse una risposta che potrebbe essere indicata come riferimento. Questo è almeno un inizio verso una tale risposta. – mghie