Laden...

(Inverse) P/I Problem (Marshalling?)

Erstellt von DarKlajid vor 16 Jahren Letzter Beitrag vor 16 Jahren 2.289 Views
D
DarKlajid Themenstarter:in
386 Beiträge seit 2007
vor 16 Jahren
(Inverse) P/I Problem (Marshalling?)

Mahlzeit.

In einem Projekt hier setze ich inverse P/I ein, um aus einer Legacy Schnittstelle trotzdem C# nutzen zu koennen. Das Problem aktuell dabei ist folgendes:

Ich werde per Inverse P/I aufgerufen, lande in Funktion X, rufe von dort teilweise wieder per normalem P/I andere alte Komponenten auf. Dieses Design laesst sich nicht aendern, also helfen keine Kommentare zur Haesslichkeit.. =)

Meine Einsprungsfunktion ist TestRule und der Code sieht grob so aus:


// Externe Funktion und benutzter Datentyp
[DllImport("Some.dll", EntryPoint="EchterName")]
public static extern Int32 GetWorkingField(ref Region region);

[StructLayout(LayoutKind.Sequential,Pack=0,CharSet=CharSet.Ansi)]
public sealed class Region {    
  [MarshalAs(UnmanagedType.ByValArray,SizeConst=8)]
  public char[] Version;
  public Int32 Size;
  public Int32 Type;                
  public Int32 Left;
  public Int32 Top;
  public Int32 Width;
  public Int32 Height;
  public Int32 SomeInt;
  public Int32 AnotherInt;
  public Int32 AThirdInt;
  [MarshalAs(UnmanagedType.LPWStr)]
  public string Result;
  public Double Value;
  public Int32 ALastInt;    
}

public static int TestRule([MarshalAs(UnmanagedType.LPWStr)]string stringParam, int intParam) {
  Region region1 = GetTestRegion();
  System.Windows.Forms.MessageBox.Show("Result first time: "+region1.Result);
  Region region2 = GetTestRegion();
  System.Windows.Forms.MessageBox.Show("Result second time: "+region2.Result);
  return 1;
}

public static Region GetTestRegion() {
  Region region = new Region();
  GetWorkingField(ref region);
  return region;
}

Resultat: Die Methode TestRule wird aufgerufen (per Inverse P/I, falls das wichtig ist), holt sich ueber GetTestRegion() (Hier habe ich natuerlich vereinfacht, aber genau so laeuft es ab) eine Datenstruktur ueber einen P/I call und nutzt den. In diesem Fall hole ich mir also zweimal die gleiche Datenstruktur und gebe den enthaltenen String aus.
Das funktioniert nur einmal, leider, d.h. in TestRule (und auch danach, was fuer die Applikation eine mittlere Katastrophe ist) ist beim ersten Popup noch ein String Wert vorhanden, beim zweiten und allen folgenden Zugriffen nicht mehr. Es scheint als wenn ich irgendwie durch den blossen Zugriff/das Marshalling mir hier diesen Wert versaue/loesche. Bei den Int-Werten in der Struktur tritt das nicht auf.
Dieses Verhalten tritt auch nicht auf, wenn ich die Funktionalitaet (GetWorkingField() und dann Zugriff auf das Result) direkt aus einer unmanaged dll nutze (aber das ist wie gesagt nicht gewollt/machbar).

Hat jemand eine Idee was ich falsch mache?

Als Referenz hier nochmal die Definition der Region auf unmanaged Seite, falls es daran liegen sollte:


typedef struct {
   char        szVersion[8];
   long        lSize;
   long        iType;
   long        left, top, width, height;
   long        someInt, AnotherInt, AThirdInt;
   wchar_t    *szResult;
   double      dValue;
   long        ALastInt;
} TREGION;

Pound for pound, plutonium is about as toxic as caffeine when eaten.

S
8.746 Beiträge seit 2005
vor 16 Jahren

Mir wäre neu, dass bei Inverse PInvoke überhaupt ein Marshalling abläuft (nur blittable types gehen, weil gleiche Repäsentation). Wie auch? Mach doch mal aus dem ersten Parameter von TestRule() ein IntPtr und mache dann das Marshalling per Hand.

D
DarKlajid Themenstarter:in
386 Beiträge seit 2007
vor 16 Jahren

Hmm.. Da hab ich mich wohl nicht klar ausgedrueckt. Die Parameter in TestRule kommen an. Ich hab das Inverse P/I nur erwaehnt, falls es irgendwie Seiteneffekte haben sollte. Ab dort (TestRule) ist es "normales" P/I.

Das genau geht anscheinend schief: Ich bekomme meine Instanz von Region, kann dort auf den String zugreifen. Ich hole die gleiche Region erneut (normalerweise nicht direkt danach, aber dieser Testcase zeigt das Problem) und nun ist der String futsch.

Bin ich schuld? Hast du ne Idee?

Pound for pound, plutonium is about as toxic as caffeine when eaten.

S
8.746 Beiträge seit 2005
vor 16 Jahren

