« 2007年02月 | メイン | 2007年04月 »

1

2007年03月 記事一覧

2007年03月16日

ネットワークに流れるパケットをキャプチャする

test125.gif
Windows 2000以降ではかなり簡単にネットワークのパケットモニターを作れる。

ここではWinSock2.2のRAWソケットを利用してパケットデータを取得している。取得結果はVisual Studioの「出力」ウインドウに表示している。

依存環境:ATL
#include <mstcpip.h>
#include <winsock2.h>
#pragma	comment(lib,"Ws2_32.lib")



//////////////////////////////////////////////////////
//以下の構造体はWindows SDKのサンプルに含まれる「iphdr.h」内からコピー
//

#pragma	pack(push,1)

//
// IPv4 Header (without any IP options)
//
typedef struct ip_hdr
{
    unsigned char  ip_verlen;        // 4-bit IPv4 version
                                     // 4-bit header length (in 32-bit words)
    unsigned char  ip_tos;           // IP type of service
    unsigned short ip_totallength;   // Total length
    unsigned short ip_id;            // Unique identifier 
    unsigned short ip_offset;        // Fragment offset field
    unsigned char  ip_ttl;           // Time to live
    unsigned char  ip_protocol;      // Protocol(TCP,UDP etc)
    unsigned short ip_checksum;      // IP checksum
    unsigned int   ip_srcaddr;       // Source address
    unsigned int   ip_destaddr;      // Source address
} IPV4_HDR, *PIPV4_HDR, FAR * LPIPV4_HDR;

//
// Define the UDP header 
//
typedef struct udp_hdr
{
    unsigned short src_portno;       // Source port no.
    unsigned short dest_portno;      // Dest. port no.
    unsigned short udp_length;       // Udp packet length
    unsigned short udp_checksum;     // Udp checksum
} UDP_HDR, *PUDP_HDR;

//
// Define the TCP header
//
typedef struct tpc_hdr				// <- スペルミス?動作に問題なし
{
    unsigned short src_portno;       // Source port no.
    unsigned short dest_portno;      // Dest. port no.
    unsigned long  seq_num;          // Sequence number
    unsigned long  ack_num;          // Acknowledgement number;
    unsigned short lenflags;         // Header length and flags
    unsigned short window_size;      // Window size
    unsigned short tcp_checksum;     // Checksum
    unsigned short tcp_urgentptr;    // Urgent data?
} TCP_HDR, *PTCP_HDR;

#pragma	pack(pop)


//////////////////////////////////////////////////////

#pragma	pack(push,1)

typedef	struct ip_hdr_tcp
{
	IPV4_HDR	sHdrIp;
	TCP_HDR		sHdrTcp;
	BYTE		pData[1];

} TCP_IP_HDR;

typedef	struct ip_hdr_udp
{
	IPV4_HDR	sHdrIp;
	UDP_HDR		sHdrUdp;
	BYTE		pData[1];

} UDP_IP_HDR;


#pragma	pack(pop)


#define	SWAP_BYTE_ALIGNMENT(data)	\
{									\
	int		i;						\
	int		nSize;					\
	int		nLoop;					\
	BYTE*	pData;					\
	BYTE	cbTmp;					\
									\
	pData = (BYTE*)&data;			\
	nSize = sizeof(data);			\
	nLoop = nSize / 2;				\
	for(i = 0; i < nLoop; i++)		\
	{								\
		cbTmp = *(pData + i);		\
		*(pData + i) = *(pData + nSize - i - 1);	\
		*(pData + nSize - i - 1) = cbTmp;			\
	}								\
}


bool	PacketCapture()
{
	int		nRet;
	SOCKET	sock;
	DWORD	dwReturned;

	//RAW SOCKETを作る
	sock = ::WSASocket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED);
	if(sock == INVALID_SOCKET)
		return false;

	{
		SOCKADDR_IN	sSockAddrIn;

		//パケットを監視するインターフェースを選ぶ
		//ここでは簡易のため最初に見つかったインターフェースを利用している
		//PCに複数のインターフェース(無線LANと有線LANなど)がある場合は適宜変更すること
		//無線LANが何番目に見つかるかなど、物理的なNICとの関連付けをしたい場合は、GetIfTableを利用する
		{
			int		nNo;
			BYTE	pBuff[1024];
			SOCKET_ADDRESS_LIST* pSockAddrList;

			//何番目に見つかったインターフェースを利用するか
			//2番目のインターフェースを利用したい場合は「1」に変更
			nNo = 0;

			// アダプタの列挙
			nRet = ::WSAIoctl(sock,SIO_ADDRESS_LIST_QUERY,NULL,0,pBuff,1024,&dwReturned,NULL,NULL);
			if(nRet == SOCKET_ERROR || dwReturned == 0)
			{
				::closesocket(sock);
				return false;
			}

			pSockAddrList = (SOCKET_ADDRESS_LIST*)pBuff;

			if(nNo >= pSockAddrList->iAddressCount)
			{
				::closesocket(sock);
				return false;
			}
			sSockAddrIn.sin_addr.s_addr= ((SOCKADDR_IN*)pSockAddrList->Address[nNo].lpSockaddr)->sin_addr.s_addr;
		}


		//バインドとキャプチャ-の有効化
		//WSAIoctlに渡すコマンドによってキャプチャ-種などを変更できる
		{
			BOOL	bOption;

			//sSockAddrIn.sin_addr.s_addr	= INADDR_ANY; <-これはNG.必ずSIO_ADDRESS_LIST_QUERYで求めた値を利用する
			sSockAddrIn.sin_family	= AF_INET;
			sSockAddrIn.sin_port	= htons(0);

			nRet = ::bind(sock,(sockaddr*)&sSockAddrIn,sizeof(SOCKADDR_IN));
			if(nRet == SOCKET_ERROR) 
			{
				::closesocket(sock);
				return false;
			}

			//パケットキャプチャの有効化(Windows 2000以降対応)
			bOption = TRUE;
			nRet = ::WSAIoctl(sock,SIO_RCVALL,&bOption,sizeof(BOOL),NULL,0,&dwReturned,NULL,NULL);
			if(nRet == SOCKET_ERROR)
			{
				::closesocket(sock);
				return false;
			}
		}
	}





	//キャプチャの実行とキャプチャデータの表示
	{
		char	pBuff[204800];
		WSABUF	sWsaBuf;
		DWORD	dwFlags;
		DWORD	dwTick;

		dwTick = ::GetTickCount();

		while(1)
		{
			::Sleep(0);

			//キャプチャ時間を10秒間に制限
			if(::GetTickCount() - dwTick > 10*1000)
				break;


			dwFlags = 0;
			::ZeroMemory(pBuff,sizeof(pBuff));
			sWsaBuf.buf = pBuff;
			sWsaBuf.len = sizeof(pBuff);

			//パケット受信
			nRet = ::WSARecv(sock,&sWsaBuf,1,&dwReturned,&dwFlags,NULL,NULL);
			if(nRet == SOCKET_ERROR)
				continue;

			//受信パケット情報の表示
			//ここではIPv4に限って送信元と送信先、TCPかUDPの場合はポート番号の表示のみを処理している
			{
				IPV4_HDR*	pHdr;

				if(dwReturned < sizeof(IPV4_HDR))
					continue;

				pHdr = (IPV4_HDR*)pBuff;

				//IPv4、かつ、IPヘッダーサイズが5×32bit以外のときは処理しない
				if(pHdr->ip_verlen != 0x45)
					continue;

				ATLTRACE("送信元:%d.%d.%d.%d\n",(pHdr->ip_srcaddr >> 0) & 0xFF,(pHdr->ip_srcaddr >> 8) & 0xFF,(pHdr->ip_srcaddr >> 16) & 0xFF,(pHdr->ip_srcaddr >> 24) & 0xFF);
				ATLTRACE("送信先:%d.%d.%d.%d\n",(pHdr->ip_destaddr >> 0) & 0xFF,(pHdr->ip_destaddr >> 8) & 0xFF,(pHdr->ip_destaddr >> 16) & 0xFF,(pHdr->ip_destaddr >> 24) & 0xFF);

				//プロトコルに応じた処理(プロトコルの一覧は「ws2def.h」内で定義されている)
				switch(pHdr->ip_protocol)
				{
				case	IPPROTO_TCP:
					if(dwReturned >= sizeof(TCP_IP_HDR) - 1)
					{
						USHORT		nSrcPort;
						USHORT		nDstPort;
						TCP_IP_HDR*	pTcp;

						pTcp = (TCP_IP_HDR*)pHdr;

						//ポート番号などはエンディアンが異なるので注意
						nSrcPort = pTcp->sHdrTcp.src_portno;
						nDstPort = pTcp->sHdrTcp.dest_portno;
						SWAP_BYTE_ALIGNMENT(nSrcPort);
						SWAP_BYTE_ALIGNMENT(nDstPort);
						ATLTRACE("送信元ポート番号:%d\n",nSrcPort);
						ATLTRACE("送信先ポート番号:%d\n",nDstPort);
					}
					break;

				case	IPPROTO_UDP:
					if(dwReturned >= sizeof(UDP_IP_HDR) - 1)
					{
						USHORT		nSrcPort;
						USHORT		nDstPort;
						UDP_IP_HDR*	pUdp;

						pUdp = (UDP_IP_HDR*)pHdr;

						//ポート番号などはエンディアンが異なるので注意
						nSrcPort = pUdp->sHdrUdp.src_portno;
						nDstPort = pUdp->sHdrUdp.dest_portno;
						SWAP_BYTE_ALIGNMENT(nSrcPort);
						SWAP_BYTE_ALIGNMENT(nDstPort);
						ATLTRACE("送信元ポート番号:%d\n",nSrcPort);
						ATLTRACE("送信先ポート番号:%d\n",nDstPort);
					}
					break;

				//今回はTCP/UDP以外は処理しない
				default:
					break;
				}

				ATLTRACE("\n");
			}
		}
	} 

	::closesocket(sock);

	return	true;
}


bool	Test()
{
	WSAData wsaData;

	//Winsock2.2
	::WSAStartup(MAKEWORD(2,2),&wsaData);

	PacketCapture();

	::WSACleanup();

	return	true;
}


プロジェクトファイルをダウンロード

NetBIOSの名前問い合わせに返答する

test126.JPG
TOSHIBAのHDDレコーダー「RD-H1」などは「ネットdeダビング」という機能によりLAN経由による映像データのコピー(ダビング)に対応している。

この「ネットdeダビング」ではダビング先を探す際にNetBIOSの名前解決を利用している。ここではUDPのポート137を使ったNetBIOSの名前問い合わせに対してRDシリーズ向けの返答を返している。ここで返す応答はNetBIOSに則っているようで反したものになっている。そのため実際のNetBIOS機器に流用する際は注意が必要だ。

#include <mstcpip.h>
#include <winsock2.h>
#pragma	comment(lib,"Ws2_32.lib")



//////////////////////////////////////////////////////
//以下の構造体はWindows SDKのサンプルに含まれる「iphdr.h」内からコピー
//

#pragma	pack(push,1)

//
// IPv4 Header (without any IP options)
//
typedef struct ip_hdr
{
    unsigned char  ip_verlen;        // 4-bit IPv4 version
                                     // 4-bit header length (in 32-bit words)
    unsigned char  ip_tos;           // IP type of service
    unsigned short ip_totallength;   // Total length
    unsigned short ip_id;            // Unique identifier 
    unsigned short ip_offset;        // Fragment offset field
    unsigned char  ip_ttl;           // Time to live
    unsigned char  ip_protocol;      // Protocol(TCP,UDP etc)
    unsigned short ip_checksum;      // IP checksum
    unsigned int   ip_srcaddr;       // Source address
    unsigned int   ip_destaddr;      // Source address
} IPV4_HDR, *PIPV4_HDR, FAR * LPIPV4_HDR;

//
// Define the UDP header 
//
typedef struct udp_hdr
{
    unsigned short src_portno;       // Source port no.
    unsigned short dest_portno;      // Dest. port no.
    unsigned short udp_length;       // Udp packet length
    unsigned short udp_checksum;     // Udp checksum
} UDP_HDR, *PUDP_HDR;

#pragma	pack(pop)


//////////////////////////////////////////////////////

#pragma	pack(push,1)

typedef	struct ip_hdr_udp
{
	IPV4_HDR	sHdrIp;
	UDP_HDR		sHdrUdp;
	BYTE		pData[1];

} UDP_IP_HDR;



typedef	struct netbios_name_reply
{
	USHORT	nTransactionID;
	USHORT	nFlags1;
	USHORT	nQuestion;
	USHORT	nAnswer;
	USHORT	nAuthority;
	USHORT	nAdditional;
	char	cbX;
	char	pszName[32];
	char	cbY;
	USHORT	nType;
	USHORT	nClass;
	USHORT	nTTL1;
	USHORT	nTTL2;
	USHORT	nLen;
	USHORT	nFlags2;
	UINT	nSrcAddr;
} NETBIOS_NAME_REPLY;


#pragma	pack(pop)


enum	RD_DEVICE_ID
{
	DEVICE_HDD			= 1,
	DEVICE_DVD			= 2,
	DEVICE_HDD_DVD		= 3,
	DEVICE_HDD2			= 4,
	DEVICE_HDD_HDD2		= 5,
	DEVICE_DVD_HDD2		= 6,
	DEVICE_HDD_DVD_HDD2	= 7,
	DEVICE_4			= 8,
	DEVICE_HDD_4		= 9,
	DEVICE_DVD_4		= 10,
	DEVICE_HDD_DVD_4	= 11,
	DEVICE_HDD2_4		= 12,
	DEVICE_HDD_HDD2_4	= 13,
	DEVICE_DVD_HDD2_4	= 14,
	//DEVICE_HDD_DVD_HDD2	= 15,
	DEVICE_5			= 16,
	DEVICE_HDD_5		= 17,
};




//
//	TOSHIBA RDシリーズ向けの名前問い合わせ返答処理
//
//【目的】
//TOSHIBA RDシリーズではネットDEダビングなどのためにダビング先機器を検索する際に
//UDP/137(NetBIOSの名前問い合わせ)を送信する。
//ここではその問い合わせに対して返答を返す。
//
//【処理の流れ】
//WinSock2のRAWソケットで全パケットをキャプチャする。
//キャプチャデータのうちUDP/137(NetBIOS名前問い合わせ)以外は無視
//NetBIOS名前問い合わせに対する返答をTOSHIBAのRDシリーズ用に送信
//
bool	ReplyNetBiosNS()
{
	int		nRet;
	SOCKET	sock;
	DWORD	dwReturned;

	//RAW SOCKETを作る
	sock = ::WSASocket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED);
	if(sock == INVALID_SOCKET)
		return false;

	{
		SOCKADDR_IN	sSockAddrIn;

		//パケットを監視するインターフェースを選ぶ
		//ここでは簡易のため最初に見つかったインターフェースを利用している
		//PCに複数のインターフェース(無線LANと有線LANなど)がある場合は適宜変更すること
		//無線LANが何番目に見つかるかなど、物理的なNICとの関連付けをしたい場合は、GetIfTableを利用する
		{
			int		nNo;
			BYTE	pBuff[1024];
			SOCKET_ADDRESS_LIST* pSockAddrList;

			//何番目に見つかったインターフェースを利用するか
			//2番目のインターフェースを利用したい場合は「1」に変更
			nNo = 0;

			// アダプタの列挙
			nRet = ::WSAIoctl(sock,SIO_ADDRESS_LIST_QUERY,NULL,0,pBuff,1024,&dwReturned,NULL,NULL);
			if(nRet == SOCKET_ERROR || dwReturned == 0)
			{
				::closesocket(sock);
				return false;
			}

			pSockAddrList = (SOCKET_ADDRESS_LIST*)pBuff;

			if(nNo >= pSockAddrList->iAddressCount)
			{
				::closesocket(sock);
				return false;
			}
			sSockAddrIn.sin_addr.s_addr= ((SOCKADDR_IN*)pSockAddrList->Address[nNo].lpSockaddr)->sin_addr.s_addr;
		}


		//バインドとキャプチャ-の有効化
		//WSAIoctlに渡すコマンドによってキャプチャ-種などを変更できる
		{
			BOOL	bOption;

			//sSockAddrIn.sin_addr.s_addr	= INADDR_ANY; <-これはNG.必ずSIO_ADDRESS_LIST_QUERYで求めた値を利用する
			sSockAddrIn.sin_family	= AF_INET;
			sSockAddrIn.sin_port	= htons(0);

			nRet = ::bind(sock,(sockaddr*)&sSockAddrIn,sizeof(SOCKADDR_IN));
			if(nRet == SOCKET_ERROR) 
			{
				::closesocket(sock);
				return false;
			}

			//パケットキャプチャの有効化(Windows 2000以降対応)
			bOption = TRUE;
			nRet = ::WSAIoctl(sock,SIO_RCVALL,&bOption,sizeof(BOOL),NULL,0,&dwReturned,NULL,NULL);
			if(nRet == SOCKET_ERROR)
			{
				::closesocket(sock);
				return false;
			}
		}
	}





	//キャプチャの実行とNetBIOS名前問い合わせへの返答
	{
		char	pBuff[204800];
		WSABUF	sWsaBuf;
		DWORD	dwFlags;
		DWORD	dwTick;

		dwTick = ::GetTickCount();

		while(1)
		{
			::Sleep(0);

			//処理時間を10秒間に制限
			if(::GetTickCount() - dwTick > 10*1000)
				break;


			dwFlags = 0;
			::ZeroMemory(pBuff,sizeof(pBuff));
			sWsaBuf.buf = pBuff;
			sWsaBuf.len = sizeof(pBuff);

			//パケット受信
			nRet = ::WSARecv(sock,&sWsaBuf,1,&dwReturned,&dwFlags,NULL,NULL);
			if(nRet == SOCKET_ERROR)
				continue;


			//受信データの解析
			{
				UDP_IP_HDR*	pHdr;

				//UDP分のデータ長がなければ処理しない
				if(dwReturned < sizeof(UDP_IP_HDR))
					continue;

				pHdr = (UDP_IP_HDR*)pBuff;

				//IPv4、かつ、IPヘッダーサイズが5×32bit以外のときは処理しない
				if(pHdr->sHdrIp.ip_verlen != 0x45)
					continue;

				//UDP/139以外は処理しない
				if(pHdr->sHdrIp.ip_protocol != IPPROTO_UDP || pHdr->sHdrUdp.dest_portno != ::htons(137))
					continue;


				//NetBIOSの名前問い合わせに返答する。。。ただしTOSHIBAのHDDレコーダーRDシリーズ形式で返す
				{
					UINT	i;
					BYTE	cbDeviceID;
					BYTE	cbTransactionID;
					NETBIOS_NAME_REPLY	sReply;
					char	pszName[] = "usefullcode.net";	//機器名、最大15文字

					//TOSHIBA RDシリーズではトランザクションIDの1バイト目がデバイス種別になっている
					cbDeviceID		= DEVICE_HDD;			//enum RD_DEVICE_ID内の各値を使用可能
					cbTransactionID = pHdr->pData[1];

					::ZeroMemory(&sReply,sizeof(NETBIOS_NAME_REPLY));
					sReply.nTransactionID = ::htons((cbDeviceID << 8) + cbTransactionID);
					sReply.nFlags1		= ::htons(0x8500);
					sReply.nQuestion	= 0;
					sReply.nAnswer		= ::htons(0x0001);
					sReply.cbX			= 0x32;

					//NetBIOS形式で名前をエンコード
					for(i = 0;i < 15; i++)
					{
						if(i < ::strlen(pszName))
						{
							sReply.pszName[i*2+0] = ((pszName[i] & 0xF0) >> 4) + 'A';
							sReply.pszName[i*2+1] = ((pszName[i] & 0x0F) >> 0) + 'A';
						}
						else
						{
							sReply.pszName[i*2+0] = ((' ' & 0xF0) >> 4) + 'A';
							sReply.pszName[i*2+1] = ((' ' & 0x0F) >> 0) + 'A';
						}
					}

					sReply.nType		= ::htons(0x0020);
					sReply.nClass		= ::htons(0x0001);
					sReply.nLen			= 0;
					sReply.nSrcAddr		= pHdr->sHdrIp.ip_srcaddr;


					SOCKET		sock;
					sockaddr_in	addr;

					sock = ::socket(AF_INET,SOCK_DGRAM,0);

					addr.sin_family		= AF_INET;
					addr.sin_port		= ::htons(137);
					addr.sin_addr.s_addr= pHdr->sHdrIp.ip_srcaddr;

					//返答の送信
					::sendto(sock,(char*)&sReply,sizeof(NETBIOS_NAME_REPLY),0,(sockaddr*)&addr,sizeof(sockaddr));

					::closesocket(sock);
				}
			}
		}
	} 

	::closesocket(sock);

	return	true;
}


