C++ WinAPI / POSIX API 封装:跨平台抽象层设计

好的,各位观众,欢迎来到今天的“C++跨平台API封装:优雅地避免重复造轮子”讲座。我是今天的搬砖工,啊不,讲师。今天咱们聊聊怎么在C++里封装WinAPI和POSIX API,搞出一个跨平台的抽象层,让你写的代码能在Windows、Linux、macOS上跑得飞起,而且不用对着不同的API文档抓狂。

第一部分:背景介绍与需求分析

咱们先来聊聊为啥要搞这个玩意儿。想象一下,你辛辛苦苦写了一个程序,用的是WinAPI,结果老板突然说:“小伙子,把这玩意儿搬到Linux上去!” 你顿时感觉眼前一黑,因为WinAPI在Linux上根本跑不起来啊!

这时候,你就需要一个跨平台的抽象层了。它可以让你用一套代码,在不同的平台上调用不同的API,就像一个翻译器,把你的代码翻译成不同平台的“方言”。

那么,我们需要一个什么样的抽象层呢?

  1. 易用性: 最好用起来像呼吸一样自然,别搞得太复杂,不然还不如直接用原生的API。
  2. 可扩展性: 以后要支持新的平台,或者要添加新的功能,应该很容易扩展。
  3. 性能: 别搞得太慢,不然用户会抱怨的。
  4. 类型安全: C++嘛,类型安全是很重要的,能避免一些奇奇怪怪的错误。

第二部分:设计思路与实现细节

我们的设计思路是这样的:

  1. 定义抽象接口: 定义一组通用的接口,比如文件操作、线程管理、网络通信等等。这些接口应该尽可能地通用,能在不同的平台上实现。
  2. 实现平台相关的类: 针对不同的平台,实现这些接口的具体类。这些类会调用平台原生的API。
  3. 使用工厂模式: 使用工厂模式来创建平台相关的对象。这样,你只需要指定平台,就能得到对应的对象,而不用关心具体的实现细节。

咱们先从一个简单的例子开始:文件操作。

2.1 文件操作的抽象

首先,我们定义一个抽象的文件操作接口:

// Abstract File Interface
class IFile {
public:
    virtual ~IFile() {}
    virtual bool open(const std::string& filename, int mode) = 0;
    virtual bool close() = 0;
    virtual size_t read(void* buffer, size_t size) = 0;
    virtual size_t write(const void* buffer, size_t size) = 0;
    virtual bool seek(long offset, int origin) = 0;
    virtual long tell() = 0;
    virtual bool isOpen() const = 0;
};

这里,我们定义了openclosereadwriteseektell等常用的文件操作。mode参数可以用来指定文件的打开模式,比如读、写、追加等等。origin参数可以用来指定seek操作的起始位置,比如文件头、当前位置、文件尾等等。

2.2 WinAPI的实现

接下来,我们实现一个基于WinAPI的文件操作类:

#ifdef _WIN32
#include <windows.h>

class WinFile : public IFile {
private:
    HANDLE m_hFile;
    bool m_isOpen;
public:
    WinFile() : m_hFile(INVALID_HANDLE_VALUE), m_isOpen(false) {}
    ~WinFile() { close(); }

    bool open(const std::string& filename, int mode) override {
        DWORD dwDesiredAccess = 0;
        DWORD dwShareMode = FILE_SHARE_READ;
        DWORD dwCreationDisposition = 0;

        if (mode & O_RDONLY) {
            dwDesiredAccess |= GENERIC_READ;
            dwCreationDisposition = OPEN_EXISTING;
        }
        if (mode & O_WRONLY) {
            dwDesiredAccess |= GENERIC_WRITE;
            dwCreationDisposition = CREATE_ALWAYS;
        }
        if (mode & O_RDWR) {
            dwDesiredAccess |= (GENERIC_READ | GENERIC_WRITE);
            dwCreationDisposition = OPEN_ALWAYS;
        }
        if (mode & O_APPEND) {
            dwDesiredAccess |= GENERIC_WRITE;
            dwCreationDisposition = OPEN_ALWAYS;
        }

        m_hFile = CreateFile(filename.c_str(), dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);

        if (m_hFile == INVALID_HANDLE_VALUE) {
            return false;
        }

        if (mode & O_APPEND) {
            seek(0, SEEK_END); // Move to end for append mode
        }

        m_isOpen = true;
        return true;
    }

    bool close() override {
        if (m_isOpen) {
            CloseHandle(m_hFile);
            m_hFile = INVALID_HANDLE_VALUE;
            m_isOpen = false;
            return true;
        }
        return false;
    }

    size_t read(void* buffer, size_t size) override {
        DWORD bytesRead;
        if (!ReadFile(m_hFile, buffer, (DWORD)size, &bytesRead, NULL)) {
            return 0;
        }
        return (size_t)bytesRead;
    }

    size_t write(const void* buffer, size_t size) override {
        DWORD bytesWritten;
        if (!WriteFile(m_hFile, buffer, (DWORD)size, &bytesWritten, NULL)) {
            return 0;
        }
        return (size_t)bytesWritten;
    }

    bool seek(long offset, int origin) override {
        DWORD dwMoveMethod;
        switch (origin) {
        case SEEK_SET:
            dwMoveMethod = FILE_BEGIN;
            break;
        case SEEK_CUR:
            dwMoveMethod = FILE_CURRENT;
            break;
        case SEEK_END:
            dwMoveMethod = FILE_END;
            break;
        default:
            return false;
        }

        LARGE_INTEGER liOffset;
        liOffset.QuadPart = offset;

        LARGE_INTEGER newPosition;
        if (!SetFilePointerEx(m_hFile, liOffset, &newPosition, dwMoveMethod)) {
            return false;
        }

        return true;
    }

    long tell() override {
        LARGE_INTEGER liOffset;
        liOffset.QuadPart = 0;

        LARGE_INTEGER newPosition;
        if (!SetFilePointerEx(m_hFile, liOffset, &newPosition, FILE_CURRENT)) {
            return -1;
        }

        return (long)newPosition.QuadPart;
    }

    bool isOpen() const override {
        return m_isOpen;
    }
};

#endif

这里,我们使用了WinAPI的CreateFileCloseHandleReadFileWriteFileSetFilePointerEx等函数来实现文件操作。注意,我们需要把标准C++的SEEK_SETSEEK_CURSEEK_END 映射到Windows的 FILE_BEGINFILE_CURRENTFILE_END

2.3 POSIX API的实现

然后,我们实现一个基于POSIX API的文件操作类:

#ifdef __linux__ || __APPLE__
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

class PosixFile : public IFile {
private:
    int m_fd;
    bool m_isOpen;
public:
    PosixFile() : m_fd(-1), m_isOpen(false) {}
    ~PosixFile() { close(); }

    bool open(const std::string& filename, int mode) override {
        int flags = 0;

        if (mode & O_RDONLY) {
            flags |= O_RDONLY;
        }
        if (mode & O_WRONLY) {
            flags |= O_WRONLY;
            flags |= O_CREAT | O_TRUNC;
        }
        if (mode & O_RDWR) {
            flags |= O_RDWR;
            flags |= O_CREAT;
        }
        if (mode & O_APPEND) {
            flags |= O_WRONLY | O_APPEND | O_CREAT;
        }

        m_fd = ::open(filename.c_str(), flags, 0666); // 0666 gives read/write permissions to owner, group, and others

        if (m_fd == -1) {
            return false;
        }

        m_isOpen = true;
        return true;
    }

    bool close() override {
        if (m_isOpen) {
            ::close(m_fd);
            m_fd = -1;
            m_isOpen = false;
            return true;
        }
        return false;
    }

    size_t read(void* buffer, size_t size) override {
        ssize_t bytesRead = ::read(m_fd, buffer, size);
        if (bytesRead == -1) {
            return 0;
        }
        return (size_t)bytesRead;
    }

    size_t write(const void* buffer, size_t size) override {
        ssize_t bytesWritten = ::write(m_fd, buffer, size);
        if (bytesWritten == -1) {
            return 0;
        }
        return (size_t)bytesWritten;
    }

    bool seek(long offset, int origin) override {
        off_t result = lseek(m_fd, offset, origin);
        if (result == (off_t)-1) {
            return false;
        }
        return true;
    }

    long tell() override {
        off_t result = lseek(m_fd, 0, SEEK_CUR);
        if (result == (off_t)-1) {
            return -1;
        }
        return (long)result;
    }

    bool isOpen() const override {
        return m_isOpen;
    }
};

#endif

这里,我们使用了POSIX API的openclosereadwritelseek等函数来实现文件操作。注意,我们需要把C++的O_RDONLYO_WRONLYO_RDWRO_APPEND 这些宏定义要保持一致。 如果不一致,需要转换。

2.4 工厂模式的实现

最后,我们实现一个工厂类,用来创建平台相关的对象:

// Factory Class
class FileFactory {
public:
    static IFile* createFile() {
#ifdef _WIN32
        return new WinFile();
#elif __linux__ || __APPLE__
        return new PosixFile();
#else
        // Default implementation (e.g., dummy implementation)
        return nullptr;
#endif
    }
};

这样,你就可以这样使用:

IFile* file = FileFactory::createFile();
if (file) {
    if (file->open("test.txt", O_RDWR)) {
        char buffer[1024];
        size_t bytesRead = file->read(buffer, sizeof(buffer));
        // ...
        file->close();
    }
    delete file;
}

2.5 一个更完善的例子:线程管理

文件操作只是个开胃菜,咱们再来个硬菜:线程管理。

// Abstract Thread Interface
class IThread {
public:
    virtual ~IThread() {}
    virtual bool create(void (*start_routine)(void*), void* arg) = 0;
    virtual bool join() = 0;
    virtual bool detach() = 0;
    virtual unsigned int getId() const = 0;
    virtual bool isValid() const = 0;
};

// Abstract Mutex Interface
class IMutex {
public:
    virtual ~IMutex() {}
    virtual bool lock() = 0;
    virtual bool unlock() = 0;
    virtual bool try_lock() = 0;
};

这里,我们定义了createjoindetach等线程操作,以及lockunlocktry_lock等互斥锁操作。

2.6 WinAPI线程的实现

#ifdef _WIN32
#include <windows.h>
#include <process.h>

class WinThread : public IThread {
private:
    HANDLE m_hThread;
    unsigned int m_threadId;
    bool m_isValid;

public:
    WinThread() : m_hThread(NULL), m_threadId(0), m_isValid(false) {}
    ~WinThread() {
        if(m_isValid) {
            CloseHandle(m_hThread);
        }
    }

    bool create(void (*start_routine)(void*), void* arg) override {
        m_hThread = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)start_routine, arg, 0, &m_threadId);
        if (m_hThread == NULL) {
            return false;
        }
        m_isValid = true;
        return true;
    }

    bool join() override {
        if (!m_isValid) return false;
        WaitForSingleObject(m_hThread, INFINITE);
        CloseHandle(m_hThread);
        m_isValid = false;
        return true;
    }

    bool detach() override {
        if (!m_isValid) return false;
        CloseHandle(m_hThread); // Detach means we don't wait for it
        m_isValid = false;
        return true;
    }

    unsigned int getId() const override {
        return m_threadId;
    }

    bool isValid() const override {
        return m_isValid;
    }
};

class WinMutex : public IMutex {
private:
    CRITICAL_SECTION m_criticalSection;
public:
    WinMutex() { InitializeCriticalSection(&m_criticalSection); }
    ~WinMutex() { DeleteCriticalSection(&m_criticalSection); }

    bool lock() override {
        EnterCriticalSection(&m_criticalSection);
        return true;
    }

    bool unlock() override {
        LeaveCriticalSection(&m_criticalSection);
        return true;
    }

    bool try_lock() override {
        return TryEnterCriticalSection(&m_criticalSection) != 0;
    }
};

#endif

2.7 POSIX线程的实现

#ifdef __linux__ || __APPLE__
#include <pthread.h>

class PosixThread : public IThread {
private:
    pthread_t m_thread;
    unsigned int m_threadId;
    bool m_isValid;

public:
    PosixThread() : m_thread(0), m_threadId(0), m_isValid(false) {}
    ~PosixThread() {
        // POSIX threads are automatically cleaned up on join/detach
    }

    bool create(void (*start_routine)(void*), void* arg) override {
        if (pthread_create(&m_thread, NULL, (void* (*)(void*))start_routine, arg) != 0) {
            return false;
        }
        m_threadId = (unsigned int)m_thread; // This is not a reliable way to get the thread ID, use syscall gettid() if needed
        m_isValid = true;
        return true;
    }

    bool join() override {
        if (!m_isValid) return false;
        void* result;
        pthread_join(m_thread, &result);
        m_isValid = false;
        return true;
    }

