Solved

CStdioFile::ReadString incorrectly reports EOF

Posted on 2009-04-03
20
2,152 Views
Last Modified: 2013-11-20
I'm using Visual C++ 6.0
I found out about an issue with CStdioFile::ReadString reporting EOF incorrectly if the last line in a file is a multiple of 128 without a newline character following it. I read about it online a while back, but I can't find that page anymore. A workaround that I did when this came up was to change my while loop from
while(inFile.ReadString(inLine))
to
while(inFile.ReadString(inLine)||inLine != "")
as it appears it reports EOF incorrectly but still puts the data in the string variable passed.
The problem is I have about 500 of these while loops scattered across different programs and I don't want to change them all to that, not to mention the other variations of EOF checking I do.
Is anyone familiar with this issue? Is there some patch that I can install to fix this?
0
Comment
Question by:Nhl2k
  • 11
  • 5
  • 4
20 Comments
 
LVL 39

Accepted Solution

by:
itsmeandnobodyelse earned 125 total points
ID: 24067108
>>>> The problem is I have about 500 of these while loops scattered across different programs and I don't want to change them all to that
You could derive from CStdioFile and provide a fix of ReadString in that derived class. So you would need to replace CStdioFile by CMyStdioFile everywhere and include your class header in stdafx.h , it perhaps is the better solution.

>>>> Is there some patch that I can install to fix this?
I actually never heard of this bug (and I don't use CStdioFile myself for at least 10 years - but std::ifstream or std::ofstream) but if it wasn't fixed in one of the latest service packs of VC6 (which I assume you already have installed) it probably won't be fixed anymore as VC6 was released in 1998 and there are three major releases of VC compiler released later.  
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 24069388
The article you mention is probably:
   FIX: ReadString Gives Wrong Result Reading Long Strings
    http://support.microsoft.com/kb/152319
But it refers to a "retired" issue, one that was fixed years ago.  You can single-step into the CStdIoFile:ReadString code and see if it matches the code in that article.  You can, as, itsme said, derive from StdioFile and override the ReadString function with one that does not have the bug.
0
 

Author Comment

by:Nhl2k
ID: 24069898
itsmeandnobodyelse,
I've thought about doing that, but it still requires modifying about 60 programs.

DanRollins,
I've read about that issue and that's not the problem. I actually did a debug way back when I found out about this issue and stepped into the ReadString code. Looking through the code I actually saw why it wasn't reporting EOF correctly.
I did a test program yesterday where I created a simple for loop that wrote a line of 1 byte to 1000 bytes into a file. For each iteration, I reopened the file and did a ReadString, checking the result returned. It would return EOF for every multiple of 128.

I realize that Microsoft probably won't release any updates, I was just hoping maybe I had missed one, like an update to MFC or something that would fix this. I have the code for my simple for loop attached. Maybe one of you could try running it and seeing if you get the same results I do? If not, I may try reinstalling visual studio with the latest service pack to make sure I'm not using something that's not updated correctly.

Thanks
void CEOFTestDlg::OnButton1() 

{

	CStdioFile file;

	CString strLine;

	for(int i=0;i<1000;i++)

	{

		file.Open("testfile.txt",CStdioFile::modeWrite|CStdioFile::modeCreate);

		CString writeLine('A',i);

		file.WriteString(writeLine);

		file.Close();

		file.Open("testfile.txt",CStdioFile::modeRead);

		if(!file.ReadString(strLine))

		{

			CString temp;

			temp.Format("%d",i);

			MessageBox(temp);

		}

		file.Close();

	}

	MessageBox("Done");

}

Open in new window

0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24071598
>>>>> but it still requires modifying about 60 programs.
In Visual  Studio later than VC6 there is a 'replace all in files' where it could be done in less than 10 minutes.

But even in VC6 I have done a similar job in less than one hour. You should ask yourself whether any other solution requires less ...

Another thought ... You said the error only would occur if a text file ends with a 128 byte text string.

I think, the chance to happen that is pretty low. If you have some influence on how these text files were created you might find ways to make the risks negligible. Or you write a little batch job which adds a linefeed to all text files not ending with a linefeed.

0
 
LVL 49

Assisted Solution

by:DanRollins
DanRollins earned 125 total points
ID: 24073101
OK I found something.
First, I can verify the bug.  It works exactly as you describe.  When the file length is an exact multiple of 128,  CStdioFile::ReadString(CString& rString) returns FALSE.  That would match with what the documentation says only in the case where the file length is 0.
It frankly amazes me that this bug has been in place for so long.  I can find nothing in MSDN about it,, except that old note that says a somewhat different problem was solved in the MFC42 DLL.
The correct data is returned (rString is populated).  So all that is wrong is the return value.  Here's what I found:
In the MFC source code that comes with Visual Studio 2008, the function is identical with one single exception.  The last line of the function is changed like so...
     ...
     // return lpszResult != NULL;
     return nLen != 0;
}
You probably can't assume that some later/fixed version of the MFC DLL is available on your target systems.  So that leaves you with doing what you have been doing:  (checking for "") or writing an alternative function like MyReadString( file,s)  that returns the correct Boolean value, or deriving from CStdioFile.  All three options require the source code changes that you hope to avoid.
Sorry, but that's my take on it.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24074706
>>>> Sorry, but that's my take on it.

