![]() |
Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr
Jerzy Wałaszek
|
Na tej lekcji zbadamy potok (pipe), czym on jest i do czego możemy go wykorzystać. Aby urozmaicić temat dorzuciłem technikę zmiany koloru tła i tekstu w kontrolce edycyjnej.

Kod źródłowy możesz pobrać {z tego archiwum}.
Potok jest przewodem komunikacyjnym lub ścieżką z dwoma końcami. Potoku można użyć do wymiany danych pomiędzy dwoma różnymi procesami, albo pomiędzy takimi samymi procesami. To jak radiotelefon walkie-talkie. Dajesz drugiej osobie jeden zestaw i możecie się nawzajem porozumiewać.
Istnieją dwa rodzaje potoków: potoki anonimowe (anonymous pipes) oraz potoki nazwane (named pipes). Potok anonimowy jest, cóż, anonimowy: tj. możesz z niego korzystać bez potrzeby znania jego nazwy. Potok nazwany jest przeciwieństwem: musisz znać jego nazwę, zanim będziesz mógł go użyć.
Potoki można również sklasyfikować zgodnie z ich własnością: jednokierunkowe lub dwukierunkowe. W potoku jednokierunkowym dane mogą przepływać tylko w jedną stronę: z jednego końca na drugi. Natomiast w potoku dwukierunkowym dane można wymieniać pomiędzy oboma końcami.
Potok anonimowy jest zawsze jednokierunkowy, natomiast potok nazwany może być jedno lub dwukierunkowy. Potok nazwany stosowany jest zwykle w środowisku sieciowym, gdzie serwer może połączyć się z kilkoma klientami.
Na tej lekcji zbadamy nieco potok anonimowy, którego głównym przeznaczeniem jest zastosowanie jako ścieżka komunikacyjna pomiędzy procesem głównym a procesem potomnym lub pomiędzy procesami potomnymi.
Potok anonimowy jest naprawdę użyteczny, gdy obsługujesz aplikację konsoli. Jest ona pewnym typem programu Win32, który używa konsoli do wprowadzania i wyprowadzania danych. Konsola przypomina okienko systemu DOS. Jednakże aplikacja konsoli jest w pełni programem 32 bitowym. Może użyć dowolnej funkcji graficznego interfejsu użytkownika (GUI - Graphic User Interface), jak inne programy GUI. Tak się tylko składa, iż ma on konsolę do swojej dyspozycji.
Aplikacja konsoli ma trzy uchwyty, których może używać do wprowadzania i wyprowadzania danych. Noszą one nazwę uchwytów standardowych: standardowe wejście, standardowe wyjście oraz standardowy potok komunikatów o błędach. Uchwyt standardowego wejścia jest stosowany do odczytu informacji z konsoli, a uchwyt standardowego wyjścia umożliwia wyprowadzanie informacji do konsoli. Uchwyt standardowego kanału błędów ma zastosowanie przy zgłaszaniu błędów, ponieważ jego wyjścia nie można przekierować.
Aplikacja konsoli może pobrać te trzy uchwyty standardowe wywołując funkcję GetStdHandle i określając pożądany uchwyt. Aplikacja graficznego interfejsu użytkownika (GUI) nie posiada konsoli. Jeśli wywołasz GetStdHandle, zwróci ona błąd. Jeśli rzeczywiście chcesz skorzystać z konsoli, możesz wywołać AllocConsole, aby przydzielić nową konsolę. Jednakże nie zapomnij wywołać FreeConsole, aby zwolnić konsolę, gdy już przestanie ci być potrzebna.
Anonimowy potok najczęściej ma zastosowanie do przekierowywania wejścia lub wyjścia potomnej aplikacji konsoli. Proces nadrzędny może być aplikacją konsoli lub aplikacją GUI, ale proces potomny musi być aplikacją konsoli, aby to zadziałało. Jak wiesz, aplikacja konsoli używa standardowych uchwytów dla swojego wejścia lub wyjścia. Jeśli chcemy przekierować wejście lub wyjście aplikacji konsoli, możemy zastąpić te uchwyty uchwytem jednego z końców potoku. Aplikacja konsoli nie będzie miała pojęcia, iż używa uchwytu do jednego z końców potoku. Użyje go jak standardowego uchwytu. Jest to pewien rodzaj poliformizmu w żargonie OOP (Object Oriented Programming - Programowanie Zorientowane Obiektowo). Podejście to jest bardzo mocne, ponieważ w żaden sposób nie musimy zmieniać procesu potomnego.
Jeśli chodzi o aplikację konsoli, to powinieneś jeszcze dowiedzieć się, skąd ona pobiera te standardowe uchwyty. Gdy aplikacja konsoli zostaje utworzona, proces nadrzędny ma dwie możliwości: może utworzyć nową konsolę dla procesu potomnego lub pozwolić mu odziedziczyć własną konsolę. Aby drugie podejście zadziałało, proces nadrzędny musi być aplikacją konsoli lub, jeśli jest aplikacją GUI, musi wywołać najpierw AllocConsole w celu przydzielenia konsoli.
Weźmy się do pracy. Aby utworzyć anonimowy potok, musisz wywołać funkcję CreatePipe o następującym prototypie:
CreatePipe PROTO pReadHandle: DWORD,\
pWriteHandle: DWORD,\
pPipeAttributes: DWORD,\
nBufferSize: DWORD
Jeśli wywołanie się powiedzie, to wartość zwrotna będzie różna od zera. W takim przypadku otrzymasz dwa uchwyty, jeden do końca odczytującego, drugi do końca zapisującego potoku. Teraz wypunktuję kroki niezbędne do przekierowania standardowego wyjścia potomnego programu konsoli do twojego własnego procesu. Moja metoda różni się od opisanej w pliku pomocy dla Win32 firmy Borland, która zakłada, iż proces nadrzędny jest aplikacją konsoli i stąd proces potomny może od niego odziedziczyć standardowe uchwyty. Częściej musimy przekierowywać wyjście z aplikacji konsoli do aplikacji GUI.
Plik PIPE.ASM.
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\user32.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\gdi32.inc
INCLUDELIB \masm32\lib\gdi32.lib
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\kernel32.lib
WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD
.CONST
IDR_MAINMENU EQU 101
IDM_ASSEMBLE EQU 40001
.DATA
ClassName DB "PipeWinClass", 0
AppName DB "Potok Jednokierunkowy", 0
EditClass DB "EDIT", 0
CreatePipeError DB "Błąd przy tworzeniu potoku", 0
CreateProcessError DB "Błąd przy tworzeniu procesu", 0
;--------------------------------------------------------------
;UWAGA:
;Poniższy wiersz umożliwia wywołanie programu ml.exe. Jeśli
;znajduje się on w innym katalogu niż podany poniżej, to
;musisz odpowiednio zmodyfikować polecenie. W przeciwnym
;razie prezentowany przykład nie uruchomi tego procesu
;--------------------------------------------------------------
CommandLine DB "\masm32\bin\ml /c /coff /Cp test.asm", 0
.DATA?
hInstance HINSTANCE ?
hwndEdit DD ?
.CODE
start:
INVOKE GetModuleHandle, NULL
mov hInstance, eax
INVOKE WinMain, hInstance, NULL, NULL, SW_SHOWDEFAULT
INVOKE ExitProcess, eax
WinMain PROC hInst: DWORD,\
hPrevInst: DWORD,\
CmdLine: DWORD,\
CmdShow: DWORD
LOCAL wc : WNDCLASSEX
LOCAL msg : MSG
LOCAL hwnd : HWND
mov wc.cbSize, SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW OR CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground, COLOR_APPWORKSPACE
mov wc.lpszMenuName, IDR_MAINMENU
mov wc.lpszClassName, OFFSET ClassName
INVOKE LoadIcon, NULL, IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax
INVOKE LoadCursor, NULL, IDC_ARROW
mov wc.hCursor, eax
INVOKE RegisterClassEx, ADDR wc
INVOKE CreateWindowEx, WS_EX_CLIENTEDGE,\
ADDR ClassName, ADDR AppName,\
WS_OVERLAPPEDWINDOW OR WS_VISIBLE,\
CW_USEDEFAULT, CW_USEDEFAULT,\
467, 200, NULL, NULL, hInst, NULL
mov hwnd, eax
.WHILE TRUE
INVOKE GetMessage, ADDR msg, NULL, 0, 0
.BREAK .IF (!eax)
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDW
mov eax, msg.wParam
ret
WinMain ENDP
WndProc PROC hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rect: RECT
LOCAL hRead: DWORD
LOCAL hWrite: DWORD
LOCAL startupinfo: STARTUPINFO
LOCAL pinfo: PROCESS_INFORMATION
LOCAL buffer[1024]: BYTE
LOCAL bytesRead: DWORD
LOCAL hdc: DWORD
LOCAL sat: SECURITY_ATTRIBUTES
.IF uMsg==WM_CREATE
INVOKE CreateWindowEx, NULL, ADDR EditClass, NULL,\
WS_CHILD OR WS_VISIBLE OR \
ES_MULTILINE OR ES_AUTOHSCROLL OR \
ES_AUTOVSCROLL, 0, 0, 0, 0,\
hWnd, NULL, hInstance, NULL
mov hwndEdit, eax
.ELSEIF uMsg==WM_CTLCOLOREDIT
INVOKE SetTextColor, wParam, Yellow
INVOKE SetBkColor, wParam, Black
INVOKE GetStockObject, BLACK_BRUSH
ret
.ELSEIF uMsg==WM_SIZE
movzx edx, WORD PTR lParam
movzx ecx, WORD PTR lParam + 2
INVOKE MoveWindow, hwndEdit, 0, 0, edx, ecx, TRUE
.ELSEIF uMsg==WM_COMMAND
.IF lParam==0
mov eax, wParam
.IF ax==IDM_ASSEMBLE
mov sat.nLength, SIZEOF SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor, NULL
mov sat.bInheritHandle, TRUE
INVOKE CreatePipe, ADDR hRead, ADDR hWrite, ADDR sat, NULL
.IF eax==NULL
INVOKE MessageBox, hWnd, ADDR CreatePipeError, ADDR AppName, MB_ICONERROR OR MB_OK
.ELSE
mov startupinfo.cb, SIZEOF STARTUPINFO
INVOKE GetStartupInfo, ADDR startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput, eax
mov startupinfo.hStdError, eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW OR STARTF_USESTDHANDLES
mov startupinfo.wShowWindow, SW_HIDE
;-------------------------------------------------
; Tworzenie procesu
;-------------------------------------------------
INVOKE CreateProcess, NULL, ADDR CommandLine,\
NULL, NULL, TRUE, NULL, NULL,\
NULL, ADDR startupinfo,\
ADDR pinfo
.IF eax==NULL
INVOKE MessageBox, hWnd,\
ADDR CreateProcessError,\
ADDR AppName,\
MB_ICONERROR OR MB_OK
.ELSE
INVOKE CloseHandle, hWrite
.WHILE TRUE
INVOKE RtlZeroMemory, ADDR buffer, 1024
INVOKE ReadFile, hRead, ADDR buffer, 1023, ADDR bytesRead, NULL
.BREAK .IF (!eax)
INVOKE SendMessage, hwndEdit, EM_SETSEL, -1, 0
INVOKE SendMessage, hwndEdit, EM_REPLACESEL, FALSE, ADDR buffer
.ENDW
.ENDIF
INVOKE CloseHandle, hRead
INVOKE CloseHandle, pinfo.hProcess
INVOKE CloseHandle, pinfo.hThread
.ENDIF
.ENDIF
.ENDIF
.ELSEIF uMsg==WM_DESTROY
INVOKE PostQuitMessage, NULL
.ELSE
INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax
ret
WndProc ENDP
END start
Plik zasobów PIPE.RC:
#define IDR_MAINMENU 101
#define IDM_ASSEMBLE 40001
IDR_MAINMENU MENU DISCARDABLE
{
POPUP "&Dzia-anie"
{
MENUITEM "&Uruchom Asembler", IDM_ASSEMBLE
}
}
Nasz przykład wywoła aplikację ml.exe (to oczywiście nasz poczciwy MASM), aby dokonać asemblacji pliku o nazwie test.asm (dołączony do archiwum zip) i przekieruje wyjście ml.exe do kontrolki edycyjnej w jej obszarze roboczym.
Gdy program zostaje załadowany, jak zwykle rejestruje on klasę okna i tworzy okno główne. Podczas tworzenia okna głównego powstaje kontrolka edycyjna, która będzie używana do wyświetlenia danych z wyjścia programu ml.exe.
A teraz interesujący fragment - zmienimy kolory tekstu oraz tła w kontrolce edycyjnej. Gdy kontrolka edycyjna ma zamiar malować swój obszar roboczy, wysyła wiadomość WM_CTLCOLOREDIT do swojego okna nadrzędnego. wParam zawiera uchwyt kontekstu urządzenia, który użyje kontrolka edycyjna do zapisu swojego własnego obszaru roboczego. Możemy wykorzystać tę okazję do zmodyfikowania charakterystyki uchwytu kontekstu urządzenia.
.ELSEIF uMsg==WM_CTLCOLOREDIT
INVOKE SetTextColor, wParam, Yellow
INVOKE SetBkColor, wParam, Black
INVOKE GetStockObject, BLACK_BRUSH
ret
SetTextColor zmienia kolor tekstu na żółty (Yellow). SetBkColor zmienia kolor tła tekstu na czarny (Black). A na koniec pobieramy uchwyt do czarnego pędzla, który zwracamy do systemu Windows. Przy wiadomości WM_CTLCOLOREDIT musisz zwrócić uchwyt do pędzla (brush), który zostanie użyty przez system Windows do pomalowania tła kontrolki edycyjnej. W naszym przykładzie chcemy mieć czarne tło, zatem zwracamy systemowi Windows uchwyt do czarnego pędzla.
Teraz gdy użytkownik wybierze opcję menu Uruchom Asembler, powstanie anonimowy potok.
.IF ax==IDM_ASSEMBLE
mov sat.nLength, SIZEOF SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor, NULL
mov sat.bInheritHandle, TRUE
Przed wywołaniem CreatePipe musimy najpierw wypełnić strukturę SECURITY_ATTRIBUTES. Zwróć uwagę, iż możemy użyć wartości NULL w polu lpSecurityDescriptor, jeśli nie zależy nam na zabezpieczeniach. A pole bInheritHandle musi zawierać wartość TRUE, aby uchwyty potoków mogły być dziedziczone w procesach potomnych.
INVOKE CreatePipe, ADDR hRead, ADDR hWrite, ADDR sat, NULL
Po wykonaniu tych czynności wywołujemy funkcję CreatePipe, która w przypadku powodzenia wypełni zmienne hRead i hWrite kolejno uchwytami do końców czytającego i zapisującego potoku.
mov startupinfo.cb, SIZEOF STARTUPINFO
INVOKE GetStartupInfo, ADDR startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput, eax
mov startupinfo.hStdError, eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW OR STARTF_USESTDHANDLES
mov startupinfo.wShowWindow, SW_HIDE
Następnie musimy wypełnić strukturę STARTUPINFO. Wywołujemy funkcję GetStartupInfo w celu wypełnienia struktury STARTUPINFO wartościami standardowymi procesu nadrzędnego. MUSISZ wypełnić tę strukturę przy pomocy tego wywołania, jeśli planujesz pracę swojego kodu zarówno pod Windows 9x jak i NT/2000/XP. Po powrocie z wywołania GetStartupInfo możesz zmodyfikować istotne pola. Kopiujemy uchwyt zapisującego końca potoku do pól hStdOutput i hStdError, ponieważ chcemy, aby proces potomny korzystał z niego w miejsce standardowych uchwytów wyjścia i błędu. Chcemy również ukryć okno konsoli procesu potomnego, zatem wstawiamy wartość SW_HIDE do pola wShowWidow. A na koniec musimy zaznaczyć, iż pola hStdOutput, hStdError i wShowWindow zawierają informację i muszą być użyte. Dokonujemy tego określając znaczniki STARTF_USESHOWWINDOW oraz STARTF_USESTDHANDLES w polu dwFlags.
INVOKE CreateProcess, NULL, ADDR CommandLine,\
NULL, NULL, TRUE, NULL, NULL,\
NULL, ADDR startupinfo,\
ADDR pinfo
Teraz tworzymy proces potomny za pomocą wywołania CreateProcess. Zwróć uwagę, iż parametr bInheritHandles musi mieć wartość TRUE, aby działał uchwyt do potoku.
INVOKE CloseHandle, hWrite
Gdy z powodzeniem utworzymy proces potomny, musimy zamknąć zapisujący koniec potoku. Przypomnij sobie, iż przekazaliśmy uchwyt zapisującego końca potoku procesowi potomnemu poprzez strukturę STARTUPINFO. Jeśli nie zamkniemy uchwytu zapisu z naszego końca, to będą istniały dwa końce zapisu. A wtedy potok nie będzie pracował prawidłowo. Musimy zamknąć uchwyt zapisu po wywołaniu CreateProcess, lecz przed odczytem danych z końca odczytującego potoku.
.WHILE TRUE
INVOKE RtlZeroMemory, ADDR buffer, 1024
INVOKE ReadFile, hRead, ADDR buffer, 1023, ADDR bytesRead, NULL
.BREAK .IF (!eax)
INVOKE SendMessage, hwndEdit, EM_SETSEL, -1, 0
INVOKE SendMessage, hwndEdit, EM_REPLACESEL, FALSE, ADDR buffer
.ENDW
Teraz jesteśmy gotowi do odczytu danych ze standardowego wyjścia procesu potomnego. Pozostajemy w nieskończonej pętli, aż zabraknie danych do odczytu z potoku. Wywołujemy funkcję RtlZeroMemory, aby wypełnić bufor zerami, a następnie wywołujemy ReadFile przekazując uchwyt potoku w miejscu uchwytu pliku. Zwróć uwagę, iż odczytujemy maksymalnie 1023 bajty, ponieważ ostatni znak (1024-ty) w buforze musi być zerem, aby powstał łańcuch ASCIIZ, który dalej możemy przekazać do kontrolki edycyjnej.
Gdy funkcja ReadFile powróci z danymi w buforze, wypełniamy nimi kontrolkę edycyjną. Jednakże mamy tutaj niewielki problem. Jeśli użyjemy funkcji SetWindowText do wstawienia danych do kontrolki edycyjnej, nowe dane zapiszą dane poprzednio wpisane! My chcemy dołączać dane na koniec tych, które tam już się znajdują.
Aby osiągnąć ten cel, musimy najpierw przesunąć kursor na koniec tekstu w kontrolce edycyjnej wysyłając wiadomość EM_SETSEL z wParam równym -1. Następnie dołączamy dane w tym punkcie przy pomocy wiadomości EM_REPLACESEL.
INVOKE CloseHandle, hRead
Gdy funkcja ReadFile zwróci wartość NULL, przerywamy wykonywanie pętli i zamykamy uchwyt odczytu.
Ta sama aplikacja w Pascalu:
{********************************
** I Liceum Ogólnokształcące **
** w Tarnowie **
** mgr Jerzy Wałaszek **
********************************}
program Pipe;
uses Windows;
const
IDR_MAINMENU = 101;
IDM_ASSEMBLE = 40001;
ClassName = 'PipeWinClass';
AppName = 'Potok Jednokierunkowy';
EditClass = 'EDIT';
CreatePipeError = 'Błąd przy tworzeniu potoku';
CreateProcessError = 'Błąd przy tworzeniu procesu';
//--------------------------------------------------------------
//UWAGA:
// Poniższy wiersz umożliwia wywołanie programu ml.exe. Jeśli
// znajduje się on w innym katalogu niż podany poniżej, to
// musisz odpowiednio zmodyfikować polecenie. W przeciwnym
// razie prezentowany przykład nie uruchomi tego procesu
//--------------------------------------------------------------
CommandLine = 'c:\masm32\bin\ml /c /coff /Cp test.asm';
var
hInstance : HINST;
hwndEdit : HANDLE;
function WndProc(hWnd:HWND;uMsg:UINT;wParam:WPARAM;lParam:LPARAM) : longint;
var
rect : RECT;
sat : SECURITY_ATTRIBUTES;
startupinfo : STARTUPINFO;
pinfo : PROCESS_INFORMATION;
buffer : array[1..1023] of byte;
hRead : longword;
hWrite : longword;
bytesRead : longword;
hdc : longword;
begin
Result := 0;
case uMsg of
WM_CREATE:
hwndEdit := CreateWindowEx(0,EditClass,0,
WS_CHILD or WS_VISIBLE or ES_MULTILINE or ES_AUTOHSCROLL or
ES_AUTOVSCROLL,0,0,0,0,hWnd,0,hInstance,0);
WM_CTLCOLOREDIT:
begin
SetTextColor(wParam,$00ffff);
SetBkColor(wParam,$000000);
Result := GetStockObject(BLACK_BRUSH);
end;
WM_SIZE:
MoveWindow(hwndEdit,0,0,lParam and $ffff,lParam shr 16,true);
WM_COMMAND:
if (lParam = 0) and ((wParam and $ffff) = IDM_ASSEMBLE) then
begin
sat.nLength := sizeof(SECURITY_ATTRIBUTES);
sat.lpSecurityDescriptor := 0;
sat.bInheritHandle := true;
if not CreatePipe(hRead,hWrite,@sat,0) then
MessageBox(hWnd,CreatePipeError,AppName,MB_ICONERROR or MB_OK)
else
begin
with startupinfo do
begin
cb := sizeof(STARTUPINFO);
GetStartupInfo(@startupinfo);
hStdOutput := hWrite;
hStdError := hWrite;
dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
wShowWindow := SW_HIDE;
end;
//-------------------------------------------------
// Tworzenie procesu
//-------------------------------------------------
if not CreateProcess(0,CommandLine,0,0,true,0,0,0,startupinfo,pinfo) then
MessageBox(hWnd,CreateProcessError,AppName,MB_ICONERROR or MB_OK)
else
begin
CloseHandle(hWrite);
while true do
begin
FillChar(buffer,1024,0);
if not ReadFile(hRead,buffer,1023,bytesRead,0) then break;
SendMessage(hwndEdit,EM_SETSEL,-1,0);
SendMessage(hwndEdit,EM_REPLACESEL,0,longint(@buffer));
end;
end;
CloseHandle(hRead);
CloseHandle(pinfo.hProcess);
CloseHandle(pinfo.hThread);
end;
end;
WM_DESTROY: PostQuitMessage(0);
else Result := DefWindowProc(hWnd,uMsg,wParam,lParam);
end;
end;
function WinMain(hInst,hPrevInst,CmdLine,CmdShow:longword) : longint;
var
wc : WNDCLASSEX;
msg : MSG;
hwnd : HWND;
begin
with wc do
begin
cbSize := sizeof(WNDCLASSEX);
style := CS_HREDRAW or CS_VREDRAW;
lpfnWndProc := @WndProc;
cbClsExtra := 0;
cbWndExtra := 0;
hInstance := hInst;
hbrBackground := COLOR_APPWORKSPACE;
lpszMenuName := IDR_MAINMENU;
lpszClassName := ClassName;
hIcon := LoadIcon(0,IDI_APPLICATION);
hIconSm := hIcon;
hCursor := LoadCursor(0,IDC_ARROW);
end;
RegisterClassEx(wc);
hwnd := CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,
WS_OVERLAPPEDWINDOW or WS_VISIBLE,CW_USEDEFAULT,CW_USEDEFAULT,
467,200,0,0,hInst,0);
while GetMessage(msg,0,0,0) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
Result := msg.wParam;
end;
begin
hInstance := GetModuleHandle(0);
ExitProcess(WinMain(hInstance,0,0,SW_SHOWDEFAULT));
end.
|
Autorem
kursu jest
Iczelion. Kurs
programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną
zgodą autora. |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe