Wie ein int null werden kann... oder auch nicht ?

Nun bin ich gestern auf ein Problem gestoßen, dass mich persönlich echt fasziniert hat - da ich es für unmöglich hielt:

 

int_null.png

Was ist die Situation?

Wie man sieht ist das eine Funktion - welche um genau zu sein einen Callback für eine C++ Library implementiert.

Dabei wird eine Dateiumwandlungs Operation durchgeführt, was an sich funktioniert nur wenn die Datei größer wird passierte dieses skurile Phemomen.

Eine NullReferenceException trat immer bei größeren Operationen - aber völlig unregelmäßig auf - zuerst hatte ich das MarshalString im Verdacht und konnte dort auch ein Problem finden was den switch zwischen x86 und x64 Architekturen anging - aber das Problem war damit nicht geöst.

Wer sich den Code aber genauer an sieht stellt fest, dass ganz am anfang ein Member-Feld int pageCount = 0 deklariert wird.

int ist ein ValueType also ein struct und somit auch NIE null.

 

Wie kann es also geschehen, dass es wie im Debugger angezeigt null ist ?

Zuerst konnte ich es auch nicht glauben und hatte den Debugger im Verdacht, aber die NullReferenceException lies mich dann nicht mehr daran zweifeln.

 

Weiters konnte ich, dann noch viel skuriler feststellen, dass nicht nur pageCount == null war sondern auch this == null !!!

Alles gelernte war somit mal vorerst vom Tisch und Eckpfeiler meines Glaubens an Computer, Programmiersprachen mal kurz im wanken...

 

Aber nach ein wenig nachdenken... noch ein wenig mehr nachdenken... und schließlich beobachten was denn da genauer passiert - war es doch zu knacken cool

 

Die Lösung

Was mir nämlich auffiel, war dass der Garbage Collector immer lief bevor es zu dem Problem kam.

Und kurz darauf fiel es mir wie Schuppen von den Augen tongue-out - Der Garbage Collector räumt ja nicht nur alte Objekte weg - nein damit er auch den nicht mehr genutzten Speicher freigeben kann muss er ihn auch verdichten - also quasi defragmentieren.

Aber was hat das mit unserem this == null Problem zu tun ?

Naja zwar war das Objekt, welches hier verwendet wurde ein Singleton, aber die Callback-Funktion darin war eine normale Member Funktion.

Nun besteht so ein Pointer auf eine Funktion für eine normale Klasse bzw. des instanzierten Objektes ja nicht nur aus der Methode (quasi the MethodInfo) sondern auch wie man es vom MethodInfo kennt auch aus der Objektreferenz. Also einem Pointer auf den Platz im Speicher !

 

Wenn nun die C++ Library arbeitet und ein wenig länger braucht räumt der Garbage Collector zusammen und  - ups da war ja Platz frei  - und so schiebt er das Objekt einfach an einen neuen Platz. Innerhalb des .Net managed Codes is das ja auch kein Problem, da der GarbageCollector ja weiß wer aller das Objekt verwendet (is ja ein ReferenceCounter) und bessert die Adresse einfach überall aus - aber davon weiß meine unmanaged C++ Library leider nix...

Sobald diese Erkenntnis mal da war war der Rest recht einfach.

Einfach die Callback Funktion static machen (damit gibts keine Objektreferenz mehr) und davon weg das aktuelle managed SingleTon Objekt aufrufen und schon wars gelöst. cool