How to append text to file in a UWP app

2019-06-11 11:59发布

问题:

I need to create a simple log class (just a "save to file" method) in a UWP app for debugging purpose but AppendTextAsync is much different from ofstream and I don't know how to use. This is my class

#pragma once
ref class winRTLog sealed
{
public:
    winRTLog();
    void save(Platform::String^ log);
private:
    Platform::String^ myFilename = L"myLog.txt";
    Windows::Storage::StorageFolder^ myFolder;
    Windows::Storage::StorageFile^ myFile;
    Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^ createMyFile;
    concurrency::task<Windows::Storage::StorageFile^> myFileTask;
};

and here's the mess I've written so far trying to understand the documentation

#include "pch.h"
#include <ppltasks.h>   
#include "winRTLog.h"

winRTLog::winRTLog()
{
    myFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
    createMyFile = myFolder->CreateFileAsync(myFilename, Windows::Storage::CreationCollisionOption::OpenIfExists);
    myFileTask = concurrency::create_task(myFolder->GetFileAsync(myFilename));
    //how to make myFile point the file I've created?
}

void winRTLog::save(Platform::String^ log)
{
    //?? 
    myFileTask.then([&]()
    {
        //how to use Windows::Storage::FileIO::AppendTextAsync
        //with myFile and log?
    });
}

回答1:

Don't use StorageFile APIs for saving data into local folder. Only use it if you need to: for APIs that require you passing them in a storage file or to access a place like pictures library which doesn't have a real path. StorageFile APIs are terribly slow compared to traditional APIs. Worst of all, all of the file operations are asynchronous, which means they're a pain to work with and even a bigger pain to debug.

For your scenario, I'd just use std::wofstream if you're familiar with it:

#include <fstream>

class winRTLog
{
public:
    winRTLog(Platform::String^ fileName);
    void save(Platform::String^ log);

private:
    std::wofstream m_OutStream;
};

winRTLog::winRTLog(Platform::String^ fileName) :
    m_OutStream(std::wstring(Windows::Storage::ApplicationData::Current->LocalFolder->Path->Data()) + L"\\" + fileName->Data(), std::ios::app)
{
    if (!m_OutStream.is_open())
        throw std::runtime_error("Failed to open the log file.");
}

void winRTLog::save(Platform::String^ log)
{
    m_OutStream << log->Data();
    if (m_OutStream.fail())
        throw std::runtime_error("Failed to write to the log file.");
}

Alternatively, you can use Win32 file APIs:

#include <memory>
#include <string>
#include <windows.h>

class winRTLog
{
public:
    winRTLog(Platform::String^ fileName);
    ~winRTLog();

    void save(Platform::String^ log);

private:
    HANDLE m_LogHandle;
};

winRTLog::winRTLog(Platform::String^ fileName)
{
    auto filePath = std::wstring(Windows::Storage::ApplicationData::Current->LocalFolder->Path->Data()) + L"\\" + fileName->Data();
    m_LogHandle = CreateFile2(filePath.c_str(), GENERIC_WRITE, 0, OPEN_ALWAYS, nullptr);
    if (m_LogHandle == INVALID_HANDLE_VALUE)
        throw std::runtime_error("Failed to open the log file: error code " + std::to_string(GetLastError()));

    if (SetFilePointer(m_LogHandle, 0, nullptr, FILE_END) == INVALID_SET_FILE_POINTER)
        throw std::runtime_error("Failed to set file pointer to the end of file: error code " + std::to_string(GetLastError()));
}

winRTLog::~winRTLog()
{
    if (m_LogHandle != INVALID_HANDLE_VALUE)
        CloseHandle(m_LogHandle);
}

void winRTLog::save(Platform::String^ log)
{
    // Convert to UTF8
    std::string utf8;
    utf8.resize(4 * log->Length());

    auto utf8Length = WideCharToMultiByte(CP_UTF8, 0, log->Data(), static_cast<int>(log->Length()), &utf8[0], static_cast<int>(4 * log->Length()), nullptr, nullptr);
    if (utf8Length == 0)
        throw std::runtime_error("Failed to convert log message to UTF8: error code " + std::to_string(GetLastError()));

    utf8.resize(utf8Length);

    // Write to actual log
    DWORD bytesWritten;
    auto writeResult = WriteFile(m_LogHandle, utf8.data(), static_cast<DWORD>(utf8.length()), &bytesWritten, nullptr);

    if (writeResult == FALSE || bytesWritten != utf8.length())
        throw std::runtime_error("Failed to write log message to the log file: error code " + std::to_string(GetLastError()));
}

Note, I used throwing std::runtime_error for error handling for example purposes: you might want to do it differently.



标签: c++-cli uwp