Problem mit RichEdit StreamCallback



  • Hallo,
    Ich habe eine Problem mit der StreamCallback Funktion die über die Nachricht EM_STREAMOUT angesprochen wird.

    Vorweg:
    Ich habe aus den RichEdit Nachrichten eine Klasse gemacht anlehnend an die von MFC CRichEdit. Generell Funktioniert die StreamCallback/EM_STREAMOUT Funktion, wenn ich mit ihr in einer Nachrichtenschleife einer Anwendung arbeite.

    Jetzt bin ich in den letzten Tagen über ein Problem gestolpert, was so noch nicht aufgetreten ist. Ich muss mit meiner RichEdit Klasse in einem Thread arbeiten, der mit der CreateThread Funktion erstellt wurde.

    Wenn ich mit meiner RichEdit Klasse in einem Thread arbeite, funktioniert die _GetStreamCallback Funktion nicht mehr richtig 😕

    der Fehler passiert in der Zeile 113 im nachfolgendem Code. die Funktion fwrite gibt mir exakt die gleiche Anzahl von geschriebenen Zeichen zurück wie die _GetStreamCallback Funktion im Parameter cb vorschreibt. ABER die Datei hFile und der damit verbundene Zeichenfolgenpuffer lpBuffer, enthalten keine Werte.

    Wie gesagt, der Code funktioniert,... nur nicht wenn er in einem Thread läuft.
    Warum werden da keine Werte in die Datei bzw. in den Puffer geschrieben?
    Weis da jemand Rat? Bin über jede Hilfe dankbar...

    [EDIT]
    Ich vergas, was ich vielleicht noch erwähnen sollte, Das komische an der Sache ist das Gegenstück zu meiner _GetStreamCallback Funktion, also _SetStreamCallback funktioniert in dem Thread. Ich kann also werte in das Steuerelement schreiben, aber keine auslesen.

    long				CRichEdit::GetTextStream			(LPSTR &lpBuffer, UINT uiFormat /*= SF_TEXT*/)
    {	/**************************************************************************************************************************************
    													GET TEXT STREAM
    							liest den Text aus dem Stream des Rich Edit Steuerelementes aus
    		- in
    			- uiFormat			Spezifiziert das Datenformat des einlesens und die Ersetzungsoptionen
    		- out
    			- lpBuffer			Zeiger auf einen Zeichenfolgenpuffer in dem die Daten aus dem Steuerelement übertragen werden.
    		- return
    			-	Integer Wert der die Länge des Zeichenfolgenpuffers bestimmt. 
    	**************************************************************************************************************************************/
    	long lReturn = 0;
    
    	//--------------------------------------------------------------------------------------------------------------
    	//		Dateistream erstellen und den Zeichenfolgenpuffer mit dem Stream verbinden
    	FILE *hFile			= SetStreamBuffer(lpBuffer);
    	//--------------------------------------------------------------------------------------------------------------
    	//		EditStream Strucktur erstellen und Rückruffunktion die den Dateistream befüllt übergeben
    	m_es.dwCookie		= (DWORD)hFile;
    	m_es.pfnCallback	= _GetStreamCallback;
    	//--------------------------------------------------------------------------------------------------------------
    	//				Daten auslesen Starten
    	lReturn = StreamOut(SF_TEXT,m_es);
    	//--------------------------------------------------------------------------------------------------------------
    	//	Willkürliche Zeichen im Zeichenfolgenpuffer abschneiden und Werte vom Dateipuffer in die Datei schreiben
    	lpBuffer[lReturn]=0;
    	fflush (hFile );
    	fclose(hFile);//Datei Schliesen
    
    	//--------------------------------------------------------------------------------------------------------------
    	//	Anzahl der Zeichenfolgen im Puffer zurück geben.
    	return lReturn;
    }
    
    FILE*					CRichEdit::SetStreamBuffer			(LPSTR &lpszBuffer)
    {	/**************************************************************************************************************************************
    										SET STREAM BUFFER
    			erstellt einen Temporäre Datei für das ein o. auslesen von Daten in/aus dem Ritch Edit Steuerelement.
    
    		- in/out
    			- lpszBuffer	Zeiger auf einen Zeichenfolgenpuffer, der für das auslesen von Daten aus dem Steuerelement
    					mit dem Dateihandel verbunden wird. In diesen Puffer werden die Daten Zeitgleich wie
    					zu der Temporären Datei geschrieben.
    			- return
    				-	Zeiger auf ein Temporäres Dateihandel, in das Daten ein oder aus dem Steuerelement gelesen werden können.
    
    			- Merke:
    				-	Der Zeichenfolgenpuffer MUSS gelöscht werden, nachdem er nicht mehr gebraucht wird um Memory Leaks
    					zu vermeiden.
    	**************************************************************************************************************************************/
    
    	int iCtrlSize		= ((lpszBuffer == NULL) ? (GetTextLength()+1) : strlen(lpszBuffer));
    	const int iSize		= (iCtrlSize <= 1) ? MAX_TEXT : iCtrlSize;
    
    	BOOL bNewBuffer	= FALSE;
    
    	//--------------------------------------------------------------------------------------------------------------
    	//		Zeichenfolgenpuffer für das auslesen der Daten aus dem Steuerelement
    	if(lpszBuffer == NULL)
    	{
    		lpszBuffer		= new char [iSize];
    		memset(lpszBuffer,0,iSize);
    		bNewBuffer = TRUE;
    	}
    	//--------------------------------------------------------------------------------------------------------------
    	//					Datei Stream erstellen 
    	FILE* hFile		= NULL;
    	hFile			= tmpfile ();
    	fseek (hFile,0,SEEK_SET);	//Cursor an den Anfang der Datei setzen
    
    	//--------------------------------------------------------------------------------------------------------------
    	//									Vom Steuerelement lesen
    	//	Zeichenfolgen Puffer mit der Temporären Datei verbinden, Daten werden direkt in den Puffer geschrieben
    	if(bNewBuffer)
    		setvbuf ( hFile , lpszBuffer , _IOFBF , iSize );	//Datei an die größe anpassen
    	//--------------------------------------------------------------------------------------------------------------
    	//									Ins Steuerelement schreiben
    	//		Datei auf die größe des Zeichenfolgenpuffers setzten und Daten in die temporäre Datei schreiben
    	else
    	{
    		setvbuf ( hFile , NULL , _IOFBF , iSize );	//Datei an die größe anpassen
    		fwrite(lpszBuffer,sizeof(char),iSize,hFile);
    		fflush (hFile );
    	}
    	fseek (hFile,0,SEEK_SET);								//Cursor an den Anfang der Datei setzen
    
    	return hFile;
    }
    DWORD		CALLBACK	CRichEdit::_GetStreamCallback		(DWORD dwCookie, LPBYTE lpBuff,LONG cb, LONG *pcb)
    {	/**************************************************************************************************************************************
    													GET STREAM CALL BACK
    						liest Daten aus dem Rich Edit Steuerelement in einen Zeichenfolgenpuffer, der mit der Datei verbunden ist.
    		- in
    			- dwCookie		Zeiger auf ein Datei Handel, in das die Daten aus dem Steuerelement geschrieben werden.
    			- pbBuff		Zeiger auf einen Zeichenfolgen Puffer, der die Daten aus dem Steuerelement übergiebt.
    			- cb			Integer wert der die Anzahl an Zeichenfolgen in dem Parameter pbBuff bestimmt. 
    			- pcb			Zeiger zu einer Integer Variable, die die Anzahl der geschrieben Daten in die Datei bestimmt.
    							Ist diese Zahl kleiner o. NULL, als der Wert im Parameter cb, wird die Funktion nicht mehr
    							aufgerufen.
    		- return
    			-	Integer Wert der bestimmt, ob die Funktion erfolgreich ausgeführt worden ist. Wird eine 0 zurück gegeben
    				wurde die Funktion erfolgreich ausgeführt, ein Wert ungleich 0 Bedeutet das ein Fehler aufgetreten ist
    				und die Funktion nicht erneut aufgerufen wird.
    
    		- Merke:
    			-	Die GetStreamCallback Funktion ist eine Anwendungs-definierte Rückruffunktion, die weiterführend mit den EM_STREAMOUT
    				Mitteilungen verwendet wird.
    			-	Wird die Klasse CRichEdit in einem Thread gestartet, werden keine Werte aus dem Puffer lpBuff in die Datei(hFile)
    				geschrieben, obwohl der Rückgabewert pcb die länge der geschriebenen Bytes enthält.
    	***************************************************************************************************************************************/
    	FILE *hFile		= (FILE*)dwCookie;
    	lpBuff[cb]		= 0;//Die Zeichenfolge abschneiden um keine willkürlichen Zeichen zu bekommen
    	*pcb			= fwrite(lpBuff,sizeof(BYTE),cb,hFile);
    
    	//--------------------------------------------------------------------------------------------------------------
    	//	Alles im Puffer auf NULL setzten um keine vorherigen Werte zu bekommen.
    	memset(lpBuff,0,cb);
    
    	//--------------------------------------------------------------------------------------------------------------
    	//	Wenn die Anzahl der geschriebenen Zeichen mit der Anzahl der Zeichen im übergebene lpBuff übereinstimmt,
    	//	wird NULL zurück gegeben. Jeglichen anderen Rückgabewerte bewirken das die Funktion nicht erneut aufgerufen wird. 
    	return (*pcb - cb);
    }
    long				CRichEdit::StreamOut				(UINT uiFormat,EDITSTREAM& es )
    {	/**************************************************************************************************************************************
    														STREAM OUT
    		Die EM_STREAMOUT Mitteilung veranlast ein RichEdit Steuerungselement, seinen Inhalt zu 
    		einer Anwendungsdefinierten EditStreamCallback Funktion zu senden. Die Wiederholungsfunktion
    		kann dann den Datenstrom zu einer Datei oder zu jeder anderen möglicher Position schreiben.
    
    		- in
    			- uiFormat		Spezifiziert das Datenformat- und -Ersetzungs optionen
    			- es			Zeiger auf eine EDITSTREAM Struktur.
    	**************************************************************************************************************************************/
    	HWND hWndRichEdit = GetDlgItem();
    
    	return (long)::SendMessage(hWndRichEdit,EM_STREAMOUT,(WPARAM)uiFormat,(LPARAM)&es);
    
    }
    


  • Text from RichEdit:
    http://stackoverflow.com/questions/20700343/how-to-get-text-from-rich-edit-control-as-a-cstring

    Threads kannst erstellen, wenn RTB Inhalt nach bestimmten zeit aktualisiren willst.
    Vermutlich beim RTB auslesen blockirst du GUI Thread, du mus Thread fur schreiben stoppen, dann kannst auslesen.



  • Sorry hab mich verschrieben....mit Thread "schreiben" blockierst den GUI Thread,darum kannst nichts in RTB auslesen. Hast Möglichkeiten, schmeise Threads raus oder bende/stoppe Thread "schreiben".



  • Hallo,
    erst mal danke für deine Antwort. Ich versteh was du meinst, mit deiner Hilfestellung, aber es ist definitiv nicht so, dass der Schreibvorgang den Lesevorgang behindert oder diese sich irgendwo kreuzen.
    der Fehler passiert in folgender Funktion. Wie du dort siehst, wird GetStreamCallback (Zeile 12) vor der Funktion SetStreamCallback (Zeile 21) angesprochen. Also der Lesevorgang, kann nicht durch den Schreibvorgang beeinflusst werden. Es gibt auch keine andere Funktion in der Klasse, die auf das RichEdit Steuerelement zugreift.

    void								CEMailSendStatusDlg::SetStatusText								()
    {	/**************************************************************************************************************************************
    															SET STATUS TEXT
    							übergibt den Text aus der m_sStatus Variable an das RichEdit Steuerelement
    	**************************************************************************************************************************************/
    	//--------------------------------------------------------------------------------------------------------------
    	//						Bisherigen Text aus dem Steuerelement auslesen
    	//	Merke:
    	//		-	GetTextStream gibt hier keine Werte zurück, weil in der Funktion CRichEdit::_GetStreamCallback
    	//			die übergebenen werte nicht in die Temporäre Datei geschrieben werden. 
    	LPSTR lpszBuffer = NULL;
    	m_CR_Status.GetTextStream(lpszBuffer);
    	//--------------------------------------------------------------------------------------------------------------
    	//						Bisherigen und neuen Text zusammenführen
    	string sBuffer	= lpszBuffer;
    	sBuffer			+= (::lstrlen(lpszBuffer) > 0) ? CRLF : _T("");
    	sBuffer			+= m_sStatus;
    	_DELETE(lpszBuffer);
    	//--------------------------------------------------------------------------------------------------------------
    	//						Text in das Steuerelement setzen
    	m_CR_Status.SetTextStream((LPSTR)sBuffer.c_str());
    	m_CR_Status.LineScroll(m_CR_Status.GetLineCount());
    	m_sStatus.clear();
    
    }
    

    Ich Beschreibe mal das ganze Scenario...
    CEMailSendStatusDlg Ist ein kleiner Dialog mit zwei Steuerelementen. Einem Progressbar- und einem RichEdit Steuerelement. Der Dialog dient dazu, mir den Versendestatus von E-Mails anzuzeigen. Das RichEdit empfängt dabei solche Nachrichten wie "Start SSL" "Login Ok" usw. Die Progressbar zeigt an wie viele Bytes bisher gesendet wurde.

    In der Klasse CEMailSendStatusDlg starten 2 Threads die via CreateThread erstellt werden und die in einer while Schleife mit WaitOnAddress Angehalten werden.

    In dem EMailDialog in dem ich die E-Mail erstelle, anzeige und versende, wird der EMailStatus Dialog, vor dem versenden der E-Mail und dem Thread der die E-Mail versendet, gestartet.

    Aus einer Klasse die in der Hierarchie etwas unterhalb der EMailDialog Klasse liegt, werden dann die Wert für den EMailStatus Dialog gesetzt und die beiden WaitOnAddress Funktionen aufgeweckt, woraufhin die Progressbar und das RichEdit Steuerelement aktualisiert wird.

    Das Ganze funktioniert soweit ganz gut, ich komme auch über WM_GETTEXT Nachricht an die bisherigen Werte des RichEdit Steuerelementes. Generell würde ich aber dennoch gerne verstehen, warum ausgerechnet in dem Moment GetStreamCallback versagt.


  • Mod

    Mir ist Dein Code zu kompliziert. Für was benötigst Du die Datei, wenn Du nur die Daten kopieren willst?

    Wir kommst Du darauf, dass Du den Puffer, den Die die Callback Funktion gibt, löschen zu dürfen?



  • Die Datei hFile benötige ich um mit dieser Funktion werte in Dateien (C:\test.txt) zu schreiben oder aus ihr zu lesen.
    das gegenstück zu Get- & SetTextStream ist Get- &SetFileStream

    Wir kommst Du darauf, dass Du den Puffer, den Die die Callback Funktion gibt, löschen zu dürfen

    welchen puffer meinst du?


  • Mod

    Zeile 117
    memset(lpBuff,0,cb);



  • weil in dem puffer, alte werte stehen bleiben, wenn die vorhergehende Zeichenfolge länge war, als die aktuelle. deshalb kam ich auf die Idee den zu löschen. ansonsten gibt es keinen Grund dafür.

    steht ja auch im Kommentar darüber.


  • Mod

    Und was interessiert dich das bitte?
    Der Callback sagt Dir genau wie weit Du den Speicher zu benutzen hast. Du hast da gar nichts selbst zu ermitteln.



  • LowFly so wie du dir vorgestellst hast, der Code aus deinem ersten Post einfach in Thread packen und ausfuhren, so wird nicht funz.
    Du versuchst aus dem Thread auf RichEdit Steuerelement mit Funktion "StreamOut" zugreifen(Threadübergreifend), dein richtige Lösung ist "Threadübergreifende programmierung",schau im netz ob wad findest, ansonsten kann ich dir paar links zu Dokus & Beispiele posten. Kannst dir sicher sein, dass du in deinem Projekt/Code wirst einiges ändern müssen.



  • @MR C
    Ah ok,
    ja dann poste doch mal die links bitte 🙄


  • Mod

    Mal Grundsätzlich: Du hast also einen anderen Thread der auf das RTF Control zugreift?
    Wenn dem so ist, kannst Du den gleich wieder vergessen, weil jede Interaktion letzten Endes durch die Messagequeue synchronisiert wird.

    Zudem besteht ja sogar die Möglichkeit, dass sich das Control in der Größe bereits verändert hat und entsprechend der Buffer gar nicht passt.

    Weiterhin ist nach wie vor, Dein Code ein Witz.
    Wir kommst Darauf über strlen auf die Größe eines allokierten Buffers zu schließen? Wenn also im letzten Zyklus nur noch ein Byte übertragen wurde wir dim nächsten Durchgang alles byteweise durchlaufen obwohl der Buffer ursprünglich mal größer war.

    Dein ganzer Konstrukt mit dem Buffer der irgendwie erhalten bleibt und wieder verwendet wird hat viel zu viele Seiteneffekte.
    Ich sehe auch keinen Schutz, dass dieser Buffer nicht aus zwei Threads verwendet werden könnte.

    Ich habe das mal durchgespielt und bekomme Deine Probleme nicht.
    Allerdings kollekiert mein Program einfach alle Daten in einem Puffer.

    Ich würde davon ausgehen, dass Du einfach einen Fehler in Deinem Code hast.





  • Martin er kann fehler suchen solange es geht. Er hat doch geschrieben, dass der Code ohne Thread funz tadenlos. Ohne "Threadübergreifende programmierung" kommt er kein Schritt weiter. Er greift nicht mit Thread auf RichEdit, sondern aus dem Thread auf RichEdit und so einfach geht das nicht, in NET mus er mit Ivnoke arbeiten.


  • Mod

    Mit einem Thread ist das doch sowieso quatsch. Er verwendet SendMessage. Also gibt es eine Threadsynchronisation. Da muss man in der Windows API gar nichts machen. Aber ein Thread bringt hier auch keinerlei Vorteile.



  • ...fur die hintergrund events ohne die GUI Threads oder andere events zu blockieren wie z.b Emails in Dateien archivieren oder Email versenden und empafngen,usw.... bringen Threads seine voteile.


  • Mod

    Klar bringen Threads was, aber nur dann wenn sie hinreichend von der UI entkoppelt werden.

    Und hier nicht, wenn er SendMessage verwendet und ausgerechnet, dass für eine Operation die länger dauern kann.



  • Martin des habe ich schon erwähnt...wiederhole nicht, dass was ich schon geschrieben habe.


Log in to reply