首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C++ 模板實踐:設計支援 ANSI/Unicode 雙版本的 Windows API 封裝

     編輯:關於C

問題背景

Windows API 多數提供 ANSI (xxxA) 與 Unicode (xxxW) 兩種版本。封裝自有 API 時,傳統做法有二:

  1. 單版本實作 + 轉換:實作 A 版,W 版內部轉碼後呼叫 A 版 (或反之) — **效率低**。
  2. 雙版本平行實作:A/W 版各自呼叫對應底層 API — **程式碼大量重複**。

本文展示如何利用 **函數模板 + 特徵萃取** 同時達成 **零轉換效率** 與 **零重複程式碼**。

核心基礎設施:select_trait

// 布林特徵基礎巨集
#define TEMPLATE_BOOL_TRAIT_DEF1(trait, T, c) \
template<typename T> struct trait { static const bool value = c; };
#define TEMPLATE_BOOL_TRAIT_SPEC1(trait, sp, c) \
template<> struct trait<sp> { static const bool value = c; };

// 類型選擇
template<bool flag, typename T1, typename T2> struct select_type;
template<typename T1, typename T2> struct select_type<true, T1, T2> { typedef T1 type; };
template<typename T1, typename T2> struct select_type<false, T1, T2> { typedef T2 type; };

// 變數/函數指標選擇 (關鍵:利用多載解析)
template<bool flag, typename T1, typename T2>
inline typename select_type<flag, T1, T2>::type select_variable(T1 t1, T2 t2) {
    return select_variable_impl(typename select_type<flag, int, long>::type(), t1, t2);
}
template<typename T1, typename T2> inline T1 select_variable_impl(int, T1 t1, T2 t2) { return t1; }
template<typename T1, typename T2> inline T2 select_variable_impl(long, T1 t1, T2 t2) { return t2; }

// 字元類型特徵:char 為 ANSI,wchar_t 為 Unicode
TEMPLATE_BOOL_TRAIT_DEF1(is_ansi_char, T, false)
TEMPLATE_BOOL_TRAIT_SPEC1(is_ansi_char, char, true)
TEMPLATE_BOOL_TRAIT_SPEC1(is_ansi_char, const char, true)
TEMPLATE_BOOL_TRAIT_SPEC1(is_ansi_char, volatile char, true)
TEMPLATE_BOOL_TRAIT_SPEC1(is_ansi_char, const volatile char, true)
#undef TEMPLATE_BOOL_TRAIT_DEF1
#undef TEMPLATE_BOOL_TRAIT_SPEC1

三大應用場景示範

1. 參數類型化:路徑判斷目錄/檔案

template<typename CharT>
inline int IsDirectoryOrFile(const CharT* path) {
    DWORD dwFlag = select_variable<is_ansi_char<CharT>::value>(GetFileAttributesA, GetFileAttributesW)(path);
    if (dwFlag == INVALID_FILE_ATTRIBUTES) return 0;
    return (dwFlag & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 2;
}

2. 回傳值類型化:取得執行檔路徑

template<typename T>
inline std::basic_string<T> GetExePath() {
    T szExePath[MAX_PATH];
    select_variable<is_ansi_char<T>::value>(GetModuleFileNameA, GetModuleFileNameW)(NULL, szExePath);
    return szExePath;
}

3. 內部實現類型化:遞迴計算目錄大小

template<typename CharT>
inline ULONGLONG GetDirSize(const CharT* path) {
    int ret = IsDirectoryOrFile(path);
    if (ret == 0) return 0;

    std::basic_string<CharT> strPath = path;
    if (ret == 1) { // 目錄
        if (strPath.back() != '\\') strPath += '\\';
        strPath += select_variable<is_ansi_char<CharT>::value>("*.*", L"*.*");
    }

    ULONGLONG sum = 0;
    // 選擇正確的 WIN32_FIND_DATA 結構
    typename select_type<is_ansi_char<CharT>::value, WIN32_FIND_DATAA, WIN32_FIND_DATAW>::type findData;
    HANDLE hFind = select_variable<is_ansi_char<CharT>::value>(FindFirstFileA, FindFirstFileW)(strPath.c_str(), &findData);

    for (BOOL bRes = (hFind != INVALID_HANDLE_VALUE); bRes; 
         bRes = select_variable<is_ansi_char<CharT>::value>(FindNextFileA, FindNextFileW)(hFind, &findData)) {
        if (findData.cFileName[0] == '.') continue;
        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            strPath = strPath.substr(0, strPath.rfind('\\') + 1) + findData.cFileName;
            sum += GetDirSize(strPath.c_str());
        } else {
            sum += (((ULONGLONG)findData.nFileSizeHigh) << 32) + findData.nFileSizeLow;
        }
    }
    FindClose(hFind);
    return sum;
}

設計精華總結

維度傳統雙版本模板特徵法
程式碼重複高 (兩份完整函數)**零** (單一模板)
執行效率高 (無轉換)**高** (編譯期解析,零轉換)
型別安全易出錯 (巨集切換)**強** (模板參數推導)
擴充性新增 API 需改兩處**單點維護**

適用原則

  • 當 API 族群存在 **固定的雙版本對應關係** (A/W、32/64、不同廠商 SDK) 時。
  • 內部邏輯相同,僅 **型別、常數、API 名稱** 隨版本變化。
  • 以 is_ansi_char 等特徵為 **單一真實來源**,避免巨集汙染全域命名空間。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved