extern "C" __declspec(dllexport) char * read_cliente ();
char * read_cliente ()
{
const char testo[] = "stringa di testo";
char *line = new char[sizeof testo];
strcpy( line, testo);
return line;
}
}
Che chiamo da vb.net:
<System.Runtime.InteropServices.DllImportAttribute("stringprovider.dll",
CharSet:=Runtime.InteropServices.CharSet.Ansi)> _
Public Shared Function read_cliente() As System.IntPtr
End Function
Mi chiedo se dopo essere stata chiamata la funzione da vb.net sia
necessario effettuare il "delete" di quella parte di memoria o ormai ᅵ
tutto gestito dal framework.
Io ho fatto cosᅵ:
Public Shared Function x() As String
Dim ptr As System.IntPtr = read_cliente()
Dim str As String =
Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr)
deletes(ptr)
Return str
End Function
ma mi chiedo se quella parte di memoria sia ancora da liberare visto
che la dll non ᅵ piᅵ attiva dopo l'inovocazione.
--
Giuseppe
Dimenticavo il codice per il delete.
Lato c++:
extern "C" __declspec(dllexport) void deletes (char * line);
void deletes (char * line)
{
delete [] line;
}
Lato Vb.net:
<System.Runtime.InteropServices.DllImport("stringprovider.dll")> _
Public Sub deletes(ByVal line As System.IntPtr)
End Sub
--
Giuseppe
E' necessario chiamare la delete[], come giustamente hai fatto.
Infatti, new[] in C++ alloca dallo heap nativo (non dal managed heap del
.NET Framework), e quindi ᅵ responsabilitᅵ del codice nativo liberare la
memoria allocata, altrimenti hai memory leak.
L'unico problema che vedo nel tuo codice riguarda la calling convention.
Infatti, di default in C/C++ la calling convention ᅵ __cdecl.
Invece, la calling convention di default per P/Invoke ᅵ
CallingConvention.StdCall (corrispondente a __stdcall in C/C++).
Quindi, hai due possibilitᅵ:
O specifichi __stdcall nelle funzioni esportate dalla DLL (cosa che
fanno ad esempio le varie API Win32, e che farei anch'io), es.
extern "C" __declspec(dllexport)
char * __stdcall read_cliente ()
extern "C" __declspec(dllexport)
void __stdcall deletes (char * line)
oppure specifichi esplicitamente
CallingConvention:=CallingConvention.Cdecl lato VB.NET.
Giovanni
Molto chiaro.
Avevo solo un dubbio.
Quando un programma in c++ termina, se non erro, tutte le variabili
allocate nell'heap vengono comunque cancellate.
Mi chiedevo allora perchᅵ in questo caso, visto che la dll non era piᅵ
"attiva", quella parte di memoria continuasse ad essere occupata.
> L'unico problema che vedo nel tuo codice riguarda la calling convention.
> Infatti, di default in C/C++ la calling convention ᅵ __cdecl.
> Invece, la calling convention di default per P/Invoke ᅵ
> CallingConvention.StdCall (corrispondente a __stdcall in C/C++).
Sembrerebbe che questo errore perᅵ non abbia compromesso il buon
funzionamento del programma. Mi chiedo come mai.
Le variabili rimanevano nello stack? Forse non davano problemi perchᅵ
la funzione vb.net all'interno di cui era richiamata la funzione della
dll in c++ terminava subito dopo ed era vb.net a liberare lo stack?
--
Giuseppe
> Avevo solo un dubbio.
> Quando un programma in c++ termina, se non erro, tutte le variabili
> allocate nell'heap vengono comunque cancellate.
Puᅵ anche darsi che quando un'applicazione C++ termina Windows rilasci
la memoria allocata dalla stessa nello heap.
In ogni caso, il non rilasciare la memoria in maniera esplicita creando
dei memory leak produce un consumo di memoria maggiore di quello
effettivamente richiesto. Infatti, si ᅵ costretti ad attendere la fine
dell'applicazione per rilasciare la memoria, e mentre l'applicazione
gira questa richiede sempre piᅵ memoria (senza rilasciare la memoria
allocata in precedenza, anche se inutilizzata), appunto perchᅵ in codice
nativo non c'ᅵ nessuno "spazzino" (garbage collector).
Un'applicazione nativa che girasse per molto tempo senza rilasciare
opportunamente la memoria potrebbe richiedere al sistema troppa memoria,
abbassando le performance globali, e saturando le risorse di memoria a
disposizione.
E' tipico del codice nativo di qualitᅵ rilasciare esplicitamente ogni
allocazione di memoria eseguita (e fortunatamente Visual Studio aiuta
nel diagnosticare i blocchi non rilasciati, con i tipici messaggi
"Detected memory leaks! Dumping objects ...".
> Mi chiedevo allora perchᅵ in questo caso, visto che la dll non era piᅵ
> "attiva", quella parte di memoria continuasse ad essere occupata.
Non so come si possa dire che la DLL sia o meno attiva. In codice nativo
per rilasciare una DLL si usa FreeLibrary. Non so quando VB.NET rilasci
la DLL.
In ogni caso, dipende anche se la DLL ᅵ linkata al CRT in maniera
statica o dinamica. Se la DLL ᅵ compilata con un linking dinamico al CRT
(opzione /MD) lo heap non ᅵ privato alla DLL, ma ᅵ condiviso anche con
l'eseguibile. Puᅵ quindi darsi che in questo caso, anche se la DLL viene
"rilasciata", il sistema non possa rilasciare lo heap, poichᅵ esso ᅵ
condiviso con l'EXE.
>> L'unico problema che vedo nel tuo codice riguarda la calling convention.
>> Infatti, di default in C/C++ la calling convention ᅵ __cdecl.
>> Invece, la calling convention di default per P/Invoke ᅵ
>> CallingConvention.StdCall (corrispondente a __stdcall in C/C++).
>
> Sembrerebbe che questo errore perᅵ non abbia compromesso il buon
> funzionamento del programma. Mi chiedo come mai.
> Le variabili rimanevano nello stack? Forse non davano problemi perchᅵ la
> funzione vb.net all'interno di cui era richiamata la funzione della dll
> in c++ terminava subito dopo ed era vb.net a liberare lo stack?
Non ho idea di come VB.NET gestisca lo stack.
Una delle differenze tra __stdcall e __cdecl ᅵ che nel caso di __stdcall
ᅵ la funzione chiamata ad avere la responsabilitᅵ di fare il pop degli
argomenti dallo stack; il contrario accade per __cdecl.
Se VB.NET rispetta questo schema, allora poichᅵ il default ᅵ
CallingConvention.StdCall, VB.NET *non* dovrebbe fare il pop dei
parametri dallo stack.
Allora, puᅵ darsi che in seguito a chiamate frequenti alla funzione lo
stack possa riempirsi (perchᅵ nessuno fai il pop degli argomenti).
Puᅵ darsi quindi che il problema si manifesti in casi particolari.
In ogni caso, ᅵ meglio scrivere codice "pulito" e di qualitᅵ
dall'inizio, piuttosto che imbattersi in fasi avanzate dello sviluppo o
del test in bug subdoli da scovare (tipo stack corrotti, stack overflow,
etc. ...).
BTW: Se ti interessa discutere di problemi di interop tra codice nativo
e codice managed (e non hai problemi con la lingua inglese), ti
consiglio questo newsgroup dedicato all'argomento:
microsoft.public.dotnet.framework.interop
Giovanni
Sei stato molto chiaro e adesso so bene cosa devo fare. Grazie.
> BTW: Se ti interessa discutere di problemi di interop tra codice nativo e
> codice managed (e non hai problemi con la lingua inglese), ti consiglio
> questo newsgroup dedicato all'argomento:
>
> microsoft.public.dotnet.framework.interop
Per togliermi qualche curiositᅵ residua forse ᅵ il caso di scrivere
qualcosa.
Mi chiedo: se in vb.net lo stack viene gestito in maniera simile
rispetto al c++ (e cosᅵ dovrebbe essere, anche se in maniera
trasparente all'utente), nel momento in cui si esce dallo scopo della
routine entro cui ᅵ avvenuto lo "Stack imbalance", tutto dovrebbe
tornare a posto?
Chissᅵ se ᅵ cosᅵ.
--
Giuseppe
>> microsoft.public.dotnet.framework.interop
>
> Per togliermi qualche curiositᅵ residua forse ᅵ il caso di scrivere
> qualcosa.
> [...]
Ho letto cosa ti hanno correttamente risposto sul newsgroup interop di
sopra, e mi ᅵ venuta in mente un'altra cosa.
Giustamente ti ᅵ stato detto che la tua funzione read_cliente ᅵ stata
dichiarata come "extern C", pertanto non dovrebbe lanciare eccezioni C++.
Esaminando il tuo codice:
<code>
extern "C" __declspec(dllexport) char * read_cliente ();
char * read_cliente ()
{
const char testo[] = "stringa di testo";
char *line = new char[sizeof testo];
strcpy( line, testo);
return line;
}
}
</code>
la strcpy() ᅵ una funzione C, quindi non lancia eccezioni C++.
Invece operator new[] potrebbe lanciare un'eccezione di tipo
std::bad_alloc se l'allocazione di memoria fallisse (questo dovrebbe
essere comunque un caso "eccezionale" per il tuo codice, considerando
che stai allocando solamente una manciata di byte per una piccola
stringa; perᅵ magari puᅵ capitare che l'applicazione giri in condizioni
di memoria limitate e che si debbano allocare molte stringhe, etc.).
Il problema ᅵ che quindi avresti un'eccezione C++ (std::bad_alloc) che
attraverserebbe i confini di una funzione definita nella DLL come extern
"C", e questa non ᅵ una buona pratica di programmazione (come ᅵ stato
fatto notare sull'altro newsgroup).
Pertanto, per aumentare la qualitᅵ del tuo codice, potresti:
1. usare "new (std::nothrow)" che ritorna NULL invece di lanciare
un'eccezione in caso di errore.
2. usare malloc(), che ᅵ una funzione C pura, e non lancia eccezioni C++
ma ritorna NULL in caso di errore (in questo caso dovresti perᅵ usare
free() nella funzione simmetrica di cleanup, invece di delete[]).
3. racchiudere in un blocco try/catch la new, e se viene lanciata una
std::bad_alloc, ritornare NULL nel corpo della catch. In questa maniera,
intrappoli l'eccezione all'interno della funzione, e non le fai varcare
i confini della DLL.
Io opterei per 1. o 2.
Nota che se stai usando Visual C++ 6, che non ᅵ molto aderente allo
standard C++, la new[] ritorna NULL in caso di errore (invece di
lanciare una eccezione std::bad_alloc, come dettato dallo standard, e
implementato correttamente in VC2003, 2005 e 2008).
Giovanni
A me piace piᅵ la 3. per il solo fatto che conosco meglio il c++ del c
(anche se ho preferito usare array di char nello stile del c).
Credo comunque che sia quasi impossibile ottenere un eccezione con
l'istruzione new.
Se farᅵ la correzione sarᅵ piᅵ che altro per evitare che in futuro
prenda spunto da questo codice per fare qualcosa di piᅵ impegnativo in
termini di memoria.
--
Giuseppe