locked
Writing lines to a file in c++ - what works? What's best? Question with WriteTextAsync

    Question

  • I tried to add some file logging capabilities to my test Metro app.  It was annoying to port working C++ code that wrote to a file, compiled fine, but would not run under Metro - returning an access error. I then figured out that perhaps it was a Metro restriction, and found the file IO sample. All well and good, except it's not clear how to set it up so that I can write successive lines of text - plus the async operation make it seem like to be correct, I'd have to store each line of text in an array and then fire off an async write to empty each array line so that I buffer the writes since I can't know that when I want to write line N+1, line N is still queued up to write.

    Here's some code that sorta works, but the 2nd write never happens. (Plus it's not clear if I should be using the .then or the .wait operators if I really don't have anything else to do, so I'm showing both variations.)

    So what is the best way to writing out chunks of text? How do I close the file when I'm done?

         task<StorageFile^>(KnownFolders::DocumentsLibrary->CreateFileAsync("sample.log", CreationCollisionOption::ReplaceExisting)).then([this](StorageFile^ file)
        {
            _sampleFile = file; // member var
    
    
         task<void>(FileIO::WriteTextAsync(_sampleFile, L"Yee Ha")).then([]() 
    		{
    		});
    
    
    	 task<void>(FileIO::WriteTextAsync(_sampleFile, "Boo Hoo")).wait();
    
    
    // what if I just want to write the text
    // and prevent another write till the previous is done?



    Wednesday, March 28, 2012 8:15 PM

Answers

  • An Example:

    	using namespace std;
    	using namespace concurrency;
    	using namespace Platform;
    	using namespace Windows::Foundation::Collections;
    	using namespace Windows::Storage;
    	using namespace Platform::Collections;
    
    	auto t = task<StorageFile^>(KnownFolders::DocumentsLibrary->CreateFileAsync("sample.log", CreationCollisionOption::ReplaceExisting)).then([&](StorageFile^ file)
    	{
    		std::array<String^,2> strings = {ref new  String(L"Yee Ha\r\n"), ref new String(L"Boo Hoo\r\n")};
    		task<void>(FileIO::AppendLinesAsync(file, ref new VectorView<String^>(strings))).wait();
    
    		return file;
    	});

    Or you could use vectors

    auto t2 = task<StorageFile^>(KnownFolders::DocumentsLibrary->CreateFileAsync("sample.log", CreationCollisionOption::ReplaceExisting)).then([&](StorageFile^ file)
    	{
    		std::vector<String^> strings;
    		strings.push_back(ref new  String(L"Yee Ha\r\n"));
    		strings.push_back(ref new String(L"Boo Hoo\r\n"));
    
    		task<void>(FileIO::AppendLinesAsync(file, ref new VectorView<String^>(strings))).wait();
    
    		return file;
    	});


    Whether you want to wait on the internal task depends on whether you want the continuation (the .then()) to be synchronous or asynchronous. The file will close when the StorageFile^ goes out of scope and it's destructor is called. You can also do this by setting it's value to nullptr.

    Here's the headers I used

    #include <ppltasks.h> 
    #include <vector>
    #include <array>
    #include <collection.h>


    -Steve

    Friday, March 30, 2012 1:43 AM
    Moderator

All replies

  • There is no order access, only have IRandomAccessStream

    http://msdn.microsoft.com/en-us/library/windows/apps/br241791.aspx

    You can store into an array, and store it once.


    NEU_ShieldEdge

    Thursday, March 29, 2012 11:27 AM
  • Take a look at MainPage::Scenario4WriteToStream_Click in the File access sample . You may also want to review the PPL documentation if you haven't used it previously. The existing PPL documentation applies as well as the additions for Metro style apps:

    Concurrency Runtime

    and

    Creating Asynchronous Operations in C++ for Metro style Apps


    David Lamb

    Friday, March 30, 2012 1:27 AM
    Moderator
  • An Example:

    	using namespace std;
    	using namespace concurrency;
    	using namespace Platform;
    	using namespace Windows::Foundation::Collections;
    	using namespace Windows::Storage;
    	using namespace Platform::Collections;
    
    	auto t = task<StorageFile^>(KnownFolders::DocumentsLibrary->CreateFileAsync("sample.log", CreationCollisionOption::ReplaceExisting)).then([&](StorageFile^ file)
    	{
    		std::array<String^,2> strings = {ref new  String(L"Yee Ha\r\n"), ref new String(L"Boo Hoo\r\n")};
    		task<void>(FileIO::AppendLinesAsync(file, ref new VectorView<String^>(strings))).wait();
    
    		return file;
    	});

    Or you could use vectors

    auto t2 = task<StorageFile^>(KnownFolders::DocumentsLibrary->CreateFileAsync("sample.log", CreationCollisionOption::ReplaceExisting)).then([&](StorageFile^ file)
    	{
    		std::vector<String^> strings;
    		strings.push_back(ref new  String(L"Yee Ha\r\n"));
    		strings.push_back(ref new String(L"Boo Hoo\r\n"));
    
    		task<void>(FileIO::AppendLinesAsync(file, ref new VectorView<String^>(strings))).wait();
    
    		return file;
    	});


    Whether you want to wait on the internal task depends on whether you want the continuation (the .then()) to be synchronous or asynchronous. The file will close when the StorageFile^ goes out of scope and it's destructor is called. You can also do this by setting it's value to nullptr.

    Here's the headers I used

    #include <ppltasks.h> 
    #include <vector>
    #include <array>
    #include <collection.h>


    -Steve

    Friday, March 30, 2012 1:43 AM
    Moderator
  • So a lot of the answers seem to miss the fact that I want to intermittently write out to the file, not store everything up and then create some extraneous event to actually trigger writing out the collected strings. I just want to log data to a file ad hoc. David's response led me to the File Access Sample, which is kinda what I wanted. At least it let's me write intermittently to the file. A data logging file is just going to keep getting written to - so there's no 'end' event to trigger, so I need to get it flushed regularly. This is the workable (but not pretty) solution I came up with.

    First you need to create the file, and create a dataWriter somwhere in you initialization code

            // create log file and datawriter
            task<StorageFile^>(KnownFolders::DocumentsLibrary->CreateFileAsync("sample.log", CreationCollisionOption::ReplaceExisting)).then([this](StorageFile^ file)
            {
                _sampleFile = file;
                task<IRandomAccessStream^>(_sampleFile->OpenAsync(FileAccessMode::ReadWrite)).then([this](IRandomAccessStream^ writeStream)
                {
    	                _dataWriter = ref new DataWriter(writeStream);
                });
            });
        );

    The data writer is the conduit for stuffing stuff in the file. I'm not terribly happy about having to create task that store and flush asyncronously just to write a one or more strings, but at least this behaves the way I want. When you want to write something out I do something like this.

                if ( _dataWriter ) // hopefully it's been created
                {
                    // hacky but it works for now
                    wchar_t info[100];
                    swprintf(info, L"%f %d\r\n",msPerFrame*1000.0f,numberRendered);
                    // need to keep WinRT happy with a refcounted String
                    String^ fred = ref new String(info);
                   // write it 
                   _dataWriter->WriteString(fred);
                   // you typically only need to do this occasionally to flush the buffer
                    task<unsigned int>(_dataWriter->StoreAsync()).then([this](unsigned int bytesWritten)
                    {
                        _dataWriter->FlushAsync();
                    });
                }
    Yeah the char to String bit is hacky, but it's good enough for now. It would be nice if there were a nonAsync Store function because I really don't think it'll take that long to write the string out. Also note that I don't Close the  dataWriter or the File - as I can't think of a good place to put them in a Metro app.

    Ron



    • Edited by Ron Fosner Tuesday, April 10, 2012 9:49 PM
    Friday, April 06, 2012 7:12 PM
  • In my own code, I'm using wfopen_s() and fwrite()/fread() without errors on plain old FILE*, but you have to do so into the appdata folder:

    StorageFolder^ localFolder = ApplicationData::Current->LocalFolder;
    create_task(localFolder->GetFolderAsync(foldername)).then([](StorageFolder^ f) { 
    	wchar_t filename[MAX_PATH];
    	wcscpy(filename, f->Path->Data());
    	wcscat(filename, L"\\somefile.dat");
    	FILE* fh = NULL;
    	errno_t err = _wfopen_s(&fh, filename, L"rb");
    	char buffer[2048];
    	fread(buffer,1,2048,fh);
    	fclose(fh);
    });

    It's not in the "don't lock the UI with synchronous code" spirit, but it works for me ...

    Note that you can't do this in Document folder, even if you declare the capability and add the file association. It makes an access denied.


    Tuesday, June 19, 2012 2:31 PM
  • That's good to know Francis - thanks. I would have though tye would have closed off fread/fwrite just to prevent folks from using serial code.

    Ron

    Tuesday, June 19, 2012 7:12 PM
  • I put this code into a function returning a task, so I can call it without disturbing the UI thread. I bet on the dual cores so the file reading doesn't make the rest of Win8 unresponsive...

    Maybe in a second version after my deadline, I will convert that to cool async open/read with .then() sequences, but I'm in a hurry and happy to find my old friends back, although I had to convert all the strings, filenames, parameters, members, function calls and constants to Unicode. (Like strcpy -> wcscpy.)

    Wednesday, June 20, 2012 7:51 AM
  • Not working on ARM, or MS surface. Always get "statechanged"exception.

    rob qqq

    Thursday, January 24, 2013 4:44 PM
  • So developing a Metro app, I myself suddenly discovered that my perfectly simple compiled old school debug logger blows up in my face under Metro in a very unglamorous way. Fine. I've moved on, I accepted that. Time to learn the new way, right?

    Ron's example is the closest I can come to understanding the best way to implement it, so I used it as an example. I could not make much sense of the File Access Example referenced above with a lot more complexity than what I'm looking for.

    Anyways, I want to write a very simple generic logger -- an intermittent one. The idea is to encapsulate it into a class that creates the dataWriter, then later, I just write to it. I want to be able to log to different files during the application's life, and keep it flexible. Once I get the basic case working, I'll throw into a singleton that manages the different datawriters and creates them as needed, so the actual logging code is super simple. 

    But I am getting incredibly obscure errors and I don't understand how to fix it. Any help would be appreciated.

    Header

    class LogToFileAsync
    {
    private:
    	LogToFileAsync(Platform::String ^ filename);
    	Windows::Storage::StorageFile^ m_file;
    	Windows::Storage::Streams::DataWriter^ m_dataWriter;
    public:
    	void write(WCHAR *str, ...);
    };

    Includes

    #include <collection.h>
    using namespace concurrency;
    using namespace Platform;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Storage;

    Constructor

    LogToFileAsync::LogToFileAsync(Platform::String ^ filename)
    {
        task<StorageFile^>(ApplicationData::Current->LocalFolder->CreateFileAsync(filename,CreationCollisionOption::OpenIfExists)).then([this](StorageFile^ file)
        {
            m_file = file;
            task<Windows::Storage::Streams::IRandomAccessStream^>(m_file->OpenAsync(FileAccessMode::ReadWrite))
    			.then([this](Windows::Storage::Streams::IRandomAccessStream^ writeStream)
            {
    	            m_dataWriter = ref new Windows::Storage::Streams::DataWriter(writeStream);
            });
        });
    }


    write function

    void LogToFileAsync::write(WCHAR *str, ...)
    {
        static int logCount = 0;
        if( m_dataWriter )
        {
            WCHAR info[1024];
    	va_list arglist;
    	va_start(arglist, str);
    	_vsnwprintf_s(info,1024,str,arglist);
    	va_end(arglist);
    
    	String^ logMsg = ref new String(info);
            m_dataWriter->WriteString(logMsg);
            task<unsigned int>(m_dataWriter->StoreAsync()).then([this](unsigned int bytesWritten)
            {
                m_dataWriter->FlushAsync();
            });
        }
    }

    Use Case

    LogToFileAsync manifestLog("manifest.txt");
    manifestLog.write(L"Missing Asset with id %llu\r\n",objectId);
    

    Forgive any obvious compile errors -- as it blows up super early for me. Once I get past this, I'll post my final solution.

    The error I am getting is task<> line of the construction:

    3>------ Build started: Project: myproject, Configuration: DebugWin8 x64 ------
    3>  LogToFileAsync.cpp
    3>..\Source\Debug\LogToFileAsync.cpp(14): error C2649: '__abi_IDelegate' : is not a '__interface'
    3>          ..\Source\Debug\LogToFileAsync.cpp(14) : see reference to class generic instantiation 'Windows::Foundation::TypedEventHandler<TSender,TResult>' being compiled
    3>          with
    3>          [
    3>              TSender=Windows::Storage::ApplicationData ^,
    3>              TResult=Platform::Object ^
    3>          ]
    3>          This diagnostic occurred while importing type 'Windows::Storage::IApplicationData ' from assembly 'Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null'.
    3>          This diagnostic occurred while importing type 'Windows::Storage::ApplicationData ' from assembly 'Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null'.
    3>..\Source\Debug\LogToFileAsync.cpp(14): fatal error C1903: unable to recover from previous error(s); stopping compilation

    This is where I am looking for help -- I can't find anything on the internet that points me in the right direction :)

    Wednesday, February 27, 2013 12:30 AM
  • None of that async stuff is necessary. It's redundant because Kernel will already cache written data and send it to disk asynchronously in the background. Follow Francis' code as an example:

    1. At app startup, get the directory for the log file into a wchar_t and append the log file part to it

    2. Create a CriticalSection so that multiple threads can write to the log file without stomping on each other

    3. Nuke the existing logfile if you always want to start clean

    Create a few simple functions to write strings, fprintf stuff, etc. to the logfile. In those functions, grab the CriticalSection before manipulating the file. Use the simple fopen( wzLogFile, "ab" ) C runtime function to open the log file, write to it, then fclose(). An alternative is to keep the file open all the time and fflush() it after writing to it.

    I don't know why the WinRT designers polluted their APIs with all the Async junk. It is *not* necessary with typical local file access Win8. If you're working with large numbers of files or with large files or nonlocal files then you would thread their access just like you would in a Desktop program. For a simple log file it is massive overkill.

    Wednesday, February 27, 2013 2:03 AM