2009-04-06 8 views
2

J'essaie d'utiliser une DLL C non managée pour charger des données d'image dans une application C#. La bibliothèque a une interface assez simple où vous passez une structure qui contient trois rappels, un pour recevoir la taille de l'image, un qui reçoit chaque ligne des pixels et enfin un appelé quand le chargement est terminé. Comme cela (C# définition gérée):Epingler un délégué dans une structure avant de passer au code non managé

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] 
public struct st_ImageProtocol 
{ 
    public st_ImageProtocol_done Done;  
    public st_ImageProtocol_setSize SetSize;  
    public st_ImageProtocol_sendLine SendLine; 
} 

Les types de départ st_ImageProtocol sont Biographie des délégués:

public delegate int st_ImageProtocol_sendLine(System.IntPtr localData, int rowNumber, System.IntPtr pixelData); 

Avec le fichier de test que j'utilise le SetSize devrait obtenir appelé une fois, le SendLine obtiendra appelé 200 fois (une fois pour chaque ligne de pixels dans l'image), enfin le rappel Done est déclenché. Qu'est-ce qui se passe réellement, c'est que le SendLine est appelé 19 fois et puis une exception AccessViolationException est levée affirmant que la bibliothèque a essayé d'accéder à la mémoire protégée. J'ai accès au code de la bibliothèque C (bien que je ne puisse pas changer la fonctionnalité) et pendant la boucle où elle appelle la méthode SendLine elle n'alloue ou ne libère aucune nouvelle mémoire, donc mon hypothèse est que le Le délégué lui-même est le problème et je dois l'épingler avant de le transmettre (je n'ai pas de code à l'intérieur du délégué, à part un compteur pour voir à quelle fréquence il est appelé, donc je doute que je casse quoi que ce soit). Le problème est que je ne sais pas comment faire cela; la méthode que j'ai utilisée pour déclarer les structures dans l'espace non géré ne fonctionne pas avec les délégués (Marshal.AllocHGlobal()) et je ne trouve aucune autre méthode appropriée. Les délégués eux-mêmes sont des champs statiques dans la classe Program donc ils ne devraient pas être collectés, mais je suppose que le runtime pourrait les déplacer.

This blog entry by Chris Brumme dit que les délégués ne doivent pas nécessairement être épinglé avant d'être passé dans le code non géré:

Il est clair que le pointeur de fonction non gérée doit se référer à une adresse fixe. Ce serait un désastre si le GC déménageait ça! Cela conduit de nombreuses applications à créer un handle d'épinglage pour le délégué. Ceci est complètement inutile. Le pointeur de fonction non géré fait référence à un bout de code natif que nous générons dynamiquement pour effectuer la transition & marshaling. Ce talon existe dans la mémoire fixe en dehors du tas GC.

Mais je ne sais pas si cela est vrai lorsque le délégué fait partie d'une structure. Cela implique qu'il est possible de les épingler manuellement, et je suis intéressé par la façon de faire ceci ou par de meilleures suggestions pour savoir pourquoi une boucle fonctionnerait 19 fois puis soudainement échouerait.

Merci.


Edité pour répondre aux questions de Johan ...

Le code qui alloue la struct est la suivante:

_sendLineFunc = new st_ImageProtocol_sendLine(protocolSendLineStub); 

_imageProtocol = new st_ImageProtocol() 
        { 
          //Set some other properties... 
          SendLine = _sendLineFunc 
        }; 

int protocolSize = Marshal.SizeOf(_imageProtocol); 
_imageProtocolPtr = Marshal.AllocHGlobal(protocolSize); 
Marshal.StructureToPtr(_imageProtocol, _imageProtocolPtr, true); 

Lorsque le _sendLineFunc et les variables de _imageProtocol sont les champs statiques du programme classe. Si je comprends correctement les internes, cela signifie que je passe un pointeur non géré à copier de la variable _imageProtocol dans la bibliothèque C, mais cette copie contient une référence à la _sendLineFunc statique. Cela devrait signifier que la copie n'est pas touchée par le GC - car elle n'est pas gérée - et le délégué ne sera pas collecté car il est toujours dans la portée (statique).

Le struct se fait passer à la bibliothèque en tant que valeur de retour d'un autre rappel, mais comme un pointeur:

private static IntPtr beginCallback(IntPtr localData, en_ImageType imageType) 
{ 
    return _imageProtocolPtr; 
} 

Fondamentalement, il existe un autre type de struct qui détient le nom du fichier d'image et le pointeur de fonction à ce rappel , la bibliothèque détermine quel type d'image est stockée dans le fichier et utilise ce rappel pour demander la structure de protocole correcte pour le type donné. Mon nom de fichier struct est déclaré et géré de la même manière que le protocole ci-dessus, donc probablement contient les mêmes erreurs, mais comme ce délégué n'est appelé qu'une seule fois et appelé rapidement, je n'ai pas encore eu de problèmes.


Edité pour mettre à jour

Merci à tous pour leurs réponses, mais après avoir passé un autre couple de jours sur le problème et de faire aucun progrès j'ai décidé de classer ce. Au cas où quelqu'un serait intéressé, j'essayais d'écrire un outil pour les utilisateurs de l'application de rendu 3D Lightwave et une fonctionnalité intéressante aurait été la possibilité de voir tous les différents formats d'image supportés par Lightwave (dont certains sont assez exotiques). Je pensais que la meilleure façon de le faire serait d'écrire un wrapper C# pour l'architecture de plugin que Lightwave utilise pour la manipulation d'image afin que je puisse utiliser leur code pour charger réellement les fichiers. Malheureusement après avoir essayé un certain nombre de plugins contre ma solution, j'ai eu une variété d'erreurs que je ne pouvais pas comprendre ou corriger et je suppose que Lightwave n'appelle pas les méthodes sur les plugins de manière standard, probablement pour améliorer la sécurité de code externe en cours d'exécution (poignarder sauvage dans le noir, je l'admets). Pour le moment, je vais laisser tomber la fonction d'image et si je décide de la rétablir, je l'aborderai différemment.

Merci encore, j'ai appris beaucoup grâce à ce processus même si je n'ai pas obtenu le résultat que je voulais.

Répondre

0

Il serait intéressant de savoir un peu plus:

  • Comment créer la struct ImageProtocol? Est-ce une variable locale ou un membre de classe ou l'alloue-t-on dans une mémoire non gérée avec Marshal.AllocHGlobal?

  • Comment est-il envoyé à la fonction C? Directement comme variable de pile ou comme pointeur?


Un problème vraiment délicat! Il semble que les données du délégué sont déplacées par le GC qui provoque la violation d'accès. La chose intéressante est que le type de données délégué est un type de données de référence, qui stocke ses données sur le tas GC. Ces données contiennent des choses comme l'adresse de la fonction à appeler (pointeur de fonction) mais aussi une référence à l'objet qui contient la fonction. Cela devrait signifier que même si le code de fonction réel est stocké en dehors du tas GC, les données qui contiennent le pointeur de fonction sont stockées dans le tas GC et peuvent donc être déplacées par le GC. J'ai beaucoup réfléchi au problème hier soir mais je n'ai pas trouvé de solution ...

+0

Merci pour les questions - J'ai modifié ma question avec plus d'informations –

0

Vous ne dites pas exactement comment le rappel est déclaré dans la bibliothèque C. À moins qu'il ne soit explicitement déclaré __stdcall vous allez lentement corrompre votre pile. Vous verrez votre méthode s'appeler (probablement avec les paramètres inversés) mais à un certain moment dans le futur le programme va planter. Autant que je sache, il n'y a aucun moyen de contourner cela, à part écrire une autre fonction de rappel dans C qui se trouve entre le code C# et la bibliothèque qui veut un rappel __cdecl.

2

J'ai eu un problème similaire lors de l'enregistrement d'un délégué de rappel (il serait appelé, puis pouf!). Mon problème était que l'objet avec la méthode étant déléguée obtenait GC'ed. J'ai créé l'objet dans un endroit plus global afin de l'empêcher d'être GC'ed.

Si quelque chose comme ça ne fonctionne pas, voici quelques autres choses à regarder:

Comme plus d'infos, jetez un oeil à GetFunctionPointerForDelegate de la classe maréchal. C'est une autre façon de le faire. Assurez-vous simplement que les délégués ne sont pas GC. Ensuite, au lieu de délégués dans votre structure, déclarez-les comme IntPtr.

Cela ne résout peut-être pas l'épinglage, mais jetez un oeil à fixed mot-clé, même si cela peut ne pas fonctionner pour vous puisque vous avez une durée de vie plus longue que pour ce qui est généralement utilisé.

Enfin, regardez stackalloc pour créer une mémoire non GC. Ces méthodes nécessiteront l'utilisation de unsafe, et pourraient donc mettre d'autres contraintes sur vos assemblys.

0

Si la fonction c est une fonction __cdecl alors vous devez utiliser le Attribut [UnmanagedFunctionPointer (CallingConvention.Cdecl)] avant la déclaration de délégué.

Questions connexes