That is great. It offers a further option, to correct the bug in MFC and build a new one.
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 24075188
I corrected an MFC bug once by adding one of the MFC files to my own project.  In this case, perhaps you could copy just that one cpp file (FileTxt.cpp) into your own project directory.  Rename it, add it to the project, make that one-line change to the src code.   Now all StdioFile object functions will go to your own code.
I tried this and got a warning and an error.  Both went away when I commented out the
   IMPLEMENT_DYNAMIC(CStdioFile, CFile)
at the very end of the file.  I'm not savvy enough about this to know what sort of "ripple-effect" such a change could have on a large project.  However, I can say that when I did this on the simple test app, the 128-byte messageboxes stopped popping up (except for the 0-length file one).
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24076165
>>>> I corrected an MFC bug once by adding one of the MFC files to my own project.  

I did it once (maybe 15 years ago)  when I had a support contract with MS. I reported a bug in the recordset classes and they helped me to rebuild MFC (more precisely the static library of MFC). I am pretty sure that the  makefiles for rebuilding MFC are available with VC6 (or can be downloaded) and as there are no further service packs it hardly will get overridden by a further update.
0
 

Author Comment

by:Nhl2k
ID: 24077379
itsmeandnobodyelse,
Unfortunately my program requires importing data from other systems into our system. So I'm at the mercy of the files created by other companies which on at least 2 occasions, have had a multiple of 128 as the last line and caused my program to stop loading the data. I had to create a fix for their program and distribute it so they could import the file. Overall the percentage is low, but if it fails just once I'll have to deal with it.

DanRollins,
Thanks for trying out my code and confirming the bug. At least now I know it's not something messed up in my setup. And I'm also amazed that it seems this isn't documented anywhere and I tried all kinds of searches on google with nothing mentioning the problem. Either it happens so rarely that it was never reported, or sometimes the last lines of files are not being read and nobody has noticed!

I'm not sure which way I'm going to handle the issue, but at least I know I'm not missing something simple like a patch. Thanks again for the help.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24078669
>>>> it happens so rarely that it was never reported
Most text files written programmatically will have a linefeed at end. I can't remember one of the thousands of text files I've created programmatically which would end without a linefeed.

With text files created in some editor it might be different though there were former IDE's (even of VC compiler) which were not able to properly resolve two include statements if the first header file wasn't proper ended with a linefeed. Though that was in the middle nineties I still was used to add linefeeds to my source files. Though of course I wouldn't expect other people to have the same experiences, I would nevertheless assume that exactly 128 characters (or a multiple) are really rare exceptions for a manually created text file either.

Do you know how your sample file was created?

Newer programs might not use CStdioFile but std::ifstream. And a loop like

   string line;
   vector<string> all;
   ifstream ifs("x.txt");
   while (getline(ifs, line))
       all.push_back(line);
   ifs.close();

hasn't any issues with missing linefeeds.

 
0
Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24078858
Note, the makefiles to rebuild MFC in VC6 should be found in vc98\mfc\src.

0
 

Author Comment

by:Nhl2k
ID: 24078958
Is it dangerous to recompile that? Could that cause other problems?
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24079519
>>>> Is it dangerous to recompile that? Could that cause other problems?

Hmmm. I would do it at a copy of current system (and I actually have done it that way). But if you only make the (little) change Dan has found out, you surely can't produce more problems than that it doesn't work nevertheless.

The greater danger might be that the makefiles don't work or produce some errors because of an insufficient environment. But if you've properly saved all files from and below the installation directory of VC6 I rarely can think of any irreparable issues. I think you've a good chance that it works at the first attempt if you work at the original folders (not on the copy) and run the batch file before which establishes the command line environment (something like vcvars32.bat, I didn't remember the name exactly).

0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24079552
>>>> I would do it at a copy of current system

No, as I told below, I did it on the original and the copy was for backup reasons only.
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 24080753
I was actually decribing a way to do it without creating a new MFC DLL.
The technique is to create your own local copy of a particular MFC object (in this case, CStdioFile).  When done that way, the linker will use your object (call your code) rather than the one in the standard DLL -- an OBJ file links before a LIB file does.  For all other MFC objects, it will use the original.  
You need not make any change to the MFC DLL itself nor experience the frustration of trying to get the make file to work (if that is even possible).
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24081615
>>>> nor experience the frustration of trying to get the make file to work (if that is even possible).
I made good experiences with that. I don't think that makefiles supported with the sources of MFC only have the purpose to arising frustrations ...

>>>> The technique is to create your own local copy of a particular MFC object (in this case, CStdioFile).  
Hmmm.

>>>> tried this and got a warning and an error.  Both went away when I commented out the
>>>>   IMPLEMENT_DYNAMIC(CStdioFile, CFile)

Do you really think, those implications are better than rebuilding - a never further updated - MFC dll?

Sorry, if the make fails with unresolvable errors that what you described might be an option (or better a last resort before deriving from CStdioFile). But never as the first way.

FYI: if you provide own object code for a class and all their members you can persuade the linker to taking your implementation rather than the one provided by an import library. However, only freshly new compiled code will take your implementation while any code already compiled takes the default import library. So actually you were using both versions of the code what may have unknown impacts if there is any optimization or static data involved.  

The   IMPLEMENT_DYNAMIC(CStdioFile, CFile) is the MFC way to send messages and notifications first to CStdioFile class and then to CFile class if they were not already resolved by CStdioFile. If commenting that you actually were spoiling MFC message management.



0
 
LVL 49

Expert Comment

by:DanRollins
ID: 24081850
The problem with creating a custom version of the MCF DLL is that you must distribute it with your application program.  It's over one megabyte.  And some of your customers will not "get the memo" and suddenly you're in DLL Hell.   Also, you have a major problem when you want to upgrade to a later version of MFC.
Yes, there is some danger in commenting out the IMPLEMENT_DYNAMIC line.  But doing so should only affect the program that links to your version of the object.  The change (and potential ripple effect) is "contained" -- affecting only this one program.   Normal QA testing -- of your one program -- will flush out problems.  And they are easy to isolate.  If something goes wrong, remove that one file from the build and see if it stops going wrong.
0
 

Author Comment

by:Nhl2k
ID: 24081879
So how does MFC link? I usually use the default option when creating a project, which is for a shared dll. Does that mean it's using whatever version of this function is on the machine it's run on?
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24082241
>>>> I usually use the default option when creating a project, which is for a shared dll.

Dan is right. If you were considering to fix the bug you either need to distribute your dll with the application (which isn't so much a danger if you put it into the folder where the app resides) *or* you build a static library of the MFC dll and link against what lets the size of your app increase but is an absolute safe method where I only have positive experiences with (it surely avoids any dll hell).
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 24082261
>>>> you have a major problem when you want to upgrade to a later version of MFC.

No, you can't upgrade a VC6 application to a later version of MFC.

You only can upgrade VC6 to a later Version of VC (and MFC) where the bug is already fixed.
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Suggested Solutions

Title # Comments Views Activity
substring method in java 1 79
wait notify demo infinite loop 3 80
haveThree challenge 22 100
firstChar challenge 13 84
Introduction: Dynamic window placements and drawing on a form, simple usage of windows registry as a storage place for information. Continuing from the first article about sudoku.  There we have designed the application and put a lot of user int…
Introduction: Ownerdraw of the grid button.  A singleton class implentation and usage. Continuing from the fifth article about sudoku.   Open the project in visual studio. Go to the class view – CGridButton should be visible as a class.  R…
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
Here's a very brief overview of the methods PRTG Network Monitor (https://www.paessler.com/prtg) offers for monitoring bandwidth, to help you decide which methods you´d like to investigate in more detail.  The methods are covered in more detail in o…

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now