Mal mit StringBuilder probiert? String-Marshalling funzt ja eigentlich nur in Richtung unmanaged Code und nicht zurück. Aber ich befürchte, du wirst eh in ein Memory-Leak hinein laufen. Der unmanaged Funktion bleibt ja mangels Längenangabe nix anderes übrig, als für den String neuen Speicher zu allokieren und dann in der Struktur abzulegen. Aber wer gibt diesen Speicher frei?

Ansonsten würde ich immer versuchen den String durch IntPtr zu ersetzen und das Marshalling per Hand zu machen. Wenn das dann immer noch nicht klappt, dann liegt der Fehler wohl im unmanaged Teil.

BTW: Dein String-Parameter beim inverse PI funktioniert, weil String ebenfals blittable ist.

D
DarKlajid Themenstarter:in
386 Beiträge seit 2007
vor 16 Jahren

Meine (absolut laienhafte) Vermutung ist halt, dass folgendes passiert (zumindest wuerde das das beobachtete Verhalten erklaeren):


// Hier kriege ich eine brauchbare (lies: Gefuelltes Result) Region
Region region1 = GetTestRegion();
System.Windows.Forms.MessageBox.Show("Result first time: "+region1.Result);
/* Ab hier weiss der Compiler, dass region1 unnuetz ist und collected sie vielleicht direkt?
   Was passiert dabei mit dem String? Referenziert der hier wirklich den unmanaged
   Kram, d.h. kann ich mir hier an dieser Stelle durch einen collect den Speicher den die
   DLL fuer den String reserviert hat zersemmeln?
\*/
Region region2 = GetTestRegion();
System.Windows.Forms.MessageBox.Show("Result second time: "+region2.Result);

Pound for pound, plutonium is about as toxic as caffeine when eaten.

D
DarKlajid Themenstarter:in
386 Beiträge seit 2007
vor 16 Jahren

StringBuilder habe ich versucht, da bekomme ich dann allerdings erst gar kein Resultat mehr zurueck. Und modifizierbar muss der String eig. auch nicht sein.. =/
Mal sehen, vielleicht muss ich damit mehr herumexperimentieren.

Speicherfreigabe ist kein Problem, weil die DLL zwingend spaeter noch mit diesen Daten weiterarbeiten muss. Es geht hier also an dieser Stelle nur um das abgreifen von Informationen.

Bloed gesagt:

  1. DLLInit()
  2. HierHoleIchdieRegion()
  3. DLLFinalize()

Der Speicher wird schon spaeter freigegeben, nur habe ich aktuell eher das Gefuel dass ICH halt beim Marshalling (und wie gesagt, da bin ich recht unbeschlagen, thematisch) irgendwie dafuer sorge, dass der Speicher von mir/.Net gefressen wird. Was falsch waere..

Danke fuer die Hilfe soweit.

Pound for pound, plutonium is about as toxic as caffeine when eaten.

S
8.746 Beiträge seit 2005
vor 16 Jahren

Original von DarKlajid
[...] irgendwie dafuer sorge, dass der Speicher von mir/.Net gefressen wird. Was falsch waere..

Ich denke nicht. Aber wie gesagt: Um Probleme beim Marshalling von Referenzen zu umgehen, kannst du immer mit IntPtr arbeiten. So kann man auch schön sehen, ob der Pointer i.O. ist.

D
DarKlajid Themenstarter:in
386 Beiträge seit 2007
vor 16 Jahren

Nur als Verstaendnisfrage:
Du meinst aber schon nur den bisherigen String als IntPtr zu behandeln, oder? Nicht die gesamte Klasse Region?
Werd mich gleich mal damit auseinander setzen. Danke nochmal.

Pound for pound, plutonium is about as toxic as caffeine when eaten.

383 Beiträge seit 2006
vor 16 Jahren

hallo DarKlajid

wie sieht denn diese "legacy schnittstelle" aus? welche sprache wird verwendet?

wakestar

D
DarKlajid Themenstarter:in
386 Beiträge seit 2007
vor 16 Jahren

So, Problem geloest, dank Svenson.

Loesung:


[StructLayout(LayoutKind.Sequential,Pack=0,CharSet=CharSet.Ansi)]
public sealed class Region {    
  public string Result { get { return Marshal.PtrToStringUni(this.resultPtr); } }

  [MarshalAs(UnmanagedType.ByValArray,SizeConst=8)]
  public char[] Version;
  public Int32 Size;
  public Int32 Type;                
  public Int32 Left;
  public Int32 Top;
  public Int32 Width;
  public Int32 Height;
  public Int32 SomeInt;
  public Int32 AnotherInt;
  public Int32 AThirdInt;
  private IntPtr resultPtr;
  public Double Value;
  public Int32 ALastInt;    
}

Damit bleibt der String auch nach mehreren Aufrufen erhalten. Warum es jedoch vorher gescheitert ist bleibt ein Raetsel fuer mich. Nach wie vor kann ich mir nur vorstellen, dass da ein uebereifriges Framework meinte, der String gehoere ihm? Wie auch immer: Scheint zu klappen.

wakestar:
Keine Ahnung. Es ist eine normale Win32/Native DLL, Schnittstellen sind definiert fuer Delphi und C. Ich vermute es steckt auch C dahinter, weiss es aber nicht.

Edit: Typo raus.

Pound for pound, plutonium is about as toxic as caffeine when eaten.