windows.h vs fstream

Pages: 12
The standard library is a monster, it has so much functionality that I don't need.
What is the smartest way to open and write to a file in C++?
My idea was to use windows.h, but it is big too!
Also, how does the standard library open files on windows without using the win32 API? How it is implemented?
What is the smartest way to open and write to a file in C++?
Define "smartest".

My idea was to use windows.h, but it is big too!
Well, operating systems are big and complicated. Some have had work piled onto them for decades. It's to be expected that their interfaces would be large.

Also, how does the standard library open files on windows without using the win32 API? How it is implemented?
It uses the Windows API, of course.
The standard library is a monster, it has so much functionality that I don't need.


The STL is like a workshop full of tools and materials: if you only need a hammer and a nail ...... It's a matter of finding what one wants and using it. If one doesn't know how to use a particular tool, then read the manual.

The other thing is that if one uses windows.h, now the code is tied to the windows OS. If one uses standard C++ code, then that restriction is removed.
Last edited on
Define "smartest".
I meant fastest* without inessential stuff.

It uses the Windows API, of course.
So fstream uses windows.h?

The other thing is that if one uses windows.h, now the code is tied to the windows OS. If one uses standard C++ code, then that restriction is removed.
The standard is often unpredictable on different operating systems, so I would rather take the time and use different OS APIs.
Last edited on
I meant fastest* without inessential stuff.
They're all basically equally fast. The standard library arguably has less "inessential stuff" than the Windows headers, because when you include <fstream> you're only including the functions and classes to deal with file streams (and those that they depend on). When you include <Windows.h> you're including pretty much the entire API.

So fstream uses windows.h?
If you go deep enough, yes, you're going to find calls into the Windows API. There's not any other reasonable way to do it.

The standard is often unpredictable on different operating systems
In my experience the behavior is pretty consistent, at least as far as file streams are concerned. I do remember a bug one version of MSVC's implementation, where passing std::ios::ate to the file stream constructor would behave in a non-compliant way. So that's one bug in 17 years.
Last edited on
If you go deep enough, yes, you're going to find calls into the Windows API. There's not any other reasonable way to do it.
If windows.h is included in fstream, and my goal is to minimize the lines of code in my program, a better option for me would be only to include windows.h (even if it bounds my code to Windows). As far as I know, you cannot include parts of windows.h because they depend of each other, so with every <iostream>, the entire Windows API is included.
So fstream uses windows.h?

Absolutely not. The WinAPI is entirely different than C++.

A C++ program written for Windows may use the WinAPI internally, but as far as C++ code is concerned well-written code will compile to use whatever the OS API uses. For that OS.

C++ is OS agnostic.

You want to open a file to write to using C++?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <fstream>

int main()
{
   // contains the default flags ios::out | ios::trunc
   std::ofstream test_file("test_file.txt");

   if (test_file.is_open())
   {
      test_file << "Some Text\n"
         << "More Text\n"
         << "Even More Text\n";
   }
   else
   {
      std::cerr << "** ERROR OPENING FILE!!! **\n";
   }

   test_file.close();
}

That code will work on any OS that has a fairly up-to-date C++ compiler.

Reading from a file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <fstream>
#include <iostream>
#include <string>

int main()
{
   // contains the default flags ios::in
   std::ifstream test_file("test_file.txt");

   if (!test_file.is_open())
   {
      std::cerr << "Error opening file!!!\n";

      // none 0 exit means there was an error (normally)
      // any other value can indicate an error
      return -1;
   }

   std::string buffer;

   while (std::getline(test_file, buffer))
   {
      if (buffer.size() != 0)
      {
         std::cout << buffer << std::endl;
      }
   }

   test_file.close();
}
Some Text
More Text
Even More Text

Again, this will work on any OS that has a recent C++ compiler.

How about writing a file, and then reading it back? Here's one way to do it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <fstream>
#include <iostream>
#include <string>

int main()
{
   std::ofstream test_file("test_file.txt");

   if (!test_file.is_open())
   {
      std::cerr << "Error opening file!!!\n";
      return -1;
   }
   else
   {
      test_file << "Some Text\n"
         << "More Text\n"
         << "Even More Text\n";

      test_file.close();

      std::ifstream test_file("test_file.txt");

      if (!test_file.is_open())
      {
         std::cerr << "Error opening file!!!\n";

         // none 0 exit means there was an error (normally)
         // any other value can indicate an error
         return -1;
      }

      std::string buffer;

      while (std::getline(test_file, buffer))
      {
         if (buffer.size() != 0)
         {
            std::cout << buffer << std::endl;
         }
      }

      test_file.close();
   }
}
Some Text
More Text
Even More Text

Again, this will work with any OS that has a C++ compiler.

The standard is often unpredictable on different operating systems, so I would rather take the time and use different OS APIs.

Absolute HOGWASH! The C++ standard is completely predictable on different operating systems. Who ever told you the above is stuffed full of muffins and doesn't have a clue about C++.

The utility of C++ is that well-written code, written to the C++ standard, won't matter which OS is used.

The WinAPI is not cross-platform, period, unlike C++.
Last edited on
If windows.h is included in fstream


As far as I know, you cannot include parts of windows.h because they depend of each other, so with every <iostream>, the entire Windows API is included.


I think you have a misconception of what is happening here.

An OS API is made of systems calls which are small programs that interface with the kernel. A program that wants to deal with the OS would have to call one of these programs at some point.

So when one uses fstream to open a file, the compiler puts the relevant system call for the OS into the executable to perform that action. So if fstream is #included , it does not mean that windows.h is included, and certainly not the whole windows API.

and my goal is to minimize the lines of code in my program


One is better off to use compiler options to minimise the size of the code.
and my goal is to minimize the lines of code in my program
LOL. Any C/++ program can be made a single line long, if you really want to. I don't think you have your priorities straight.
And using the Windows API is definitely more verbose than using fstreams for 99% of use cases, so you'd be getting the worst of both worlds.
Here's a very simple console mode program written in C++, ignoring file I/O and displaying "Hello World:
1
2
3
4
5
6
#include <iostream>

int main()
{
   std::cout << "Hello World!\n";
}

A simple WinAPI app, using a message box to display "Hello Windows!"
1
2
3
4
5
6
7
8
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
   MessageBoxW(NULL, L"Hello Windows!", L"Hello Message", MB_OK);

   return 0;
}

A fully functional simple desktop WinAPI GUI app, displays "Hello Windows!" centered in the client area:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// INCLUDES ====================================================================
#include <windows.h>

// FUNCTION PROTOTYPES =========================================================
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


// entry point for a Windows application =======================================
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nWinMode)
{
   // define the window class name
   static const WCHAR szAppName[] = L"MinimalWindowsApp";

   // create an instance of the window class structure
   WNDCLASSEXW wc;

   wc.cbSize        = sizeof(WNDCLASSEXW);
   wc.style         = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc   = WndProc;
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = 0;
   wc.hInstance     = hInstance;
   wc.hIcon         = (HICON)   LoadImageW(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
   wc.hIconSm       = (HICON)   LoadImageW(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
   wc.hCursor       = (HCURSOR) LoadImageW(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
   wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
   wc.lpszMenuName  = NULL;
   wc.lpszClassName = szAppName;

   if (0 == RegisterClassExW(&wc))
   {
      MessageBoxW(NULL, L"Can't Register the Window Class!", szAppName, MB_OK | MB_ICONERROR);
      return E_FAIL;
   }

   // define the application title
   static const WCHAR szAppTitle[] = L"WinAPI Skeletal Application";

   // create the window
   HWND hwnd = CreateWindowW(szAppName, szAppTitle,
                             WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT,
                             CW_USEDEFAULT, CW_USEDEFAULT,
                             NULL, NULL, hInstance, NULL);

   // check if the window was created, exit if fail
   if (NULL == hwnd)
   {
      MessageBoxW(NULL, L"Unable to Create the Main Window!", szAppName, MB_OK | MB_ICONERROR);
      return E_FAIL;
   }

   // show and update the window
   ShowWindow(hwnd, nWinMode);
   UpdateWindow(hwnd);

   static BOOL bRet;
   static MSG  msg;

   // enter the main message loop
   while ((bRet = GetMessageW(&msg, NULL, 0, 0)) != 0) // 0 = WM_QUIT
   {
      // check for error
      if (-1 == bRet)
      {
         // handle the error and possibly exit

         // for this app simply report error and exit
         MessageBoxW(NULL, L"Unable to Continue!", szAppName, MB_OK | MB_ICONERROR);
         return E_FAIL;
      }
      else
      {
         TranslateMessage(&msg);
         DispatchMessageW(&msg);
      }
   }

   // the app is done, exit normally, return control to Windows
   return (int) msg.wParam;
}

// processes the messages that Windows sends to the application ================
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   // choose which Windows messages you want to use
   switch (message)
   {
   case WM_PAINT:
      HDC         hdc;
      PAINTSTRUCT ps;
      hdc = BeginPaint(hwnd, &ps);

      // draw some text centered in the client area
      RECT rect;
      GetClientRect(hwnd, &rect);
      DrawTextW(hdc, L"Hello, Windows!", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

      EndPaint(hwnd, &ps);
      return S_OK;

   case WM_DESTROY:
      // exit the application
      PostQuitMessage(0);
      return S_OK;
   }

   // let Windows process any unhandled messages
   return DefWindowProcW(hwnd, message, wParam, lParam);
}

The cost of setting up a proper WinAPI GUI app is a lot of work, no way around that.

There are ways to reduce the amount of code that needs to be written, but the resulting source still won't be a couple of lines.

FYI, the Desktop WinAPI is C-based, not C++.

There are frameworks for creating WinAPI apps, one of them is the Microsoft Foundation Classes (MFC) using C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// compile for Windows 7 or higher
#define WINVER       0x0601
#define _WIN32_WINNT 0x0601

// compiling for Windows 10 (recommended)
//#define WINVER       0x0A00
//#define _WIN32_WINNT 0x0A00

#include <afxwin.h>

// application class
class CTheApp : public CWinApp
{
public:
   BOOL InitInstance();
};

// main window class
class CMainWnd : public CFrameWnd
{
public:
   CMainWnd();

protected:
   afx_msg void OnPaint();

   DECLARE_MESSAGE_MAP()
};

// construct a window
CMainWnd::CMainWnd()
{
   Create(NULL, L"An MFC Application Skeleton");
}

// initalize the application
BOOL CTheApp::InitInstance()
{
   m_pMainWnd = new CMainWnd;

   m_pMainWnd->ShowWindow(m_nCmdShow);
   m_pMainWnd->UpdateWindow();

   return TRUE;
}

// application's message map
BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd)
   ON_WM_PAINT()
END_MESSAGE_MAP()

// draw some text centered in the client area
void CMainWnd::OnPaint()
{
   CRect    rect;

   GetClientRect(&rect);

   CPaintDC dc(this);

   dc.DrawTextW(L"Hello, MFC!", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
}

// instantiate the application
CTheApp  theApp;

Still is a lot of boiler-plate code that is needed to create a functional GUI app, though the MFC framework hides a lot of the details in pre-defined C++ classes.

All 4 of the code snippets will compile using a Windows-based compiler such as Visual Studio or MinGW/TDM/GCC.

The only code snippet that can be compiled on other OSes is the first one.

If you are serious about wanting to learn Standard ISO C++ there is a free online tutorial you might want to poke around in, Learn C++.

https://www.learncpp.com/

You won't be exposed to ALL of what C++ has to offer, but then most programs don't need all of what is available.

The take-away from this should be:

As complicated as the C++ Standard appears to be at first glance, the WinAPI is a quantum leap into really mind-boggling complicated.
FILE* inherited into C++ from C is fairly slim and fast, if you want minimal tag-along junk and bloat etc. Being C, it is more crude and harder to work with in some ways, depending on what you really need.
Memory mapped files have been shown to be the 'fastest' usually.
Binary files are usually faster than text files just because you avoid all those calls from text to number as you read it in, and other reasons that can happen depending on your data. If its actually words for the most part, it may not matter.

But your question is something we often asked back in the late 80s. Because back then, at 10hz cpu with 1MB of ram and reading from a floppy because no hard disk... it mattered what was smallest/fastest/slickest way. Those ideas circle back around a little bit on some embedded computers. But generally, the compiler will make a fine program for you if you just do it reasonably clean in c++ with fstream.

If you really want to get fiesty you can DIY in assembly using the memory mapped idea. That will be faster and smaller than just about anything else you can do, and if the files are big, go ahead and thread that out too.
10hz
I think you missed the mark by 5 or so orders of magnitude. :-)
Last edited on
I do seem to have lost an M there.
Using OS specific base functions for file i/o will be faster than using C++ streams. The question is is this speed-up noticeable and is tying to one OS worth sacrificing portability? The speed-up from reading a few thousand bytes using OS means will be difficult to measure and almost certainly not justified. If you are dealing with files of several GBs and larger of data then the speed-up obtained can be noticeable and certainly measurable Then having os-specific code needs to be justified.

Also, how the file is parsed can be important. If the whole of the data file can fit into a std::string, then first read the whole of the file into one and then parse this string to obtain the data (don't parse text to std::string but use std::string_view if possible). This can give a significant speed improvement - std::getline() is a very slow function (certainly with VS?).
Using OS specific base functions for file i/o

How can you use a "base function" without importing the whole windows.h?
You need to import the base functions. This will have a minor impact on compile times only. To make this smaller, you can use #define WIN32_LEAN_AND_MEAN before the #include.

See https://docs.microsoft.com/en-us/windows/win32/winprog/using-the-windows-headers

Last edited on
Using OS specific base functions for file i/o will be faster than using C++ streams

Not necessarily, I think.

std::fstream as well as FILE* streams have a built-in buffering mechanism. Conversely, low-level OS routines, like ReadFile() and WriteFile() on Windows, or read() and write() on Unix, do not have this kind of additional buffering! That is why using the low-level OS routines directly can be detrimental to performance! This will probably be most evident, if your code needs to perform a large number of small reads or writes. That is because, thanks to the buffering implemented by std::fstream and FILE*, multiple read/write operations in your program code will often be coalesced to just a single OS-level read/write operation (syscall).

(There still is some sort of buffering going on at the hardware and/or OS level, which is why system functions like FlushFileBuffers() an fsync() exist. But that's not the "user level" buffering I mean here)


How can you use a "base function" without importing the whole windows.h?

The Microsoft documentation tells you which header file you are supposed to include in order to use a certain Win32 API function, and that simply is Windows.h, most of the time:
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile

Trying to use a different include file than what the spec says could easily break with the next Windows SDK update, even if it somehow works now. So, just define WIN32_LEAN_AND_MEAN and include Windows.h :-)
Last edited on
> Using OS specific base functions for file i/o will be faster than using C++ streams

The OS functions can give better performance when an appropriate caching hint flag is used.
More information:
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#caching_behavior

Last edited on
Also need to ensure that comparisons are equal. ReadFile/WriteFile() will only read/write a specified number of bytes to/from a buffer. You can't directly read say a number stored in text format as a number.

Pages: 12