Entwicklung neues Plugin - Probleme mit C++ und Char/String und Objekten

  • Hallo,


    ich kann zwar programmieren, auch objektorientiert, allerdings ist C++ für mich bisher ein Buch mit sieben Siegeln gewesen. Ich habe mich dennoch mal daran gewagt und versuche nun ein VDR-Plugin zur EPG-Aufbesserung zu schreiben, und brauche jetzt den einen oder anderen Wink in die richtige Richtung.


    In folgender Funktion soll einfach die "Description" mit dem String "\n\n !!! TEST !!!" verkettet werden. Ein einfaches >Description + "\n\n !!! TEST !!!"< wie von anderen Sprachen bekannt scheint schon mal nicht zu funktionieren, zudem gibt es immer wieder Probleme mit dem Typ "const char *". Nachfolgender Code kompiliert zwar, führt aber zu einem Segfault des VDR.


    Code
    bool cBroadcastedEpgHandler::SetDescription(cEvent *Event, const char *Description)
    {
      isyslog("Changing Description ...");
      Event->SetDescription(strcat(const_cast<char*>(Description), "\n\n!!! TEST !!!"));
      return true;
    }


    Wie stellt man sowas richtig an?


    Bitte jetzt keinen Rat, dass ich das bei diesem Kenntnisstand aufgeben soll. Mit ein paar Hinweisen zum Grundverständnis habe ich das erfahrungsgemäß schnell auf der Reihe.


    Danke,
    CafeDelMar


    PS: Und obwohl ich mich bereits darüber informiert habe, ist mir immer noch nicht ganz klar, wann man das * (Dereferenzierung) bei den Variablen benutzen muss.

  • Moin!


    Also erst mal vorweg, damit du schnell zu einem Ziel kommst: am besten benutzt du die Klasse cString des vdr.
    Einen neuen erstellst du am einfachsten mit:

    Code
    cString desc = cString::sprintf("das übliche %s printf-Gedöns", Description);


    und da "SetDescription" einen "const char*" (das ist ein Pointer auf ein char-Type, meistens ein Array, das somit zu einem String wird), und cString da einen passenden Operator hat (schau einfach mal in tools.[hc]):

    Code
    Event->SetDescription(*desc);


    Um ein wenig auszuholen... Ich versuch das mal aus dem Stehgreif. Gleich kommt noch ein Post...


    Lars.

  • Hi,
    Ich komme von der Delphi Ecke also reiner "Hobbyverbrecher", der sich sich auch schon daran versucht hat C++ zu lernen.
    Mit wenig Erfolg ;(

    Und obwohl ich mich bereits darüber informiert habe, ist mir immer noch nicht ganz klar, wann man das * (Dereferenzierung) bei den Variablen benutzen muss.

    Hier würde ich sagen, wenn du auf den Wert des Pointers zugreifen willst.

    VDR 1 (SD) : ASRock A330 GC, 1 GB RAM, TT- FF Karte rev. 2.3, 7'' TFT, Lirc X10 - Selbstbau Gehäuse - Suse 11.3 (64) vdr-1.7.10 diverse Plugins
    VDR 2 (HD) : MSI G41M-P25, 2 GB RAM, E6700 2x3.20GHz, Gainward GT220, 2TB HD, Lirc X10, TT S2-3600 USB, TT S2-1600, - Suse 11.3 (64) NvidiaTreiber 260.19 vdr-1.7.18 - xineliboutplugin 1.0.90 cvs, xine-lib 1.1.90 , s2-liplianin DVB Treiber

  • So, nun geht's weiter...


    Einen festen String deklarieren, z.B.

    Code
    char text[255];


    Da hast du nun 255 Zeichen Platz, um deine Buchstaben loszuwerden.
    Auf einen bestimmten Platz greifst du mit dem Index-Operator zu

    Code
    text[0] = 'a';


    Das macht natürlich erst mal keinen Spaß...


    Da der vdr intern eher C statt C++ benutzt (libc statt stl), also nicht std::string, musst du einfach mal ein paar Manpages zu printf usw. lesen.
    Prinzipiell musst du selbst für das Alloziieren (und Freigeben!) des Speichers sorgen, sonst fliegt's dir um die Ohren.


    So eine Funktion wie SetDescription erwartet einfach einen Pointer auf einen String, das kann dann wie oben einfach "text" sein (der Name des Arrays ist gleichzeitig ein Pointer auf das erste Element). Das Ende des Strings ist dann einfach ein Nullbyte. Füllst du also alle deine Arrayfelder mit Werten ungleich Null, bringst du den vdr auch zum Abstürzen, da er über das Ende des Arrays hinaus liest und damit auf nicht alloziierten Speicher zugreift. In andern Sprachen (Java etc.) bekommst du eine "Index out of Range"-Exception, wenn du auf ein Element eines Arrays zugreifst, dass nicht da ist.


    Die einfachste Art, einen vorhandenen String zu kopieren, ist strdup (immer manpages lesen, wenn ich sowas erwähne).

    Code
    char *text = strdup(Description);


    Wenn du den Text nicht mehr brauchst, musst du den Speicher wieder freigeben (nix da mit Garbage Collector usw.):

    Code
    free(text)


    Das darfst du aber erst, wenn der String nicht mehr benötigt wird. Im Falle von SetDescription macht diese Funktion seine eigene Kopie des Strings und braucht den Pointer also anschließend nicht mehr.
    Übrigens ist das ein "char *", weil du den Inhalt deiner Kopie durchaus verändern darfst.


    Weil das ganze etwas umständlich ist, hat Klaus seine eigene String-Klasse geschrieben. Vor über 10 Jahren war die stl (C++ Standard Template Library) einfach noch nicht so flott. Damals hab ich auch meine eigenen Klassen geschrieben (die heute auch immer noch produktiv bei uns in der Firma benutzt werden). Immer, wenn ich was für den vdr programmiere, benutze ich die, weil's damit einfacher geht und ich nicht so leicht vergessen kann, den Speicher wieder freizugeben - das macht der Destruktor.


    Code
    cString text(Description);


    erstellt einfach eine Kopie.
    Der Konstruktor hat noch einen weiteren Parameter "TakePointer". Den setzt du auf true, wenn du selbst schon eine Kopie erstellt hast (z.B. mit strdup) und dich nicht mehr selbst um die Freigabe kümmern willst. Dann übernimmt das cString-Objekt die Herrschaft über den Pointer.

    Code
    char *text = strdup(Description);
    cString c(text, true);
    //free(text); ist nicht mehr nötig, würde doppelt ausgeführt werden, weil der Destruktor es auch macht => Crash


    Wenn du einen String aus unterschiedlichen Dingen wie Textfragmenten, Integern usw. zusammensetzen willst, ist sprintf dein Freund. Und auch dafür hat Klaus die schon erwähnte Funktion erstellt, die einem die meiste Arbeit abnimmt:

    Code
    int zahl = 123;
    const char *text = "456";
    cString ergebnis = cString::sprintf("Ein Integer mit Wert %d und ein Stückchen Text %s", zahl, text);


    So, wann braucht man nun die Dereferenzierung?
    "int i = 5;" ist ein Werttyp, da wird also Speicher (in diesem Fall auf dem Stack) reserviert, in der man einen Integer speichern kann.
    "int *j = &i;" j ist ein Zeiger auf einen Integer, Zeiger erkennt man an dem Sternchen hinter dem Typ bei der Deklaration der Variablen. Mit dem Adressoperator & bekommst du die Speicheradresse der Variablen i, und diese Adresse kannst du in dem Zeiger j speichern.
    Um nun auf den eigentlichen Wert zugreifen zu können, musst du den Zeiger dereferenzieren, und das passiert auch mit dem Sternchen, allerdings in einem anderen Kontext, weshalb das anfangs manchmal etwas verwirrend ist...

    Code
    int i = 5;
    int *j = &i; // j selbst enthält eine Adresse 
    int k = *j; // der Zeiger wird ausgewertet und das, worauf er zeigt, benutzt. k ist nun also auch 5


    Und dann gibt's natürlich noch die fiese Zeigerarithmetik...

    Code
    int zahlen[] = { 1, 2, 3, 4, 5, 0}; // der Compiler rechnet sich selbst die nötige Größe für das Array aus, deshalb kann man die in den eckigen Klammern weglassen
    int *j = zahlen; // oder ausführlich: = &zahlen[0];
    while (*j != 0) {
           j++;
           }


    Hier wird nicht etwa "zahlen[0]", also die 1 hochgezählt, sondern j, der Zeiger. "j + 1" zeigt dann auf das zweite Arrayelement usw. Würde jetzt keine Null im Array vorkommen, würde die Schleife mit einem Zugriffsfehler abbrechen...
    Aber sowas tust du dir am besten gar nicht erst an.


    Um dann die Verwirrung noch komplett zu machen (was dir aber evtl. bekannter vorkommt), gibt's in C++ natürlich auch noch Referenzen.

    Code
    void Funktion(int &i)
    {
      i = i + 5;
    }
    
    
    int j = 0;
    Funktion(j);
    // jetzt hat j den Wert 5, weil es nicht per Wert, sondern per Referenz übergeben wurde


    Und es gibt natürlich noch "const"-Referenzen, das ist insbesondere bei größeren "struct"-Gebilden usw. schneller in der Übergabe (weil nicht der ganze Struct kopiert werden muss), aber verhindert, dass die Funktion irgendwas an dem Inhalt verändern kann.

    Code
    void Funktion(const int &i)
    {
      i = i + 5; // Compiler meckert, er darf i nicht ändern
    }


    Das alles reicht jetzt natürlich überhaupt nicht, um ernsthaft in die C/C++-Programmierung einzusteigen, da musst du andere Quellen noch zu Rate ziehen.
    Ich weiß auch nicht, ob ich nun mehr Fragen aufgeworfen als beantwortet habe, aber zumindest mit meiner ersten Antwort weißt du eigentlich genug, um zumindest deinen EpgHandler programmieren zu können.
    Ich lese mir meinen Text jetzt auch nicht mehr durch, ich hoffe, ich hab keine zu großen Fehler drin... :)


    Und wichtig: Niemals aufgeben! :]


    Lars.

  • mini73:


    Wow, vielen Dank, Lars! Die Erklärungen haben tatsächlich etwas Licht ins Dunkel gebracht und werden mir sicher helfen.
    Ist aber auch wirklich eine komplizierte bzw. aufwendige Sprache. ;)


    Das Schreiben der Description hat auf jeden Fall so schon mal funktioniert. :)


    Ich bin gerade noch etwas am Rätseln/Überlegen, wie ich alles sinnvoll zusammenfüge und auch die ChannelID ausgelesen bekomme. Über die "IgnoreChannel" Funktion konnte ich immerhin schon mal den Klarnamen des jeweiligen Kanals auslesen.
    Im Prinzip will ich ChannelID, EventID, StartTime, Duration, Title, ShortText und Description von den EIT-Daten zuerst auslesen und dann später Title, ShortText und Description geändert zurückschreiben.


    CafeDelMar

  • Ich komme von der Delphi Ecke also reiner "Hobbyverbrecher", der sich sich auch schon daran versucht hat C++ zu lernen.


    Mit Delphi habe ich vor längerer Zeit auch viel gemacht, war da auch schon sehr tief in der objektorientierten Programmierung (neue GUI-Objekte zur Laufzeit, vererbte Objekte, ...) drin, aber selbst das war im Vergleich hierzu noch einfach.


    Mal sehen, wie weit ich komme ...


    CafeDelMar


  • Ausgabe: Test ist OK


    Nur mal interessehalber und zum besseren Verständnis: Kann ich nicht auch die "neue" String-Bibliothek von C++ verwenden?
    Ich bin jetzt nach den ganzen Infos darauf gestoßen und sie scheint das "const char" zu schlucken.


    CafeDelMar


    PS: Jetzt bin ich mir schon wieder mit den * nicht ganz sicher. ?(

  • Nur mal interessehalber und zum besseren Verständnis: Kann ich nicht auch die "neue" String-Bibliothek von C++ verwenden?


    Bloß nicht! Dann kommt der Mreimer und schimpft, dass er wieder eine Library mehr dazu linken muss.


    Gerald


    HP Proliant MicroServer Gen8, Xeon E3-1230, 12 GB RAM, 3xWD red 2TB im RAID 5, 2xSundtek MediaTV Home DVB-C/T, L4M TWIN-C/T, Ubuntu Server 14.04.1, Plex Media Server
    Samsung UE55H6470


  • Natürlich soolte das funktionieren. Die hatte ich mir auch mal angeschaut. Du musst aber nicht zuerst einen cString deklarieren sondern kannst gleich mit c++ strings arbeiten. Und wenn der string fertig ist gibts sogar ne funktion die einen cstring zurück liefert.
    Bei der vekettung mit "+" hat mir der compiler allerding eine warnung ausgegeben, dass diese Methode veraltet ist.


    Mfg Thomas

    VDR:
    Hardware: Thermaltake DH102, Zotac ION ITX-F-E, 2Gig Ram, TechnoTrend
    dual DVB-S2 6400, TechnoTrend Connect CT-3650,


    Software: EasyVDR 1.0

  • Hallo,


    jetzt hänge ich an einer Stelle doch wieder ...


    Code
    cString chanInfo = cString::sprintf("Changing Description 4 for Channel %s", Event->ChannelID());


    Hier möchte ich die ChannelID des Events auslesen, welche vom Typ tChannelID ist. So wie oben funktioniert es schon mal nicht, also keine automatische Umwandlung in einen String, hatte ich auch nicht wirklich erwartet.
    Es scheint aber bei tChannelID eine Funktion "ToString" zu geben, nur weiß ich nicht wie ich diese genau ansprechen muss, alle Versuche schlugen bisher fehl.
    Einmal war ich schon soweit, dass ich den Hinweis "Warnung: Format »%s« erwartet Argumenttyp »char*«, aber Argument 2 hat Typ »cString« [-Wformat]" erhalten habe, allerdings in Verbindung mit "Fehler: Objekte des nicht trivial kopierbaren Typs »class cString« können nicht über »...« übergeben werden". Der Hinweis wundert mich trotzdem, da %s doch einen String repräsentiert und kein Char?


    CafeDelMar

  • Moin!


    Genau, tChannelID ist ein struct mit einer passenden Methode.
    Die liefert ein cString, "%s" von printf kann aber nur "char*", weshalb du dann noch den cString dereferenzieren musst (der überladene *-Operator, der bei cString dann einfach ein "const char*" auf den internen Zeiger zurückgibt).

    Code
    cString chanInfo = cString::sprintf("Changing Description 4 for Channel %s", *Event->ChannelID().ToString());


    "cString" ist nicht dasselbe wie "char". An manchen Stellen kann der Compiler selbst umwandeln, aber nur, wenn er ganz genau weiß, was zu tun ist. Bei printf funktioniert das nicht.


    Immer in die vdr-Quellen gucken, ohne kann man kein Plugin programmieren... :)
    Bei mir sind die immer in einem extra Editor geöffnet, insbesondere die Header-Dateien.


    Und genau, eine Shell im vdr-Verzeichnis hab ich auch immer offen, um danach zu greppen und zu gucken, wie das Zeug benutzt wird.


    Lars.

  • Ohne es getestet zu haben würde ich es mal mit Event->ChannelID().ToString() probieren. Im VDR-Quelltext findet man mit grep hin und wieder auch einige passende Beispiele.


    Genau das hatte ich schon probiert, hätte ich wohl besser beschreiben sollen:


    Code
    epgplus.c:27:109: Fehler: Objekte des nicht trivial kopierbaren Typs »class cString« können nicht über »...« übergeben werden
    epgplus.c:27:109: Warnung: Format »%s« erwartet Argumenttyp »char*«, aber Argument 2 hat Typ »cString« [-Wformat]
  • Moin Lars,


    Die liefert ein cString, "%s" von printf kann aber nur "char*", weshalb du dann noch den cString dereferenzieren musst (der überladene *-Operator, der bei cString dann einfach ein "const char*" auf den internen Zeiger zurückgibt).


    wieder einmal vielen Dank, so funktioniert es. :)


    Die VDR-Quellen habe ich bereits parallel geöffnet, sonst ist man tatsächlich ganz verloren. Aber auf die Idee mit dem greppen bin ich noch nicht gekommen, ist sicher hilfreich.


    CafeDelMar

  • Eigentlich sieht es schon ganz gut aus, aber ich würde gerne auf eine beim Plugin-Start definierte Variable im EPG-Handler zugreifen, nur will mir das nicht gelingen ...


    Es geht um "epgDatabaseQuery":


    Und hier komme ich dann nicht weiter:

    Code
    bool cBroadcastedEpgHandler::HandleEvent(cEvent *Event)
    {
      cPluginEpgplus::epgDatabaseQuery->Sql("SELECT * FROM epg WHERE startTime = '0'");


    CafeDelMar


    PS: Ich glaube man erkennt langsam worauf ich hinaus will. ;)

  • Moin!


    Ok, "cPluginEpgplus::epgDatabaseQuery" ist ein privates Member deiner Plugin-Klasse, d.h. du kannst nur von innerhalb der Klasse drauf zugreifen, aber nicht von anderen Klassen aus.
    Dazu kommt, dass es nicht statisch ist, weshalb du immer ein Objekt brauchst, um darauf zugreifen zu können.


    Ich würde dem Konstruktor deines EPG-Handlers einen Zeiger auf dein Datenbank-Objekt mitgeben:

    Code
    bool cPluginEpgplus::Initialize(void)
    {
      cString epgDatabaseFilename = cString::sprintf("%s/epgplus.sqlite", cPlugin::ConfigDirectory("epgplus"));
      epgDatabase = new Kompex::SQLiteDatabase(epgDatabaseFilename, SQLITE_OPEN_READWRITE, 0);
      //epgDatabaseQuery = new Kompex::SQLiteStatement(epgDatabase); <= das hier in den Konstruktor des EPG-Handlers verschieben?
      new cBroadcastedEpgHandler(epgDatabase);
      return true;
    }


    Die Deklaration musst du dann entsprechend anpassen und den Zeiger dir batürlich dort merken.
    Außerdem würde ich dann im EPG-Handler das SQLiteStatement instantiieren, nicht im Plugin, da du es dort nicht brauchst (oder?).


    Dann hast du innerhalb deines EPG-Handlers ein Objekt für deinen SQL-Zugriff "ganz für dich alleine". :)


    Evtl. wäre es sogar sinnvoll, alle Datenbank-Objekte in den EPG-Handler zu verlagern. Oder musst du noch an anderen Stellen irgendwas damit machen?
    Das hängt vom restlichen Code ab. Prinzipiell sollten Objekte dort sein, wo sie gebraucht werden.
    Dein EPG-Handler-Objekt wird die ganze Laufzeit des vdr's verfügbar sein, es wird ja einmal am Anfang von dir erstellt und dann bei Programmende vom vdr gelöscht.
    Deshalb dann im Destruktor des Handlers die Datenbank-Objekte wieder löschen (wenn du sie denn da erstellst). Wenn sie in der Plugin-Klasse bleiben, dann natürlich im Destruktor von cPluginEpgplus.


    Aber ohne jetzt den ganzen Code zu sehen, wird es langsam schwieriger, die richtigen Tipps zu geben.
    Hol dir doch einen github.com-Account und hoste den Code da. Ist ja egal, ob's schon läuft oder "häßlich" ist usw..


    Lars.

  • Moin,


    das mit "private" hatte ich dann später auch bemerkt, allerdings müsste dann doch eine entsprechende Meldung vom Compiler kommen?


    Ich hatte da auch noch einen Fehler drin:
    epgDatabaseQuery muss eh in cBroadcastedEpgHandler::HandleEvent und epgDatabase soll global erreichbar sein; aber das hast Du ja schon gesehen.
    Ich wollte die Datenbank einmal beim Start des Plugins öffnen und erst beim Beenden des VDR wieder schließen. Die Datenbank soll später auch für den zweiten Teil des Plugins, der nicht vorhandene Epg-Events hinzufügen kann, verwendet werden. Ich bin mir nicht sicher, ob ich die Datenbank dann zweimal öffnen kann, eine Konkurrenzsituation entsteht auf jeden Fall nicht bei den Zugriffen. Der zweite Teil ist eh noch ein Rätsel für mich, das muss ja irgendwie eine Hintergrundaktivität werden, die je Kanal das Datum des letzten Events ermittelt und dann alle in der Datenbank verfügbaren Events mit Table-ID 0xFF (damit sie in jedem Fall vom VDR überschrieben werden) hinzufügt.


    CafeDelMar


    PS: GIT kenne ich bisher nur aus Anwendersicht, ich werde es mir mal anschauen, ist sicher praktisch.

  • CafeDelMar:

    Zitat

    Mit Delphi habe ich vor längerer Zeit auch viel gemacht, war da auch schon sehr tief in der objektorientierten Programmierung (neue GUI-Objekte zur Laufzeit, vererbte Objekte, ...) drin, aber selbst das war im Vergleich hierzu noch einfach.

    Sowas mache ich in Delphi auch - mit spitzer Zunge würde man sagen Delphi ist besser zu verstehen und das Ergebnis ist das Selbe.
    Mit den Anleitungen von den C++ Profis hier fällt es auch mir leichter dies zu verstehen - Ich habe heute etliche C++ Einsteiger Websites angesehen. *Kopfrauch*


    Respekt wie weit du schon gekommen bist :tup


    Ich will deinen Thread nicht Off Topic machen, evtl. mach ich einen C++ Anfänger Thread auf.

    VDR 1 (SD) : ASRock A330 GC, 1 GB RAM, TT- FF Karte rev. 2.3, 7'' TFT, Lirc X10 - Selbstbau Gehäuse - Suse 11.3 (64) vdr-1.7.10 diverse Plugins
    VDR 2 (HD) : MSI G41M-P25, 2 GB RAM, E6700 2x3.20GHz, Gainward GT220, 2TB HD, Lirc X10, TT S2-3600 USB, TT S2-1600, - Suse 11.3 (64) NvidiaTreiber 260.19 vdr-1.7.18 - xineliboutplugin 1.0.90 cvs, xine-lib 1.1.90 , s2-liplianin DVB Treiber

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!