melange de code .NET et natif

melange de code .NET et natif - C#/.NET managed - Programmation

Marsh Posté le 12-07-2005 à 18:44:49    

Voilà, je viens de m'apercevoir d'un léger problème que je rencontre ... Je ne suis toujours pas familier avec .Net, mais je ne perds pas espoir.
 
J'ai un projet en C# qui fait des appels à WMI (via System.Management.ManagementObjectSearcher) et tout se passe très bien. Seulement, dès que je fais un appel à ces fonctionnalités dans la méthode de gestion des messages systèmes (WndProc) de ma fenêtre, des exceptions inattendues sont levées. J'avais remarqué un problème similaire lorsque je tentais d'utiliser la libusb-win32 (écrite en C) ...
 
Cette découverte m'a amené à me poser des questions sur un autre morceau de code utilisant Python for .Net (ou plutôt tentant de l'utiliser) où j'avais un problème d'exceptions de ce genre.
 
Tout d'abord, il faut savoir que la lib Python for .Net remplace la fonction d'import de cpython (vu qu'elle s'appuye dessus) par une méthode C# afin d'intercepter les imports du type "import CLR.*" pour pouvoir importer des objets .Net en Python.  
 
Donc, schématiquement :
 
Appli C# : exécution d'une ligne python pour importer un script perso
 -> P.Net : appel de CPython
 -> CPython : appel de la fonction __import__ remplacée donc remontée dans P.Net
 -> P.Net : on réalise que l'import est "normal", donc on rappelle la méthode __import__ d'origine de cpython
 -> CPython : Exception levée !
 
Alors que le scénario suivant se déroule sans aucun problème :
 
Appli C# : appel de la fonction de remplacement d'__import__ de P.Net
 -> P.Net : on réalise que l'import est "normal" => CPython
 -> CPython : l'import de la lib se fait bien
 
Y aurait-il une subtilité à connaître au sujet des changements de contexte (.Net / natif) ? Car j'ai la forte impression que le problème survient dès qu'on a un enchainement du type Natif -> .Net -> Natif ...
 
un avis sur le sujet ? Une idée ?
 
 
Edit : typo ...


Message édité par theshockwave le 12-07-2005 à 18:45:17
Reply

Marsh Posté le 12-07-2005 à 18:44:49   

Reply

Marsh Posté le 12-07-2005 à 20:19:19    

le mélange managed et natif est un casse tête incommensurable, mon plugin dans ma signature est un mix de ce genre (because plugin en C# et SDK de winamp en C), et c'est plus que casse couilles... surtout au niveau de la conversion des types.
faut jouer avec la classe Marshal, faire les allocs/désallocs à la main, etc... finalement, j'ai tout fait en C# unsafe (donc avec pointeurs). c'est crade, mais ça fonctionne et c'est bien moins prise de tête... mais bon, je suis quand même en train de virer le mode unsafe, trop incertain à long terme
c'est quoi les exceptions qui sont levées ?


---------------
J'ai un string dans l'array (Paris Hilton)
Reply

Marsh Posté le 12-07-2005 à 20:46:24    

je te dis ca demain, j'ai pas mon code sous la main, là

Reply

Marsh Posté le 13-07-2005 à 12:10:22    

Bon, j'ai un peu plus poussé ma recherche au sujet des problèmes évoqués plus haut ... Pour celui qui est relatif aux WMI, j'ai pu produire un fichier qui met en avant ce problème :
 

Code :
  1. using System;
  2. using System.Drawing;
  3. using System.Collections;
  4. using System.ComponentModel;
  5. using System.Windows.Forms;
  6. using System.Data;
  7. namespace ExceptionSample
  8. {
  9. public class Form1 : System.Windows.Forms.Form
  10. {
  11.  bool showDiskDrivesOnNextMessage = false;
  12.  private System.Windows.Forms.Button BtnThroughWndProc;
  13.  private System.Windows.Forms.Button BtnDirectCall;
  14.  private System.ComponentModel.Container components = null;
  15.  public Form1()
  16.  {
  17.   InitializeComponent();
  18.  }
  19.  protected override void Dispose( bool disposing )
  20.  {
  21.   if( disposing  && (components != null) )
  22.    components.Dispose();
  23.   base.Dispose( disposing );
  24.  }
  25.  #region Windows Form Designer generated code
  26.  /// <summary>
  27.  /// Required method for Designer support - do not modify
  28.  /// the contents of this method with the code editor.
  29.  /// </summary>
  30.  private void InitializeComponent()
  31.  {
  32.   this.BtnThroughWndProc = new System.Windows.Forms.Button();
  33.   this.BtnDirectCall = new System.Windows.Forms.Button();
  34.   this.SuspendLayout();
  35.   //  
  36.   // BtnThroughWndProc
  37.   //  
  38.   this.BtnThroughWndProc.Location = new System.Drawing.Point(0, 0);
  39.   this.BtnThroughWndProc.Name = "BtnThroughWndProc";
  40.   this.BtnThroughWndProc.Size = new System.Drawing.Size(144, 32);
  41.   this.BtnThroughWndProc.TabIndex = 0;
  42.   this.BtnThroughWndProc.Text = "WMI Through WndProc";
  43.   this.BtnThroughWndProc.Click += new System.EventHandler(this.BtnThroughWndProc_Click);
  44.   //  
  45.   // BtnDirectCall
  46.   //  
  47.   this.BtnDirectCall.Location = new System.Drawing.Point(0, 32);
  48.   this.BtnDirectCall.Name = "BtnDirectCall";
  49.   this.BtnDirectCall.Size = new System.Drawing.Size(144, 32);
  50.   this.BtnDirectCall.TabIndex = 1;
  51.   this.BtnDirectCall.Text = "WMI Direct Call";
  52.   this.BtnDirectCall.Click += new System.EventHandler(this.BtnDirectCall_Click);
  53.   //  
  54.   // Form1
  55.   //  
  56.   this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
  57.   this.ClientSize = new System.Drawing.Size(144, 69);
  58.   this.Controls.Add(this.BtnDirectCall);
  59.   this.Controls.Add(this.BtnThroughWndProc);
  60.   this.Name = "Form1";
  61.   this.Text = "Form1";
  62.   this.ResumeLayout(false);
  63.  }
  64.  #endregion
  65.  #region Methods
  66.  public static void showDiskDrives()
  67.  {
  68.   System.Management.ManagementObjectSearcher mos = new System.Management.ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive" );
  69.   foreach (System.Management.ManagementObject mo in  mos.Get())
  70.   {
  71.    string desc = "";
  72.    foreach(System.Management.PropertyData pd in mo.Properties)
  73.     if(pd.Value != null)
  74.      desc += pd.Name + "=" + pd.Value + "\r\n";
  75.     else
  76.      desc += pd.Name + "\r\n";
  77.    MessageBox.Show(desc);
  78.   }
  79.  }
  80.  protected override void WndProc(ref Message m)
  81.  {
  82.   switch(m.WParam.ToInt32())
  83.   {
  84.    case 0x8000: // New device connection (DBT_DEVICEARRIVAL)
  85.     showDiskDrives(); // raises an exception
  86.     break;
  87.    case 0x8004: // Removing a device (DBT_DEVICEREMOVECOMPLETE)
  88.     showDiskDrives(); // raises an exception
  89.     break;
  90.    default:
  91.     if(showDiskDrivesOnNextMessage)
  92.     {
  93.      showDiskDrivesOnNextMessage = false;
  94.      showDiskDrives(); // runs as expected
  95.     }
  96.     break;
  97.   }
  98.   base.WndProc (ref m);
  99.  }
  100.  #endregion
  101.  [STAThread]
  102.  static void Main()
  103.  {
  104.   Application.Run(new Form1());
  105.  }
  106.  private void BtnThroughWndProc_Click(object sender, System.EventArgs e)
  107.  {
  108.   showDiskDrivesOnNextMessage = true;
  109.  }
  110.  private void BtnDirectCall_Click(object sender, System.EventArgs e)
  111.  {
  112.   showDiskDrives(); // runs as expected
  113.  }
  114. }
  115. }


 
avec les références qui vont bien, évidemment ... Enjoy  :sol:  
NB : l'exception est levée lorsqu'on ajoute ou qu'on retire un périphérique (clé USB, par exemple)
 
Je retourne voir du côté de python for .Net de ce pas
 
Edit : légère épuration du code


Message édité par theshockwave le 13-07-2005 à 14:51:46
Reply

Marsh Posté le 13-07-2005 à 12:35:58    

theshockwave a écrit :

Appli C# : exécution d'une ligne python pour importer un script perso
 -> P.Net : appel de CPython
 -> CPython : appel de la fonction __import__ remplacée donc remontée dans P.Net
 -> P.Net : on réalise que l'import est "normal", donc on rappelle la méthode __import__ d'origine de cpython
 -> CPython : Exception levée !


 
 
En fait, ce n'est pas tout à fait vrai, le scénario serait plutôt le suivant :
 
Appli C# : exécution d'une ligne python pour importer un script perso
 -> P.Net : appel de CPython
 -> CPython : appel de la fonction __import__ remplacée donc remontée dans P.Net
 -> P.Net : on réalise que l'import est "normal", donc on rappelle la méthode __import__ d'origine de cpython
 -> CPython : chargement de la lib ok, remontée dans le code appelant
 -> P.Net : réception du pointeur sur la lib importée ok (même valeur que lorsqu'on fait l'import directement via la fonction de remplacement)
 ---------- remontée dans le code appelant du pointeur sur la lib
 -> CPython : levée d'une exception System.NullReferenceException
 
 
 
Ce qui est surprenant, c'est que ce genre de manip passe pour la console d'interprétation fournie par Zope (ce sont eux qui font Python for .Net) Enfin, en tout cas, lorsqu'on fait de simples imports, ca passe bien par leur "hook" pour ensuite revenir dans CPython, et ca ne lève pas d'exception :/

Reply

Marsh Posté le 13-07-2005 à 12:42:16    

Ce qu'il faut éviter, c'est utiliser des ressources non-managées dans des classes où y'a des ressources managées. C'est là où ça devient galère et bordélique. Il vaut mieux faire une ch'tite classe qui wrap une ressource non-managées, qui implémentera IDisposable. On n'oubliera pas d'y mettre un finalizeur...

Reply

Marsh Posté le 13-07-2005 à 14:35:53    

oui, enfin ... Ce genre de classe est proposé directement par la lib python for .Net ... normalement, lorsque je récupère mon pointeur, je le fait passer en PyObject (avec le constructeur qui va bien) et je peux en tirer une lib avec des fonctions pour récupérer des symboles dedans, etc ... Le problème en lui-même est simplement que là, je ne peux même pas récupérer le pointeur vu qu'il y a un souci dans la DLL de CPython qui lève une exception.

Reply

Marsh Posté le 13-07-2005 à 15:18:34    

et la stacktrace de l'exception, elle dit quoi ?

Reply

Marsh Posté le 13-07-2005 à 15:26:04    

StackTrace :

Citation :

at Python.Runtime.Runtime.PyImport_ImportModule(String name)
at Python.Runtime.PythonEngine.ImportModule(String name) in d:\\XXXXXXX\\spikes\\YYYYYYYYYY_client_python\\XXXXXXXprod\\python.runtime\\pythonengine.cs:line 260
at XXXXXXX.YYDownloader.Downloader.StartDownload(String file, String saveTo) in d:\\XXXXXXX\\spikes\\YYYYYYYYYY_client_python\\XXXXXXXprod\\XXXXXXX.YYdownloader\\downloader.cs:line 68"


 
 
Edit : désolé pour l'obfuscation, mais les personnes avec qui je travaille sont assez strictes sur la confidentialité (y compris pour les noms de code des projets)
 
Edit 2 : vu qu'il y a deux sujets dans le même topic, au final, j'ai un doute sur l'exception à laquelle tu t'intéressais [:petrus75]


Message édité par theshockwave le 13-07-2005 à 15:28:54
Reply

Marsh Posté le 13-07-2005 à 16:34:12    

Je suis toujours preneur de remarques sur le code que j'ai posté plus haut, sinon parce que bon ... Qu'un appel de fonction passe ou non selon le type de message passé à la fonction, ca fait un peu science fiction, non ?

Reply

Marsh Posté le 13-07-2005 à 16:34:12   

Reply

Marsh Posté le 13-07-2005 à 16:40:49    

laisse moi rentrer chez moi, je testerais ce soir :o

Reply

Marsh Posté le 13-07-2005 à 16:42:54    

t'es pas à la plage, ce soir ? :o
(je serai en week end, moi, en tout cas [:petrus75] )

Reply

Marsh Posté le 13-07-2005 à 16:51:01    

ah mais attend... tu ne testes pas si ta fenêtre reçoit WM_DEVICECHANGE apparemment ! essaie de tester la valeur de wParam, mais seulement si tu reçois un WM_DEVICECHANGE :

Code :
  1. case WM_DEVICECHANGE:
  2.    if (wParam == DBT_DEVICEARRIVAL)
  3.       showDiskDrives();
  4.    else if (wParam == DBT_DEVICEREMOVECOMPLETE)
  5.       showDiskDrives();
  6.    break;


Message édité par Harkonnen le 13-07-2005 à 16:51:33
Reply

Marsh Posté le 13-07-2005 à 16:52:26    

Je teste immédiatement, même si j'ai un doute, vu que l'exception est levée par une fonction qui ne s'occupe pas du message ni de sa signification, à vrai dire :/

Reply

Marsh Posté le 13-07-2005 à 16:56:05    

on sait jamais avec Win32 [:joce]

Reply

Marsh Posté le 13-07-2005 à 17:06:26    

d'un seul coup, j'ai comme un doute que ce soit un WM_DEVICECHANGED, le message en question. En effet, je ne capture plus l'évènement avec le code suivant :
 

Code :
  1. protected override void WndProc(ref Message m)
  2.  {
  3.   switch(m.LParam.ToInt32())
  4.   {
  5.    case 0x0219: // WM_DEVICECHANGE
  6.     switch(m.WParam.ToInt32())
  7.     {
  8.      case 0x8000: // New device connection (DBT_DEVICEARRIVAL)
  9.       showDiskDrives(); // raises an exception
  10.       break;
  11.      case 0x8004: // Removing a device (DBT_DEVICEREMOVECOMPLETE)
  12.       showDiskDrives(); // raises an exception
  13.       break;
  14.     }
  15.     break;
  16.    default:
  17.     if(showDiskDrivesOnNextMessage)
  18.     {
  19.      showDiskDrivesOnNextMessage = false;
  20.      showDiskDrives(); // runs as expected
  21.     }
  22.     break;
  23.   }
  24.   base.WndProc (ref m);
  25.  }


 
je vais regarder quel est le code du message qu'il sort quand j'arrive à le capturer, ca donnera peut-être une indication

Reply

Marsh Posté le 13-07-2005 à 17:06:35    

:heink: le LParam a pour valeur 1242332, soit 0x12F4DC, ce qui ne correspond à aucun des messages décrits dans WinUser.h :sweat:

Reply

Marsh Posté le 13-07-2005 à 18:22:24    

c'est pas le LParam qui a l'id du message (qui peut être à WM_DEVICECHANGE) c'est la property Msg qui donne cet identifiant ... Donc, c'est bon, c'est bien ce message là
 
 
...
 
 
on ne se moque pas :o  
 
(cependant, ca ne change absolument rien au problème)

Reply

Marsh Posté le 18-07-2005 à 09:43:26    

De retour sur le sujet ! [:pitouxm]

Reply

Marsh Posté le 18-07-2005 à 11:12:54    

pour en revenir à http://www.zope.org/Members/Brian/PythonNet, je n'arrive même pas à recompiler leur console, donc mon problème doit être en dehors du code. Pour info, j'ai exactement le même problème : lors du lancement de Py_Main, cpython tente de faire au moins un import, et le premier que j'intercepte (vu que ca remonte dans la fonction crochet dont je parlais avant) lève une exception de la même manière que dans mon programme ...
 
Je dois vraiment louper quelque chose à un endroit :/
 
Edit : j'ai un peu de mal avec la balise [ url ]
 
Edit 2 : OK, j'ai trouvé ce qui clochait ... J'ai regardé le makefile fourni et ... quelle horreur  :sweat:  Ils désassemblent la dll compilée (en IL, donc) pour changer la convention d'appel des fonctions dedans, puis réassemblage en dll et là, ca donne une dll qui fonctionne pour les besoins du programme
 
Bon ... Il ne reste plus que ce problème de comportement qui diffère suivant le message qui déclenche l'appel  :p


Message édité par theshockwave le 18-07-2005 à 11:43:10
Reply

Marsh Posté le 18-07-2005 à 15:53:38    

Au sujet du message précédent ... Existe-t-il un autre moyen que de préfixer ses déclarations de fonction par cpp][CallConvCdecl()][/cpp] pour obtenir cet effet ? Parce que c'est sur la convention d'appel que portent les modifications faites comme le met en valeur ce début de fichier diff :

--- Python.Runtime.il 2005-07-18 11:32:07.750000000 +0200
+++ Python.Runtime.il2 2005-07-18 11:32:45.875000000 +0200
@@ -659,6 +659,8 @@
     } // end of method ClassBase::CanSubclass
 
     .method public hidebysig static int32  
+
+ modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)
             tp_init(native int ob,
                     native int args,
                     native int kw) cil managed
@@ -676,6 +678,8 @@
     } // end of method ClassBase::tp_init
 
     .method public hidebysig static int32  
+
+ modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)
             tp_compare(native int ob,
                        native int other) cil managed
     {


Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed