Laden...

externe DLL mit Bitmaps

Erstellt von S.R. vor 14 Jahren Letzter Beitrag vor 14 Jahren 1.958 Views
S
S.R. Themenstarter:in
221 Beiträge seit 2007
vor 14 Jahren
externe DLL mit Bitmaps

Hallo,

ich habe hier eine externe DLL, die in Delphi 7 geschrieben ist. Diese bekommt zwei Bitmaps übergeben und liefert dann ein Bitmap als Ergebnis zurück. Wie die DLL aus Delphi (ohne Fehler) aufgerufen wird, zeigt folgender Code:

function CompareTwoBitmaps(const Bitmap1, Bitmap2: TBitmap): TBitmap; stdcall; external 'BitmapCompare.dll';

procedure TForm1.ExecuteButtonClick(Sender: TObject);
var
  img1, img2, res: TBitmap;
begin
  img1 := TBitmap.Create;
  img2 := TBitmap.Create;

  img1.LoadFromFile(Bitmap1Edit.Text);
  img2.LoadFromFile(Bitmap2Edit.Text);

  res := CompareTwoBitmaps(img1, img2);
  res.SaveToFile('d:\res.bmp');
end;

Nun soll ich das gleiche in C-Sharp realisieren - also die Einbindung der DLL. Bisher habe ich folgendes gemacht:

[DllImport("BitmapCompare.dll", EntryPoint = "CompareTwoBitmaps", CharSet = CharSet.Ansi, ExactSpelling = true,
            CallingConvention = CallingConvention.Winapi)]
private static extern Bitmap CompareTwoBitmaps(Bitmap Bitmap1, Bitmap Bitmap2);

itmap bmp1 = (Bitmap.FromFile("d:\\t1.bmp") as Bitmap);
Bitmap bmp2 = (Bitmap.FromFile("d:\\t3.bmp") as Bitmap);

Bitmap bmp3 = CompareTwoBitmaps(bmp1, bmp2);
bmp3.Save("d:\\result.png", System.Drawing.Imaging.ImageFormat.Png);

Dann erhalte ich allerdings an der Stelle, wo CompareTwoBitmaps aufgerufen wird, folgende Exception:

Eine Ausnahme vom Typ "System.ExecutionEngineException" wurde ausgelöst.

Auf die gleiche Weise habe ich schon mal eine andere Delphi-Dll eingebunden - ohne Probleme, allerdings wurden da nur Integer bzw. String übergeben und kein so "komplexes Objekt" wie Bitmap.

Hat jemand von euch n' Ahnung, wie ich das ganze in den Griff kriege.

Dankend

Stefan

1.044 Beiträge seit 2008
vor 14 Jahren

Hallo S.R.,

in deinem Projekt in Delphi verwendet du als Paramter Konstanten. Versuch doch einfach mal in C# dies auch als Konstante zu ändern. Zudem verwendet du die TBitmap-Klasse in Delphi, was evtl. unter C# nicht ganz hinhaut.

zero_x

S
S.R. Themenstarter:in
221 Beiträge seit 2007
vor 14 Jahren

Hallo,

vielen Dank für deine Hilfe. Mit den Konstanten sollte eigentlich egal sein - weil diese ja nur verhindern, dass der Wert überschrieben wird. Trotzdem werde ich dies mal testen.

Welche Klasse kann ich denn unter Delphi und/oder C# verwenden, dass hier eine reibungslose Kommunikation stattfindet? Da ich Zugriff auf den Delphi-Code habe kann ich hier auch schauen, in wie weit sich hier "schnell" der Datentyp anpassen lässt.

Vielen Dank für eure Mühen

Stefan

4.938 Beiträge seit 2008
vor 14 Jahren

Die Bitmapdaten kannst du nur mittels den Rohdaten zwischen C# und einer anderen Sprache (egal ob Delphi, C++ oder sonstwie) übertragen, denn die beiden Bitmap-Klassen werden wohl auf Byteebene unterschiedlich deklariert sein.
(aber da dies ja eine externe DLL ist, kannst du wohl nicht die Funktion ändern - außer du schreibst wiederum eine 2. Delphi-DLL welche die andere Funktion kapselt und nur die Zeiger auf die Bitmapdaten sowie evtl. weitere Parameter wie Breite und Höhe entgegennimmt - quasi die Elemente der TBitmap-Struktur).

Evtl. könntest du auch mithilfe der Marshal-Klasse die Delphi-TBitmap-Struktur nachbilden und dann auf C#-Seite eine Kopiermethode schreiben - ist aber genauso ein Aufwand.

S
S.R. Themenstarter:in
221 Beiträge seit 2007
vor 14 Jahren

Hi,

vielen Dank für eure Hilfe. Die Dll bietet noch eine weitere Möglichkeit, die Daten anzunehmen. Der Delphi-Code sieht dafür wie folgt aus:

unction CompareTwoBitmaps(const Width, Height: Integer;
  Bitmap1, Bitmap2: PRGBQuad): Integer; stdcall; external 'BitmapCompare.dll';

