C# GCHandle.Alloc

roker002

Commander
Registriert
Dez. 2007
Beiträge
2.114
Theoretisch gesehen sollte dieser Aufruf ja funktionieren oder?
Code:
[StructLayout(LayoutKind.Explicit)]
public struct Test
{
    [MarshalAs(UnmanagedType.U2)]
    [FieldOffset(0)]
    public Int16? Var1;

    [MarshalAs(UnmanagedType.U2)]
    [FieldOffset(2)]
    public Int16? Var2;
}


....
var data = new Byte?[4] { 0, 23, null, null };
var TestObj = (Test)Marshal.PtrToStructure(GCHandle.Alloc(data.ToArray(), GCHandleType.Pinned).AddrOfPinnedObject(), typeof(Test));

....

Aber es tut nicht. Offenbar kann GCHandle nicht mit generischen Daten umgehen. Ist das richtig? Heißt, ich kann diesen Aufruf nicht benutzen oder?

Ich brauche einen Struct der auch Null Werte beinhaltet. Gibt es einen anderen Weg? Man kann auch alle Daten per Hand zu definieren, aber das ist echt blöd, wenn man größere Datenstrukturen hat.

Reflection ist auch kein weg, da das Programm auf Performance setzt.

Danke
 
Zuletzt bearbeitet:
roker002 schrieb:
Aber es tut nicht.

Damit meinst du wahrscheinlich die ArgumentException. Das ist aber zu erwarten.

Offenbar kann GCHandle nicht mit generischen Daten umgehen. Ist das richtig?

"Generisch" wäre hier nicht das richtige Wort, das Problem ist dass du Byte? verwursten willst, und das ist nunmal kein primitiver Datentyp sondern eine normale Klasse.

Heißt, ich kann diesen Aufruf nicht benutzen oder?

Ja.

Ich brauche einen Struct der auch Null Werte beinhaltet.

Das struct kannste ja machen, aber ohne ein Handle von GC.Alloc(). Was willst du denn mit der ganzen Aktion erreichen? Ein byte-Array in ein struct vom Typ "Test" konvertieren?
 
Ich lese eine Binäre Datei nach einen bestimmten Format. Es gibt dabei eine Bitmaske die angibt ob der Wert vorhanden ist. Die Binäre Datei soll aber in die DB importiert werden. Wenn ich Statt "NULL" einen "0" in die DB schreibe, dann macht es ja schon einen gewaltigen unterschied. Dieser Tool sollte schnell arbeiten. Daher wäre die Erwägung das ganze über Structs zu programmieren und diese dann mittels GCHandle zu erzeugen. Tool ist an sich schon ausprogrammiert. Verbraucht aber immens viel Ram, da die Daten aufgeblasen mittels Nullable Klasse.
Über den GCAlloc habe ich mich informiert. Wollte aber nur sicher gehen ob es so stimmt.

Eine andere Möglichkeit wäre wie mann das ganze umgehen kann ist einen eigenen Int16 Struct zu erstellen, dass auch null implicit operator annehmen kann. Dieser Struct hat dann einen Int16 und Boolean als Felder. Wenn man null übergebt, wird boolean auf False gestellt und der Int16 Wert kann aber auch 0 sein. so kann man später überprüfen ob der Wirt wirklich vorhanden ist. Dann kann man auch mit primitiven Datentypen arbeiten.
 
roker002 schrieb:
Eine andere Möglichkeit wäre wie mann das ganze umgehen kann ist einen eigenen Int16 Struct zu erstellen, dass auch null implicit operator annehmen kann. Dieser Struct hat dann einen Int16 und Boolean als Felder. Wenn man null übergebt, wird boolean auf False gestellt und der Int16 Wert kann aber auch 0 sein. so kann man später überprüfen ob der Wirt wirklich vorhanden ist. Dann kann man auch mit primitiven Datentypen arbeiten.
Genau so ist Nullable<T> doch implementiert.

So sieht das ganze aus:
Code:
public struct Nullable<T> where T : struct
{
	private bool hasValue;
	internal T value;

        //... Methoden weggelassen, der Übersicht wegen

	public static implicit operator T?(T value)
	{
		return new T?(value);
	}

	public static explicit operator T(T? value)
	{
		return value.Value;
	}
}


Ich bezweifle dass der hohe Speicherverbrauch an Nullable<T> liegt.
 
@Grantig
Danke, werde bei der nächste Gelegenheit ausprobieren. Hm ob GCAlloc das schluckt?
Ja es liegt teilweise an dem Nullable, WEIL...
Boolean in .NET genau 4 Byte groß ist. Frag mich nicht wieso.

Wenn man bedenkt, dass ein Datensatz mehr als 50 Felder hat und es insgesamt mehr als 100.000 Einträge pro Datei gibt.
Wenn man nur 4 Bytes große felder hat ist es kein Problem. Mehr Problem habe ich mit den dynamisch Datengrößen. So muss ich die Daten in Strings abspeichern, was bei einem Unicode String teilweise sehr viel Speicheradressierung bedeutet.

Naja, solange es nur 2 Dateien gleichzeitig geladen werden bedeutet das nicht viel, wenn das System 32 Kerne und 12 GB RAM hat ;)

Wäre natürlich besser, wenn man Speicherverbrauch abspecken kann.
 
