FC2ブログ

[Win32API][Winsock][Development]WSAGetLastError()

 Windowsのネイティブで開発をしている(.NET 上のCLRではなく)と、エラーコードが、「誰の」エラーコードであるかということを理解していないとデバッグに困ってしまう。
 WSAGetLastError()は、このSocket APIを呼び出したスレッド上でのWinsockエラーコードのようだ。つまり内部的には、TLS(Thread LocalStorage)にエラーコードを保存しているように振舞っている。
 これは、プロセス単位のエラーコードではないということだ。スレッドAの上でWinsockエラーが起きた直後に、スレッドB上でWSAGetLastError()を実行しても、信頼できるエラーコードが取得できないということを意味する。
 .NET FrameworkやMFCのメッセージマップを使っていたり、スレッドプールをバリバリ使っているようなプログラムだと、とくに問題になりそうな予感がする。
 エラー処理をする際には、「誰が」エラーを引き起こしたのか、「誰の」エラーコードを参照したのか、ということを意識しなければならない。スレッド構成が複雑なプログラムであればなおのことこれは難しい問題となるだろう。
 だからこそ、マルチスレッドは難しい、と言われるゆえんなのだが。
#pragma once

#include <winsock2.h>
#include <windows.h>
#include <process.h>

#pragma comment(lib,"ws2_32.lib")

unsigned int ThreadFunc(void)
{
unsigned int iResult=0;
SOCKET sock;
char Message[256];
DWORD dwErrorCode=0;
DWORD dwWrittenLen=0;


// AF_UNSPEC:未定義のアドレスファミリー:socket()に失敗する
sock=socket(AF_UNSPEC,SOCK_STREAM,0);
if(sock!=INVALID_SOCKET)
{
closesocket(sock);
} else
{
// ソケットの初期化に失敗すると、以下のエラー処理が開始される。
dwErrorCode=WSAGetLastError();
wsprintf((LPTSTR)Message,
"PID:%d, TID:%d, Function<%s>, ErrorCode(%d).\r\n\0",
::GetCurrentProcessId(),
::GetCurrentThreadId(),
"socket\0",
dwErrorCode);
// 注目点はスレッドID(TID)とエラーコード(dwErrorCode)の値
::WriteConsole(::GetStdHandle(STD_OUTPUT_HANDLE),
Message,
strlen(Message),
&dwWrittenLen,
NULL);
iResult=0xFFFFFFFF;
}

wsprintf((LPTSTR)Message,
"どれかキーを押すとプログラムを終了します.\r\n\0");
::WriteConsole(::GetStdHandle(STD_OUTPUT_HANDLE),
Message,
strlen(Message),
&dwWrittenLen,
NULL);
// 何かコンソール上で入力されるまで待機する。
::ReadFile(::GetStdHandle(STD_INPUT_HANDLE),
Message,
sizeof(Message),
&dwWrittenLen,
NULL);
return iResult;
}

int main(int argc, char* argv[])
{
WSADATA wsa;
char Message[256];
DWORD dwErrorCode=0;
DWORD dwWrittenLen=0;
DWORD dwThreadId=0;
HANDLE hThread=INVALID_HANDLE_VALUE;

if(WSAStartup(MAKEWORD(2,2),&wsa)!=0)
{
dwErrorCode=WSAGetLastError();
wsprintf((LPTSTR)Message,
"PID:%d, TID:%d, Function<%s>, ErrorCode(%d).\r\n\0",
::GetCurrentProcessId(),
::GetCurrentThreadId(),
"WSAStartup\0",
dwErrorCode);
::WriteConsole(::GetStdHandle(STD_OUTPUT_HANDLE),
Message,
strlen(Message),
&dwWrittenLen,
NULL);
} else {
hThread=(HANDLE)_beginthreadex(NULL,
0,
(unsigned int (__stdcall *)(void *))ThreadFunc,
NULL,
0,
(unsigned int*)&dwThreadId);
}

Sleep(100); // 確実な同期は取れないが、
// 子スレッドでsocket()がエラーを起こす
// までは確実に待機するだろうと期待。

// WSAGetLastError()の値を参照する。
// main()関数内ではWinsockエラーが発生することはないので、
// WSAGetLastError()の値は0になる(はず)。
wsprintf((LPTSTR)Message,
"PID:%d, TID:%d, WSAGetLastError() result=%d.\r\n\0",
::GetCurrentProcessId(),
::GetCurrentThreadId(),
::WSAGetLastError());
::WriteConsole(::GetStdHandle(STD_OUTPUT_HANDLE),
Message,
strlen(Message),
&dwWrittenLen,
NULL);
// 注目点はスレッドID(TID)とエラーコード(dwErrorCode)の値

::WaitForSingleObject(hThread,INFINITE);
WSACleanup();

return 0;
}

 WSAGetLastError()にはもう一つ、恐るべき問題点があることを確認した(WindowsXP SP3+VC2008環境で確認)。
 WSAGetLastError()が返すエラーコードは、Winsock関連のエラーコードだけをフィルタリングしているわけではないということだ。

 Winsock APIでエラーが発生した後、同じスレッドでWinsock以外のAPIが呼び出されると、Winsockエラーコードが上書きされる現象を確認した。すなわち、WSAGetLastError()の返すエラーコードが、Winsock APIのエラーコードではない場合がある。

スポンサーサイト



FC2ブックマーク | この記事をokyuuへインポート | このエントリーを含むはてなブックマーク | ニフティクリップへ追加 | この記事をクリップ! | イザ!ブックマーク | POOKMARK Airlinesに登録する | del.icio.us |
動作未検証 | | 動作未検証 | 動作未検証 | 動作未検証 | 動作未検証 | 動作未検証 | 動作未検証 | 動作未検証 | 動作未検証 | 動作未検証 | 動作未検証

comment

管理者にだけメッセージを送る

カレンダー
11 | 2019/12 | 01
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 - - - -
最近の記事
月別アーカイブ
カテゴリー
ブログ内検索
RSSフィード
リンク
いろいろリンクボタン
埋め込みe-Words