7
votes

Allocation de mémoire gratuite non gérée à partir du code géré

Une application .NET appelle C DLL. Le code C attribue la mémoire pour un tableau de caractères et renvoie ce tableau en conséquence. Les applications .NET obtiennent ce résultat en tant que chaîne.

Le code C: xxx

le code C #: xxx < P> Quelques tests de celui-ci montrent que le collecteur des ordures ne libère pas la mémoire allouée par le code C.

Toute aide sera appréciée. :)


0 commentaires

7 Réponses :


6
votes

Vous ne pouvez pas libérer la mémoire non gérée du code géré. Vous devez écrire une routine en C qui appelle gratuit code> sur le pointeur renvoyé par la fonction exécuter code> et p / l'invoque à partir de .net.

Une autre option est d'allouer Mémoire non gérée dans .NET, transmettez le pointeur sur la fonction C qui le remplira avec des données et enfin libérer ce pointeur: P>

IntPtr ptr = Marshal.AllocHGlobal(100 * sizeof(char));
SomeUnmanagedFunc(ptr);
Marshal.FreeHGlobal(ptr);


3 commentaires

Pourriez-vous fournir un exemple simple, s'il vous plaît.


L'attribution de la mémoire dans l'application .NET suppose que nous connaissons la taille de la matrice à l'avance. Dans la solution réelle (le problème), c'est un mystère total :).


@Darin - Comment pensez-vous que la première méthode fonctionne de manière fiable? Y a-t-il des astuces ou quelque chose qui doit être mis en œuvre. Devrais-je envelopper un appel à une option libre de mémoire non gérée dans une méthode injuste pour ma classe qui gère la mémoire non gérée?



-1
votes

.NET La mémoire doit être attribuée dans le CLR à effacer par le GC. Vous devez ajouter une fonction pour libérer le bloc dans la DLL C.

N'oubliez pas de libérer la mémoire dans la même instance de la DLL C qui a créé la mémoire. Vous ne pouvez pas mélanger et correspondre.


1 commentaires

C'est incorrect. Le CLR sait comment libérer certains types de blocs de mémoire alloués à partir de code non géré, par exemple une mémoire allouée à l'aide de Sysallocstring ou de CoTaskMemallOC.



3
votes

Une autre façon de le faire serait de passer une chaîne gérée (une instance StringBuilder) via p / invoquer (en tant que paramètre à votre fonction code> exécuter code>).

De cette façon, aucune allocations n'est faite Sur le côté non exploité. p>

En d'autres termes, vous auriez quelque chose comme: p> xxx pré>

et appelez-le comme ceci: p>

[DllImport("Unmanaged.dll", CharSet = CharSet.Ansi)]
static extern void Run(StringBuilder result);

StringBuilder result = new StringBuilder(100);
Run(result);


1 commentaires

Comme la réponse de Darin, celle-ci fonctionnerait si la taille de la mémoire nécessaire est connue.



7
votes

La chaîne gérée n'est pas la même que Char *. Ce qui se passe sous couverture, c'est que le code de marshaling de la couche Interop fait une copie de la chaîne non gérée afin de la convertir en une chaîne gérée, mais elle ne peut pas libérer cette mémoire car elle ne sait pas comment elle a été allouée.

Cependant, vous pouvez essayer d'allouer et de retourner un BST au lieu d'un char *. La couche Interop traite de manière meilleur avec les types de données d'automatisation que les types de données classiques non gérés.

La raison pour laquelle cela importe est la façon dont le charnel * et les BSTR sont alloués dans la mémoire.

Les tampons Char * sont alloués sur le tas de l'exécution C ++ à l'aide de routines d'allocation privée / de translocation que le CLR ne sait rien. Il n'y a donc aucun moyen de supprimer cette mémoire. Et pour rendre les choses encore pires, la mémoire tampon que les points Char * peuvent être attribués par une mise en œuvre de la DLL interne du code DLL ou même signaler à une variable de membre dans une classe privée.

Les BSTRs d'autre part sont alloués à l'aide de Sysallocstring Windows API et sont libérés par SYFREESTIRNG et, étant donné que la couche CLR Interop sait à propos de ces API Windows, il sait comment libérer un CAST it it obtenu du code non géré.


0 commentaires

7
votes

Le marshallateur P / invoke supposera que la mémoire du type de retour a été attribuée avec CoTaskMAllAlC () et appellera CotaskMemFree () pour la publier. Si cela n'a pas été fait, le programme échouera avec une exception sur Vista et Win7 mais de la mémoire de fuite silencieusement sur XP. Utiliser Sysallocstring () peut être effectué pour fonctionner, mais vous devez annoter le type de retour dans l'attribut [Dllimport]. Ne pas faire, cela va toujours causer une fuite, sans diagnostic sur Win7. Un bSTR est pas un pointeur sur un bloc de mémoire attribué par CoTaskMemallOC, il y a 4 octets devant l'adresse pointue à la taille de la chaîne.

L'une des combinaisons suivantes fonctionnera correctement. : xxx

ou: xxx

Vous devez prendre en compte permettre au code client de passer un tampon afin qu'il n'y ait pas de gestion de la mémoire problèmes. Cela devrait ressembler à ce problème: xxx


0 commentaires

2
votes

Je lisais quelques questions sur Pinvoke et j'ai arrêté ici. Je ne sais pas si le problème est toujours pertinent pour vous, mais j'ai décidé de poster ma réponse aux futurs lecteurs.

Il s'agit de votre dernier commentaire à la réponse de Darin Dimitrov. Lorsque la taille de la mémoire allouée n'est pas connue, la solution Tipical consiste à appeler la fonction non géographique avec un pointeur NULL et à recevoir la taille dans un paramètre OUT. Ensuite, nous allons tous l'espace nécessaire et appelons à nouveau la fonction non gérée. P>

Exemple ci-dessous: P>

//MANAGED SIDE  
IntPtr ptr = IntPtr.Zero;  
int size = 0;  
myFunc(ptr, out size);  
ptr = Marshal.AllocHGlobal(size);  
myFunc(ptr, out size);  
//Do your work..  
Marshal.FreeHGlobal(ptr);  



//UNMANEGED SIDE  
int myFunc(void* dest, size_t& size){  
   if(dest==NULL)  
       //calculate de size..  
       size = 'resul'  
       return 0;  
    }  
    // create the array and copy all elements   
    memcopy(dest, ... , size);  
    //free all allocated space in unmanaged and return success  
    return 0;  
}  


1 commentaires

Approche assez intéressante, mais cela suppose que l'heure d'exécution du code géré est très rapide afin que nous puissions l'appeler deux fois. En pratique, le code géré pourrait a) exécuter lentement b) renvoyer des ensembles de résultats légèrement différents afin que la mémoire allouée ne suffira pas



0
votes
public class Memory
{
    [DllImport("kernel32.dll")]
    private static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max);

    //[DllImport("kernel64.dll")]
    //private static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max);

    public String Errores = "";
    public void Limpiar()
    {
        try
        {
            Process Mem;
            Mem = Process.GetCurrentProcess();
            SetProcessWorkingSetSize(Mem.Handle, -1, -1);
            Mem = null;
        }
        catch (Exception ex)
        {
            Errores = ex.ToString() + " " + ex.StackTrace.ToString();
        }

    }
}
public class LimpiadodeMemoria
{

    private Boolean Monitorear;
    private Boolean Salida;
    private String ElMensajeBitacoras;
    private String Error;

    public delegate void Errores(string Elerror);
    public event Errores OnErrores;

    public delegate void Bitacora(string LaBitacora);
    public event Bitacora OnBitacora;

    public void Iniciar()
    {
        Monitorear = true;
        Salida = false;
        ElMensajeBitacoras = "LimpiadodeMemoria - " + DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss") + " Iniciada";
        OnBitacora(ElMensajeBitacoras);
        while (Monitorear == true)
        {
            Salida = false;
            Memory _Memori = new Memory();
            _Memori.Limpiar();
            Error = _Memori.Errores;
            _Memori = null;
            if (Error != "")
            {
                OnErrores(Error);
            }
            Salida = true;
            System.Threading.Thread.Sleep(1000);
        }
    }

    public void Detener()
    {
        Monitorear = false;
        while (Salida == false)
        {
            System.Threading.Thread.Sleep(100);
        }
        ElMensajeBitacoras = "LimpiadodeMemoria - " + DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss") + " Detenida";
        OnBitacora(ElMensajeBitacoras);
    }

}

0 commentaires