roker002 schrieb:
Boolean in .NET genau 4 Byte groß ist. Frag mich nicht wieso.
Ist nicht nur in .NET so, sondern auch der WIN32 BOOL Typ belegt 4 byte. Der Grund ist, dass ein 32bit Prozessor 32bit Integer schneller verarbeiten kann.
Das Memory Alignment ist deswegen dahingegen optimiert.
Im Endeffekt braucht ein bool natürlich trotzdem nur 1 byte, die anderen 3 byte sind nur "Lückenfüller".
Das sieht man auch, wenn man statt einem einzelnen bool ein bool Array anlegt, denn im Array braucht ein einzelnes bool auch nur 1 byte und es werden nur am Ende des Arrays "leere" bytes angehängt um eine gerade Anzahl an 32bit Integern zu bekommen.

Wenn du eine eigene Nullable Structure mit explizitem memory layout implementierst, kannst du tatsächlich etwas Speicher sparen.
Das Problem ist nur, dass Structures mit explizitem Layout nicht generisch sein dürfen, also musst du für jeden Typ ne eigene Nullable Structure implementieren.

Ich hab mal schnell ein kleines Beispiel für ushort geschrieben:
Code:
[StructLayout(LayoutKind.Explicit)]
public struct NullableUShort
{
    #region Private Fields

    [MarshalAs(UnmanagedType.I1)]
    [FieldOffset(0)]
    private bool hasValue;

    [MarshalAs(UnmanagedType.U2)]
    [FieldOffset(1)]
    private ushort value;

    #endregion

    #region Constructor

    public NullableUShort(ushort value)
    {
        this.value = value;
        this.hasValue = true;
    }

    #endregion

    #region Properties

    public bool HasValue
    {
        get { return this.hasValue; }
    }

    public ushort Value
    {
        get
        {
            if (!this.HasValue)
            {
                throw new InvalidOperationException("Value is not initialized.");
            }

            return this.value;
        }
    }

    #endregion

    #region Operators

    public static implicit operator NullableUShort(ushort value)
    {
        return new NullableUShort(value);
    }

    public static explicit operator ushort(NullableUShort nullable)
    {
        return nullable.value;
    }

    #endregion
}
 
Zuletzt bearbeitet:
Ja danke.
Ich kenne schon explizite Allozierung des Speichers. ja leider kann man immer noch nicht null byte array an GC Alloc übergeben. Sonst muss man immer mehr als 1 byte oder in deinen beispiel 3 Byte array pro NullableUShort übergeben. Gut zu wissen, wie es genau funktioniert.
 
Doch, mit meiner Beispiel Struktur kannst du auch einfach ein leeres byte Array übergeben.
NullableUInt ist dann eben "null" (nicht wirklich, da nicht das selbe wie ein Nullable<T>).

Kleines Beispiel:
Code:
byte[] buffer = { 
                    1, // hasValue
                    0, // value, least significant byte
                    2  // value, most significant byte
                };

// byte[] buffer = {}; funktioniert auch, NullableUShort ist dann "null" (hasValue == false)

GCHandle hBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
NullableUShort nullable = (NullableUShort)Marshal
    .PtrToStructure(hBuffer.AddrOfPinnedObject(), typeof(NullableUShort));

hBuffer.Free();

if (nullable.HasValue)
{
    Console.WriteLine(nullable.Value);
}
 
Was ich eigentlich gemeint habe:
Code:
byte?[] buffer = { 
                    0, // value, least significant byte
                    2,  // value, most significant byte
                    null,
                    null
                };

oder einen eigenen Byte Typen der halt ein Struct ist aber null akzeptiert. Da mein Beispiel sich nicht aus primitiven Datentypen setzt, wird es ja auch nicht gehen.

@Grantig
Einen beispiel muss du nicht schreiben
 
Ach jetzt kapier ich erst was du willst.
Sorry aber dein Beispiel aus dem Startpost ist kompletter Blödsinn.

Das Layout deines Structs ist falsch:
Code:
[StructLayout(LayoutKind.Explicit)]
public struct Test
{
[MarshalAs(UnmanagedType.U2)]
[FieldOffset(0)]
public Int16? Var1;
 
[MarshalAs(UnmanagedType.U2)]
[FieldOffset(2)]
public Int16? Var2;
}
Ein Int16? ist nicht nur 2 byte breit.
Du manipulierst also jedesmal wenn du in Var1 schreibst gleichzeitig auch Var2 und umgekehrt.
Schau dir doch mal an wie Nullable<T> implementiert ist, ich hab doch extra weiter oben den Auszug gepostet.
Es wird immer auch noch ein boolean mit gespeichert.
Ein Int16? ist normalerweise 4 byte breit.
Du versuchst aber deine beiden Int16? auf je 2 byte unterzubringen was nicht möglich ist, dadurch "überschneiden" sie sich.

So wie du dir das vorstellst kanns garnicht funktionieren.
Nochmal ein Stück deines Codes - mit Kommentar- um deinen Denkfehler zu verdeutlichen:
Code:
byte?[] buffer = {
    0, // --> 2 bytes: hasValue = 1, value = 0
    2, // --> 2 bytes: hasValue = 1, value = 2
    null, // --> 2 bytes: hasValue = 0, value = 0
    null // -- 2 bytes: hasValue = 0, value = 0
    };
Du denkst ein byte? besteht nur aus einem byte in dem auf magische Weise die Information untergebracht ist, ob es initialisiert wurde oder nicht. Dem ist aber nicht so.
Ein byte? ist nur syntaktischer Zucker - die Kurzschreibweise von Nullable<byte>.
 
Zuletzt bearbeitet: (Ergänzung)
Zurück
Oben