Posts mit dem Label Bug werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Bug werden angezeigt. Alle Posts anzeigen

Freitag, 25. Juli 2008

WTF of the Month

Ich poste das jetzt mal ohne Worte:

   1:  [...]
   2:   
   3:  boolean leaveout = false;
   4:   
   5:  [...]
   6:   
   7:  // if this is not used, don't forget to set "leaveout" to false a few lines up
   8:   
   9:  [...]
  10:   
  11:  ResultSet rs = db.executeQuery(sql);
  12:   
  13:  if (rs.next())
  14:  {
  15:      x();
  16:      y();
  17:      z(leaveout);
  18:   
  19:      while(rs.next())
  20:      {
  21:          x();
  22:          y();
  23:          z(true);
  24:      }
  25:  }

desillusionierte Grüße
SH

Dienstag, 22. Juli 2008

OPC: DataChange-Event

Ein OPC-Client hat die Möglichkeit, vom Server Nachrichten über Aktualisierungen von Werten zu empfangen. Das ist natürlich sehr nützlich, wenn man gerne eine bestimmte Aktion ausführen möchte, wenn ein definierter Wert erreicht wird. Kommt ja auch nicht gerade selten vor, diese Anforderung, also hat die OPC Foundation da schon ganz schön schlau definiert.

Nicht ganz so schlau ist jedoch die Umsetzung innerhalb der OpcDAAuto.dll gelungen. Ich habe heute etwa einen halben Tag gekämpft, bis ich tatsächlich in der Lage war, den DataChange-Event, der vom Server bei Veränderungen von zuvor dafür angemeldeten Werten gesendet wird, zu empfangen. Ich habe Tutorials gewälzt, das Internet durchforstet und experimentiert - alles ohne Erfolg. Alle Voraussetzungen, die ich finden konnte, schienen mir erfüllt. Nach ewigem Herumprobieren gab es nur noch einen einzigen Unterschied zwischen den Tutorials - die, heruntergeladen und kompiliert im Übrigen einwandfrei funktionierten - und meiner Applikation. Dieser fand sich beim Anlegen von OPC-Items, also jenen Werten, die ich überwachen wollte. Mein Code - im Übrigen eigentlich absolut korrekt - sah folgendermaßen aus:

   1:  OPCGroupClass opcGroupTriggerItems;
   2:   
   3:  // Gruppe anlegen
   4:  opcGroupTriggerItems = ( OPCGroupClass ) opcServer.OPCGroups.Add( "TriggerItems" );
   5:   
   6:  // Items hinzufügen
   7:  opcGroupTriggerItems.OPCItems.AddItem( connection + "/" + alias + "/TriggerItem1", 0 ) );
   8:  opcGroupTriggerItems.OPCItems.AddItem( connection + "/" + alias + "/TriggerItem2", 1 ) );
   9:  opcGroupTriggerItems.OPCItems.AddItem( connection + "/" + alias + "/TriggerItem3", 2 ) );
  10:  opcGroupTriggerItems.OPCItems.AddItem( connection + "/" + alias + "/TriggerItem4", 3 ) );
  11:   
  12:  // Event Handler Methode registrieren
  13:  // WICHTIG: Die Event Handler Methode wird erst nach dem hinzufügen der Items zur Gruppe registriert,
  14:  //          da sonst beim Hinzufügen für jedes Item ein Event ausgelöst wird.
  15:  opcGroupTriggerItems.DataChange += new DIOPCGroupEvent_DataChangeEventHandler( opcGroupTriggerItems_DataChange );

Hier wird zuerst eine Gruppe auf dem OPC-Server angelegt, zu der dann anschließend die Items hinzugefügt werden, die die Werte darstellen, die überwacht werden sollen. Um zu vermeiden, dass für alle Items ein initialer Event ausgelöst wird, der über die Änderung von "nicht vorhanden" nach "vorhanden" und die damit verbundene Wertänderung benachrichtigt, wird die Event-Handler-Methode erst nach dem Anlegen der nötigen Items registriert. So wurden die Items korrekt angelegt, sie konnten ausgelesen werden, aber die Benachrichtigung über Wertänderungen funktionierte, wie bereits erwähnt, überhaupt nicht.

Der Code, der mir von den Tutorials aufgezeigt wurde und der jetzt, nachdem ich ihn so auch in meiner Applikation habe, zu dem gewünschten Ergebnis führt, sieht so aus:

   1:  OPCGroupClass opcGroupTriggerItems;
   2:  string[] itemIds = new string[5];
   3:  int[] clientHandles = new int[5];
   4:  int[] serverHandles;
   5:  int[] errors;
   6:   
   7:  // Gruppe anlegen
   8:  opcGroupTriggerItems = ( OPCGroupClass ) opcServer.OPCGroups.Add( "TriggerItems" );
   9:   
  10:  // Items erstellen
  11:  itemIds.SetValue( connection + "/" + alias + "/TriggerItem1", 1 );
  12:  itemIds.SetValue( connection + "/" + alias + "/TriggerItem2", 2 );
  13:  itemIds.SetValue( connection + "/" + alias + "/TriggerItem3", 3 );
  14:  itemIds.SetValue( connection + "/" + alias + "/TriggerItem4", 4 );
  15:   
  16:  // ClientHandles erstellen
  17:  clientHandles.SetValue( 1, 1 );
  18:  clientHandles.SetValue( 2, 2 );
  19:  clientHandles.SetValue( 3, 3 );
  20:  clientHandles.SetValue( 4, 4 );
  21:   
  22:  // Items der Gruppe hinzufügen
  23:  opcGroupTriggerItems.OPCItems.AddItems( 4, ref itemIds, ref clientHandles, out serverHandles, out errors, null, null );
  24:   
  25:  // Event Handler Methode registrieren
  26:  // WICHTIG: Die Event Handler Methode wird erst nach dem hinzufügen der Items zur Gruppe registriert,
  27:  //          da sonst beim Hinzufügen für jedes Item ein Event ausgelöst wird.
  28:  opcGroupTriggerItems.DataChange += new DIOPCGroupEvent_DataChangeEventHandler( opcGroupTriggerItems_DataChange );

Auch hier wird zunächst eine Gruppe angelegt. Es werden hier jedoch die Items nicht, wie zuvor, einzeln der Gruppe hinzugefügt. Statt dessen werden ein String-Array mit den Werten und ein Integer-Array mit den Client-Handles, unter denen die Items eindeutig zu identifizieren sind, angelegt. Hier ist die Besonderheit zu beachten, dass der erste Wert des Arrays jeweils nicht beachtet wird, das Array wird immer nur von 1 bis max durchlaufen. Das hängt, so weit ich informiert bin, damit zusammen, dass die von mir verwendete OpcDAAuto.dll in VB geschrieben ist. Jedenfalls werden anschließend die Items und ihre Client-Handles gesamt an eine Methode übergeben, die sie der Gruppe hinzufügen. Anschließend wieder die Event-Handler-Methode registriert. Test anwerfen - funzt.  Sehr erstaunlich und für mich nicht wirklich nachvollziehbar, aber im Endeffekt ist es jetzt auch egal, es läuft ja nun :)

Donnerstag, 3. Juli 2008

log4net, IBaseComponent & 64Bit Windows Server

log4net 1.2.10, IBaseComponent & 64Bit Windows Server funktionieren irgendwie nicht zusammen. Das witzige unter 32Bit funktioniert es!

Für eine Biztalk Pipeline habe ich zwei Komponenten entwickelt die aus einem Flatfile Format eine XML Datei erzeugt. Beim einbinden der Komponten war im Browser von VisualStudio bei einer der korrekte Name aus der IBaseComponent zu sehen, bei der anderen der Assembly Name.
Dumm denn auf meiner 32Bit Entwicklungsmaschine war dem nicht so, aber auf dem 64Bit Zielsystem. Nach einer etwas längeren Remotedebuggingsession stellte sich dann heraus das bei der Komponente mit dem "falschen Namen", nicht einmal der Konstruktor angesprungen wird, geschweige denn die Informationen aus IBaseComponent, wenn man diese in VisualStudio "an"browst.