bool	Test()
{
	WSAData wsaData;

	//Winsock2.2
	::WSAStartup(MAKEWORD(2,2),&wsaData);

	ReplyNetBiosNS();

	::WSACleanup();

	return	true;
}

プロジェクトファイルをダウンロード

2007年03月17日

TOSHIBA RDシリーズからネットdeダビング経由で映像を取得する

test127.JPG
TOSHIBA RD-H1などのHDDレコーダーには「ネットdeダビング」という機能により機器間で録画した映像をやりとりできる。ここではこのネットdeダビングを利用してTOSHIBA RDシリーズからPCへ映像をダビング(コピー)する。

今回のソースコードにはFTPサーバー部しか実装されていない。そのため実際にTOSHIBA RDシリーズから映像データをダビングするには「NetBIOSの名前問い合わせに返答する 」を起動しておく。

■「ネットdeダビング」での処理の流れ

1.(RD上で操作)赤外線リモコンの「ネットdeダビング」ボタンを押す
2.(RD上で操作)ダビング先として「ネットワーク」を選択する
3.(RD→PC)NetBIOS(UDP/137)を利用した名前の問い合わせが送信される
4.(PC→RD)NetBIOS(UDP/137)による名前を応答する
5.(RD上で表示)「ネットワーク機器名」としてNetBIOSで応答のあった機器が列挙される
6.(RD上で操作)PCを示すダビング先を選択する
7.(RD⇔PC)PCがFTPサーバー、RDがFTPクライアントとして動作して映像がPCにアップロードされる

前回の「NetBIOSの名前問い合わせに返答する 」により1~6の処理が可能になる。今回は7の処理を実装した。
ただし実装はシングルスレッドで、かつ、ネットワーク遅延やパケット分割、各種エラー発生時の処理は省略している。そのため実際に流用する場合はそれらを考慮したものに変更する必要がある。


■「ネットdeダビング」でのFTPの流れ

1.(PC)(TCP/21で受信待機する)
2.(RD→PC)(FTPサーバー(PC)へTCP/21で接続する)
3.(RD←PC)220
4.(RD→PC)USER b2b404fc9e0344e64c40d549d0c21219
5.(RD←PC)331
6.(RD→PC)PASS b2b404fc9e0344e64c40d549d0c21219
7.(RD←PC)230
8.(RD→PC)TYPE I
9.(RD←PC)200

10.(RD→PC)PASV
11.(PC)(データ転送用に任意のポートを開ける)
12.(RD←PC)227 Entering Passive Mode (x,x,x,x,y,y). (xは自分のIPアドレス、yはデータ転送用のポート番号)
13.(PC)(データ転送用のポートで受信待機する)
14.(RD→PC)(データ転送用のポートにアクセスする)
15.(RD→PC)STOR $netdubbing$dubbinginfo.xml
16.(RD←PC)150
17.(RD→PC)(データ転送用のポートにXMLが送られる。内容は以下のようなもの)

<?xml version="1.0" encoding="shift_jis"?>
<dubbinginfo>
<titlesize>2246</titlesize>
<device>0</device>
</dubbinginfo>

18.(RD←PC)226

19.(RD→PC)PASV
20.(PC)(データ転送用に任意のポートを開ける)
21.(RD←PC)227 Entering Passive Mode (x,x,x,x,y,y). (xは自分のIPアドレス、yはデータ転送用のポート番号)
22.(PC)(データ転送用のポートで受信待機する)
23.(RD→PC)(データ転送用のポートにアクセスする)
24.(RD→PC)RETR $xml$status.xml
25.(RD←PC)150
26.(RD←PC)(データ転送用のポートにXMLを送る。内容は以下のようなもの)

<?xml version=\"1.0\" encoding=\"shift_jis\"?>
<status>
<dubbing>ready</dubbing>
<accept_dubbing>ready</accept_dubbing>
<device0_remain>167772160</device0_remain>
<device0_recorded>0</device0_recorded>
<device0_title_remain>350</device0_title_remain>
<device1_remain>167772160</device1_remain>
<device1_recorded>0</device1_recorded>
<device1_title_remain>350</device1_title_remain>
</status>

27.(RD←PC)226

28.(RD→PC)PASV
29.(PC)(データ転送用に任意のポートを開ける)
30.(RD←PC)227 Entering Passive Mode (x,x,x,x,y,y). (xは自分のIPアドレス、yはデータ転送用のポート番号)
31.(PC)(データ転送用のポートで受信待機する)
32.(RD→PC)(データ転送用のポートにアクセスする)
33.(RD→PC)STOR $netdubbing$dev0.dat
34.(RD←PC)150
35.(RD→PC)(データ転送用のポートにmpegファイルの内容が送られる。内容は以下のようなもの)
36.(RD←PC)226

37.(RD→PC)PASV
38.(PC)(データ転送用に任意のポートを開ける)
39.(RD←PC)227 Entering Passive Mode (x,x,x,x,y,y). (xは自分のIPアドレス、yはデータ転送用のポート番号)
40.(PC)(データ転送用のポートで受信待機する)
41.(RD→PC)(データ転送用のポートにアクセスする)
42.(RD→PC)STOR $netdubbing$dubbinginfo.xml
43.(RD←PC)150
44.(RD→PC)(データ転送用のポートにXMLが送られる。内容は以下のようなもの)

<?xml version="1.0" encoding="shift_jis"?>
<dubbinginfo>
<titlesize>0</titlesize>
<device>0</device>
</dubbinginfo>

45.(RD←PC)226

46.(RD→PC)PASV
47.(PC)(データ転送用に任意のポートを開ける)
48.(RD←PC)227 Entering Passive Mode (x,x,x,x,y,y). (xは自分のIPアドレス、yはデータ転送用のポート番号)
49.(PC)(データ転送用のポートで受信待機する)
50.(RD→PC)(データ転送用のポートにアクセスする)
51.(RD→PC)RETR $xml$status.xml
52.(RD←PC)150
53.(RD←PC)(データ転送用のポートにXMLを送る。内容は以下のようなもの)

<?xml version=\"1.0\" encoding=\"shift_jis\"?>
<status>
<dubbing>ready</dubbing>
<accept_dubbing>ready</accept_dubbing>
<device0_remain>167772160</device0_remain>
<device0_recorded>0</device0_recorded>
<device0_title_remain>350</device0_title_remain>
<device1_remain>167772160</device1_remain>
<device1_recorded>0</device1_recorded>
<device1_title_remain>350</device1_title_remain>
</status>

54.(RD←PC)226

55.(RD→PC)RETR QUIT

※PCからRDへの送信例は適当に作成しています。本来はきちんとHDD容量や転送デバイスを考慮した値を入れる必要があります。

依存環境:ATL
#include <winsock2.h>
#pragma	comment(lib,"Ws2_32.lib")

