Laden...

Frage zu IntPtr und Marshal.Copy

Erstellt von digi333 vor 15 Jahren Letzter Beitrag vor 15 Jahren 2.001 Views
D
digi333 Themenstarter:in
290 Beiträge seit 2006
vor 15 Jahren
Frage zu IntPtr und Marshal.Copy

Ich hab ein eindimesionales Byte-Array voll mit Bilddaten und ich möchte es mit Marshal.Copy in ein Zeiger überführen. Wie kann ich einen neuen IntPtr erzeugen? Hab die SuFu verwendet aber da war immer ein Bitmap und Lockbits voraus... geht das auch anders?

Einige Codefragmente:


public unsafe override void getResult(Frame frame)
{
   ...
   byte[] output = new byte[3*frame.Width*frame.Height];
   ...
   System.Runtime.InteropServices.Marshal.Copy(output, 0, ptr, output.Length);
   _bitmap = new Bitmap(frame.Width, frame.Height, 3*frame.Width, PixelFormat.Format24bppRgb, ptr);
}

Wie gesagt Probleme hab ich bei ptr.

643 Beiträge seit 2006
vor 15 Jahren

IntPtr ptr = new Inptr(); ?

1.361 Beiträge seit 2007
vor 15 Jahren

Hi digi333,

schau mal bei MemBitmap vorbei.

  1. Du musst aufpassen, wenn bei dir frame.Width kein vielfaches von 4 ist.
    Es ist nämlich wichtig, dass jede neue Zeile eines Bitmaps 4-byte-aligned ist. Deshalb gibts bei nem Bitmap am Ende immer noch son Offset.
    Und die gesamtBreite in Bytes einer Zeile wird oft als Stride, pitch oder WidthStep bezeichnet

  2. ein byte[]-Array hat in C# wie jedes Objekt eigentlich nie eine feste Position im RAM. kann jederzeit verschoben werden um die Fragmentierung klein zu halten.
    Deshalb musst du dein Objekt zuerst "pinnen". Und erst dann kannst du eine feste Speicheradresse holen.
    Wichtig dabei ist, am Ende das Pinnen wieder rückgängig zu machen um der GarbageCollection so wieder die Kontrolle zu geben.

  3. Mit Marshal.Copy hat das erstmal nix zu tun. du willst ja nix kopieren, sondern nur die Speicheradresse haben.

Beispiel:


byte[] output;
...
GCHandle dataHandle = GCHandle.Alloc(output, GCHandleType.Pinned);
IntPtr ptr = dataHandle.AddrOfPinnedObject();

wichtig am Ende wie gesagt:


dataHandle.Free();

Sonst bleibt das Array immer weiter gepinned. Auch wenn du das Bitmap schon lange nicht mehr verwendest. Und der speicher wird voller und voller.

//Edit:
natürlich muss das byte[]-Array mindestens so lange existieren wie das Bitmap.
_bitmap scheint ne Klassenvariable zu sein, während output nur lokal ist.
Aber gut, solange du das GCHandle als Klassenvariable hast, kann auch das byte[]-Array nicht am Ende der Funktion von der GC collected werden.

beste Grüße
zommi

D
digi333 Themenstarter:in
290 Beiträge seit 2006
vor 15 Jahren

IntPtr ptr = new Inptr(); ?

Dann bekomm ich aber bei


System.Runtime.InteropServices.Marshal.Copy(output, 0, ptr, output.Length);

eine ArgumentNullException.

Wie umgehe ich diese? Was darf den dabei nicht NULL sein?

🤔

S
119 Beiträge seit 2008
vor 15 Jahren

Wenn du ne neue Instanz von nem Objekt erstellst ist es meistens null. Das sollte deine Frage beantworten

"2 Dinge sind unendlich die Dummheit der Menschen und das Universum, aber beim Universum bin ich mir noch nicht so ganz sicher."

  • Albert Einstein
1.361 Beiträge seit 2007
vor 15 Jahren

Wenn du ne neue Instanz von nem Objekt erstellst ist es meistens null

Mhh..naja... eigentlich nicht. bevor ich den Konstruktor aufrufe ist die Referenz meistens Null. Danach ja eben nicht mehr.

new Inptr();

erzeugt nen neuen Null-Pointer.

Und weil dieser Points auf nix (null) zeigt, gibs auch konsequenterweise ne ArgumentNullException, wenn du den an den Bitmap-Konstruktor übergibt.
Denn der erwartet einen Pointer auf nen Speicherbereich.
Und bei "null" ist aber kein gültiger Speicherbereich.

//Edit:
Ne aber mal im Ernst: nimm GCHandle und gut ist 🙂

beste Grüße
zommi

D
digi333 Themenstarter:in
290 Beiträge seit 2006
vor 15 Jahren

Hallo zommi,

was du oben geschrieben hast, klingt eigentlich super... ich möchte mehr hören, aber ich verstehe fast nur Bahnhof.

Wie gesagt habe ich ja ein volles ByteArray für einen Pointer vorbereitet. Aber wie erstelle ich den Pointer so das ich ihn dem Bitmap geben kann? Was ist mit den überzähligen Bits? Die höhe und Breite ändert sich mit der Auflösung. Das ByteArray ist in dem Sinne fast selber gezeichnet, aber hat die gleiche Größe wie frame.

Wie sieht es mit output aus? Soll der erst hinterher beschrieben werden?

S
119 Beiträge seit 2008
vor 15 Jahren

@zommi
Naja ich konnte dir jetzt aus dem Gedächtnis 20 Klassen auzählen die nach dem Instanzieren null sind. 😉

"2 Dinge sind unendlich die Dummheit der Menschen und das Universum, aber beim Universum bin ich mir noch nicht so ganz sicher."

  • Albert Einstein
Gelöschter Account
vor 15 Jahren

Wenn du ne neue Instanz von nem Objekt erstellst ist es meistens null. Das sollte deine Frage beantworten

??? seit wann? wenn du eine instanz erzeugst, dann ist sie da und auch im speicher zu finden. wenn du eine variable anlegst, kannst du sie mit null vorbelegen (wenn es ein nullable typ ist) aber sobald du eine instanz anlegst, ist diese nicht null.

Naja ich konnte dir jetzt aus dem Gedächtnis 20 Klassen auzählen die nach dem Instanzieren null sind.

na jetzt bin ich mal gespannt. das was du vermutlich meinst: manche feldvariablen sind mit null vorinitialisiert. (in diesem fall ist IntPtr.m_value == null)

1.361 Beiträge seit 2007
vor 15 Jahren

Hi digi333,

aber ich verstehe fast nur Bahnhof.

Mhh...ich dachte es wär verständlicher X( ( 😉)

Befrag mal Google/Forum/Wiki/MSDN zu folgenden Themen:*"Byte Alignment" (wiki nennts Speicherausrichtung) *"Memory Management c#" bzw. "Garbage Collection c#" *"Large Object Heap" *"Pinning c#" *"device independent bitmap"

Das sollte vorerst reichen. 🙂

Also nochmal:

  1. Du hast ein byte[]-Array wo deine pixelwerte drin stehn... immer RGB-Wert.
    (Allerdings in der Reihenfolge BGRBGRBGRBGRBGR.....)
  2. Du willst daraus ohne Umwege ein Bitmap erstellen, dass du einfach anzeigen kannst (Picturebox.Image= oder Graphics.DrawImage())
  3. Du fixierst (pinning) das byte[]-Array mittels eines GCHandles auf dem Large-Object-Heap (Speicherbereich), sodass die normale Garbage-Collection es nicht mehr hin und her verschieben kann im Hauptspeicher.
  4. du holst dir mittels AddrOfPinnedObject() einen Zieger (IntPtr) auf den nun festen Speicherbereich
  5. du übergibst den dem Bitmap-Konstruktor diesen Zeiger und erhälst zurück ein schönes Bitmap mit den Pixeln

Wichtig ist: Das Bitmap verweist intern immernoch auf den Speicherbereich.
Du darfst also solange du mit dem Bitmap arbeiten willst, nicht den Speicherbereich loslassen (GCHandle.Free). Aber sobald du das Bitmap nicht mehr brauchst, solltest du das unbedingt tun.

Wenn du nachträglich noch was im Bitmap rumwerkeln willst, dann solltest du dir die Referenz auf das byte[]-Array merken.
Denn du kannst immernoch die Pixel manipulieren und da das Bitmap ja DIREKT darauf verweist, musst du kein neues Bitmap o.Ä. erstellen.

beste Grüße
zommi

Zur Offtopic-null-Diskussion:
new ***();
ruft ja einen Konstruktor auf und wir haben ein reales Objekt (!=null)

(Ich weiß jetzt nich was passiert, wenn im Konstruktor ne Exception geworfen wird...dann ist das Ergebnis evtl. null)

Gelöschter Account
vor 15 Jahren

(Ich weiß jetzt nich was passiert, wenn im Konstruktor ne Exception geworfen wird...dann ist das Ergebnis evtl. null)

dann wird zum catchblock gesprungen oder das programm beendet (unhandled exception). die frage ist dann: wo befindet sich der catchblock? wenn er im konstruktor ist, dann kann man die instanziierung weitermachen ansonsten je nach scope nicht mehr. tatsache ist, das bereits vor dem konstruktor, das objekt im speicher liegt (also != null ist), da vor dem konstruktor bereits alle klassenvariablen angelegt werden und der speicher hierfür bereits reserviert sein muss.

D
digi333 Themenstarter:in
290 Beiträge seit 2006
vor 15 Jahren

Hab momentan noch nicht das Bild so wie ich es haben wollte. Ich habe aber "Angst" vor den Bits am Ende jeder Zeile, da ich davon keine Ahnung hab. du meintest, durch 4 teilbar. Das kann ich nicht immer gewährleisten. Videos haben unterschiedliche Auflösungen. Die Manipulation der Bilder passieren davor im Bytearray.