Erst nach der vollständigen Entfernung der log4net Referenzierung (darauf muss man erstmal kommen), wurde der korrekte Name aus dem Interface angezeigt. Befand sich log4net gar im GAC war die Pipeline Komponente überhaupt nicht mehr auffindbar, beim manuellen Browsen gabs dann nur noch den lapidaren Hinweis das wohl etwas mit der Sicherheitseinstellung der Komponente nicht stimmen würde... sehr merkwürdig....

Eine Sicherheitseinstellung in Windows Server? Ein Problem mit log4net? :-/

Donnerstag, 29. Mai 2008

Seltsames Verhalten im Event Log

Zur Zeit schreibe ich, wenn es die Zeit und die Laune zulassen, an einem kleinen Tool zum Auslesen des Windows Event Log. Dabei habe ich eine recht interessante Feststellung gemacht.
Zum Auslesen der Einträge einer bestimmten Quelle, die im Event Log registriert ist, habe ich folgendes getan:

   1:  public static EventLogEntryCollection GetEntriesForSource( string logSource )
   2:  {
   3:      EventLog log;
   4:      EventLogEntryCollection entries;
   5:   
   6:      source = logSource;
   7:   
   8:      // EventLog vorbereiten
   9:      log = new EventLog();
  10:      log.Source = logSource;
  11:              
  12:      // EventLog-Einträge auslesen
  13:      entries = log.Entries;
  14:   
  15:      return entries;
  16:  }

Hier tue ich nichts anders, als ein EventLog-Objekt zu erstellen, das, wer hätte es gedacht, den Zugriff auf das Windows Event Log erlaubt.
Anschließend wird die Quelle (Source) gesetzt, deren Einträge ausgelesen werden sollen. Damit ist die Vorbereitung auch schon abgeschlossen und die Einträge innerhalb der Source können in eine EventLogEntryCollection eingelesen werden.

Damit ist das Auslesen aus der gewünschten Quelle auch schon abgeschlossen - dachte ich. Seltsamerweise werden trotz des Setzens der zu lesenden Quelle alle Einträge des Logs, in dem sich die Quelle befindet, in die EventLogEntryCollection geschrieben.

Ich möchte jetzt nicht so weit gehen zu sagen, dass es sich hierbei um einen Fehler im Konzept des Event Log-Handlings handelt, aber für mich ist es nicht logisch, alle Einträge in meine Collection geschrieben werden, obwohl ich - für meine Begriffe - eine Eingrenzung auf eine ganz bestimmte Source vorgenommen habe.

Das Schlimme daran ist nicht die Tatsache an sich, sondern viel eher, dass man sie nicht direkt wahrnimmt. Ich habe beispielsweise in eigenen Tests nie zwei mal hintereinander Quellen aus dem gleichen Log verwendet, so dass mir nie aufgefallen ist, dass alle Quellen des Logs exakt gleich viele (und vor allem die selben) Einträge hat. Das hat erst ein Betatester festgestellt (vielen Dank dafür an tropensturm!).

Die Beseitigung des Problems gestaltet sich glücklicherweise denkbar einfach, so dass in meiner Anzeige jetzt tatsächlich nur noch die erwarteten Einträge zu finden sind. Hierfür wird einfach nachträglich nochmals die Source eingegrenzt.

   1:  if( !entry.Source.Equals( source ) )

Mittwoch, 2. April 2008

Bug des Monats - März 2008

"Ja haben wir denn schon den 32. März? "

   1:  ...
   2:  DateTime morgen = new DateTime(
   3:      DateTime.Now.Year, 
   4:      DateTime.Now.Month, 
   5:      DateTime.Now.Day + 1, 
   6:      0, 
   7:      0, 
   8:      0);
   9:  ...

wirkt auf den ersten Blick nicht nur furchtbar verstörend, hat auch Auswirkungen am Monatsende ;)

Thx @ SHo

PS: Schön wenn man Code von echten "Könnern" debuggen darf, die eine ganz genaue Vorstellung haben, wie man richtig zu programmieren hat. Daher schon jetzt meine Lieblingskategorie auf unserem Blog: Code Bug des Monats