Forum: PC-Programmierung Problem mit setmode() und fwrite()


von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Servus,

es klemmt bei einem kleinen Programm (WinXP prof, Watcom C 1.9):
1
#include        <stdio.h>
2
#include        <stdlib.h>
3
#include        <fcntl.h>
4
5
char    fbuf[500000];
6
7
        main (
8
        int     argc,
9
        char    *argv[]
10
        )
11
{
12
        int     st, size;
13
14
        memset( fbuf, '*', sizeof( fbuf));
15
16
        st = setmode( fileno( stdout), O_BINARY);
17
        printf( "Zeile %d\n", st);
18
19
        size = 200000;
20
        fwrite( fbuf, size, 1, stdout);
21
        fputs( "Noch eine Zeile\n", stdout);
22
23
        size = 0xff;
24
        fwrite( fbuf, size, 1, stdout);
25
26
        return 0;
27
}

Auf der console aufgerufen kommt die Ausgabe:


G:\test>usget
Zeile 256
Noch eine Zeile
************************************************************************ 
********
************************************************************************ 
********
************************************************************************ 
********
***************
G:\test>

dh, fwrite gibt nichts aus, return-Code ist 0 (probeweise mal in W98
probiert, da schreibet fwrite das komplett raus).

Entferne ich setmode() gibt fwrite() alles aus.

Der obige Code ist radikal zum Testen zusammengestrichen. Das Original
gibt mir ein PDF über den Apache aus, ich muß also stdout auf binär
umschalten. Komischerweise funktioniert das auch in einem anderen
Programm - nur das obige Primitivbeispiel zickt.

Die Ausgaben mit printf() und fputs() funktionieren ohne Probleme
auf stdout, nur fwrite() nicht.

Ich blick's nicht mehr. Mit dem vorhergehenden Release sind mir da
nie Probleme aufgefallen. Das vollständige Programm hat schon einige
100.000 PDF abgeschickt, Probleme wären da aufgefallen.

Es zickt seit dem Umstieg von Watcom C32 v 11 auf Open Watcom 1.9.

In welcher Richtung kann ich noch suchen ?

PS: Gegooglet habe ich, überall wird setmode für diesen Zweck empfohlen.

von Karl H. (kbuchegg)


Lesenswert?

Ich denke das Problem ist die ANzahl der Zeichen.
Mach die halt kleiner.
Du kriegst ja sowieso von fwrite die ANzahl der geschriebenen Zeichen 
zurück und musst das ganze in eine Schleife packen, bis alles ausgegeben 
ist.
So gesehen sind das dann halt einfach nur ein paar Aufrufe mehr.

Warum bei dir da jetzt die Beschränkung existiert, kann ich dir auch 
nicht sagen. Ich hab aber im Laufe der Zeit gelernt, Datenfelder mit 
moderaten Größen zu benutzen. Irgendwo gibt es dann immer wieder mal 
eine Beschränkung, weil irgendwer vergessen hat, einen 16-Bit int als 
unsigned zu machen, von 16 Bit auf 32 Bit hochzugehen, etc.

1
        size = 200000;
2
        chunk_size = min( size, 4096 );   // nicht mehr als 4K auf einmal
3
        total = 0;
4
5
        do( total += fwrite( &fbuf[total], chunk_size, 1, stdout) )
6
        {
7
        } while( total < size );

Sicherheitshalber noch eine Abfrage auf einen Returnwert von fwrite von 
0 einbauen, der weißt immer auf einen Fehler hin.
Die maximale Chunk Size konfigurierbar machen. Das ist nichts weswegen 
man da jetzt in Panik verfallen muss.

von Peter II (Gast)


Lesenswert?

ja mit einer richten fehlerbehandlung bekommt man auch einen Fehler:
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <fcntl.h>
4
#include <string>
5
#include <io.h>
6
#include <errno.h>   
7
8
char    fbuf[500000];
9
10
int main (
11
        int     argc,
12
        char    *argv[]
13
        )
14
{
15
        int     st, size;
16
17
        memset( fbuf, '*', sizeof( fbuf));
18
19
        st = _setmode( fileno( stdout), O_BINARY);
20
        printf( "Zeile %d\n", st);
21
22
        size = 100000;
23
        _set_errno( 0 );
24
        int r = fwrite( fbuf, size, 1, stdout);
25
        if ( r <> size )
26
           printf("r=%d  errno=%d error:%s\n", r, _errno, _strerror(NULL) );
27
                
28
        fputs( "Noch eine Zeile\n", stdout);
29
30
        size = 0xff;
31
        fwrite( fbuf, size, 1, stdout);
32
33
        return 0;
34
}

r=0  errno=4198958 error:Not enough space

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Danke, hmmmm ...

Wie gesagt, ich verwende fwrite() schon ewig, teilweise mit
erheblich mehr Zeichen wie schlappen 200.000. Irgendwie vermute
ich ja auch einen Bug in der C-Lib.

ohne setmode: einwandfreie Funktion

mit setmode: fwrite gibt nur bis 0xffff Bytes aus (bei einem
Kollegen nur 0xa9fff (experimentell ermittelt)).

Sobald auf eine Datei umgeleitet wird (zB "usget >q") wird
isatty() FALSE und es geht auch. Alternativ fbuf mit fputc()
ausgeben ist auch ok (overkill).

Das gleiche Programm funktioniert auf W98 problemlos.

--

Im Original holt das Programm ein PDF aus einem Container und
sendet den über CGI an den Apache Webserver. Aufgerufen wird es
über spawn. Als Workaround habe ich die identische Funktion in
das Programm rüberkopiert (spawn entfällt dadurch), alles
funktioniert bestens.

Es spinnt ;)

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Peter II schrieb:
> r=0  errno=4198958 error:Not enough space

Die Meldungen habe ich wegen der besseren Übersichtlichkeit
rausgelassen - habe die auch. Nur, was sollte ein primitives
write groß an Speicher brauchen ? Es soll die Daten ja nur
auf einen Stream schieben.

Wie oben gesagt: Auf Win XP mit 4 GB mault er über low mem,
auf meiner W98-Kiste mit 640 KB macht er's.

von Karl H. (kbuchegg)


Lesenswert?

Joachim Drechsel schrieb:
> Peter II schrieb:
>> r=0  errno=4198958 error:Not enough space
>
> Die Meldungen habe ich wegen der besseren Übersichtlichkeit
> rausgelassen - habe die auch. Nur, was sollte ein primitives
> write groß an Speicher brauchen ? Es soll die Daten ja nur
> auf einen Stream schieben.

Irgendwelche Caches, Buffer, Speicherfelder um den Aufruf asynchron 
machen zu können, ....

Was auch immer da intern passiert, es ist nichts was du jetzt gross 
beeinflussen kannst.

Wie gesagt: du musst ja sowieso in einer Schleife mit dem Returnwert von 
fwrite arbeiten (*). Von daher ist das nur ein Zahlenwert, den du jetzt 
eben kleiner machen musst.

Spielt auch keine grosse Rolle. Denn die eigentliche Ausgabe dauert ein 
vielfaches dessen, was dir die paar Aufrufe kosten.


(*) das allerdings musst du auf jeden Fall. Die Annahme, du könntest 
beliebig grosse Speicherbereiche mit lediglich 1 Aufruf rausblasen, ist 
gefährlich! Die Macher von C haben sich bei den Returnwerten schon was 
gedacht (zumindest meistens).

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Was auch immer da intern passiert, es ist nichts was du jetzt gross
> beeinflussen kannst.

Vermutlich das Hauptproblem ...

Ok, ich danke auch und baue mir einen fwrite-Ersatz zusammen.
Auswürfeln mit Blockgröße 4KB sollte gehen.

Grüße Joe.

von Klaus W. (mfgkw)


Lesenswert?

Warum nimmst du überhaupt gepufferte Ausgabe? Um Datenblöcke 
durchzuschieben hat das doch nur unnötigen Overhead.
Vielleicht wäre ungepuffertes Schreiben nach fd=1 schneller und 
unkomplizierter.

Das Vermischen von gepuffertem Zugriff (fwrite) und ungepuffertem 
(setmode) ist inhärent fragwürdig, auch wenn es manchmal gutgeht.

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Ich muß die Konvertierung von \n nach \r\n abschalten. Das
ist der einzige Grund. Mit write() statt fwrite() kommt das
gleiche heraus.

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Umgebaut auf 4 KB-Blöcke, der gleiche Fehler (egal ob ich
write() oder fwrite() zum Schreiben der Blöcke verwende).
Ein Test mit Borland C ergab das gleiche Problem.

Blockweise schreibt er das jetzt immerhin in de console
aber nicht zum Indianer.

(herumhüpf - lautlach - der Wahn droht - Katze fangen -
da kommt die weiße Jacke - Augenrollen).

Ich lasse das jetzt mal gären. Vielleicht kommt mir irgendwann
die Erleuchtung.

---

Vermutlich covern beide die Windows-API-Funktionen.
Vielleicht klemmt es da (bei beiden gleich ?).

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Nachtrag (falls es jemanden interessiert):

Ich habe mir die Quellcodes bei Watcom heruntergeladen und mal
verfolgt wie die das machen. Wie bei einem cross platform-Projekt
zu erwarten eine Unzahl an #defines um alles auseinanderzuhalten.

fwrite verwendet bei einem Textstream schlicht fputc(), bei binary
streams wird es interessant: dort wird write() aufgerufen, write()
packt das in 512 Byte Häppchen und schiebt es an os_write() weiter.

os_write() übergibt es dann an die Win32-Funktion WriteFile().

Die stößt sich an den 200 KB und gibt die Meldung "F³r diesen
Befehl ist nicht gen³gend Speicher verf³gbar." aus.

The WriteFile function may fail with ERROR_INVALID_USER_BUFFER or 
ERROR_NOT_ENOUGH_MEMORY whenever there are too many outstanding 
asynchronous I/O requests.

Na ja ... Im Moment habe ich noch 2 GB Speicher frei.

Borland macht das offensichtlich genauso, deshalb das identische
Verhalten. Von einer Grenze der Datenmenge steht im Helpfile nichts.

von Klaus W. (mfgkw)


Lesenswert?

Joachim Drechsel schrieb:
> fwrite verwendet bei einem Textstream schlicht fputc(), bei binary
> streams wird es interessant: dort wird write() aufgerufen, write()
> packt das in 512 Byte Häppchen und schiebt es an os_write() weiter.

Aber merkt dein fwrite() überhaupt etwas davon, daß du es binär ausgeben 
willst?
Vom deinem setmode() merken die f...-Funktionen doch nichts, und sonst 
könnten sie es doch nur beim Öffnen merken - was du aber bei stdout ja 
nicht tust.

(Ceterum censeo... ich halte es nach wie vor für keine gute Idee, 
gepufferte und ungepufferte IO zu mischen)

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Klaus Wachtler schrieb:
> (Ceterum censeo... ich halte es nach wie vor für keine gute Idee,
> gepufferte und ungepufferte IO zu mischen)

Gerade DAS passiert aber in der C-Lib.

von Klaus W. (mfgkw)


Lesenswert?

Nein.
Die C-Lib macht für die f...-Funktionen die Pufferung, und weiß was sie 
darunter mit den ungepufferten Funktionen anstellt; natürlich nutzt sie 
letztlich dann die ungepufferten für die eigentliche Übertragung.
Aber an den Bibliotheksfunktionen vorbei etwas auf der ungepufferten 
Seite vorbei zu manipulieren und für dieselben Dateien sonst die 
Pufferung zu verwenden, ist das was ich vermeiden würde, soweit möglich.
Genau an diesem Beispiel sieht man es doch: du stellst etwas auf binäre 
Übertragung, aber die f...-Funktionen merken es vielleicht nicht und 
nehmen trotz der abgeschalteten Übersetzung womöglich einen 
Funktionsaufruf für jedes einzelne Zeichen.

Wenn du eh nur Blöcke durchschiebst, brauchst du die gepufferten 
Funktionen doch gar nicht?
Aber letztlich ist es mir natürlich egal - wenn es geht, ist es gut.
Ich wollte ja nur mein übliches Mißtrauen kundtun :-)

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Klaus Wachtler schrieb:
> Ich wollte ja nur mein übliches Mißtrauen kundtun :-)

Was genau das ist was ich höhren möchte. Ja-Sager gibt es
bekanntlicherweise genug ;)

Ich klebe an den stream-Funktionen weil stdout halt ein stream ist.

Übrigends wird in allen cover-Funktionen getestet ob die
vorhergehende Ausgabe abgeschlossen ist ... vermutlich, damit
sich buffered und unbuffered I/O nicht ins Gehege kommen.

von Klaus W. (mfgkw)


Lesenswert?

Joachim Drechsel schrieb:
> Ich klebe an den stream-Funktionen weil stdout halt ein stream ist.

Dann nimm doch als fd einfach 1 für die ungepufferten Funktionen.
0 ist klassischerweise Standardeingabe, 1 ist Standardausgabe und 2 
Standardfehlerausgabe.
Dein fileno( stdout) ganz oben sollte also eine schnöde 1 liefern.

Als Lohn der Umstellung geht das Schreiben wohl auch noch schneller, 
weil fwrite() über ein paar Umwege ja letztlich auch zu einem write() 
kommt (im besten Fall, und nicht mit etwas Pech auch noch zeichenweise 
ausgibt).

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Klaus Wachtler schrieb:
> Dann nimm doch als fd einfach 1 für die ungepufferten Funktionen.
> 0 ist klassischerweise Standardeingabe, 1 ist Standardausgabe und 2
> Standardfehlerausgabe.
> Dein fileno( stdout) ganz oben sollte also eine schnöde 1 liefern.

Mache ich ja.

Ich muß nur für die binäre Ausgabe stdout mit setmode() auf binary
schalten (sonst kann ich zB keine Bilder oder PDFs ausgeben). Und
damit fängt der Schlamassel an ...

Ohne setmode() gibt es alles aus (halt ggfls falsch).

Dann verhalten sich fwrite/write etc noch unterschiedlich abhängig
von isatty().

Mit WriteFile() zeigt sich übrigends der anfangs genannte Fehler
auch. Das Filehandle habe ich dazu mit GetStdHandle( STD_OUTPUT_HANDLE)
geholt. Die in der Watcom surce vorhandene Funktion __getOSHandle()
habe ich nicht gefunden.

von Klaus W. (mfgkw)


Lesenswert?

Ich störe mich ja auch nicht am setmode(), sondern am anschließenden 
fwrite() :-)
Mein Vorschlag geht dahin, das fwrite( stdout, ... ) durch write( 1, ... 
) zu ersetzen.

Aber wie gesagt, ich will mich nicht einmischen - du wirst wissen, was 
du willst.

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Klaus Wachtler schrieb:
> Ich störe mich ja auch nicht am setmode(), sondern am anschließenden
> fwrite() :-)
> Mein Vorschlag geht dahin, das fwrite( stdout, ... ) durch write( 1, ...
> ) zu ersetzen.

Alles probiert.

write() berücksichtigt leider auch den mode. Ich habe das im
Quelltext verfolgt. fwrite verwendet write, write verwendet dann
WriteFile.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.