Problem mit Get-Methode



  • IN der Membermethode prüfen ob das Objekt bereits erzeugt wurde geht schlecht.
    Probleme sollten eh nur auftreten können im Zusammenhang mit Zeigern. Objekte im automatischen Speicher sind garantiert erzeugt, wenn du drauf zugreifen willst.
    Zeiger solltest du eh brav mit NULL initialisieren und vor einem Zugriff auch auf NULL prüfen.



  • da hab ich auch was falsches geschrieben, hab die Objekte über den Konstruktor der Formklasse initialisiert mit NULL. Also gibt es die Objekte schon. Bei den Klassen handelt es sich um Klassen, die eine Verbindung zu Messgeräten herstellen und die Messwerte in deren Membervariablen speichern. Da mein Programm diese Daten ständig ausliest kann es vorkommen, das das Messgerät noch gar nicht verbunden ist. Damit es keinen Speicherfehler gibt prüfe ich ob das Objekt erzeugt wurde und ob die Verbindung aktiv ist:

    c_Edu32fp* TF_Gaszaehler::GetEdu32fp() //in der Form des Messgeräts
    {
       if (o_Edu32fp!=NULL) return o_Edu32fp;
       else return 0;
    }
    

    //Klasse des Messgeräts:

    double c_Edu32fp::ErfasseVolumen()
    {
    	if (this && m_connect)
       {
       	char rec[50] = {0};
       	unsigned char send[] = {0x16};
       	while(m_port.ReceiveData(rec,1)>0); //Buffer leeren
       	m_port.SendData(send,1);
       	m_port.ReceiveData(rec,50);
       	return StrToFloat(AnsiString(rec).SubString(4,9).Trim());
       }
       else return 0;
    }
    

    Prüfe ich mit this gibt es keinen Speicherfehler mehr wenn das Programm Daten sammelt obwohl das Gerät noch nicht verbunden wurde.
    Is alles etwas komplex.



  • Was du da machst ist humbug. Du prüfst ob o_Edu32fp != NULL (was in C++ auch einfach nur 0 entspricht) und gibst 0 zurück wenn es 0 ist. Dann kannst du gleich 0 zurück geben.

    rudpower schrieb:

    c_Edu32fp* TF_Gaszaehler::GetEdu32fp() //in der Form des Messgeräts
    {
    //   if (o_Edu32fp!=NULL) return o_Edu32fp;
    //   else return 0;
         return o_Edu32fp;
    }
    

    Dein fehler liegt einfach bei der verwendung von TF_Gaszaehler::GetEdu32fp().

    double volumen = 0 ;
    if( gas_zaehler.GetEdu32fp() ){ // Wenn nicht 0
       volumen = gas_zaehler.GetEdu32fp()->ErfasseVolumen();
    }
    


  • ja mit dem return 0 das ist quatsch 🙂

    Das Programm ist umfangreicher, ich versuchs mal kurz zu zeigen:
    Habe eine Klasse c_Messwerte:

    class c_Messwerte
    {
       private:
          double m_volumen;
       public:
          double GetVolumen(void)const;
          void SetMesswerte(double p_vol);
    };
    c_Messwerte::c_Messwerte() : m_volumen(0.0) {}
    void c_Messwerte::SetMesswerte(double p_vol) {m_volumen = p_vol;}
    double c_Messwerte::GetVolumen(void)const {return m_volumen;}
    

    dann hab ich eine Klasse für das Messgerät, hier c_Edu32fp:

    class c_Edu32fp
    {
    private:
       bool m_connect;
       comclass m_port;
    public:
       c_Edu32fp(AnsiString p_port);
       ~c_Edu32fp();
       bool GetConnect();
       double ErfasseVolumen();
    };
    c_Edu32fp::c_Edu32fp(AnsiString p_port) : m_connect(0)
    {
       m_connect = m_port.OpenComm(p_port.c_str());
       if(m_connect)
       {
          m_port.SetDCB(9600);
          m_port.SetReadTimeouts(100, 10, 10);
       }
       else ShowMessage("Ritteruhr EDU32FP nicht verbunden!");
    }
    //---------------------------------------------------------------------------
    c_Edu32fp::~c_Edu32fp() {if(m_connect) m_port.CloseComm();}
    //---------------------------------------------------------------------------
    double c_Edu32fp::ErfasseVolumen()
    {
    	if (this && m_connect)
       {
       	char rec[50] = {0};
       	unsigned char send[] = {0x16};
       	while(m_port.ReceiveData(rec,1)>0); //Buffer leeren
       	m_port.SendData(send,1);
       	m_port.ReceiveData(rec,50);
    		return StrToFloat(AnsiString(rec).SubString(4,9).Trim());
       }
       else return 0;
    }
    bool c_Edu32fp::GetConnect() {return m_connect;}
    

    Nun gibt es die Form für das Messgerät wo auch das Objekt erzeugt wird:

    class TF_Gaszaehler : public TForm
    {
    ...
    private:
       c_Edu32fp* o_Edu32fp;
    public:		
       ...
       c_Edu32fp* GetEdu32fp();
    };
    void __fastcall TF_Gaszaehler::Gaszaehler_B_PortOeffnenClick(TObject *Sender)
    {
       o_Edu32fp = new c_Edu32fp(Gaszaehler_CB_Schnittstelle->Text); 
    }
    //---------------------------------------------------------------------------
    c_Edu32fp* TF_Gaszaehler::GetEdu32fp()
    {
       if (o_Edu32fp!=NULL) return o_Edu32fp;
    }
    

    diese GetMethode brauch ich um in einer Threadklasse darauf zuzugreifen:

    class c_ThreadMesswerte : public TThread
    {
       private:
          LONGLONG frequenz, alt, neu;
       protected:
    	void __fastcall Execute();
       public:
          __fastcall c_ThreadMesswerte(bool CreateSuspended);
          void __fastcall VCLAccess();
    };
    
    __fastcall c_ThreadMesswerte::c_ThreadMesswerte(bool CreateSuspended)
    	: TThread(CreateSuspended)
    {
        QueryPerformanceFrequency((LARGE_INTEGER*)&frequenz);
        Priority = tpHighest;
    }
    //---------------------------------------------------------------------------
    void __fastcall c_ThreadMesswerte::Execute()
    {
       QueryPerformanceCounter((LARGE_INTEGER*)&alt);
       while(!Terminated)
       {
          QueryPerformanceCounter((LARGE_INTEGER*)&neu);
          if ((neu-alt)/(double)frequenz >= 0.1) Synchronize(VCLAccess);
          Sleep(1);
       }
    }
    //---------------------------------------------------------------------------
    void __fastcall c_ThreadMesswerte::VCLAccess()
    {
       F_HO->HO_E_AnzeigeGaszaehlerVolumen->Text = FloatToStr(F_Gaszaehler->GetEdu32fp()->ErfasseVolumen()); // Anzeige des Messwerts auf der Hauptoberfläche (HO)
    }
    

    Zusätzlich wird in der Formklasse der Hauptoberfläche alles in einem Vektor gespeichert. Hier wird auch der Thread gestartet oder pausiert.

    class TF_HO : public TForm
    {
    private:
       std::vector<c_Messwerte>v_Messwerte;
       c_ThreadMesswerte* o_ThreadMesswerte;
       c_Messwerte o_Messwerte;
    public:
       void ErfasseMessdaten(int AnzahlMesswerte);
    };
    void TF_HO::ErfasseMessdaten(int AnzahlMesswerte)
    {
       o_Messwerte.SetMesswerte(F_Gaszaehler->GetEdu32fp()->ErfasseVolumen());
       v_Messwerte.reserve(AnzahlMesswerte);
       v_Messwerte.push_back(o_Messwerte);
    }
    

    Natürlich sind es noch viel mehr Messgeräte und Messwerte. Habe der Einfachheit halber mal alles rausgenommen und auch die Methoden der Forms usw rausgenommen.
    Die Daten werden auch noch gespeichert, angezeigt usw.
    Deshalb muss ich aufpassen, da eine Messung (in dem Fall starten des Threads und gleichzeitiges Füllen des Vektors) auch möglich sein soll wenn nicht alle Geräte verbunden sind. Es gibt einen Start Button, der den Thread startet (Resume) und gleichzeitig den Vektor füllt (zB 100 mal alle 1 Sek.).

    Funktionieren tut es so, ausser dass es Probleme gibt bei der Typumwandlung AnsiString in double.



  • was mich grad etwas wundert ist, dass m_connect beim Start nicht false ist obwohl ich es mit false initialisiere im Konstruktor. Das habe ich gerade mit dem Debugger erfahren.



  • Bei sowas

    o_Messwerte.SetMesswerte(F_Gaszaehler->GetEdu32fp()->ErfasseVolumen());
    

    Wunderst du dich über SegFaults?
    Wenn F_Gaszaehler->GetEdu32fp() NULL zurück liefert, passiert das ja.
    Du solltest da den Rückgabewert erst auf NULL prüfen (am besten speicherst du den) bevor du Funktionen darauf aufrufst.

    Wo hast du dich mit dem Debugger reingehängt, um das m_connect zu überprüfen?
    Im übrigen ist m_connect ein bool und kein int, den darf man dann direkt mit "false" statt "0" initialisieren. Find ich persönlich einfacher, da ich gleich sehe "Ah, bool".



  • Du kannst das z.B. so machen:

    o_Messwerte.SetMesswerte( 
          F_Gaszaehler->GetEdu32fp() 
        ? F_Gaszaehler->GetEdu32fp()->ErfasseVolumen()  // Wenn der zurück gegebene Zeiger nicht 0 ist, setze den Wert von ErfasseVolumen()
        : 0 // Wenn der Zeiger 0 ist dann setz 0 
    );
    

    Oder so, was das gleiche ist nur mit if else anstatt dem ternären operator

    if( F_Gaszaehler->GetEdu32fp() ) { 
       o_Messwerte.SetMesswerte( F_Gaszaehler->GetEdu32fp()->ErfasseVolumen() ); // Wenn der zurück gegebene Zeiger nicht 0 ist, setze den Wert von ErfasseVolumen()
    } else {
       o_Messwerte.SetMesswerte( 0 ); // Wenn der Zeiger 0 ist dann setz 0 
    }
    


  • ja ok, das mit dem bool leuchtet ein. Hab ich gleich mal geändert. Hab mich bei if (m_connect) mit dem Debugger reingehängt. Dann zeigt er an, dass m_connect==true obwohl kein Gerät verbunden ist.



  • rudpower schrieb:

    Dann zeigt er an, dass m_connect==true obwohl kein Gerät verbunden ist.

    Schau eine Zeile höher:

    m_connect = m_port.OpenComm(p_port.c_str());
    

    Da weist du dem m_connect einen neuen Wert zu. Wenn eine Zeile darunter m_connect nicht mehr false ist heißt das m_port.OpenComm() hat true zurück gegeben.
    Wenn also keine Verbindung besteht heißt das wiederum dass OpenComm Mist baut.
    Außer

    {
          m_port.SetDCB(9600);
          m_port.SetReadTimeouts(100, 10, 10);
       }
    

    machen m_port irgendwie ungültig. Da aber hier über comclass nichts bekannt ist kann man auch nichts darüber sagen.



  • hier ist die comclass:

    // Öffnet den Port und gibt ein Handle zurück
    bool comclass::OpenComm(char* port){
            hCom = CreateFile(port,// z.B. "COM1",
            GENERIC_READ|GENERIC_WRITE,//zum Senden und Empfangen
            0, // Für comm devices exclusive-access notwendig
            0, // Keine security attributes
            OPEN_EXISTING, // Für comm devices notwendig
            0, // Kein overlapped I/O
            0); // Für comm devices muss hTemplate NULL sein
    
            if(hCom == INVALID_HANDLE_VALUE) return false;
               else return true;
    }
    
    // setzen der Einstellungen
    void comclass::SetDCB(int baud){
            DCB dcb; // Device Control Block
            GetCommState(hCom, &dcb); // DCB lesen
    
            dcb.BaudRate = baud; // Baudrate z.B.:9600
            dcb.ByteSize = 8; // 8 Datenbits
            dcb.Parity = NOPARITY; // keine Parity
            dcb.StopBits = ONESTOPBIT; // 1 Stopbit
            dcb.fDtrControl = 1;
            // SetCommState konfiguriert die serielle Schnittstelle
            SetCommState(hCom, &dcb);
    }
    
    // setzen der Timeout-Zeiten
    void comclass::SetReadTimeouts(int intervalTO, int readSingleTO, int writeSingleTO){
            COMMTIMEOUTS t;
            // Alle Wert in Millisekunden
            // Werte für ReadFile:
            t.ReadIntervalTimeout=intervalTO; // Zeit zwischen zwei Zeichen
            t.ReadTotalTimeoutMultiplier=readSingleTO; // pro Zeichen
            t.ReadTotalTimeoutConstant=readSingleTO;
            // Werte für WriteFile: wenn beide 0 sind, kein Timeout
            // beim Schreiben
            t.WriteTotalTimeoutMultiplier=writeSingleTO;
            t.WriteTotalTimeoutConstant=writeSingleTO;
            SetCommTimeouts(hCom,&t);
    }
    
    // Daten senden
    int comclass::SendData(char Data[],int n){
            DWORD NumberOfBytesWritten; //Anzahl der gesendeten Bytes
            WriteFile(hCom, // handle des Com-Ports
            Data, // Adresse der Daten
            n, // Anzahl der zu sendenden Bytes
            &NumberOfBytesWritten, // Adresse übergeben
            0); // kein overlapped I/O
    
            return NumberOfBytesWritten;
    }
    
    // Daten empfangen
    DWORD comclass::ReceiveData(char* Data,int n){
            DWORD NumberOfBytesRead; // Anzahl der gelesenen Bytes
            ReadFile(hCom, // handle des Com-Ports
            Data, // Adresse des Datenpuffers
            n, // Anzahl der zu lesenden Bytes
            &NumberOfBytesRead, // Adresse übergeben
            0); // kein overlapped I/O
    
            return NumberOfBytesRead;
    }
    
    // schließen der seriellen Schnittstelle
    void comclass::CloseComm(void){
         try {
         if(hCom) CloseHandle(hCom);
         } catch(...) {
         ;
         }
    }
    


  • double c_Edu32fp::ErfasseVolumen()
    {
    	if (m_connect) //hier hatte ich den Haltepunkt gesetzt
            ....
    }
    

    Hier dürfte er ja gar nicht reinlaufen, da ich doch m_connect mit false im Konstruktor initialisiert habe.



  • Da er dort in die Schleife läuft gibt es denk ich mal meinen EConvertError "" ist kein gültiger Gleitkommawert.
    zeigt er in dieser Zeile an:
    "F_HO->HO_E_AnzeigeGaszaehlerVolumen->Text = FloatToStr(F_Gaszaehler->GetEdu32fp()->ErfasseVolumen());"



  • ich glaube, das Problem ist, dass er den Konstruktor gar nicht aufruft, da dieser ja erst mit dem Klick auf den Button Gaszaehler_B_PortOeffnen aufruft. Normalerweise würde ich ja das Objekt im Konstruktor der Form F_Gaszaehler erzeugen. Da aber der Konstruktor der Klasse c_Edu32fp einen Parameter erwartet (den Namen der Schnittstelle) kann ich das nicht machen. Wie kann ich das Problem beheben?



  • habe das Problem insoweit gelöst, dass ich nun in der Geräteklasse eine neue Methode erstellt hab, die den COM-Port öffnet und als Parameter diesen entgegennimmt. Den Konstruktor konnte ich so mit dem Konstruktor der Form aufrufen. So existiert das Objekt immer während der Laufzeit. Jetzt kann ich das Programm auch laufen lassen wenn das Gerät ausgeschaltet ist bzw man nicht den Button Port öffnen klickt, der ja die neue Methode öffnet. Verbindet man aber das Gerät (also ruft die Get Methode auf:

    void c_Messgeraet::PortOeffnen(AnsiString p_port)
    {
    	m_connect = m_port.OpenComm(p_port.c_str());
       if(m_connect)
       {
          m_port.SetDCB(9600);
          m_port.SetReadTimeouts(100, 10, 10);
       }
       else ShowMessage("Messgerät nicht verbunden!");
    }
    

    und schaltet dann das Gerät ab hab ich wieder eine Exception. Dann funktioniert die Umwandlung String in double nicht mehr. Er versucht dann "" in double zu wandeln (also einen Leerstring). Das ist nicht gut. Das gleiche passiert beim einschalten, da das Gerät dann als String die Typbezeichnung sendet und die natürlich nicht in double gewandelt werden kann.



  • nun funktioniert das soweit ganz gut. Hier meine Methode zur Erfassung:

    double c_Messgeraet::ErfasseVolumen()
    {
    	if (m_connect)
        {
    		char rec[50] = {0};
    		unsigned char send[] = {0x16};
    		while(m_port.ReceiveData(rec,1)>0); // Buffer leeren
    		m_port.SendData(send,1);
          m_port.ReceiveData(rec,50);
          try {return StrToFloat(AnsiString(rec).SubString(4,9).Trim());}
          catch(...)
          {
             m_connect = false;
             return 0;
          }
       }
       else return 0;
    }
    

    wenn das Gerät nicht verbunden ist, steht in dem Feld rec alles /0. Durch die Umwandlung in AnsiString und Trim bleibt dann nur ein Leerstring "".Da dies nicht in ein Float umgewandelt werden kann fange ich dies mit try/catch ab. Das funktioniert.
    Beim Aufruf der Methode in meinem Thread wird dann im Editfeld entweder der Messwert ausgegeben oder der AnsiString "getrennt":

    if (F_Messgeraet->GetMessgeraet()->GetConnect())
       F_HO->E_AnzeigeVolumen->Text = FloatToStr(F_Messgeraet->GetMessgeraet()->ErfasseVolumen());
    else F_HO->E_AnzeigeVolumen->Text = "getrennt";
    

    Problem gibt es in der PortOeffnen-Methode:

    void c_Messgeraet::PortOeffnen(AnsiString p_port)
    {
    	m_connect = m_port.OpenComm(p_port.c_str());
       if(m_connect)
       {
          m_port.SetDCB(9600);
          m_port.SetReadTimeouts(100,10,10);
       }
       else
       {
          m_port.CloseComm();
          ShowMessage("Messgerät nicht verbunden!");
       }
    }
    

    Gebe ich jetzt nachdem verbunden wurde und die Messwerte auch angezeigt werden hier

    void __fastcall TF_Messgeraet::B_OpenPortClick(TObject *Sender)
    {
       o_Messgeraet->SetConnect(false);
    	o_Messgeraet->PortOeffnen(E_SerialPort->Text); //Argument aus Editfeld
       Close();
    }
    

    einen anderen Port an zeigt er wie gewünscht "getrennt" an. Gebe ich nun wieder den richtigen Port an läuft er immer wieder in die else Verzweigung der PortOeffnen(AnsiString p_port)-Methode. Er setzt m_connect alo nicht auf true und verbindet nicht neu.
    Ich hab dann versucht einfach m_port.CloseComm(); vorher aufzurufen. Das gibt dann gleich eine "externe Exception C0000008".


Anmelden zum Antworten