Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Prozedur als Eigenschaft


FinnO - Sa 23.05.09 22:49
Titel: Prozedur als Eigenschaft
Hi Leute,

Ist es möglich, einem Objekt eine Prozedur als Eigenschaft zu verpassen? Ich meine dass in etwa so:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
type
  TFoo = class
  
    private
   
    public
      Action : Procedure;  

  end;


Ziel soll es sein, dass ich dem Foo dann hinterher sowas zuweisen kann:


Delphi-Quelltext
1:
Foo.Action := Button1Click(Sender);                    


geht das? Und wenn ja, wie?


DHE - Sa 23.05.09 23:39

Was du suchst nennt sich Methodenzeiger

Schau mal hier [http://www.delphi-forum.de/viewtopic.php?t=81909&highlight=methodenzeiger] nach

oder such nach Methodenzeigern in der Delphi-Dokumentation oder hier im Forum


ffgorcky - So 24.05.09 08:20

Mir sieht das eher nach einer Funktion aus.
Also die wäre dann z.B. so in etwa:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
function AdditionsErgebnisAlsoSumme(Addiator1,Addiator2:int):int;
begin
//also entweder dem Funktionsnamen den Wert übergeben
AdditionsErgebnisAlsoSumme:=Addiator1+Addiator2;
// oder so:
result:=Addiator1+Addiator2;
end;

Ich frage mich immer, welches sauberer ist - gehen tut doch beides -- oder?

Die beiden Parameter könntest Du Dir natürlich auch sparen, wenn Du stattdessen direkt auf Deine Objekt-Werte zugreifst.
Oder habe ich die Frage gerade total verkehrt verstanden?


FinnO - So 24.05.09 11:20

user profile iconDHE hat folgendes geschrieben Zum zitierten Posting springen:
Was du suchst nennt sich Methodenzeiger

Schau mal hier [http://www.delphi-forum.de/viewtopic.php?t=81909&highlight=methodenzeiger] nach

oder such nach Methodenzeigern in der Delphi-Dokumentation oder hier im Forum


So ich habe das jetzt so zum laufen gebracht. Was mich jetzt ein bisschen stört ist, dass ich nur Methoden aufrufen kann, die genau die Parameter haben, wie ich in meinem Zeiger da festgelegt habe. Kann ich das auch so lösen, dass beliebige Prozeduren aufgerufen werden können?


Delete - So 24.05.09 11:50

Ganz so, wie Du das willst, geht es nicht, aber Du könntest Deine Methoden überladen (Stichwort overload).


DHE - So 24.05.09 17:28

Statt Methoden zu überladen kannst du auch einfach Interfaces nutzen.

Dann übergibst du der Methode als Parameter einfach eine vom Interface abgeleitete Klasse und
diese implementiert dann einfach die Funktion.


Yogu - So 24.05.09 19:28

user profile iconFinnO hat folgendes geschrieben Zum zitierten Posting springen:
Was mich jetzt ein bisschen stört ist, dass ich nur Methoden aufrufen kann, die genau die Parameter haben, wie ich in meinem Zeiger da festgelegt habe. Kann ich das auch so lösen, dass beliebige Prozeduren aufgerufen werden können?

Wenn du eine Prozedur mit Parametern aufrufst, musst du ja auch irgendwelche Parameter übergeben. Wo legst du diese Werte fest?

Eine Möglichkeit wäre eine Assembler-Anweisung. So habe ich externe Funktionen meiner Script-Eingine gelöst:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
var ParameterCount, Result, Paramter1, Parameter2, Parameter3: Integer; Pntr: Pointer;
begin
        case ParameterCount of
          0:
          asm
            CALL Pntr
            MOV tmp1, EAX
          end;

          1
          begin
            asm
              MOV EAX, Parameter1
              CALL Pntr
              MOV Result, EAX
            end;
          end;    

          2
          begin
            asm
              MOV EAX, Parameter1
              MOV EDX, Parameter2
              CALL Pntr
              MOV Result, EAX
            end;
          end;     

          3
          begin
            asm
              MOV EAX, Parameter1
              MOV EDX, Parameter2
              MOV ECX, Parameter3
              CALL Pntr
              MOV Result, EAX
            end;
          end;

          4:
          begin
            asm
              MOV EAX, Parameter1
              MOV EDX, Parameter2
              MOV ECX, Parameter3
              PUSH Parameter4
              CALL Pntr
              MOV Result, EAX
            end;
          end;
              end;

ParameterCount gibt die Anzahl der Parameter an, Pntr ist ein Zeiger auf die Funktion und in der Variable Result ist nachher der Rückgabewert gespeichert. Allerdings musst du selbstverständlich vorher wissen, wie viele Parameter die Funktion hat, denn wie willst du sonst wissen, wie viele Werte auf den Stack geschrieben werden sollen (siehe PUSH).

Ich weiß selbst, dass meine Case-Konstruktion nicht gerade das Optimum ist, und würde mich selbst um eine Verbesserung des Codes freuen. Das soll aber nicht Thema dieses Threads sein, falls user profile iconFinnO die Variante nicht will.


FinnO - So 24.05.09 19:44

also momentan habe ich folgendes Konstrukt:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
type
  TAction = procedure of object;

  TFoo = class
    public
      Action : TAction;
  end;


Zuweisung:

Delphi-Quelltext
1:
Foo.Action := Form1.Close; // es funktionieren aber nur Prozeduren ohne Parameter                    

Aufruf:

Delphi-Quelltext
1:
Foo.Action;                    


Mein Wunsch wäre jetzt ja eine Universallösung, in der ich JEDE beliebige Prozedur so zuweisen kann und später wieder aufrufen kann. Wenn das gar nicht klappen sollte, muss ich da wohl eine andere, unbequeme Lösung finden :(

€: Vielleicht noch zum Verwendungszweck: Ich möchte recht variabel einem Objekt eine Methode zuweisen. Diese Methode soll halbwegs zur Laufzeit zugewiesen werden - ich nehme aus einem Pool an Möglichen Funktionen eben die gewünschte. Da sollten so Dinge wie Showmessage('Hallo Welt');genau so funktionieren wie eben FormX.Close;


Yogu - So 24.05.09 20:01

user profile iconFinnO hat folgendes geschrieben Zum zitierten Posting springen:
Da sollten so Dinge wie Showmessage('Hallo Welt');genau so funktionieren wie eben FormX.Close;

Ist diese 'Hallo Welt' konstant, oder soll das tatsächlich als Parameter übergeben werden? Dein Code zum Aufruf lässt keine Parameter zu:

user profile iconFinnO hat folgendes geschrieben Zum zitierten Posting springen:
Aufruf:

Delphi-Quelltext
1:
Foo.Action;                    

Oder wo soll dda bitte schön das 'Hallo Welt' hin? Erklär doch mal genau, was du machen möchtest, oder gehe auf einzelne Vorschläge von uns ein. Ich denke, davon gibt es jetzt bereits genug.


FinnO - So 24.05.09 20:23

Nein die soll nicht als Parameter übergeben werden, sonder das Hallo Welt ist ja Parameter von Showmessage. Jetzt möchte ich erreichen, dass erstens:


Delphi-Quelltext
1:
Foo.Action := Showmessage('Hallo Welt'); // oder 'Hallo Yogu' oder sonstwas                    

funktioniert und, dass ich das ganze dann mitt
Foo.Action auch wieder aufrufen kann.

aber genau so auch noch Foo.Action := Form1.Close oder Foo.Action := Button1OnClick(Sender); Halt etwas beliebiges stehen kann. Einfach eine Prozedur oder Funktion als Eigenschaft in einer Klasse.


Yogu - So 24.05.09 21:17

Ah, jetzt verstehe ich. Das ist gar nicht mal so schwer, wie du es dir vorstellst. Du musst nur ein wenig "umdenken":

Erstelle einfach eine kleine Funktion, die den Aufruf beinhaltet:


Delphi-Quelltext
1:
2:
3:
4:
procedure SayHelloToTheWorld;
begin
  ShowMessage('Hallo Welt');
end;

Und diese Funktion kannst du nun ganz einfach für Action verwenden:


Delphi-Quelltext
1:
Foo.Action := SayHelloToTheWorld;                    

Allerdings weiß ich nicht, ob du Klassenmethoden (also zum Beispiel Form1.Close) und normale Prozeduren (z.B. SayHelloToTheWorld) mischen kannst. Für den Fall würde ich einen einfachen Pointer als Typ für Action vorschlagen:


Delphi-Quelltext
1:
2:
3:
4:
5:
var Action: Pointer;
begin
  Action := @SayHelloToTheWorld;
  Action := @Form1.Close;
end;

Und diese Prozedur rufst du wie folgt auf:


Delphi-Quelltext
1:
2:
3:
asm
  CALL Action;
end;

Eigentlich ganz einfach, wenn man das Prinzip mal verstanden hat.


ffgorcky - So 24.05.09 21:23

Das würde ich dann eher so machen, dass man da einen Integer-Wert nimmt, der dann genau festlegt, welche Prozedur im Moment aufgerufen werden soll - also so wie user profile iconYogu das ja eigentlich auch schon (fast) [in dem hier 6. Posting] vorgeschlagen hat:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
case welcheProzedur of
  0:macheDieseProzedur();
  1:macheJeneProzedur();
  2:macheEineAndereProzedur();
  3:macheNochEineAndereProzedur();
  4:macheKeineAhnungMehrWas();
end;

Aber ich würde daraus wirklich voneinander unabhängige Prozeduren machen und da ganze nicht einfach nur mit begin und end in die case-Abfrage reinbauen. Das sieht ja bei Dir etwas umfassender aus!


FinnO - So 24.05.09 21:29

@Yogu
hmm okay, diesen(s?) Workaround benutze ich auch gerade, fand ihn aber irgendwie unsauber. Aber wenns nicht anders geht dann... mein Gott mach ichs eben so ;) kann als gelöst betrachtet werden.