subs aus main im eigenen Package bekannt geben

Begonnen von Damian, 24 September 2018, 07:54:22

Vorheriges Thema - Nächstes Thema

Damian

Hallo,

ich habe im Modul ein eigenes package erstellt, um Anwender-Funktionen (mit eval erzeugt) zu kapseln.

Wie kann ich nun in diesem package die subs aus dem main-package bekannt geben?

Hintergrund: der Anwender soll weiterhin die Standard-Funktionen, wie z. B. ReadingsVal nutzen können, ohne jedes mal :: ReadingsVal angeben zu müssen.

Meine Versuche mit use sind bisher gescheitert.

Damian
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

hexenmeister

Moin!

Ich benutze dafür die GPUtils (https://svn.fhem.de/trac/browser/trunk/fhem/FHEM/GPUtils.pm).
Beispiel findest Du z.B. in meiner MQTT_GENERIC_BRIDGE (https://svn.fhem.de/trac/browser/trunk/fhem/FHEM/10_MQTT_GENERIC_BRIDGE.pm)

grobes Schema:
...

package XYZ;

use GPUtils qw(:all);
...
BEGIN {
GP_Import(qw(
    AttrVal
    ReadingsVal
    ...
  ))
};
...

Maintainer: MQTT_GENERIC_BRIDGE, SYSMON, SMARTMON, systemd_watchdog, MQTT, MQTT_DEVICE, MQTT_BRIDGE
Contrib: dev_proxy

CoolTux

#2
Sollten wir mal anfangen im Wiki fest zu halten. Es sollte so oder so mit packages gearbeitet werden, bei jedem Modul. Ist einfach sauberer und woeit mir bekannt auch von vielen Kerndevelopern gewünscht.
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

Sidey

Hi cooltux,

Finde deine Idee gut .
Kannst Du grob beschreiben, was nötig ist um ein Modul zu ändern?

Wenn ich es richtig verstehe, dann schreibe ich package oben rein (Modulname) und importiere dann die benötigten FHEM Funktionen.

Wie könnte der Übergang aussehen, wenn ein anderes Modul eine meiner Funktionen aufruft?
Das müsste die Funktion ja dann via package::Name aufrufen?

Eventuell lässt sich das halt nicht zeitgleich umstellen.

Grüße Sidesy
Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker

CoolTux

#4
Zitat von: Sidey am 24 September 2018, 09:08:17
Hi cooltux,

Finde deine Idee gut .
Kannst Du grob beschreiben, was nötig ist um ein Modul zu ändern?

Wenn ich es richtig verstehe, dann schreibe ich package oben rein (Modulname) und importiere dann die benötigten FHEM Funktionen.

Wie könnte der Übergang aussehen, wenn ein anderes Modul eine meiner Funktionen aufruft?
Das müsste die Funktion ja dann via package::Name aufrufen?

Eventuell lässt sich das halt nicht zeitgleich umstellen.

Grüße Sidesy

Hallo,

Eigentlich sieht es genau so aus wie Du es beschrieben hast. Ich habe damit in meinem neusten Modul angefangen und werde einige ältere wo ich noch aktiv dran arbeite auch umbauen.


## oberer Teil
package main;

use strict;
use warnings;

...


sub ModulName_Initialize($) {
    my ($hash) = @_;

## Da ich mit package arbeite müssen in die Initialize für die jeweiligen hash Fn Funktionen der Funktionsname
#  und davor mit :: getrennt der eigentliche package Name des Modules
    $hash->{SetFn}      = "PackageName::Set";
    $hash->{GetFn}      = "PackageName::Get";
    $hash->{DefFn}      = "PackageName::Define";
    $hash->{NotifyFn}   = "PackageName::Notify";
    $hash->{UndefFn}    = "PackageName::Undef";
    $hash->{AttrFn}     = "PackageName::Attr";
    $hash->{AttrList}   = "disable:0,1 ".
                            "disabledForIntervals ".
                            $readingFnAttributes;
}

## unserer packagename der Funktion
package PackageName;


use strict;
use warnings;
use POSIX;

use GPUtils qw(:all);  # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt

my $missingModul = "";


## Import der FHEM Funktionen
BEGIN {

    GP_Import(qw(
        readingsSingleUpdate
        readingsBulkUpdate
        readingsBulkUpdateIfChanged
        readingsBeginUpdate
        readingsEndUpdate
        Log3
        AttrVal
        ReadingsVal
        Value
        IsDisabled
        deviceEvents
        init_done
    ))
};


sub Define($$) {

    my ( $hash, $def ) = @_;
my @a = split( "[ \t][ \t]*", $def );

...
...
...

Bei dem GP_Import musst man natürlich schauen was man da braucht. Da müssen alle Funktionen aus fhem main rein welche man braucht. Manchmal bestehen auch Abhängigkeiten zum Beispiel bei Attr. Aber das findet man schnell raus, am Anfang wird Dir FHEM oft um die Ohren knallen  ;D

Sinn und Zweck ist eine Kapselung ähnlich wie bei Java oder C++ die Klassen.
Der Aufruf einer Deiner Funktion innerhalb des Packages aus einer externen Anwendung heraus erfolgt dann über PACKAGENAME::FUNKTIONSNAME. Im übrigen wer zum Beispiel mit der InternalTimer Funktion arbeitet und über diese eine seiner Funktionen nach Ablauf aufrufen lassen möchte muss dies genau so machen.

InternalTimer($timestamp, $packageName::functionName, $arg);



Grüße
Leon
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

Damian

Ich habe jetzt mal folgendes bei mir eingebaut:


...
package DOIF;

no strict qw/refs/;

BEGIN {
  foreach(qw(
    CommandAttr
    readingsSingleUpdate
    readingsBeginUpdate
    readingsBulkUpdate
    readingsEndUpdate
    Log3
    DoSet
    fhem
    defs
    AttrVal
    ReadingsVal
    ReadingsTimestamp
    ReadingsAge
    deviceEvents
    AssignIoPort
    addToDevAttrList
    delFromDevAttrList
    devspec2array
    gettimeofday
    InternalTimer
    RemoveInternalTimer
    )) {
      *{'DOIF::'.$_} = *{'main::'.$_};
    }
};
...


Finde es aber recht umständlich. Vor allem, wenn man alle Methoden aus main nutzen möchte.
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Benni

#6
Ich finde die Idee der Namespace-Kapselung auch sehr gut und hänge mich mitlesenderweise mal hier ran.  ;)

Edit: Zur Übung habe ich jetzt mal damit angefangen, meine persönlichen 99er-Module auf Packages umzustellen. Wird ne ganze Menge Arbeit. Kann aber jetzt schon feststellen, dass das hinterher für mehr Übersicht sorgt, da dann ganz klar ersichtlich ist, welche Funktion in welchem Package und damit in welcher Moduldatei zu finden ist. Somit lässt sich letztendlich auch ersehen, welches Modul ggf. von anderen Modulen abhängig ist.

dev0

ZitatSinn und Zweck ist eine Kapselung ähnlich wie bei Java oder C++ die Klassen.
Mir ist der Mehrwert von Namespaces für FHEM Module nicht klar. Das mag daran liegen, dass ich kein Programmierer bin und mir Perl 'nur so' angeeignet habe...
Kapselung hört sich interessant an, aber auch nach dem Lesen von perldoc/package, sehe ich keine Vorteile für (meine?) FHEM Module.

Mag mich jemand aufklären oder auf einen Link verweisen?

Damian

Zitat von: dev0 am 24 September 2018, 12:16:34
Mir ist der Mehrwert von Namespaces für FHEM Module nicht klar. Das mag daran liegen, dass ich kein Programmierer bin und mir Perl 'nur so' angeeignet habe...
Kapselung hört sich interessant an, aber auch nach dem Lesen von perldoc/package, sehe ich keine Vorteile für (meine?) FHEM Module.

Mag mich jemand aufklären oder auf einen Link verweisen?

In meinem Modul dürfen Anwender eigene Funktionen definieren - entspricht im wesentlichen dem Konzept von myUtils. Mit der Kapselung kann sich der Anwender jetzt eine Funktion on oder fhem oder ReadingsVal oder sonst was definieren, ohne Gefahr zu laufen, eine wichtige Funktion irgendwo im ganzen FHEM umzudefinieren.

Der bisherige Weg war, möglichst eindeutige Namen (z. B. mit Modulbezeichnung im Namen zu definieren), das kann der Modulentwickler noch hinbekommen, der FHEM-Anwender dagegen ist sich oft der Problematik aber nicht bewusst.
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

rudolfkoenig

Die Absicht: mit einem Package soll nicht versehentlich passieren, dass Du in deinem Modul eine Routine mit dem gleichen Namen anlegst, wie sie schon anderswo existiert, und dann je nach Ladereihenfolge die eine oder die andere Implementation aufgerufen wird, weil die zuletzt geladene die Erste ueberschreibt.Z.Zt. werden diese Probleme meist mit dem Voranstellen des Modulnamens geloest.

"Leider" kann man bei den Packages nach "use PackageName" die exportierten(!) Funktionen ohne Prefix ansprechen, was dann je nach geladenes Modul und exportierten Funktionen zu den gleichen Problemen fuehrt, das hatten wir unlaengst mit der "stat" Funktion erfahren.

CoolTux

Ja es ist am Anfang etwas umständlich, weil man immer darauf achten muss was man aus der main wirklich braucht. Aber dafür rechnet sich der Mehrwert. Du musst Dir keine Sorgen mehr machen ob es den Namen der Funktion schon in FHEM, also auch in anderen Modulen, gibt. Du kannst innerhalb Deines Modules FHEM Funktionen überschreiben. Du kannst also auch eine ReadingsVal erstellen wenn Dir danach ist. Darfst die original FHEM ReadingsVal dann aber nicht importieren. Kannst aber die Funktionen verknüpfen in dem Du due FHEM Funktion mittels main:: auf rufst.
Jeder Ansatz von OO Programming arbeitet auf Basis dieses Denkens.
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

Damian

#11
Ok.

Ich denke das Importieren (Überschreiben der Methoden mit *{'DOIF::'.$_} = *{'main::'.$_}) hat sich dann für meinen Zweck erledigt. Denn dann würde
eine Definition vom Anwender z. B. sub {fhem{}} selbst im gekapselten Package alles (auch in main) zunichte machen - das wollte ich eigentlich verhindern.

Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

hexenmeister

Zitat von: Damian am 24 September 2018, 15:12:13
Ok.

Ich denke das Importieren (Überschreiben der Methoden mit *{'DOIF::'.$_} = *{'main::'.$_}) hat sich dann für meinen Zweck erledigt. Denn dann würde
eine Definition vom Anwender z. B. sub {fhem{}} selbst im gekapselten Package alles (auch in main) zunichte machen - das wollte ich eigentlich verhindern.
Ich meine nicht. Die subs auf 'main' werden in 'DOIF' verlinkt, nicht andersrum.
Maintainer: MQTT_GENERIC_BRIDGE, SYSMON, SMARTMON, systemd_watchdog, MQTT, MQTT_DEVICE, MQTT_BRIDGE
Contrib: dev_proxy

Sidey

Interessant wird es dann, wenn jemand aus dem DoIf eine Funktion eines Moduls oder der myutils aufruft. Dann muss er den Namen des Packages voran stellen.


Grüße Sidey

Gesendet von meinem XT1650 mit Tapatalk

Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker

Damian

Zitat von: hexenmeister am 24 September 2018, 15:28:41
Ich meine nicht. Die subs auf 'main' werden in 'DOIF' verlinkt, nicht andersrum.
Die Vorgehensweise habe aus deinem Code entnommen:

  no strict qw/refs/; ## no critic
  my $pkg = caller(0);
  foreach (@_) {
  *{$pkg.'::'.$_} = *{'main::'.$_};

Der Aufruf findet im eigenen Package statt.
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF