使用 Win32 API 函数 ReadFile 和 WriteFile 将代码迁移到 Linux 的正确方法

The right way to migrate a code using Win32 API functions ReadFile and WriteFile to Linux

提问人:Dmitriano 提问时间:10/6/2022 更新时间:10/6/2022 访问量:121

问:

我有一个使用 Win32 API 函数的自定义流实现,例如 、 。此外,流实现和函数不受 (它刷新其内部缓冲区,但不刷新操作系统缓冲区):::CreateFile2::ReadFile::WriteFileFlushTruncatestd::fstreamflush()

class WinStream : public IoStream
{
public:

    size_t Read(uint8_t* buffer, size_t count) override
    {
        const DWORD nNumberOfBytesToRead = static_cast<DWORD>(count);
        assert(nNumberOfBytesToRead == count);
        DWORD NumberOfBytesRead = 0;

        Check(::ReadFile(m_hFile, buffer, nNumberOfBytesToRead, &NumberOfBytesRead, NULL) != FALSE);

        return NumberOfBytesRead;
    }

    void Write(const uint8_t* buffer, size_t count) override
    {
        const DWORD nNumberOfBytesToWrite = static_cast<DWORD>(count);
        assert(nNumberOfBytesToWrite == count);
        DWORD NumberOfBytesWritten = 0;

        if (::WriteFile(m_hFile, buffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, NULL) == FALSE)
        {
            throw IoError(format()
                << _T("::WriteFile failed. This may indicate that the disk is full. Win32 Error: ")
                << ::GetLastError());
        }

        if (nNumberOfBytesToWrite != NumberOfBytesWritten)
        {
            throw IoError(format() << _T("Requested ") << nNumberOfBytesToWrite
                << _T(" bytes, but actually written ") << NumberOfBytesWritten << _T("."));
        }

    bool End() override
    {
        return GetFileSizeHelper() == GetFilePointerHelper();
    }

    void Seek(std::size_t pos, bool begin = true) override
    {
        LARGE_INTEGER li;

        li.QuadPart = pos;

        Check(::SetFilePointerEx(m_hFile, li, NULL, begin ? FILE_BEGIN : FILE_END) != INVALID_SET_FILE_POINTER);
    }

    void Move(std::ptrdiff_t offset) override
    {
        LARGE_INTEGER li;

        li.QuadPart = offset;

        Check(::SetFilePointerEx(m_hFile, li, NULL, FILE_CURRENT) != INVALID_SET_FILE_POINTER);
    }

    void Flush() override
    {
        Check(::FlushFileBuffers(m_hFile) != FALSE);
    }

    void Truncate() override
    {
        Check(::SetEndOfFile(m_hFile) != FALSE);
    }

private:

    void Check(bool success)
    {
        if (!success)
        {
            throw Win32Exception();
        }
    }
    
    LONGLONG GetFileSizeHelper()
    {
        LARGE_INTEGER li;

        li.QuadPart = 0;

        Check(::GetFileSizeEx(m_hFile, &li) != FALSE);

        return li.QuadPart;
    }

    LONGLONG GetFilePointerHelper()
    {
        LARGE_INTEGER liOfs = { 0 };
        LARGE_INTEGER liNew = { 0 };

        Check(::SetFilePointerEx(m_hFile, liOfs, &liNew, FILE_CURRENT) != INVALID_SET_FILE_POINTER);

        return liNew.QuadPart;
    }

    FileHandle m_hFile;
}

inline UniqueFileHandle CreateUniqueFile(const String& file_name)
{
    HANDLE hFile = ::CreateFile2(
        file_name.c_str(),
        GENERIC_READ | GENERIC_WRITE,
        0, //FILE_SHARE_READ | FILE_SHARE_WRITE,
        OPEN_ALWAYS,
        NULL //&extendedParams
    );

    if (hFile == INVALID_HANDLE_VALUE)
    {
        DWORD dw_err = ::GetLastError();

        throw IoError(format() << _T("Cannot open file ')" << file_name << "' for updating, error = " << dw_err));
    }

    return hFile;
}

将此代码迁移到 Linux 的正确(或现代)方法是什么?那么 Android、MacOS 和 iOS 呢?

它应该使用非缓冲读/写函数。

C++ Linux IO Fstream

评论

2赞 Homer512 10/6/2022
由于您想要无缓冲操作,并且已经有一个可以处理生命周期的专用类,因此您可以简单地将 POSIX 文件描述符直接与 、 、 等一起使用。所有类 Unix 系统(如 Android 和 iOS)都支持这些。如果您想要缓冲 I/O,使用旧的 I/O 效果很好,因为它带有线程安全缓冲区,并允许您通过以下方式访问原始文件描述符openwritereadfsyncFILEfileno(FILE*)

答:

0赞 cs1349459 10/6/2022 #1

正如 Homer512 所说,您应该使用 POSIX 文件函数,例如 、 、 和 。您可以在此处找到这些函数的参考。fopenfwritefclosefread

我个人更喜欢让代码在许多系统上工作,即使用预处理器指令,并为每个操作系统提供代码。可以在此处找到 os 指令的完整列表。

若要分离操作系统的代码,可以执行如下操作:

#ifdef   _WIN32
    //Windows code here
#elifdef __APPLE__
    //MacOS code here
#elifdef __linux__
    //Linux code here
#endif

然后,您可以使用一组特定的函数,例如打开文件、写入文件,然后使用其特定方式在不同的系统上定义它们。您也可以轻松地将其扩展到其他操作系统。

评论

0赞 Dmitriano 10/6/2022
没有 ,en.cppreference.com/w/c/io 页面上,它们是 POSIX 函数,但不是标准的 C++ 函数。openreadwrite