bool	Send(SOCKET sock,const char* pszMessage)
{
	int		nRet;

	nRet = ::send(sock,pszMessage,(int)::strlen(pszMessage),0);

	return (nRet == SOCKET_ERROR) ? false : true;
}


bool	SendResponseCode(SOCKET sock,int nCode,const char* pszMessage=NULL)
{
	char	pszData[128];

	if(pszMessage == NULL)
		::sprintf_s(pszData,128,"%d \r\n",nCode);		//TOSHIBA RDシリーズでは%d直後のスペースが必須!
	else
		::sprintf_s(pszData,128,"%d %s\r\n",nCode,pszMessage);

	return	Send(sock,pszData);
}




bool	FTPServer()
{
	int		nRet;
	SOCKET	sock;
	SOCKET	sockDataConnection;
	sockaddr_in	addr;

	sock = ::socket(PF_INET,SOCK_STREAM,0);
	if(sock == SOCKET_ERROR)
		return	false;

	sockDataConnection = SOCKET_ERROR;


	addr.sin_family		= PF_INET;
	addr.sin_port		= ::htons(21);		//FTPポート
	addr.sin_addr.s_addr= INADDR_ANY;

	nRet = ::bind(sock,(sockaddr*)&addr,sizeof(sockaddr_in));
	if(nRet == SOCKET_ERROR)
	{
		::closesocket(sock);
		return	false;
	}

	nRet = ::listen(sock,SOMAXCONN);
	if(nRet == SOCKET_ERROR)
	{
		::closesocket(sock);
		return	false;
	}

	while(1)
	{
		int			nAddrLen;
		SOCKET		sockAccept;
		sockaddr_in	addrAccept;

		ATLTRACE("FTP Server Start...\n");
		nAddrLen = sizeof(sockaddr_in);
		sockAccept = ::accept(sock,(sockaddr*)&addrAccept,&nAddrLen);
		if(sockAccept == SOCKET_ERROR)
			break;

		//FTPサーバーへのコネクト開始メッセージ
		SendResponseCode(sockAccept,220);
		ATLTRACE("FTP Connected\n");

		while(1)
		{
			char	pBuffer[2048];

			nRet = ::recv(sockAccept,pBuffer,sizeof(pBuffer),0);
			if(nRet == SOCKET_ERROR)
				break;
			if(nRet == 0)
				continue;

			pBuffer[nRet] = NULL;
			ATLTRACE("%s\n",pBuffer);

			if(nRet < 4)
				break;

			if(::StrCmpNIA(pBuffer,"USER",4) == 0)
			{
				SendResponseCode(sockAccept,331);
				continue;
			}
			if(::StrCmpNIA(pBuffer,"PASS",4) == 0)
			{
				SendResponseCode(sockAccept,230);
				continue;
			}
			if(::StrCmpNIA(pBuffer,"TYPE",4) == 0)
			{
				SendResponseCode(sockAccept,200);
				continue;
			}
			if(::StrCmpNIA(pBuffer,"PASV",4) == 0)
			{
				//データ転送用ソケットを作って、そのポートをRDに知らせる

				int		i;
				int		nPort;
				char	pszMessage[1024];

				SOCKET		sockData;
				sockaddr_in	sDataAddr;

				sockData = ::socket(PF_INET,SOCK_STREAM,0);
				if(sockData == SOCKET_ERROR)
					break;

				nPort = 22222;
				for(i = 0; i < 100; i++)
				{
					::ZeroMemory(&sDataAddr,sizeof(sockaddr_in));
					sDataAddr.sin_family		= AF_INET;
					sDataAddr.sin_addr.s_addr	= INADDR_ANY;
					sDataAddr.sin_port			= ::htons(nPort);

					nRet = ::bind(sockData,(sockaddr*)&sDataAddr,sizeof(sockaddr_in));
					if(nRet != SOCKET_ERROR)
						break;

					//ポート番号を変えて再試行
					nPort++;
				}
				if(nRet == SOCKET_ERROR)
					break;
				::listen(sockData,1);

				int			nAddrLen;
				sockaddr	addr;

				nAddrLen = sizeof(sockaddr);
				::getsockname(sockAccept,&addr,&nAddrLen);

				::sprintf_s(pszMessage,1024,"Entering Passive Mode (%u,%u,%u,%u,%u,%u).\r\n"
					,addr.sa_data[2] & 0xFF
					,addr.sa_data[3] & 0xFF
					,addr.sa_data[4] & 0xFF
					,addr.sa_data[5] & 0xFF
					,(nPort >> 8) & 0x00FF
					,(nPort >> 0) & 0x00FF);

				//待機アドレス、ポートを知らせる
				SendResponseCode(sockAccept,227,pszMessage);

				//RDからの接続を待つ
				nAddrLen = sizeof(sockaddr_in);
				sockDataConnection = ::accept(sockData,(sockaddr*)&sDataAddr,&nAddrLen);
				::closesocket(sockData);
				if(sockDataConnection == SOCKET_ERROR)
					break;

				continue;
			}
			if(::StrCmpNIA(pBuffer,"STOR $netdubbing$dev0.dat",(int)::strlen("STOR $netdubbing$dev0.dat")) == 0)
			{
				//MPEGデータの受信

				char	pBuffer[2048];
				HANDLE	hFile;

				//ファイル決め打ちで保存
				hFile = ::CreateFile(_T("c:\\_Down\\test2.mpg"),GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
				if(hFile == INVALID_HANDLE_VALUE)
					break;

				ATLTRACE("Receiving mpeg file...\n");

				//転送開始
				SendResponseCode(sockAccept,150);

				while(1)
				{
					::Sleep(0);

					nRet = ::recv(sockDataConnection,pBuffer,sizeof(pBuffer),0);
					if(nRet == SOCKET_ERROR)
						break;
					if(nRet == 0)
						break;

					BOOL	ret;
					DWORD	dwWrittenSize;

					ret = ::WriteFile(hFile,pBuffer,nRet,&dwWrittenSize,NULL);
					if(ret == FALSE)
					{
						SendResponseCode(sockAccept,450);
						break;
					}
				}

				//転送終了
				SendResponseCode(sockAccept,226);

				::CloseHandle(hFile);
				ATLTRACE("Received\n");

				::closesocket(sockDataConnection);
				sockDataConnection = SOCKET_ERROR;

				continue;
			}
			if(::StrCmpNIA(pBuffer,"STOR",4) == 0)
			{
				//mpegファイル以外のデータ受信
				//dubbinginfoやコマンドなどを処理

				char	pBuffer[2048];

				//転送開始
				SendResponseCode(sockAccept,150);

				while(1)
				{
					::Sleep(0);

					nRet = ::recv(sockDataConnection,pBuffer,sizeof(pBuffer),0);
					if(nRet == SOCKET_ERROR)
						break;
					if(nRet == 0)
						break;
					pBuffer[nRet] = NULL;

					ATLTRACE("%s\n",pBuffer);
				}

				//転送終了
				SendResponseCode(sockAccept,226);

				::closesocket(sockDataConnection);
				sockDataConnection = SOCKET_ERROR;

				continue;
			}
			if(::StrCmpNIA(pBuffer,"RETR",4) == 0)
			{
				//ステータス情報の送信
				//本来ならdubbinginfoを見て返答すべきだが決め打ち

				char	pBuffer[4096];

				strcpy_s(pBuffer,4096,
					"<?xml version=\"1.0\" encoding=\"shift_jis\"?>\r\n"
					"<status>\r\n"
					"<dubbing>ready</dubbing>\r\n"
					"<accept_dubbing>ready</accept_dubbing>\r\n"
					"<device0_remain>167772160</device0_remain>\r\n"
					"<device0_recorded>0</device0_recorded>\r\n"
					"<device0_title_remain>350</device0_title_remain>\r\n"
					"<device1_remain>167772160</device1_remain>\r\n"
					"<device1_recorded>0</device1_recorded>\r\n"
					"<device1_title_remain>350</device1_title_remain>\r\n"
					"</status>\r\n");

				//転送開始
				SendResponseCode(sockAccept,150);

				Send(sockDataConnection,pBuffer);

				//転送終了
				SendResponseCode(sockAccept,226);

				::closesocket(sockDataConnection);
				sockDataConnection = SOCKET_ERROR;

				continue;
			}
			if(::StrCmpNIA(pBuffer,"QUIT",4) == 0)
				break;


			ATLTRACE("unknown cmd\n");
		}

		::shutdown(sockAccept,SD_BOTH);
		::closesocket(sockAccept);
	}


	if(sockDataConnection != SOCKET_ERROR)
	{
		::shutdown(sockDataConnection,SD_BOTH);
		::closesocket(sockDataConnection);
	}

	::shutdown(sock,SD_BOTH);
	::closesocket(sock);

	return	true;
}




bool	Test()
{
	WSAData wsaData;

	//Winsock2.2
	::WSAStartup(MAKEWORD(2,2),&wsaData);

	FTPServer();

	::WSACleanup();

	return	true;
}

プロジェクトファイルをダウンロード

Vistaのバグ(その3) ファイルサイズがマイナスの値になる!

vista3.gif
Windows VistaのエクスプローラーはWindows XPの頃と比べて非常に多くのバグがある。さすがに致命的なものは"少ない"(無いわけでない)のであまり問題はないのだが不便なことが多い。

そんな致命的ではないバグの1つがこれ。エクスプローラーのステータスバー部分に表示されているファイルサイズがマイナスになっている。残念ながら「最新の情報に更新」をしても正常な値は表示されない。正常なファイルサイズを知りたい場合は右クリックして「プロパティ」を表示する。

このバグは必ず起きるというわけではない。発生条件はよく分からないものの頻繁に発生する。


Vistaのバグ(その4) デスクトップにコピーや移動したファイルが表示されない!

vista4_1.gif
これもよく生じるちょっと不便なバグ。

任意のフォルダーからファイルをデスクトップにコピーや移動したとき、もしくは使っているアプリケーションがデスクトップ上にファイルを作成したときに起きる。このバグも必ず起きるというわけではないが頻発する。

vista4_2.gif
ここでは画像ファイルをデスクトップへ移動したにも関わらずデスクトップ上にアイコンが表示されていない。もちろん移動したので元のフォルダからはファイルのアイコンは消えている。

vista4_3.gif
バグでファイルが失われたというわけではない。

デスクトップ上で右クリックをして現れるメニューから「最新の情報に更新」を選択して画面表示を更新する。

vista4_4.gif
するとファイルのアイコンが現れる。

「Excelなどで編集をして、デスクトップに保存。そしてExcelを閉じてからデスクトップを見たら保存したはずのファイルがない!」というように焦ることもあるだろう(少なくとも私はこのようなことがあった)。

また、このバグではデスクトップのアイコンが自動的に整列されるようにしていても、新しく移動したファイルなどは「コンピュータ」と「ごみ箱」のアイコンの間に現れる。つまりアイコンの位置が勝手に変わってしまう。そのためアイコンを表示してからアイコンを手動で動かさなければならず二度手間なのが不便だ。

2007年03月18日

HRESULT型とは?

HRESULTとはlong型の数値だ。この型はCOMインターフェース関連の関数の戻り値として利用されることが多い。

関数の戻り値として使われることの多いBOOL型が成功を意味するのは「1」(=TRUE)で失敗が「0」(=FALSE)なのと似ている。

大きな違いはBOOL型がTRUEとFALSEの2値しか取らないのに対して、HRESULT型では0x00000000~0xFFFFFFFFまでの多くの値が利用される。そのためBOOL型では失敗の原因を取得するためにGetLastError()を利用する必要があったが、HRESULT型では値を見るだけで失敗の原因まで分かる。

HRESULT型として使われる値は下のような規則に則って決定されている。これは「winerror.h」のコメント内に記述されているものだ。

//  HRESULTs are 32 bit values layed out as follows:
//
//   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
//   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
//  +-+-+-+-+-+---------------------+-------------------------------+
//  |S|R|C|N|r|    Facility         |               Code            |
//  +-+-+-+-+-+---------------------+-------------------------------+
//
//  where
//
//      S - Severity - indicates success/fail
//
//          0 - Success
//          1 - Fail (COERROR)
//
//      R - reserved portion of the facility code, corresponds to NT's
//              second severity bit.
//
//      C - reserved portion of the facility code, corresponds to NT's
//              C field.
//
//      N - reserved portion of the facility code. Used to indicate a
//              mapped NT status value.
//
//      r - reserved portion of the facility code. Reserved for internal
//              use. Used to indicate HRESULT values that are not status
//              values, but are instead message ids for display strings.
//
//      Facility - is the facility code
//
//      Code - is the facility's status code


要約すると

・上位1bitが「1」なら"失敗"、「0」なら"成功"
・16~26bitまでの11ビットの値はカテゴリ
・0~15bitまでの下位WORDの値は詳細情報

のように値が決定されている。つまり、あるHRESULT型の値が"成功"を示しているか、それとも"失敗"を示しているのかは

	HRESULT	hr;

	hr = function_example();

	if(hr & 0x80000000)
	{
		//上位1bitが「1」なので成功
	}
	else
	{
		//失敗
	}


というように判定できる。

この判別を簡単にするために「winerror.h」内では以下の2つのマクロが定義されている。

#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0)
#define FAILED(hr) (((HRESULT)(hr)) < 0)


これを利用すると成功と失敗の判別は以下のようにできる。

	HRESULT	hr;

	hr = function_example();

	if(SUCCEEDED(hr))
	{
		//成功
	}
	else
	{
		//失敗
	}

	if(SUCCEEDED(hr))
	{
		//成功
	}

	if(FAILED(hr))
	{
		//失敗
	}


また、前に述べたようにHRESULT型では値を参照することで失敗の原因を知ることができる。例えば...

	HRESULT	hr;

	hr = function_example();

	switch(hr)
	{
	case	S_OK:
		//成功
		break;

	case	E_FAIL:
		//一般的な失敗
		break;

	case	E_OUTOFMEMORY:
		//メモリー不足による失敗
		break;

	case	E_INVALIDARG:
		//引数不正による失敗
		break;

	case	E_UNEXPECTED:
		//予期せぬ失敗
		break;

	case	E_OUTOFMEMORY:
		//による失敗
		break;

	case	E_POINTER:
		//不正なポインターによる失敗
		break;

	(...省略...)

	default:
		//その他(の失敗時もしくは成功時)
		break;
	}


のようにできる。ここで、例えば「E_OUTOFMEMORY」が示す数値はWin32の場合は「0x8007000E」だがMacの場合は「0x80000002」のようにプラットフォームによって値が変わることがある。そのため具体的な数値をソースコードに記述してはいけない。

定義されている値から推測できるように「S_」から始まるものは"成功"を示し、「E_」から始まるものは"失敗"を示す。また、多くの場合成功時は「S_OK」が返る。しかし、成功時は必ずS_OKというわけではない。そのため先に述べた「SUCCEEDED()」マクロによって判定すること。

2007年03月29日

たくさんある文字列比較用関数から必要なものを見つける

C言語をやっていた頃はstrcmp(とstrncmp)だけ知っていれば文字列比較には十分だった。

しかしWindowsプログラミングになるとユニコードやらTCHAR型が登場したおかげで非常に複雑になっている。さらに同じ挙動を示す関数が名前違いで複数用意されているものだから混沌としている。

しかし関数の名前の法則(?)を押さえておけば何となく使うべき関数をチョイスできる。

・「w」がついている場合はユニコード文字列の比較
・「t」が付いている場合はTCHAR型文字列の比較
・「str」が付いている場合は通常の文字列(SHIFT-JISやANSI文字列)の比較

・「n」が付いている場合は文字数を指定しての比較
・「i」が付いている場合は大文字と小文字を区別しない比較

という命名慣習だ。

例えば「strcmp」は「str」が含まれるから通常文字列用で、「i」や「n」が含まれないから大文字と小文字を区別して文字数の指定ができない比較用となるし、「stricmp」は大文字小文字を同一視する比較用、「strncmp」は文字数を指定しての比較用となる。


実際のプログラミングでは...

通常の文字列に対応したstrcmp、strncmp(文字数指定)、stricmp(大文字小文字同一視)、_strnicmp(文字数指定かつ大文字小文字同一視)。

TCHAR型文字列に対応した_tcscmp、_tcsncmp(文字数指定)、_tcsicmp(大文字小文字同一視)、_tcsnicmp(文字数指定かつ大文字小文字同一視)。

の合計8個程度を覚えておけば問題ないだろう。


MSDNライブラリから適当にピックアップしただけで以下のものが見つかった。きちんと探せばまだまだある。いったいいくつあるのだろう?

char wchar_t TCHAR 比較長 大小同一 ロケール 対応OS 補足
_tcscmp
_tccmp
strcmp
wcscmp
_mbscmp MBCS
_tcsncmp
_tcsnccmp
strncmp
wcsncmp
_mbsnbcmp  MBCS
_mbsncmp MBCS
_tcsncmp_l MBCS
_mbsnbcml MBCS
_mbsncmp_l MBCS
_tcsicmp
_stricmp
_wcsicmp
_mbsicmp MBCS
_tcsncicmp
_strnicmp
_wcsnicmp
_mbsnicmp MBCS
_tcsnicmp
_strnicmp
_wcsnicmp
_mbsnbicmp MBCS
_tcsncicmp_l
_strnicmp_l
_wcsnicmp_l
_mbsnicmp_l MBCS
StrCmp
StrCmpA
StrCmpW
StrCmpN
StrCmpNA
StrCmpNW
StrCmpI
StrCmpIA
StrCmpIW
StrCmpNI
StrCmpNIA
StrCmpNIW
StrNCmp
StrNCmpI
StrCmpC Windows 2000以降
StrCmpCA Windows 2000以降
StrCmpCW Windows 2000以降
StrCmpNC Windows 2000以降
StrCmpNCA Windows 2000以降
StrCmpNCW Windows 2000以降
StrCmpIC Windows 2000以降
StrCmpICA Windows 2000以降
StrCmpICW Windows 2000以降
StrCmpNIC Windows 2000以降
StrCmpNICA Windows 2000以降
StrCmpNICW Windows 2000以降
StrCmpLogicalW Windows XP以降イコウ 含まれる数値を比較に考慮
strcmpi (現在は利用しない)
CompareString 可能 可能 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
CompareStringA 可能 可能 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
CompareStringW 可能 可能 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
CompareStringEx 可能 可能 Windows Vista以降 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
CompareStringOrdinal 可能 可能 Windows Vista以降 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
CompareStringWrapW 可能 可能 Windows 2000以降 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
strcoll
wcscoll
_mbscoll MBCS
_tcsncoll
_strncoll
_wcsncoll
_mbsnbcoll MBCS
 
_tcsncoll_l
_strncoll
_wcsncoll
_mbsncoll MBCS
_strncoll_l
_wcsncoll_l
_mbsncoll_l  MBCS
_mbsnbcoll_l  MBCS
_tcsnicoll
_strnicoll
_wcsnicoll
_mbsnbicoll MBCS
_tcsnicoll_l
_strnicoll_l
_wcsnicoll_l
_mbsnbicoll_l MBCS
 
_tcsncicoll
_strnicoll
_wcsnicoll
_mbsnbicoll MBCS
_tcsnicoll
_strnicoll
_wcsnicoll
_mbsnbicoll MBCS
_tcsnicoll_l
_strnicoll_l
_wcsnicoll_l
_mbsnbicoll_l MBCS

簡単に処理時間を計測する

test128.gif
ベンチマークソフトを作りたい!と本格的に思わなくとも処理時間を計測したい場面は多々ある。簡易的に測定したのならGetTickCountを利用すると簡単に計測できる。

GetTickCountはPCを起動してからの経過時間をミリ秒単位で返すための関数だ。この関数を計測したい処理の前後に実行して、取得した値の差を取れば処理に要した時間となる。

ただしGetTickCountを利用する上で注意しておきたい点がある。

まず、この関数で返される値は「まれにゼロに戻る」という点だ。具体的には49.7日周期でゼロに戻る。そのため49.7日を超える処理時間は確実に計測できないし、処理時間が仮に数秒の場合でもちょうどゼロに戻るタイミングに当たった場合は処理時間が負の値となりきちんと計測できない。

また、WindowsはマルチタスクOSなため、バックグラウンド処理が重たいと計測時間が長くなるという点だ。これはGetTickCountの欠点ではないが場合によっては問題となり得る。


何はともあれ、「GetTickCountの値がゼロに戻ることがある」という点だけは絶対に頭に入れておかないとダメだ。

#include "atlstr.h"

bool	Test()
{
	DWORD		dwStart;
	ULONGLONG	dwEnd;

	dwStart = ::GetTickCount();

	//処理時間を計測したい処理
	{
		int		i;
		int		j;

		for(i = 0; i < 1000; i++)
		{
			for(j = 0; j < 1000; j++)
			{
				char	pszBuff[256];

				::sprintf_s(pszBuff,256 * sizeof(char),"%d %d",i,j);
			}
		}
	}

	dwEnd = ::GetTickCount();

	//GetTickCountは49.7日に1回ゼロに戻る。
	//計測中にゼロに戻っても正常な値になるようにゼロに戻った場合は桁上がりさせる
	//※注意:この桁上がり処理をした場合も49.7日を超える時間は計測できない
	if(dwStart > dwEnd)
		dwEnd += 0x100000000;		//桁上がり

	CAtlString	strMessage;

	strMessage.Format(_T("処理にかかった時間は %d ミリ秒です"),dwEnd - dwStart);
	::MessageBox(NULL,strMessage,_T(""),MB_OK);

	return	true;
}
プロジェクトファイルをダウンロード

2007年03月30日

DLLなどからエクスポートされている関数を一覧する

test129.gif
DLLからエクスポートされている関数を調べるにはDLLをPEファイルの構造を元にバイナリレベルで解析して調べる方法とAPIを利用して情報を取得する方法とがある。
ここではデバッグ用に用意されているAPIのImageDirectoryEntryToDataを利用して取得した。

依存環境:ATL
#include "atlstr.h"

#include "Dbghelp.h"
#pragma	comment(lib,"Dbghelp.lib")


bool	ShowExportedFunction(LPCTSTR pszDLLPath)
{
	HMODULE	hModule;
	ULONG	nSize;
	DWORD	i;
	DWORD	j;
	char**	ppszFunctionName;
	WORD*	pwFunctionOrdinal;
	IMAGE_EXPORT_DIRECTORY*	pImageExportDir;

	CAtlString	strMessage;

	hModule = ::LoadLibrary(pszDLLPath);
	if(hModule == NULL)
		return	false;

	pImageExportDir = (IMAGE_EXPORT_DIRECTORY*)::ImageDirectoryEntryToData((PVOID)hModule,TRUE,IMAGE_DIRECTORY_ENTRY_EXPORT,&nSize);
	if(pImageExportDir == NULL)
	{
		::FreeLibrary(hModule);
		return	false;
	}

	strMessage.AppendFormat(_T("%sがエクスポートしている関数一覧\n\n"),pszDLLPath);


	//pImageExportDir内のアドレスはhModuleからの相対的なアドレス
	ppszFunctionName	= (char**)(pImageExportDir->AddressOfNames + (ULONGLONG)hModule);
	pwFunctionOrdinal	= (WORD*)(pImageExportDir->AddressOfNameOrdinals + (ULONGLONG)hModule);

	for(i = 0; i < pImageExportDir->NumberOfFunctions; i++)
	{
		strMessage.AppendFormat(_T("序数:%4d(0x%04x)"),pImageExportDir->Base + i,pImageExportDir->Base + i);

		for(j = 0; j < pImageExportDir->NumberOfNames; j++)
		{
			if(pwFunctionOrdinal[j] != i)		//pwFunctionOrdinal[j]は序数ではなくインデックス
				continue;

			//hModuleからの相対アドレスで関数名が格納されている
			strMessage.AppendFormat(_T(" 関数名:%s"),(char*)(ppszFunctionName[j] + (ULONGLONG)hModule));
			break;
		}

		strMessage += _T("\n");
	}

	::FreeLibrary(hModule);

	::MessageBox(NULL,strMessage,_T(""),MB_OK);

	return	true;
}


bool	Test()
{
	return	ShowExportedFunction(_T("hotplug.dll"));
}

プロジェクトファイルをダウンロード

Office 2007はライセンス認証しないと閲覧モードに移ってしまう


office2007_01.gif
Microsoft Office 2007はインストールにプロダクトキーが必要ない。ただしWordやExcelの起動時に「25文字のプロダクトキーを入力してください。」という旨の画面が表示される。

office2007_02.gif
「×」ボタンを押してウインドウを閉じようとすると「セットアップをキャンセルしてもよろしいですか?」と聞かれるので「はい」ボタンを押す。

office2007_03.gif
すると「Microsoft Office Professional 2007の構成が中止されました。」という画面が表示され...

office2007_04.gif
問題なくWordやExcelなどの全機能を利用できていた(既に過去形)。このようにしてプロダクトキーを指定しない状態でOfficeを使っていても、起動時に入力用の画面が表示される以外はまったく問題なく利用できていた。バージョン情報の画面を見ても「残り39回利用できます」のようなメッセージが表示されているわけでもなかった。
しかし、今日起動してみると図のようにメニュー項目が灰色になってしまい。まったくと言っていいほど利用できなくなった。

office2007_05.gif
Excelだけでなく、Wordでも同様だ。ファイルを開いて閲覧することはできるが、入力や編集が一切できない。Excelは検索機能が動いたが、Wordでは検索機能も動かない。


何も表示されていなかったのだが、おそらくプロダクトキーを入力しないで利用している場合は50回の利用制限があったのだろう。つまり51回目の起動からこのように機能が利用できなくなるのだろう。




office2007_06.gif
当然ことながらこのままでは使い物にならないのでプロダクトキーを入力した。

office2007_07.gif
すると「インストールの種類を選択してください」と言われた。しかし種類を選択するも何も、「今すぐインストール」と「ユーザー設定」しかない。ここではとりあえず「ユーザー設定を」を選択した。

office2007_08.gif
すると氏名などの編集画面が表示された。ユーザー設定項目はこれだけのようだ。

office2007_09.gif
「今すぐインストール」ボタンを押すと「Microsoft Office Professional 2007を構成しています...」と10秒程度待たされた。

office2007_10.gif
そして「Microsoft Office Professional 2007の構成が正常に完了しました。」という表示。

office2007_11.gif
しかし、ここまで進んでもまだOfficeの機能は灰色になっていて利用できなかった。




office2007_12.gif
一度Excelを終了して、再度起動すると今度は「Microsoft Office ライセンス認証ウィザード」が開いた。インターネット経由でのライセンス認証(アクティベーション)は気分的に嫌なのでここでは電話経由を選択した。

ちなみにWindows Vistaの場合はインターネットに接続していると強制的にネット経由でアクティベーションを行ってしまうということがあったので、事前にネットワーク接続を切った状態でプロダクトキーの入力などをしていた。その可能性は低いだろうがもしかすると接続時は強制的にオンライン認証されるのかもしれない。

office2007_13.gif
そして表示されているフリーダイヤルに電話をして確認コードを入力。電話のアナウンスでは「ステップ4のA~Gの欄に...」のように言っていたが、確認コードの入力はステップ3のようだ。

office2007_14.gif
これで無事にライセンス認証が終了した。

office2007_15.gif
これで機能が灰色化することもなく正常に利用できるようになった。

1





usefullcode@gmail.com

About 2007年03月

2007年03月にブログ「UsefullCode.net」に投稿されたすべてのエントリーです。

前の記事は2007年02月です。

次の記事は2007年04月です。

他にも多くのエントリーがあります。メインページ記事一覧も見てください。