procedure TForm1.ExecuteButtonClick(Sender: TObject);
var
  img1, img2, res: TBitmap;
  P1, P2: PRGBQuad;
begin
  img1 := TBitmap.Create;
  img2 := TBitmap.Create;

  img1.LoadFromFile(Bitmap1Edit.Text);
  img2.LoadFromFile(Bitmap2Edit.Text);

  img1.PixelFormat := pf32bit;
  img2.PixelFormat := pf32bit;

  P1 := img1.ScanLine[img1.Height - 1];
  P2 := img2.ScanLine[img2.Height - 1];

  CompareTwoBitmaps(img1.Width, img1.Height, P1, P2);
end;

(Hier gibt es zwar noch keinen Rückgabewert, das ist aber momentan auch egal - bild wird im Tmp-Verzeichnis gespeichert)

Jetzt stehe ich aber auf dem Schlauch, wie ich diesen Übergang in Delphi einbauen soll - eine für mich noch unlösbare Aufgabe. Wenn ich das richtig sehe, dann ist P1 und P2 ein Pointer auf eine ganz simple Struktur:

  PRGBQuad = ^TRGBQuad;
  {$EXTERNALSYM tagRGBQUAD}
  tagRGBQUAD = packed record
    rgbBlue: Byte;
    rgbGreen: Byte;
    rgbRed: Byte;
    rgbReserved: Byte;
  end;
  TRGBQuad = tagRGBQUAD;
  {$EXTERNALSYM RGBQUAD}
  RGBQUAD = tagRGBQUAD;

Diese habe ich wie folgt in C-Sharp nachgebaut:

public struct TRGBQuad
{
  Byte rgbBlue;
  Byte rgbGreen;
  Byte rgbRed;
  Byte rgbReserved;
}

Allerdings habe ich keinen Plan, wie ich da jetzt n' Pointer-Array machen soll, was als Übergabe erwartet wird. In Delphi kann ich mit den Bilddaten wie folgt umgehen:

P1.rgbRed => direkt den Rot-Wert abrufen
Inc(P1) => Pointer eins hochzählen => gehe ins nächste Feld. Das Feld ist wie folgt aufgebaut: Bild unten links ist der erste Eintrag - mit dem Hochzählen komme ich nach rechts, dann springt man eine Zeile nach oben und fängt dort wieder links an - also eine Art Band, wo alle Pixel hintereinander stehen.

Allerdings habe ich noch keinen Plan, wie ich dieses ScanLine in C-Sharp umsetzen soll. Dazu habe ich noch nichts passendes gefunden. Hat da von euch jemand einen Tipp?

Dankend

Stefan

M
1.439 Beiträge seit 2005
vor 14 Jahren

Mit der Bitmap.LockBits - Methode liefert dir einen IntPtr auf die erste ScanLine im Bild.
Und diesen übergibst du einfach an deine Delphi-Methode.

S
S.R. Themenstarter:in
221 Beiträge seit 2007
vor 14 Jahren

Hi,

vielen Dank für eine Mühen, aber bist du dir mit LockBits sicher?

Bin dies grad am Umsetzen und habe in C-Sharp die Funktion wie folgt definiert:

[DllImport("BitmapCompare.dll", EntryPoint = "CompareTwoBitmaps", CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
private static extern Int32 CompareTwoBitmaps(IntPtr Bitmap1, IntPtr Bitmap2);

Der Aufruf-Code sieht dann wie folgt aus:

Bitmap bmp1 = (Bitmap.FromFile("d:\\t1.bmp") as Bitmap); // GetDesktopImage();
Bitmap bmp2 = (Bitmap.FromFile("d:\\t2.bmp") as Bitmap);

CompareTwoBitmaps(
  bmp1.LockBits(new Rectangle(0, 0, 5, 5), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb),
  bmp2.LockBits(new Rectangle(0, 0, 5, 5), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb));

Die Funktion LockBits liefert allerdings die Klasse BitmapData zurück. Daher kommt es dazu einem Compiler-Fehler, da BitmapData nicht in IntPtr konvertiert werden kann.

Hast du n' Idee, wo da der Fehler liegt? stehe da grad was auf dem Schlauch - dankend

Stefan

4.938 Beiträge seit 2008
vor 14 Jahren

Dann schau dir einfach mal die Member der Klasse BitmapData an...

Tip: Scan0

S
S.R. Themenstarter:in
221 Beiträge seit 2007
vor 14 Jahren

Hi,

wer suchet der findet - danke! Damit bin ich gerade einen riesen Schritt weiter gekommen.

Schönen Abend wünscht euch allen

Stefan

S
S.R. Themenstarter:in
221 Beiträge seit 2007
vor 14 Jahren

Hi,

habe nun noch folgendes Problem in Zusammenhang mit der Dll und der Übergabe zu lösen, wo es leider noch knallt:

Die C-Sharp-Applikation soll der (Delphi-)DLL ein eindimensionales Array zur Verfügung stellen (gerne auch noch ein Int mit der Größe des Arrays). In dieses Array schreibt die Applikation ihre Daten und beendet sich dann. Das Array muss vom Type PixelInformation sein, was wie folgt aufgebaut ist:

        private struct PixelInformation
        {
            public Int32 x;
            public Int32 y;
            public Byte Red;
            public Byte Green;
            public Byte Blue;
        }

Bisher wird das ganze so übergeben:

        private static extern Int32 CompareTwoBitmaps(Int32 Width, ref PixelInformation[] changes);

PixelInformation[] changes = new PixelInformation[5];

Int32 tmp = CompareTwoBitmaps(5, ref changes);

Allerdings kommt damit Delphi nicht ganz klar und endet mit einer Exception. Der Code in Delphi sieht bisher wie folgt aus (und muss vermutlich ebenfalls angepasst werden):

  TPixelInformation = packed record
    x: Integer;
    y: Integer;
    red: Byte;
    green: Byte;
    blue: Byte;
  end;
  TPixelInformations = TPixelInformation;

function CompareTwoBitmaps(const Width, var Changes: TPixelInformations): Integer; stdcall;
begin

end;

Wie man sieht, macht die Delphi-Funktion bisher nichts und trotzdem tritt folgender Fehler auf:

Vor dem Aufruf der DLL ist changes 25-Dimensionen groß, nach dem Aufruf der DLL nur noch eine. Da scheint also schon irgendwas am Ansatz nicht zu stimmen. Bin dankbar für jeden Tipp, wie ich hier korrekt das Array in Delphi (in Kombination mit C#) richtig füllen kann.

Dankend

Stefan

4.938 Beiträge seit 2008
vor 14 Jahren

TPixelInformations = TPixelInformation

Steht das wirklich so im Delphi-Sourcecode? Denn dann wäre es ja kein Array, sondern nur ein einzelnes Record-Element (bzw. Struct in C#).

S
S.R. Themenstarter:in
221 Beiträge seit 2007
vor 14 Jahren

uups - copy-past-fehler 😃 da steht natürlich was anderes:

TPixelInformations = array of TPixelInformation

Das stammte noch aus Test-Zeiten, wo ich nur ein Record/Struct übergeben habe. Das hatte nämlich reibungslos funktioniert. Nur mit dem Array steh ich auf Kriegsfuß 😃

Dankend

Stefan

K
133 Beiträge seit 2009
vor 14 Jahren
  
        private struct PixelInformation  
        {  
            public Int32 x;  
            public Int32 y;  
            public Byte Red;  
            public Byte Green;  
            public Byte Blue;  
        }  
  

Wie siehts denn mit dem

    [StructLayout(LayoutKind.Sequential)]  

aus? Mit der Attributklasse kannst du das physikalische Layout der Datenfelder deiner Struktur steuern.

S
S.R. Themenstarter:in
221 Beiträge seit 2007
vor 14 Jahren

Hi,

vielen Dank für deinen Eintrag, auch wenn ich aktuell damit nichts anfangen kann, weil ich rein gar nix davon verstehe 😃

Versuche gerade das Problem zu vereinfachen, um so ans Ziel zu kommen. Anstatt ein Array von meinem eigenen Record/Struct zu übergeben, möchte ich erst Mal nur ein Array von Int32 übergeben. Das sollte doch zumindest problemlos schaffbar sein, oder?

Aber auch in diesem Falle bleiben die Ergebnisse gleich. Ich rufe die Delphi-Funktion auf, mache sozusagen nichts und zack ist das Array in C-Sharp nur noch von der Länge eins. Da geht also einiges schief - selbst bei der einfachen Struktur.

Verständnisfrage:
Wenn ich "Int32[] changes" habe und dies dann mit "ref changes" übergebe, dann sollte doch in Delphi ein Pointer auf den ersten Eintrag in dem Array ankommen, oder?
Was ich dann nicht verstehe - wenn in Delphi doch nur ein Pointer ankommt (wegen Referenz) und in der Routine nix gemacht wird, wieso ist in C-Sharp dann das Array nur noch von der Länge 1 (nach Delphi-Funktion-Aufruf).

Dankend

Stefan

K
133 Beiträge seit 2009
vor 14 Jahren
[StructLayout(LayoutKind.Sequential)] 
private struct PixelInformation
        {
            public Int32....

Mit [StructLayout(LayoutKind.Sequential)] legst du das Layout (Anordnung) deiner Member in deinem Struct fest. Es kann sein (so hab ich das jedenfalls interpretiert) das die Member nicht immer so im speicher sind wie "niedergeschrieben".

[DllImport("blubb")]
private static extern Int32 CompareTwoBitmaps(Int32 Width, 
[MarshalAs(UnmanagedType.LPArray)]
ref PixelInformation[] changes);

Schau mal bei Marshallen von Arrays vorbei.