Announcement

Collapse
No announcement yet.

Objekte aus Callback-Funktionen ansprechen

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Objekte aus Callback-Funktionen ansprechen

    Hallo!

    Ich versuche gerade, in Delphi 5 eine Klasse zu entwerfen, mit der man über die un/zip.dll von Info-Zip (geschrieben in C) und den Delphi-Ports von Theo Bebekis zip-Dateien entpacken kann.
    Zum Datenaustausch zwischen dll und Programm wird dabei ein Record aus Funktionspointern verwendet (wenn ich richtig weiß, nennt man das Callback - bin Delphi-Anfänger!).

    Wenn ich diese Funktionen als Klassenmethoden implementiere, läuft das Programm nicht (dieser Versuch ist schon ein bisschen her, ich glaube, es ist dann mit Seg.faults ausgestiegen). Ich habe mir sagen lassen, dass Delphi dabei einen zusätzlichen Parameter in die Funktion einbaut (den self-Pointer).
    Wenn ich die Funktionen global definiere, kann ich die Klasse (bzw. die entsprechenden Instanzen) logischerweise nicht derefenzieren, weil mir ein Pointer (eben z.B. self) fehlt.
    Jetzt habe ich einen globalen Pointer definiert, den ich vor jedem DLL-Aufruf auf das entsprechende Objekt setze und über eine bool-Variable sperre ich für andere Instanzen den Zugriff. Das ist aber ... elendes Gefuddel ;o).

    Wie stelle ich es an, dass ich in den der DLL-übergebenen Callback-Funktionen das richtige Objekt ansprechen kann?

    Vielen Dank!
    Daniel Fiederling

  • #2
    Oft enthalten die Callbackfunktionen einen frei verwendbaren Parameter. Dort kann man das Objekt uebergeben. Da das Objekt nur ein Pointer ist, reicht Pointer(Self) fuer die Zuweisung. Ist der freie Parameter ein DWORD, dann DWORD(Self)

    Comment


    • #3
      Da seh ich leider keine Chance, die Callbackfunktionen sind folgendermaßen def.:<br>
      <br>
      type<br>
      TDllPrnt = function(Buffer: PChar; Size: ULONG): integer; stdcall;<br>
      TDllPassword = function(P: PChar; N: Integer; M, Name: PChar): integer; stdcall;<br>
      TDllService = function (CurFile: PChar; Size: ULONG): integer; stdcall;<br>
      TDllSnd = procedure; stdcall;<br>
      TDllReplace = function(FileName: PChar): integer; stdcall;<br>
      TDllMessage = procedure (UnCompSize : ULONG; CompSize : ULONG Factor : UINT Month : UINT; Day : UINT; Year : UINT; Hour : UINT; Minute : UINT; C : Char FileName : PChar; MethBuf : PChar; CRC : ULONG; Crypt : Char); stdcall;<br>
      <br>
      Übergeben wird das ganze an die DLL als Record:<br>

      type<br>
      PUserFunctions = ^TUserFunctions;<br>
      USERFUNCTIONS = record<br>
      Print : TDllPrnt;<br>
      Sound : TDllSnd;<br>
      Replace : TDllReplace;<br>
      Password : TDllPassword;<br>
      SendApplicationMessage : TDllMessage;<br>
      ServCallBk : TDllService;<br>
      TotalSizeComp : ULONG;<br>
      TotalSize : ULONG;<br>
      CompFactor : Integer;<br>
      NumMembers : UINT;<br>
      cchComment : UINT;<br>
      end;<br>
      TUserFunctions = USERFUNCTIONS;<br>
      <br>
      Die letzten 5 Variablen werden lt. Doku nur von der DLL gefüllt.<br>
      Schade ;-(, aber trotzdem danke!<br>
      <br>
      Ciao<br>
      Daniel<br>
      &#10

      Comment


      • #4
        Es gibt dann zwei Möglichkeiten:
        <li>1. eine globale Variable die auf das Object zeigt
        <li>2. die Callback-funktionen zeigen auf einen Callback-Handler der den zusätzlichen Self zeiger enthält und damit eine methode des Objectes aufruft.<br>

        Im ersten Fall ist wohl klar das nur EIN Object zu selben Zeit benutzt werden kann, da ja nur eine globale Variable existiert. Das sähe so aus:<br>

        <pre>

        var
        MyObj: TMyObj = nil;<br>

        function DllPrintCallback(..)..;
        begin
        MyObj.DllPrintCallback(...);
        end;

        </pre>

        Die zweite Variante kann dagegen mehrere Objecte nutzen da nun die Callbackfunction eine Dispatcher-Callback ist, das sähe im Prinzip so aus:<br>

        <pre>
        type
        TMyObject = class
        function DllPrintCallback(); stdcall;
        end;

        function ObjDllPrintCallback();
        asm
        modifiziere Stackparameter und füge Self hinzu
        springe zu TMyObj.DLLHandler
        end;

        </pre>

        Dieser Fall benötigt die dynamische Erzeugung dieser Dispatcher-Callbacks. D.h. für jede Objectinstance müssen eigene Dispatcher erzeugt werden. Dazu bieten sich Klassen methoden an.<br>

        Ein cleverer Programmier definiert seine Callbacks IMMER nach folgendem Schema:

        <pre>

        type
        TMyCallback = function(UserParam: Pointer; Data1, Data2: TData): Result; stdcall;<br>

        procedure DoCallback(Proc: TMyCallback; UserParam: Pointer);<br>

        </pre>

        In diesem Moment wird immer ein UserParam übergeben und dieser ist in der Callback immer der ERSTE Parameter. Damit können wir dann folgendes nutzen:

        <pre>

        type
        TMyObj = class
        function MyCallback(Data1, Data2: TData): Result; stdcall;
        end;<br>

        DoCallback(@TMyObj.MyCallback, MyObjInstance);

        </pre>

        Im Falle eines Callback Records könnte es so aussehen:

        <pre>

        type
        TCallbackRec = packed record
        PrintCallbackUserData: Pointer;
        PrintCallback: function(UserData: Pointer; Data1: TData): Result; stdcall;
        TestCallbackUserData: Pointer;
        TestCallback: function(UserData: Pointer; Data1,Data2: TData): Result; stdcall;
        end;<br>

        transliert in einen Delphi Record sähe es so aus<br>

        type
        TCallbackRec = packed record
        PrintEvent: function(Data1: TData): Result; stdcall; of Object;
        TestEvent: function(Data1,Data2: TData): Result; stdcall; of Object;
        end;<br>

        und die Initalisierung dann so:<br>

        var
        Callback: TCallbackRec;
        begin
        Callback.PrintEvent := MyObjectInstance.PrintCallback;
        Callback.TestEvent := MyObjectInstance.TestCallback;
        end;<br>

        und in der DLL dann so:<br>

        Callback.PrintCallback(Callback.PrintUserData, Data1);<br>

        </pre>

        Das wäre wohl das intelligenteste.

        gruß Hage

        Comment


        • #5
          Hallo Hagen!<br>
          <br>
          Danke erstmal für die schnelle und ausführliche Antwort, nur leider bin ich ein bissl zu dumm dazu :-):<br>
          zu 1.) so habe ich das im Moment auch gemacht, allerdings soll das ganze mal eine Thread-Klasse werden - und nicht nur deshalb ist es nicht gerade sicher bzw. sauber.<br>
          <br>
          zu 2.) Der letzte Teil - wenn ich das richtig verstehe - nützt in meinem Fall recht wenig, da die DLLs in C geschrieben sind und ich da auch bestimmt nicht meine Finger reinlegen will und auch nicht die Zeit dazu habe.<br>
          Der Teil mit den Dispatcher-Routinen hört sich interessant an. Ich kann mir ungefähr vorstellen, was du damit meinst, nur leider bin ich in asm nicht so fit: vermutlich sieht das irgendwie so aus:<br>
          <br>
          push irgendwas<br>
          call TMyObj.DLLHandler<br>
          pop irgendwas<br>
          <br>
          und irgendwas = self? Oder wie sprech ich denn in asm Pascal-Variablen an? Meine Zeiten mit TP6 und Interrupts sind lange her und waren auch nicht so tiefgreifend ).<br>
          Und dann kann ich in der Funktion per self auf das entsprechende Objekt zugreifen? Oder muss ich mir die Variable erst vom Stack holen? Wenn ja, wie?<br>
          <br>
          Danke!<br>
          Bye Daniel<br&gt

          Comment


          • #6
            Ne nicht ganz so. Das Problem was dabei entsteht ist das man alle Parameter vom Stack holen muß um sie dann nachdem Self gepusht wurde wieder auf dem Stack abzulegen. Nur so wird die richtige Reihenfolge der Params möglich.

            Also z.B. so:

            <pre>

            function DllPrintDispatcher(Param1, Param2: DWord): DWord; stdcall;
            asm
            MOV EAX,$123456 // adresse des Objectes
            POP EDX // Param2
            POP ECX // Param1
            PUSH EAX // Self
            PUSH ECX // Param1
            PUSH EDX // Param2
            JMP TMyObject.DllPrint
            end;<br>

            // oder so

            function DllPrintDispatcher(Param1, Param2: DWord): DWord; stdcall;
            asm
            POP EAX // Param2
            POP EDX // Param1
            PUSH $1234567 // Self
            PUSH EDX // Param1
            PUSH EAX // Param2
            JMP TMyObject.DllPrint
            end;<br>

            </pre>

            Diesen Code testen und wenn fertig im CPU Window die HEX-Werte auslesen und als const record definieren, etwa so <br>

            <pre>
            type
            TDllPrintDispatcher = packed record
            MOV_EAX: Byte;
            SelfPtr: Pointer;
            POP_EDX: Byte;
            POP_ECX: Byte;
            PUSH_EAX: Byte;
            PUSH_EDX: Byte;
            PUSH_ECX: Byte;
            JMP: Byte;
            Offset: PChar;
            Dummy: Byte;
            end;

            </pre>

            Nun wird im private Teil des Objectes ein solcher Record deklariert, und ausgefüllt:

            <pre>

            type
            TMyClass = class
            private
            FPrintDispatcher: TDllPrintDispatcher;
            function DllPrint(Param1,Param2: DWord): DWord; stdcall;
            function DllPrintCallback: Pointer;
            end;<br>

            function TMyClass.DllPrintCallback: Pointer;
            begin
            with FPrintDispatcher do
            begin
            MOV_EAX := $xx;
            SelfPtr := Self;
            POP_EDX := $xx;
            POP_ECX := $xx;
            PUSH_EAX := $xx;
            PUSH_EDX := $xx;
            PUSH_ECX := $xx;
            JMP := $xx;
            Offset := PChar(@FPrintDispatcher.Dummy) - PChar(@TMyClass.DllPrint);
            end;
            Result := @FPrintDispatcher;
            end;

            </pre>

            Bei den $xx Stellen muß der entsprechende OpCode eingetragen werden. Mit MyClass.DllPrintDispatcher bekommste nun den Callbackfunctionpointer für die C DLL zurück.

            Gruß Hage

            Comment


            • #7
              DllMessage dürfte wohl die schwierigste Proc werden.
              Dort könntest Du die Parameterübergabe durch den Compiler erledigen lassen:

              <pre>

              function Dll2Dispatcher: DWord;: stdcall;
              asm
              POP ECX // Caller
              PUSH $123456
              PUSH ECX
              JMP DllReorder
              end;<br>

              fucntion DllReorder(P1,P2,P3,P4,P5,P6,P7: DWord; Self: TMyClass): DWord; stdcall;
              begin
              Result := Self.DllMessage(P1, P2, P3, P4, P5, P6, P7);
              end;<br>

              </pre>

              Übrigens im vorherigen Posting habe ich vergessen zu erwähnen das der oberste Parameter auf dem Stack der Zeiger zum aufrufenden Code ist. Dieser muß natürlich berücksichtigt werden !!!<br>
              Natürlch kannste obiges Konstrukt für alle deine Callbacks benutzen. Dann bräuchteste nur einen "generischen" Basisdispatcher:

              <pre>

              type
              TDispatcher = packed record
              POP_EAX: Byte; // Caller
              PUSH_CONST: Byte; // PUSH Self
              SelfPtr: Pointer;
              JMP: Byte; // rel. jump
              OFFSET: PChar;
              end;

              </pre>

              Dann bastelste so wie oben gezeigt für jede Callback eine "Zwischenfunktion" die identisch mit den C-Callbackdeklarationen ist mit der Ausnahme das ein zusätzlicher Param Self als letzter Param hinzugefügt wird.<br>

              Gruß Hage

              Comment


              • #8
                <pre>

                <code><font size=2 face="Courier New"><b>type
                </b>TDispatcher = <b>packed record
                </b>POP_EAX: Byte;
                PUSH_CONST: Byte;
                SelfPtr: Pointer;
                PUSH_EAX: Byte;
                JMP: Byte;
                Offset: DWord;
                <b>end</b>;
                <br>
                PUserFunctions = ^TUserFunctions;
                TUserFunctions = <b>packed record
                </b>Print: <b>function</b>(Buffer: PChar; Size: ULONG): Integer; <b>stdcall</b>;
                Sound: <b>procedure</b>; <b>stdcall</b>;
                Replace: <b>function</b>(FileName: PChar): Integer; <b>stdcall</b>;
                Password: <b>function</b>(P: PChar; N: Integer; M, Name: PChar): Integer; <b>stdcall</b>;
                AppMessage: <b>procedure</b>(UnCompSize : ULONG; CompSize : ULONG; Factor : UINT;
                Month : UINT; Day : UINT; Year : UINT; Hour : UINT;
                Minute : UINT; C : Char; FileName : PChar; MethBuf : PChar;
                CRC : ULONG; Crypt : Char); <b>stdcall</b>;
                Service: <b>function</b>(CurFile: PChar; Size: ULONG): integer; <b>stdcall</b>;
                <br>
                TotalSizeComp : ULONG;
                TotalSize : ULONG;
                CompFactor : Integer;
                NumMembers : UINT;
                cchComment : UINT;
                <b>end</b>;
                <br>
                TMyClass = <b>class
                private
                </b>FDispatcher: <b>array</b>[<font color="#0000FF">0</font>..<font color="#0000FF">5</font>] <b>of </b>TDispatcher;
                FUser: TUserFunctions;
                <b>function </b>UserFunctions: PUserFunctions;
                <b>protected
                function </b>Print(Buffer: PChar; Size: ULONG): Integer; <b>stdcall</b>;
                <b>function </b>Password(P: PChar; N: Integer; M, Name: PChar): Integer; <b>stdcall</b>;
                <b>function </b>Service(CurFile: PChar; Size: ULONG): integer; <b>stdcall</b>;
                <b>procedure </b>Sound; <b>stdcall</b>;
                <b>function </b>Replace(FileName: PChar): integer; <b>stdcall</b>;
                <b>procedure </b>AppMessage(UnCompSize : ULONG; CompSize : ULONG; Factor : UINT; Month : UINT; Day : UINT; Year : UINT; Hour : UINT; Minute : UINT; C : Char; FileName : PChar; MethBuf : PChar; CRC : ULONG; Crypt : Char); <b>stdcall</b>;
                <b>end</b>;
                <br>
                <b>function </b>TMyClass.Print(Buffer: PChar; Size: ULONG): Integer;
                <b>begin
                end</b>;
                <br>
                <b>function </b>TMyClass.Password(P: PChar; N: Integer; M, Name: PChar): Integer;
                <b>begin
                end</b>;
                <br>
                <b>function </b>TMyClass.Service(CurFile: PChar; Size: ULONG): integer;
                <b>begin
                end</b>;
                <br>
                <b>procedure </b>TMyClass.Sound;
                <b>begin
                end</b>;
                <br>
                <b>function </b>TMyClass.Replace(FileName: PChar): integer;
                <b>begin
                end</b>;
                <br>
                <b>procedure </b>TMyClass.AppMessage(UnCompSize : ULONG; CompSize : ULONG; Factor : UINT; Month : UINT; Day : UINT; Year : UINT; Hour : UINT; Minute : UINT; C : Char; FileName : PChar; MethBuf : PChar; CRC : ULONG; Crypt : Char);
                <b>begin
                end</b>;
                <br>
                <b>function </b>TMyClass.UserFunctions: PUserFunctions;
                <br>
                <b>function </b>GetCallback(Index: Integer; DispatchProc: PChar): Pointer;
                <b>begin
                with </b>FDispatcher[Index] <b>do
                begin
                </b>POP_EAX := <font color="#0000FF">$58</font>;
                PUSH_CONST := <font color="#0000FF">$68</font>;
                SelfPtr := Self;
                PUSH_EAX := <font color="#0000FF">$50</font>;
                JMP := <font color="#0000FF">$E9</font>;
                Offset := DispatchProc - PChar(@FDispatcher[Index]) - SizeOf(TDispatcher);
                <b>end</b>;
                Result := @FDispatcher[Index];
                <b>end</b>;
                <br>
                <b>begin
                </b>FUser.Print := GetCallback(<font color="#0000FF">0</font>, @TMyClass.Print);
                FUser.Sound := GetCallback(<font color="#0000FF">1</font>, @TMyClass.Sound);
                FUser.Replace := GetCallback(<font color="#0000FF">2</font>, @TMyClass.Replace);
                FUser.Password := GetCallback(<font color="#0000FF">3</font>, @TMyClass.Password);
                FUser.AppMessage := GetCallback(<font color="#0000FF">4</font>, @TMyClass.AppMessage);
                FUser.Service := GetCallback(<font color="#0000FF">5</font>, @TMyClass.Service);
                Result := @FUser;
                <b>end</b>;
                </font>
                </code></pre>
                &#10

                Comment


                • #9
                  <pre>

                  <code><font size=2 face="Courier New"><b>procedure </b>Test;
                  <b>var
                  </b>Self: TMyClass;
                  User: PUserFunctions;
                  <b>begin
                  </b>Self := TMyClass.Create;
                  User := Self.UserFunctions;
                  User.Print(<font color="#0000FF">'Hagen'</font>, <font color="#0000FF">5</font>);
                  <b>end</b>;
                  <br>
                  </font>
                  </code></pre&gt

                  Comment


                  • #10
                    So, obiger Code funktioniert. Ich musste mal wieder feststellen das ich in meinen vorherigen Postings Schwachsinn gefasselt habe. Die Paramter bei <b>stdcall</b> Callingconvention müssen NICHT auf dem Stack bearbeitet werden. Dies wäre bei <b>cdecl</b> der Fall. Stattdessen muss nur der oberste Param = Calleraddress vom Stack geholt werden, Self gepusht werden und dann wieder die Calleraddress. Dann reicht ein relativer Jump zur statischen Objectmethode aus. Das liegt daran das bei <b>stdcall</b> die Paramter ja "umgekehrt" auf dem Stack liegen.<br>

                    Eventuell sollte noch in der UserFunctions Methode nach dem Initailisieren mit VirtualProtect(@FDispatcher, SizeOf(FDispatcher), PAGE_EXECUTE_READ{WRITE), nil); die Zugriffsrechte gesetzt werden. Normalerweise ist die nicht nötig da eigentlich jedes Usersegment auch ausführbaren Code enthalten kann. Unter meinem Win2000 lief alles auch ohne VirtualProtect().<br>

                    Gruß Hage

                    Comment


                    • #11
                      Der erzeugte Code im FDispatcher[] sieht dann so aus

                      <pre>

                      <code><font size=2 face="Courier New"><b>function </b>FDispatcher: ...; <b>stdcall</b>;
                      <b>asm
                      </b><font color="#008080">POP EAX <i>// caller poppen
                      </i>PUSH $1234567 <i>// SelfPtr pushen
                      </i>PUSH EAX <i>// caller pushen
                      </i>JMP -$1234567 <i>// relative jump to TMyClass.DllMethod()
                      </i></font><b>end</b>;
                      </font>
                      </code></pre>

                      Gruß Hage

                      Comment


                      • #12
                        Tausendfachen Dank für deine Mühe!<br>
                        Läuft prima!<br>
                        ;o)<br&gt

                        Comment


                        • #13
                          Nochwas, beim Translieren habe ich festgestellt das deine Namensconventionen uneinheitlich ist. Man sollte für die gleiche procedure + methode + type deklaration immer auch die gleiche Namensgebung und Parameterdefinition verwenden. Dies macht den späteren Support wesentlich besser.<br>
                          Zusätzlich habe ich es mir angewöhnt die verkorkste C/C++ Syntax NICHT in Delphi zu übernehmen. Stattdessen transliere ich solche Typen/Namen in Delphi-Syntax konforme Form.<br>
                          Grunsätzlich sah dein Source durch die C Deklaration sehr unleserlich aus (was wohl bei den meisten C Sourcen der Fall ist

                          Gruß Hage

                          Comment

                          Working...
                          X