    bool detach() override {
        if (!m_isValid) return false;
        pthread_detach(m_thread);
        m_isValid = false;
        return true;
    }

    unsigned int getId() const override {
        return m_threadId;
    }

    bool isValid() const override {
        return m_isValid;
    }
};

class PosixMutex : public IMutex {
private:
    pthread_mutex_t m_mutex;
public:
    PosixMutex() { pthread_mutex_init(&m_mutex, NULL); }
    ~PosixMutex() { pthread_mutex_destroy(&m_mutex); }

    bool lock() override {
        return pthread_mutex_lock(&m_mutex) == 0;
    }

    bool unlock() override {
        return pthread_mutex_unlock(&m_mutex) == 0;
    }

    bool try_lock() override {
        return pthread_mutex_trylock(&m_mutex) == 0;
    }
};

#endif

2.8 线程相关的工厂模式

// Thread Factory Class
class ThreadFactory {
public:
    static IThread* createThread() {
#ifdef _WIN32
        return new WinThread();
#elif __linux__ || __APPLE__
        return new PosixThread();
#else
        return nullptr;
#endif
    }

    static IMutex* createMutex() {
#ifdef _WIN32
        return new WinMutex();
#elif __linux__ || __APPLE__
        return new PosixMutex();
#else
        return nullptr;
#endif
    }
};

使用例子:

void thread_function(void* arg) {
    // Do something
    IMutex* mutex = (IMutex*)arg;
    mutex->lock();
    // Critical section
    mutex->unlock();
}

int main() {
    IMutex* mutex = ThreadFactory::createMutex();
    IThread* thread = ThreadFactory::createThread();
    thread->create(thread_function, mutex);
    thread->join();

    delete thread;
    delete mutex;
    return 0;
}

第三部分:更高级的封装技巧

上面的例子只是简单的封装,实际项目中,我们可能需要更高级的封装技巧。

  1. RAII: 使用RAII来管理资源,避免内存泄漏和资源泄漏。例如,可以创建一个FileGuard类,在构造函数中打开文件,在析构函数中关闭文件。
  2. 模板: 使用模板来创建泛型类,可以处理不同类型的数据。例如,可以创建一个Buffer类,用来管理内存缓冲区。
  3. 异常处理: 使用异常处理来处理错误,可以使代码更简洁、更易读。
  4. 命名空间: 使用命名空间来避免命名冲突。

3.1 使用RAII封装文件操作

class FileGuard {
private:
    IFile* m_file;
    std::string m_filename;
    int m_mode;
    bool m_ownsFile;

public:
    FileGuard(const std::string& filename, int mode) : m_filename(filename), m_mode(mode), m_file(nullptr), m_ownsFile(true) {
        m_file = FileFactory::createFile();
        if (!m_file) {
            throw std::runtime_error("Failed to create file object");
        }
        if (!m_file->open(m_filename, m_mode)) {
            delete m_file;
            m_file = nullptr;
            throw std::runtime_error("Failed to open file: " + m_filename);
        }
    }

    FileGuard(IFile* file) : m_file(file), m_ownsFile(false){
        m_filename = "";
        m_mode = 0;
        if (!m_file || !m_file->isOpen())
            throw std::runtime_error("Invalid IFile object provided to FileGuard");
    }

    ~FileGuard() {
        if (m_file) {
            m_file->close();
            if (m_ownsFile)
                delete m_file;
        }
    }

    IFile* getFile() {
        return m_file;
    }
};

使用例子:

try {
    FileGuard file("test.txt", O_RDWR);
    IFile* f = file.getFile();
    char buffer[1024];
    size_t bytesRead = f->read(buffer, sizeof(buffer));
    // ...
} catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << std::endl;
}

第四部分:总结与展望

今天,我们聊了聊如何在C++中封装WinAPI和POSIX API,搞出一个跨平台的抽象层。我们从简单的文件操作开始,到复杂的线程管理,一步步地介绍了设计思路和实现细节。

当然,这只是一个简单的例子,实际项目中,我们可能需要封装更多的API,比如网络通信、图形界面等等。而且,我们还需要考虑更多的因素,比如性能、安全性、可维护性等等。

但是,只要我们掌握了基本的设计思路和实现技巧,就能轻松地应对各种挑战,写出高质量的跨平台代码。

最后,希望大家都能成为优秀的跨平台开发者,让我们的代码在不同的平台上闪耀光芒! 谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注