584 lines
18 KiB
C
584 lines
18 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 1999, Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
tcbview.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
This module contains code for a utility program which monitors
|
||
|
the variables for the active TCP/IP control blocks in the system.
|
||
|
The program optionally maintains a log for a specified TCB
|
||
|
in CSV format in a file specified by the user.
|
||
|
|
||
|
Author:
|
||
|
|
||
|
Abolade Gbadegesin (aboladeg) January-25-1999
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include <nt.h>
|
||
|
#include <ntrtl.h>
|
||
|
#include <nturtl.h>
|
||
|
#include <windows.h>
|
||
|
#include <commctrl.h>
|
||
|
#include <winsock2.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <io.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <ntddip.h>
|
||
|
#include <ntddtcp.h>
|
||
|
#include <ipinfo.h>
|
||
|
#include <iphlpapi.h>
|
||
|
#include <iphlpstk.h>
|
||
|
|
||
|
//
|
||
|
// GLOBAL DATA
|
||
|
//
|
||
|
|
||
|
ULONG DisplayInterval = 500;
|
||
|
HWND ListHandle;
|
||
|
SOCKADDR_IN LogLocal;
|
||
|
FILE* LogFile = NULL;
|
||
|
PCHAR LogPath;
|
||
|
SOCKADDR_IN LogRemote;
|
||
|
HANDLE TcpipHandle;
|
||
|
UINT_PTR TimerId;
|
||
|
typedef enum {
|
||
|
LocalAddressColumn,
|
||
|
LocalPortColumn,
|
||
|
RemoteAddressColumn,
|
||
|
RemotePortColumn,
|
||
|
SmRttColumn,
|
||
|
DeltaColumn,
|
||
|
RtoColumn,
|
||
|
RexmitColumn,
|
||
|
RexmitCntColumn,
|
||
|
MaximumColumn
|
||
|
} LIST_COLUMNS;
|
||
|
CHAR* ColumnText[] = {
|
||
|
"LocalAddress",
|
||
|
"LocalPort",
|
||
|
"RemoteAddress",
|
||
|
"RemotePort",
|
||
|
"SmRtt",
|
||
|
"Delta",
|
||
|
"Rto",
|
||
|
"Rexmit",
|
||
|
"RexmitCnt",
|
||
|
};
|
||
|
|
||
|
VOID
|
||
|
AllocateConsole(
|
||
|
VOID
|
||
|
)
|
||
|
{
|
||
|
INT OsfHandle;
|
||
|
FILE* FileHandle;
|
||
|
|
||
|
//
|
||
|
// Being a GUI application, we do not have a console for our process.
|
||
|
// Allocate a console now, and make it our standard-output file.
|
||
|
//
|
||
|
|
||
|
AllocConsole();
|
||
|
OsfHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT);
|
||
|
FileHandle = _fdopen(OsfHandle, "w");
|
||
|
if (!FileHandle) {
|
||
|
perror("_fdopen");
|
||
|
exit(0);
|
||
|
}
|
||
|
*stdout = *FileHandle;
|
||
|
setvbuf(stdout, NULL, _IONBF, 0);
|
||
|
}
|
||
|
|
||
|
LRESULT CALLBACK
|
||
|
DisplayWndProc(
|
||
|
HWND WindowHandle,
|
||
|
UINT Message,
|
||
|
WPARAM Wparam,
|
||
|
LPARAM Lparam
|
||
|
)
|
||
|
{
|
||
|
//
|
||
|
// Handle the few window-messages that we care about.
|
||
|
// Our window will contain a listview as soon as we've initialized,
|
||
|
// and we always resize that listview to fill our client area.
|
||
|
// We also set up a timer which periodically triggers refresh
|
||
|
// of the TCBs displayed.
|
||
|
//
|
||
|
|
||
|
if (Message == WM_CREATE) {
|
||
|
CREATESTRUCT* CreateStruct = (CREATESTRUCT*)Lparam;
|
||
|
LVCOLUMN LvColumn;
|
||
|
RECT rc;
|
||
|
do {
|
||
|
//
|
||
|
// Create the child listview, and insert columns
|
||
|
// for each of the TCB fields that we'll be displaying.
|
||
|
//
|
||
|
|
||
|
GetClientRect(WindowHandle, &rc);
|
||
|
ListHandle =
|
||
|
CreateWindowEx(
|
||
|
0,
|
||
|
WC_LISTVIEW,
|
||
|
NULL,
|
||
|
WS_CHILD|LVS_REPORT|LVS_NOSORTHEADER,
|
||
|
0,
|
||
|
0,
|
||
|
rc.right,
|
||
|
rc.bottom,
|
||
|
WindowHandle,
|
||
|
NULL,
|
||
|
CreateStruct->hInstance,
|
||
|
NULL
|
||
|
);
|
||
|
if (!ListHandle) { break; }
|
||
|
ZeroMemory(&LvColumn, sizeof(LvColumn));
|
||
|
for (; LvColumn.iSubItem < MaximumColumn; LvColumn.iSubItem++) {
|
||
|
LvColumn.mask = LVCF_FMT|LVCF_SUBITEM|LVCF_TEXT|LVCF_WIDTH;
|
||
|
LvColumn.fmt = LVCFMT_LEFT;
|
||
|
LvColumn.pszText = ColumnText[LvColumn.iSubItem];
|
||
|
LvColumn.cx = 50;
|
||
|
ListView_InsertColumn(ListHandle, LvColumn.iSubItem, &LvColumn);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Initialize our periodic timer, and display our window.
|
||
|
//
|
||
|
|
||
|
TimerId = SetTimer(WindowHandle, 1, DisplayInterval, NULL);
|
||
|
ShowWindow(WindowHandle, SW_SHOW);
|
||
|
ShowWindow(ListHandle, SW_SHOW);
|
||
|
if (!TimerId) { break; }
|
||
|
return 0;
|
||
|
} while(FALSE);
|
||
|
PostQuitMessage(0);
|
||
|
return (LRESULT)-1;
|
||
|
} else if (Message == WM_DESTROY) {
|
||
|
|
||
|
//
|
||
|
// Stop our periodic timer, close the log-file (if any),
|
||
|
// close the handle on which we communicate with the TCP/IP driver,
|
||
|
// and post a quit message to cause the message-loop of our process
|
||
|
// to end.
|
||
|
//
|
||
|
|
||
|
KillTimer(WindowHandle, TimerId);
|
||
|
if (LogFile) { fclose(LogFile); }
|
||
|
NtClose(TcpipHandle);
|
||
|
PostQuitMessage(0);
|
||
|
return 0;
|
||
|
} else if (Message == WM_SETFOCUS) {
|
||
|
|
||
|
//
|
||
|
// Always pass the focus to our child-control, the listview.
|
||
|
//
|
||
|
|
||
|
SetFocus(ListHandle);
|
||
|
return 0;
|
||
|
} else if (Message == WM_WINDOWPOSCHANGED) {
|
||
|
RECT rc;
|
||
|
|
||
|
//
|
||
|
// Always resize our listview to fill our client-area.
|
||
|
//
|
||
|
|
||
|
GetClientRect(WindowHandle, &rc);
|
||
|
SetWindowPos(
|
||
|
ListHandle,
|
||
|
WindowHandle,
|
||
|
0,
|
||
|
0,
|
||
|
rc.right,
|
||
|
rc.bottom,
|
||
|
((WINDOWPOS*)Lparam)->flags
|
||
|
);
|
||
|
return 0;
|
||
|
} else if (Message == WM_TIMER) {
|
||
|
COORD Coord = {0, 0};
|
||
|
DWORD Error;
|
||
|
ULONG i;
|
||
|
LONG Item;
|
||
|
ULONG Length;
|
||
|
LVITEM LvItem;
|
||
|
CHAR Text[20];
|
||
|
TCP_FINDTCB_REQUEST Request;
|
||
|
TCP_FINDTCB_RESPONSE Response;
|
||
|
PMIB_TCPTABLE Table;
|
||
|
|
||
|
//
|
||
|
// If we're configured to use a log-file and we haven't created one,
|
||
|
// do so now, and print the CSV header to the file.
|
||
|
//
|
||
|
|
||
|
if (LogPath && !LogFile) {
|
||
|
LogFile = fopen(LogPath, "w+");
|
||
|
if (!LogFile) {
|
||
|
return 0;
|
||
|
} else {
|
||
|
fprintf(
|
||
|
LogFile,
|
||
|
"#senduna,sendnext,sendmax,sendwin,unacked,maxwin,cwin,"
|
||
|
"mss,rtt,smrtt,rexmitcnt,rexmittimer,rexmit,retrans,state,"
|
||
|
"flags,rto,delta\n"
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Clear our listview and retrieve a new table of TCP connections.
|
||
|
// It would be less visually jarring if, instead of deleting all
|
||
|
// the list-items each time, we used a mark-and-sweep to just update
|
||
|
// the ones which had changed. However, that sounds too much like work.
|
||
|
//
|
||
|
|
||
|
ListView_DeleteAllItems(ListHandle);
|
||
|
Error =
|
||
|
AllocateAndGetTcpTableFromStack(
|
||
|
&Table,
|
||
|
TRUE,
|
||
|
GetProcessHeap(),
|
||
|
0
|
||
|
);
|
||
|
if (Error) { return 0; }
|
||
|
|
||
|
//
|
||
|
// Display each active TCP control block in the listview.
|
||
|
// For each entry, we retrieve the partial TCB using IOCTL_TCP_FINDTCB,
|
||
|
// and then display it in the list.
|
||
|
// If we are generating a log-file for one of the TCBs,
|
||
|
// we append the current information to that log-file.
|
||
|
//
|
||
|
|
||
|
for (i = 0, Item = 0; i < Table->dwNumEntries; i++) {
|
||
|
if (Table->table[i].dwState < MIB_TCP_STATE_SYN_SENT ||
|
||
|
Table->table[i].dwState > MIB_TCP_STATE_TIME_WAIT) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
Request.Src = Table->table[i].dwLocalAddr;
|
||
|
Request.Dest = Table->table[i].dwRemoteAddr;
|
||
|
Request.SrcPort = (USHORT)Table->table[i].dwLocalPort;
|
||
|
Request.DestPort = (USHORT)Table->table[i].dwRemotePort;
|
||
|
ZeroMemory(&Response, sizeof(Response));
|
||
|
if (!DeviceIoControl(
|
||
|
TcpipHandle,
|
||
|
IOCTL_TCP_FINDTCB,
|
||
|
&Request,
|
||
|
sizeof(Request),
|
||
|
&Response,
|
||
|
sizeof(Response),
|
||
|
&Length,
|
||
|
NULL
|
||
|
)) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
lstrcpy(Text, inet_ntoa(*(PIN_ADDR)&Request.Src));
|
||
|
ZeroMemory(&LvItem, sizeof(LvItem));
|
||
|
LvItem.mask = LVIF_TEXT;
|
||
|
LvItem.iItem = Item;
|
||
|
LvItem.iSubItem = LocalAddressColumn;
|
||
|
LvItem.pszText = Text;
|
||
|
LvItem.iItem = ListView_InsertItem(ListHandle, &LvItem);
|
||
|
if (LvItem.iItem == -1) { continue; }
|
||
|
|
||
|
ListView_SetItemText(
|
||
|
ListHandle, Item, RemoteAddressColumn,
|
||
|
inet_ntoa(*(PIN_ADDR)&Request.Dest)
|
||
|
);
|
||
|
_ltoa(ntohs(Request.SrcPort), Text, 10);
|
||
|
ListView_SetItemText(ListHandle, Item, LocalPortColumn, Text);
|
||
|
_ltoa(ntohs(Request.DestPort), Text, 10);
|
||
|
ListView_SetItemText(ListHandle, Item, RemotePortColumn, Text);
|
||
|
_ltoa(Response.tcb_smrtt, Text, 10);
|
||
|
ListView_SetItemText(ListHandle, Item, SmRttColumn, Text);
|
||
|
_ltoa(0, /* Response.tcb_delta, */ Text, 10);
|
||
|
ListView_SetItemText(ListHandle, Item, DeltaColumn, Text);
|
||
|
wsprintf(
|
||
|
Text, "%d.%d", 0, // Response.tcb_rto / 10,
|
||
|
0 // (Response.tcb_rto % 10) * 100
|
||
|
);
|
||
|
ListView_SetItemText(ListHandle, Item, RtoColumn, Text);
|
||
|
_ltoa(Response.tcb_rexmit, Text, 10);
|
||
|
ListView_SetItemText(ListHandle, Item, RexmitColumn, Text);
|
||
|
_ltoa(Response.tcb_rexmitcnt, Text, 10);
|
||
|
ListView_SetItemText(ListHandle, Item, RexmitCntColumn, Text);
|
||
|
++Item;
|
||
|
|
||
|
//
|
||
|
// If we are generating a log-file, update it now.
|
||
|
// We allow the user to specify a wildcard for either or both port
|
||
|
// on the command-line, so if a wildcard was specified
|
||
|
// in 'LogLocal' or 'LogRemote', we now instantiate the wildcard
|
||
|
// for the first matching session.
|
||
|
//
|
||
|
|
||
|
if (Request.Src == LogLocal.sin_addr.s_addr &&
|
||
|
Request.Dest == LogRemote.sin_addr.s_addr &&
|
||
|
(LogLocal.sin_port == 0 ||
|
||
|
Request.SrcPort == LogLocal.sin_port) &&
|
||
|
(LogRemote.sin_port == 0 ||
|
||
|
Request.DestPort == LogRemote.sin_port)) {
|
||
|
|
||
|
//
|
||
|
// This assignment instantiates the user's wildcard, if any,
|
||
|
//
|
||
|
|
||
|
LogLocal.sin_port = Request.SrcPort;
|
||
|
LogRemote.sin_port = Request.DestPort;
|
||
|
|
||
|
fprintf(
|
||
|
LogFile, "%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,"
|
||
|
"%x,%u,%u\n",
|
||
|
Response.tcb_senduna,
|
||
|
Response.tcb_sendnext,
|
||
|
Response.tcb_sendmax,
|
||
|
Response.tcb_sendwin,
|
||
|
Response.tcb_unacked,
|
||
|
Response.tcb_maxwin,
|
||
|
Response.tcb_cwin,
|
||
|
Response.tcb_mss,
|
||
|
Response.tcb_rtt,
|
||
|
Response.tcb_smrtt,
|
||
|
Response.tcb_rexmitcnt,
|
||
|
Response.tcb_rexmittimer,
|
||
|
Response.tcb_rexmit,
|
||
|
Response.tcb_retrans,
|
||
|
Response.tcb_state,
|
||
|
0, // Response.tcb_flags,
|
||
|
0, // Response.tcb_rto,
|
||
|
0 // Response.tcb_delta
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
HeapFree(GetProcessHeap(), 0, Table);
|
||
|
UpdateWindow(ListHandle);
|
||
|
return 0;
|
||
|
}
|
||
|
return DefWindowProc(WindowHandle, Message, Wparam, Lparam);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
DisplayUsage(
|
||
|
void
|
||
|
)
|
||
|
{
|
||
|
AllocateConsole();
|
||
|
printf("tcbview [-?] [-tcbhelp] [-refresh <ms>] [-log <path> <session>\n");
|
||
|
printf("\t<session> = <local endpoint> <remote endpoint>\n");
|
||
|
printf("\t<endpoint> = <address> { <port> | * }\n");
|
||
|
printf("Press <Ctrl-C> to continue...");
|
||
|
Sleep(INFINITE);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
DisplayTcbHelp(
|
||
|
void
|
||
|
)
|
||
|
{
|
||
|
AllocateConsole();
|
||
|
printf("tcbview: TCB Help\n");
|
||
|
printf("tcb fields:\n");
|
||
|
printf("\tsenduna = seq. of first unack'd byte\n");
|
||
|
printf("\tsendnext = seq. of next byte to send\n");
|
||
|
printf("\tsendmax = max. seq. sent so far\n");
|
||
|
printf("\tsendwin = size of send window in bytes\n");
|
||
|
printf("\tunacked = number of unack'd bytes\n");
|
||
|
printf("\tmaxwin = max. send window offered\n");
|
||
|
printf("\tcwin = size of congestion window in bytes\n");
|
||
|
printf("\tmss = max. segment size\n");
|
||
|
printf("\trtt = timestamp of current rtt measurement\n");
|
||
|
printf("\tsmrtt = smoothed rtt measurement\n");
|
||
|
printf("\trexmitcnt = number of rexmit'd segments\n");
|
||
|
printf("\trexmittimer = rexmit timer in ticks\n");
|
||
|
printf("\trexmit = rexmit timeout last computed\n");
|
||
|
printf("\tretrans = total rexmit'd segments (all sessions)\n");
|
||
|
printf("\tstate = connection state\n");
|
||
|
printf("\tflags = connection flags (see below)\n");
|
||
|
printf("\trto = real-time rto (compare rexmit)\n");
|
||
|
printf("\tdelta = rtt variance\n");
|
||
|
printf("\n");
|
||
|
printf("flags:\n");
|
||
|
printf("\t00000001 = window explicitly set\n");
|
||
|
printf("\t00000002 = has client options\n");
|
||
|
printf("\t00000004 = from accept\n");
|
||
|
printf("\t00000008 = from active open\n");
|
||
|
printf("\t00000010 = client notified of disconnect\n");
|
||
|
printf("\t00000020 = in delayed action queue\n");
|
||
|
printf("\t00000040 = completing receives\n");
|
||
|
printf("\t00000080 = in receive-indication handler\n");
|
||
|
printf("\t00000100 = needs receive-completes\n");
|
||
|
printf("\t00000200 = needs to send ack\n");
|
||
|
printf("\t00000400 = needs to output\n");
|
||
|
printf("\t00000800 = delayed sending ack\n");
|
||
|
printf("\t00001000 = probing for path-mtu bh\n");
|
||
|
printf("\t00002000 = using bsd urgent semantics\n");
|
||
|
printf("\t00004000 = in 'DeliverUrgent'\n");
|
||
|
printf("\t00008000 = seen urgent data and urgent data fields valid\n");
|
||
|
printf("\t00010000 = needs to send fin\n");
|
||
|
printf("\t00020000 = using nagle's algorithm\n");
|
||
|
printf("\t00040000 = in 'TCPSend'\n");
|
||
|
printf("\t00080000 = flow-controlled (received zero-window)\n");
|
||
|
printf("\t00100000 = disconnect-notif. pending\n");
|
||
|
printf("\t00200000 = time-wait transition pending\n");
|
||
|
printf("\t00400000 = output being forced\n");
|
||
|
printf("\t00800000 = send pending after receive\n");
|
||
|
printf("\t01000000 = graceful-close pending\n");
|
||
|
printf("\t02000000 = keepalives enabled\n");
|
||
|
printf("\t04000000 = processing urgent data inline\n");
|
||
|
printf("\t08000000 = inform acd about connection\n");
|
||
|
printf("\t10000000 = fin sent since last retransmit\n");
|
||
|
printf("\t20000000 = unack'd fin sent\n");
|
||
|
printf("\t40000000 = need to send rst when closing\n");
|
||
|
printf("\t80000000 = in tcb table\n");
|
||
|
printf("Press <Ctrl-C> to continue...");
|
||
|
Sleep(INFINITE);
|
||
|
}
|
||
|
|
||
|
INT WINAPI
|
||
|
WinMain(
|
||
|
HINSTANCE InstanceHandle,
|
||
|
HINSTANCE Unused,
|
||
|
PCHAR CommandLine,
|
||
|
INT ShowWindowCode
|
||
|
)
|
||
|
{
|
||
|
LONG argc;
|
||
|
PCHAR* argv;
|
||
|
LONG i;
|
||
|
IO_STATUS_BLOCK IoStatus;
|
||
|
MSG Message;
|
||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
|
NTSTATUS Status;
|
||
|
HANDLE ThreadHandle;
|
||
|
ULONG ThreadId;
|
||
|
UNICODE_STRING UnicodeString;
|
||
|
HWND WindowHandle;
|
||
|
WNDCLASS WndClass;
|
||
|
|
||
|
//
|
||
|
// Process command-line arguments. See 'DisplayUsage' above for help.
|
||
|
//
|
||
|
|
||
|
argc = __argc;
|
||
|
argv = __argv;
|
||
|
for (i = 1; i < argc; i++) {
|
||
|
if (lstrcmpi(argv[i], "-?") == 0 || lstrcmpi(argv[i], "/?") == 0) {
|
||
|
DisplayUsage();
|
||
|
return 0;
|
||
|
} else if (lstrcmpi(argv[i], "-tcbhelp") == 0) {
|
||
|
DisplayTcbHelp();
|
||
|
return 0;
|
||
|
} else if (lstrcmpi(argv[i], "-refresh") == 0 && (i + 1) >= argc) {
|
||
|
DisplayInterval = atol(argv[++i]);
|
||
|
if (!DisplayInterval) {
|
||
|
DisplayUsage();
|
||
|
return 0;
|
||
|
}
|
||
|
} else if (lstrcmpi(argv[i], "-log") == 0) {
|
||
|
if ((i + 5) >= argc) {
|
||
|
DisplayUsage();
|
||
|
return 0;
|
||
|
}
|
||
|
LogPath = argv[++i];
|
||
|
LogLocal.sin_addr.s_addr = inet_addr(argv[++i]);
|
||
|
if (lstrcmpi(argv[i+1], "*") == 0) {
|
||
|
LogLocal.sin_port = 0; ++i;
|
||
|
} else {
|
||
|
LogLocal.sin_port = htons((SHORT)atol(argv[++i]));
|
||
|
}
|
||
|
LogRemote.sin_addr.s_addr = inet_addr(argv[++i]);
|
||
|
if (lstrcmpi(argv[i+1], "*") == 0) {
|
||
|
LogRemote.sin_port = 0; ++i;
|
||
|
} else {
|
||
|
LogRemote.sin_port = htons((SHORT)atol(argv[++i]));
|
||
|
}
|
||
|
if (LogLocal.sin_addr.s_addr == INADDR_NONE ||
|
||
|
LogRemote.sin_addr.s_addr == INADDR_NONE) {
|
||
|
DisplayUsage();
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Open a handle to the TCP/IP driver,
|
||
|
// to be used in issuing IOCTL_TCP_FINDTCB requests.
|
||
|
//
|
||
|
|
||
|
RtlInitUnicodeString(&UnicodeString, DD_TCP_DEVICE_NAME);
|
||
|
InitializeObjectAttributes(
|
||
|
&ObjectAttributes,
|
||
|
&UnicodeString,
|
||
|
OBJ_CASE_INSENSITIVE,
|
||
|
NULL,
|
||
|
NULL
|
||
|
);
|
||
|
Status =
|
||
|
NtCreateFile(
|
||
|
&TcpipHandle,
|
||
|
GENERIC_EXECUTE,
|
||
|
&ObjectAttributes,
|
||
|
&IoStatus,
|
||
|
NULL,
|
||
|
FILE_ATTRIBUTE_NORMAL,
|
||
|
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
||
|
FILE_OPEN_IF,
|
||
|
0,
|
||
|
NULL,
|
||
|
0
|
||
|
);
|
||
|
if (!NT_SUCCESS(Status)) {
|
||
|
printf("NtCreateFile: %x\n", Status);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Register our window class and create the sole instance
|
||
|
// of our main window. Then, enter our application message loop
|
||
|
// until the user dismisses the window.
|
||
|
//
|
||
|
|
||
|
ZeroMemory(&WndClass, sizeof(WndClass));
|
||
|
WndClass.lpfnWndProc = DisplayWndProc;
|
||
|
WndClass.hInstance = InstanceHandle;
|
||
|
WndClass.lpszClassName = "TcbViewClass";
|
||
|
Message.wParam = 0;
|
||
|
if (!RegisterClass(&WndClass)) {
|
||
|
printf("RegisterClass: %d\n", GetLastError());
|
||
|
} else {
|
||
|
WindowHandle =
|
||
|
CreateWindowEx(
|
||
|
0,
|
||
|
"TcbViewClass",
|
||
|
"TcbView",
|
||
|
WS_TILEDWINDOW,
|
||
|
CW_USEDEFAULT,
|
||
|
CW_USEDEFAULT,
|
||
|
CW_USEDEFAULT,
|
||
|
CW_USEDEFAULT,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
InstanceHandle,
|
||
|
NULL
|
||
|
);
|
||
|
if (!WindowHandle) {
|
||
|
printf("CreateWindowEx: %d\n", GetLastError());
|
||
|
} else {
|
||
|
while(GetMessage(&Message, NULL, 0, 0)) {
|
||
|
TranslateMessage(&Message);
|
||
|
DispatchMessage(&Message);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return (LONG)Message.wParam;
|
||
|
}
|