2015-10-30 23 views
1

後來,我爲Windows API函數編寫了一個簡單的包裝類。我編寫了一組單元測試來驗證該類生成的結果是否與直接調用API相匹配。在線程中使用GetLastError

最近我回過頭來添加有關在不同線程中使用包裝類的單元測試。我發現::GetLastError函數存在一些問題。據MSDN功能應該保留的最後一個錯誤代碼每個線程:

檢索調用線程的最後一個錯誤代碼值。最後的錯誤代碼以每個線程爲基礎進行維護。多線程不會覆蓋彼此的最後一個錯誤代碼。

我發現在某些情況下,最後的錯誤代碼實際上變成了零。我已成功地複製問題的單元測試之外與下面簡單的程序:

#include "stdafx.h" 

#include <condition_variable> 
#include <mutex> 
#include <thread> 
#include <Windows.h> 

int main(int argc, char* argv[]) 
{ 
    ::DWORD setError1 = 123; 
    ::DWORD setError2 = 456; 

    // scenario 1 - show that main thread not polluted by sub-thread 
    const auto act1 = [](::DWORD errorNo) 
    { 
     ::SetLastError(errorNo); 
     const auto a = ::GetLastError(); // a = 123 
    }; 

    ::SetLastError(setError2); 
    const auto b = ::GetLastError(); // b = 456 
    const auto c = ::GetLastError(); // c = 456 
    std::thread sub1(act1, setError1); 
    sub1.join(); 
    const auto d = ::GetLastError(); // d = 0 - WHY??? 


    // scenario 2 - show that sub thread not polluted by main thread 
    std::condition_variable conditional; 
    std::mutex mutex; 
    bool flag = false; 
    ::DWORD e; 
    const auto act2 = [&mutex, &flag, &e, &conditional](::DWORD errorNo) 
    { 
     std::unique_lock<std::mutex> lock(mutex); 
     ::SetLastError(errorNo); 
     conditional.wait(lock, [&flag] { return flag; }); 
     e = ::GetLastError(); // e = 456 in Windows 8.1, 0 in Windows 10.0.10240.0 - WHY??? 
    }; 

    std::thread sub2(act2, setError2); 
    { 
     std::lock_guard<std::mutex> guard(mutex); 
     ::SetLastError(setError1); 
     flag = true; 
    } 
    conditional.notify_all(); 
    sub2.join(); 
    const auto f = ::GetLastError(); // f = 123; 
    return 0; 
} 

我遇到的問題是與de

  • d - 我發現,最後主線程的錯誤代碼在使用子線程時看到復位爲零。

  • ë - 當使用Windows 10 SDK,我發現子線程看到最後一個錯誤的復位至零,而守候在std::condition_variable。這在使用Windows 8.1 SDK時不會重置。

有人可以幫助解釋我看到的結果嗎?這是Windows API中的一個錯誤,是微軟C++實現中的一個錯誤?或者在我自己的代碼中的錯誤?

回答

4

這很簡單。以下是發生了什麼情況:

  1. 您致電SetLastError
  2. 您調用另一個函數,該函數又調用SetLastError
  3. 你叫GetLastError並獲得最新的價值,這是該值在第2項設置,而不是在第1項

所以,看你的代碼,我的註解:

::SetLastError(setError2);   // item 1 from the list above 
std::thread sub1(act1, setError1); // item 2 
sub1.join();      // item 2 
const auto d = ::GetLastError(); // item 3 

還有你的另一例子:

::SetLastError(errorNo);       // item 1 
conditional.wait(lock, [&flag] { return flag; }); // item 2 
e = ::GetLastError();        // item 3 

在每一種情況下,你的代碼,我已經標註爲項目2正在調用API函數,後者又調用SetLastError

+0

因此微軟的標準庫的實現,或者至少它的線程實現,使用Windows API? – camelCase

+4

當然。不通過Windows API就無法創建線程。同樣也適用於同步對象。這些將在可能的情況下在系統對象之上實現。 –

+1

^^大衛說。I/O,線程,線程間同步等必須由操作系統管理,否則會出現混亂。如果您通過庫明確或間接地進行API調用,並且失敗,則應該在下一步調用GetLastError,否則可能會將錯誤信息丟失給另一個成功的API。 –

相關問題