Grundlagenproblem: Zeiger (^) CLI eigener Typen (System::Int32 System::Decimal)



  • Hallo,

    im Moment verzweifel ich ein wenig an den Grundlagen. In der annahme, das ich Typen wie System::Decimal per Zeiger übergeben und per dereferenzierung einen Wert zurück liefern kann, schrieb ich eine Methode die zwischen übergeben und nullptr unterscheiden kann.

    Es kam jedoch nie der Wert nach verlassen der Methode zustande, der innerhalb der Methode übergeben wurde.

    Dies lies mich zu folgenden Experiment kommen:

    ref struct test
    {
    	System::Decimal t;
    };
    
    System::Void bar(test ^ tst)
    {
    	tst->t = 12;
    }
    
    System::Void foo(System::Decimal ^ t)
    {
    	*t = 12;
    }
    
    System::Void foo2(System::Decimal % t)
    {
    	t = 12;
    }
    
    System::Void foo3(System::Int32 ^ t)
    {
    	*t = 12;
    }
    
    int main(array<System::String ^> ^args)
    {
    	System::Decimal dec;
    	System::Int32   inttst;
    	test xtst;
    	foo(%dec);
    	foo2(dec);
    	foo3(inttst);
    	bar(%xtst);
        return 0;
    }
    

    Die übergabe der Klasse (ref struct) klappt wie erwartet: Der Member hat den Wert 12.

    foo lässt jedoch dec bei 0 (foo2 - also Referenz lässt foo 12 sein) und foo3 lässt inttst ebenso auf 0.

    SP1 ist NICHT installiert (da ich in dem Projekt sonst nicht den Formdesigner verwenden kann).

    Sollte das ein "normales" Verhalten der CLI eigenen Typen sein, wäre ich dankbar wenn mir jemand ein Stichwort zum suchen geben könnte. Mir fallen keine brauchbaren Suchbegriffe ein um etwas passendes zu finden.



  • Am SP1 liegt es nicht.

    Hat das was mit implizitem Boxing zu tun ? 😕

    Der Aufruf von foo3 lässt auch darauf schliessen. Man bekommt nur eine Warnung, das inttst nicht initialisiert ist.



  • nn schrieb:

    Hat das was mit implizitem Boxing zu tun ? 😕

    Hab es mir gerade noch einmal mit dem Debugger angesehen:

    In main ist dec ein System::ValueType und in foo ist t ein System::Object.
    Die Zuweisung in foo erfolgt also an ein temporäres Objekt.



  • Knuddlbaer schrieb:

    ...
    schrieb ich eine Methode die zwischen übergeben und nullptr unterscheiden kann.

    Wie wäre es mit

    Nullable<decimal>
    

    als Parameter ?

    Entspricht einem

    decimal?
    

    in C#.



  • Nullable<T> ist mir unbekannt, werde ich nachlesen, vielen Dank für den Tipp.
    (Derzeit sieht der Workarround eine Referenz und einen zweiten Parameter vor)

    Initialisiert man inttest

    System::Int32   inttst = 12;
    

    kommt nurnoch eine Warnung (W4) über den nicht verwendeten Parameter args.

    Ich steige nicht so recht durch. Das Beispiel lässt sich noch weiter vereinfachen:

    int main(array<System::String ^> ^args)
    {
    	System::Decimal dec;
    	System::Decimal ^ d = %dec;
    
    	test x;
    	test ^ pt = %x;
    
    	*d = 12;
    	(*pt).t = 13;
    
    	Console::WriteLine(dec);
    	Console::WriteLine(x.t);
    }
    

    Die Ausgabe ist 0 und 13.

    Die Annahme das %x den Zeiger (^) auf x liefert scheint zu stimmen.

    Ich werde mich mal intensiev mit dem Thema Boxing beschäftigen müssen.

    http://msdn2.microsoft.com/de-de/library/yz2be5wk (und Verwandte Abschnitte).
    http://www.microsoft.com/germany/msdn/library/net/cplusplus/CPlusPlusManagedExtensions.mspx?mfr=true

    Bin dann mal schmökern....



  • Knuddlbaer schrieb:

    Nullable<T> ist mir unbekannt, werde ich nachlesen, vielen Dank für den Tipp.

    Nullable Types sind in C# wesentlich einfacher als in C++/CLI

    int? x = null;
    int y = x ?? 0;
    

    Das C++ Gegenstück findest du hier
    http://www.codeproject.com/managedcpp/CNullable.asp

    (Vom gleichen Autor gibt es übrigens bald ein C++/CLI Buch.)

    Nochmal zum Boxing:

    Typen wie decimal sind Value-Types und liegen auf dem Stack (auch in C#). Ein Tracking-Pointer (^) zeigt aber in C++/CLI immer auf den Heap. Daher muss der decimal verpackt (daher boxing) werden. Übrigens eine ziemliche Performance-Bremse...

    Auch wenn man Strings als Referenz übergibt, gibt es eine ähnliche Falle:

    void test(ref string str)
    

    entspricht

    void test(System::String^% str)
    

    Sollte man besser gleich in die FAQ aufnehmen ...



  • Ok, vielen Dank für die Infos.

    Ergänzend vllt noch, das Objekte die auf dem Heap liegen einen Header haben, in dem u.A. der Typ und Tabellen (?!) für virtuelle Methoden gelagert werden. Diesen Header gibt es nicht bei den o.g. Valuetypes die auf dem Stack liegen.

    Die Objekte werden daher für den Heap via boxing in ein Objekt gepackt.

    Klassen erben von System::Object und besitzen somit den Header und die Informationen für den GC.

    In diesem Zusammenhang sei dann noch die Destruktion von ref Klassen auf dem "Stack" erwähnt:

    When you create an instance of a reference type using stack semantics, the compiler does internally create the instance on the garbage collected heap (using gcnew).

    Somit wird es langsam klar warum das mit der Klasse klappte.

    ref struct test
    {
    	System::String ^ t;
    	test(System::String ^ name) : t ( name)
    	{
    
    	}
    
    	~test()
    	{
    		Console::WriteLine("~" + t);
    		this->!test();
    	}
    	!test()
    	{
    		Console::WriteLine("!" + t);
    	}
    };
    
    int main(array<System::String ^> ^args)
    {
    	{
    		test    t1 (gcnew System::String("t1"));
    		test  ^ t2  = gcnew test(gcnew System::String("t2"));
    		test  ^ t3  = gcnew test(gcnew System::String("t3"));
    		delete t2;
    	}
    
    	Console::WriteLine("Main ende");
    	return 0;
    }
    

    Ausgabe:

    ~t2
    !t2
    ~t1
    !t1
    Main ende
    !t3

    ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.de/dv_vclang/html/0d09d3f1-13a0-4041-8178-402aad667edd.htm
    ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.de/dv_vclang/html/319a1304-f4a4-4079-8b84-01cec847d531.htm

    Es wird wohl noch eine weile brauchen bis ich in den "Boxing" regeln sicher bin.
    (Mal hoffen das man sich warenen lassen kann wenn man boxing betreibt. Bei einem System::Boolean = true warnt er ja, aber bei versteckteren Stellen wie im obrigen Beispiel nicht :o( )

    (Aus dem Stand weiß ich jetzt nicht, wo das Problem mit T ^% ist , bin aber bisher nur zum Überfliegen gekommen).

    thx @ll, das war der Schubs in die richtige Richtung.


Anmelden zum Antworten