델파이 프로그래밍에서 "흐름"의 적용에 대해 이야기하기
첸징타오
스트림이란 무엇입니까? 스트림(Stream)은 간단히 말해서 객체지향을 기반으로 한 추상적인 데이터 처리 도구입니다. 스트림에는 데이터 읽기, 데이터 쓰기 등과 같은 데이터 처리를 위한 몇 가지 기본 작업이 정의되어 있습니다. 프로그래머는 스트림의 다른 쪽 끝에서 데이터의 실제 흐름 방향을 고려하지 않고 스트림에서 모든 작업을 수행합니다. 스트림은 파일뿐만 아니라 동적 메모리, 네트워크 데이터 및 기타 데이터 형식도 처리할 수 있습니다. 스트림 작업에 능숙하고 프로그램에서 스트림의 편리함을 활용한다면 프로그램 작성 효율성이 크게 향상됩니다.
아래에서 저자는 델파이 프로그래밍에서 "스트림"의 사용을 설명하기 위해 EXE 파일 암호화기, 전자 인사말 카드, 자체 제작 OICQ 및 네트워크 화면 전송이라는 네 가지 예를 사용합니다. 이 예제의 일부 기술은 한때 많은 소프트웨어의 비밀이었고 대중에게 공개되지 않았습니다. 이제 모든 사람이 무료로 코드를 직접 인용할 수 있습니다.
"높은 건물은 땅에서 솟아오른다." 사례를 분석하기 전에 먼저 흐름의 기본 개념과 기능을 이해해야 다음 단계로 진행할 수 있습니다. 이러한 기본 방법을 주의 깊게 이해하시기 바랍니다. 물론, 이미 익숙하다면 이 단계를 건너뛰어도 됩니다.
1. 델파이의 스트림 기본 개념과 함수 선언
Delphi에서 모든 스트림 객체의 기본 클래스는 모든 스트림의 공통 속성과 메서드를 정의하는 TStream 클래스입니다.
TStream 클래스에 정의된 속성은 다음과 같이 소개됩니다.
1. 크기: 이 속성은 스트림의 데이터 크기를 바이트 단위로 반환합니다.
2. 위치: 이 속성은 흐름에서 액세스 포인터의 위치를 제어합니다.
Tstream에는 네 가지 가상 메서드가 정의되어 있습니다.
1. 읽기: 이 방법은 스트림에서 데이터를 읽습니다. 함수 프로토타입은 다음과 같습니다.
함수 읽기(var Buffer;Count:Longint):Longint;virtual;abstract;
Buffer 매개변수는 데이터를 읽을 때 배치되는 버퍼입니다. Count는 읽을 데이터의 바이트 수입니다. 이 메서드의 반환 값은 읽은 실제 바이트 수입니다. 세다.
2. 쓰기: 이 방법은 스트림에 데이터를 씁니다. 함수 프로토타입은 다음과 같습니다.
함수 쓰기(var Buffer;Count:Longint):Longint;virtual;abstract;
매개변수 Buffer는 스트림에 기록될 데이터의 버퍼이고, Count는 데이터의 길이(바이트)이며, 이 메서드의 반환 값은 실제로 스트림에 기록되는 바이트 수입니다.
3. 탐색: 이 메서드는 스트림에서 읽기 포인터의 이동을 구현합니다. 함수 프로토타입은 다음과 같습니다.
함수 탐색(오프셋:Longint;원본:Word):Longint;가상;추상;
Offset 매개변수는 오프셋 바이트 수이며 Origin 매개변수는 Offset의 실제 의미를 나타냅니다.
soFromBeginning:Offset은 이동 후 데이터의 시작 부분부터 포인터의 위치입니다. 이때 Offset은 0보다 크거나 같아야 합니다.
soFromCurrent:Offset은 이동 후 포인터와 현재 포인터의 상대적 위치입니다.
soFromEnd:Offset은 이동 후 데이터 끝에서부터 포인터의 위치입니다. 이때 Offset은 0보다 작거나 같아야 합니다. 이 메서드의 반환 값은 이동 후 포인터의 위치입니다.
4. Setsize: 이 방법은 데이터의 크기를 변경합니다. 함수 프로토타입은 다음과 같습니다.
함수 Setsize(NewSize:Longint);virtual;
또한 TStream 클래스에는 여러 정적 메서드도 정의되어 있습니다.
1. ReadBuffer: 이 메서드의 기능은 스트림의 현재 위치에서 데이터를 읽는 것입니다. 함수 프로토타입은 다음과 같습니다.
PRocedure ReadBuffer(var Buffer;Count:Longint);
매개변수의 정의는 위의 읽기와 동일합니다. 참고: 읽은 데이터 바이트 수가 읽어야 하는 바이트 수와 같지 않으면 EReadError 예외가 생성됩니다.
2. WriteBuffer: 이 메소드의 기능은 현재 위치의 스트림에 데이터를 쓰는 것입니다. 함수 프로토타입은 다음과 같습니다.
프로시저 WriteBuffer(var Buffer;Count:Longint);
매개변수 정의는 위의 쓰기와 동일합니다. 참고: 기록된 데이터 바이트 수가 기록되어야 하는 바이트 수와 동일하지 않으면 EWriteError 예외가 생성됩니다.
3. CopyFrom: 이 방법은 다른 스트림에서 데이터 스트림을 복사하는 데 사용됩니다. 함수 프로토타입은 다음과 같습니다.
함수 CopyFrom(소스:TStream;Count:Longint):Longint;
매개변수 Source는 데이터를 제공하는 스트림이고 Count는 복사된 데이터 바이트 수입니다. Count가 0보다 크면 CopyFrom은 Source 매개 변수의 현재 위치에서 Count 바이트의 데이터를 복사합니다. Count가 0이면 CopyFrom은 Source 매개 변수의 Position 속성을 0으로 설정한 다음 Source의 모든 데이터를 복사합니다. ;
TStream에는 다른 파생 클래스가 있으며, 가장 일반적으로 사용되는 클래스는 TFileStream 클래스입니다. TFileStream 클래스를 사용하여 파일에 액세스하려면 먼저 인스턴스를 생성해야 합니다. 성명은 다음과 같습니다 :
생성자 Create(const 파일 이름:문자열;모드:Word);
Filename은 파일명(경로 포함)이고, Mode는 파일을 여는 방식으로, 파일 열기 모드와 공유 모드를 포함할 수 있는 값과 의미는 다음과 같습니다.
개방형 모드:
fmCreate: 지정된 파일 이름으로 파일을 생성하거나, 이미 존재하는 경우 파일을 엽니다.
fmOpenRead: 지정된 파일을 읽기 전용 모드로 엽니다.
fmOpenWrite: 지정된 파일을 쓰기 전용 모드로 엽니다.
fmOpenReadWrite: 쓰기를 위해 지정된 파일을 엽니다.
공유 모드:
fmShareCompat: 공유 모드는 FCB와 호환됩니다.
fmShareExclusive: 다른 프로그램이 어떤 방식으로든 파일을 열 수 없도록 허용합니다.
fmShareDenyWrite: 다른 프로그램이 쓰기 위해 파일을 여는 것을 허용하지 않습니다.
fmShareDenyRead: 다른 프로그램이 읽기 모드에서 파일을 여는 것을 허용하지 않습니다.
fmShareDenyNone: 다른 프로그램이 어떤 방식으로든 파일을 열 수 있습니다.
TStream에는 실제 애플리케이션에서 매우 자주 사용되는 파생 클래스인 TMemoryStream도 있습니다. 이를 메모리 스트림이라고 하며, 이는 메모리에 스트림 개체를 생성하는 것을 의미합니다. 기본적인 방법과 기능은 위와 동일합니다.
자, 위의 기반이 마련되면 프로그래밍 여정을 시작할 수 있습니다.
------------------------------------- -------------
2. 실제 적용 1: 스트림을 사용하여 EXE 파일 암호화기, 번들, 자동 추출 파일 및 설치 프로그램 생성
먼저 EXE 파일 암호화기를 만드는 방법에 대해 이야기해 보겠습니다.
EXE 파일 암호화기의 원리: 두 개의 파일을 생성합니다. 하나는 추가 기능 프로그램이라고 하는 다른 EXE 파일에 리소스를 추가하는 데 사용됩니다. 추가되는 또 다른 EXE 파일을 헤더 파일이라고 합니다. 이 프로그램의 기능은 자신에게 추가된 파일을 읽는 것입니다. Windows의 EXE 파일 구조는 상대적으로 복잡하며 일부 프로그램에는 체크섬도 있습니다. 변경된 것을 발견하면 바이러스에 감염된 것으로 간주하여 실행을 거부합니다. 그래서 원래 파일 구조가 변경되지 않도록 파일을 자체 프로그램에 추가합니다. 먼저 add 함수를 작성해 보겠습니다. 이 함수의 기능은 파일을 다른 파일의 끝에 스트림으로 추가하는 것입니다. 기능은 다음과 같습니다:
함수 Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
대상,소스:TFileStream;
MyFileSize:정수;
시작하다
노력하다
소스:=TFileStream.Create(SourceFile,fmOpenRead 또는 fmShareExclusive);
대상:=TFileStream.Create(TargetFile,fmOpenWrite 또는 fmShareExclusive);
노력하다
Target.Seek(0,soFromEnd);//끝에 리소스 추가
Target.CopyFrom(소스,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//리소스 크기를 계산하여 보조 프로세스 끝에 씁니다.
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
마지막으로
대상.무료;
소스.무료;
끝;
제외하고
결과:=거짓;
출구;
끝;
결과:=참;
끝;
위의 기초를 바탕으로 이 기능을 쉽게 이해할 수 있습니다. SourceFile 매개변수는 추가할 파일이고, TargetFile 매개변수는 추가할 대상 파일입니다. 예를 들어 a.exe를 b.exe에 추가하려면 Cjt_AddtoFile('a.exe',b.exe'); 추가에 성공하면 True를 반환하고, 그렇지 않으면 False를 반환합니다.
위 함수를 기반으로 반대 읽기 함수를 작성할 수 있습니다.
함수 Cjt_LoadFromFile(SourceFile,TargetFile:string):Boolean;
var
출처:TFileStream;
대상:TMemoryStream;
MyFileSize:정수;
시작하다
노력하다
대상:=TMemoryStream.Create;
소스:=TFileStream.Create(SourceFile,fmOpenRead 또는 fmShareDenyNone);
노력하다
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//리소스 크기 읽기
Source.Seek(-MyFileSize,soFromEnd);//리소스 위치 찾기
Target.CopyFrom(Source,MyFileSize-sizeof(MyFileSize));//리소스 제거
Target.SaveToFile(TargetFile);//파일에 저장
마지막으로
대상.무료;
소스.무료;
끝;
제외하고
결과:=false;
출구;
끝;
결과:=true;
끝;
SourceFile 파라미터는 파일이 추가된 파일의 이름이고, TargetFile 파라미터는 파일을 꺼낸 후 저장되는 대상 파일의 이름입니다. 예를 들어 Cjt_LoadFromFile('b.exe','a.txt'); b.exe에서 파일을 꺼내서 a.txt로 저장합니다. 추출이 성공하면 True를 반환하고 그렇지 않으면 False를 반환합니다.
Delphi를 열고 새 프로젝트를 만든 다음 편집 컨트롤 Edit1과 두 개의 버튼(Button1 및 Button2)을 창에 놓습니다. 단추의 캡션 속성은 각각 "확인"과 "취소"로 설정됩니다. Button1의 Click 이벤트에 코드를 작성합니다.
var S: 문자열;
시작하다
S:=ChangeFileExt(application.ExeName,'.Cjt');
Edit1.Text='790617'이면
시작하다
Cjt_LoadFromFile(Application.ExeName,S);
{파일을 꺼내서 현재 경로에 저장하고 이름을 "original file.Cjt"로 지정하세요.}
Winexec(pchar(S),SW_Show);{"원본 파일.Cjt" 실행}
신청.종료;{프로그램 종료}
끝
또 다른
Application.MessageBox('비밀번호가 틀렸습니다. 다시 입력해주세요!', '비밀번호가 틀렸습니다.', MB_ICONERROR+MB_OK);
이 프로그램을 컴파일하고 EXE 파일의 이름을 head.exe로 바꿉니다. head exefile head.exe라는 내용으로 새 텍스트 파일 head.rc를 생성한 다음 이를 Delphi의 BIN 디렉터리에 복사하고 Dos 명령 Brcc32.exe head.rc를 실행하면 head.res 파일이 생성됩니다. 파일은 먼저 원하는 리소스 파일을 유지합니다.
헤더 파일이 생성되었습니다. 추가 기능 프로그램을 만들어 보겠습니다.
새 프로젝트를 만들고 다음 컨트롤을 배치합니다. 두 Button1의 편집, 열기 대화 상자 및 캡션 속성은 각각 "파일 선택"과 "암호화"로 설정됩니다. 소스 프로그램에 {$R head.res}라는 문장을 추가하고 head.res 파일을 프로그램의 현재 디렉터리에 복사합니다. 이런 식으로 방금 head.exe가 프로그램과 함께 컴파일됩니다.
Button1의 Clck 이벤트에 코드를 작성합니다.
if OpenDialog1.Execute then Edit1.Text:=OpenDialog1.FileName;
Button2의 Clck 이벤트에 코드를 작성합니다.
var S:문자열;
시작하다
S:=ExtractFilePath(Edit1.Text);
if ExtractRes('exefile','head',S+'head.exe') 그러면
if Cjt_AddtoFile(Edit1.Text,S+'head.exe') 그러면
if DeleteFile(Edit1.Text) 다음
if RenameFile(S+'head.exe',Edit1.Text) 그러면
Application.MessageBox('파일 암호화에 성공했습니다!','메시지',MB_ICONINFORMATION+MB_OK)
또 다른
시작하다
if FileExists(S+'head.exe') then DeleteFile(S+'head.exe');
Application.MessageBox('파일 암호화에 실패했습니다!','메시지',MB_ICONINFORMATION+MB_OK)
끝;
끝;
그 중 ExtractRes는 리소스 파일에서 head.exe를 추출하는 데 사용되는 사용자 정의 함수입니다.
함수 ExtractRes(ResType, ResName, ResNewName : String):boolean;
var
해상도: TResourceStream;
시작하다
노력하다
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
노력하다
Res.SavetoFile(ResNewName);
결과:=true;
마지막으로
해상도 무료;
끝;
제외하고
결과:=false;
끝;
끝;
참고: 위의 기능은 단순히 한 파일을 다른 파일의 끝에 추가합니다. 실제 응용에서는 오프셋 주소가 실제 크기와 개수에 따라 정의되는 한 여러 파일을 추가하도록 변경할 수 있습니다. 예를 들어, 파일 번들러는 헤더 파일에 두 개 이상의 프로그램을 추가합니다. 자동 압축 풀기 프로그램과 설치 프로그램의 원리는 동일하지만 압축률이 더 높습니다. 예를 들어 LAH 단위를 참조하고 스트림을 압축한 다음 추가하면 파일이 더 작아집니다. 읽기 전에 압축을 풀면 됩니다. 또한, 기사에 나온 EXE 암호화기의 예는 아직 불완전한 부분이 많습니다. 예를 들어, 비밀번호가 "790617"로 고정되어 있고, EXE를 꺼내 실행한 후 실행이 끝나면 삭제해야 하는 등의 문제가 있습니다. . 독자가 직접 수정할 수 있습니다.
------------------------------------- ------
3. 실제 적용 2: 스트림을 사용하여 실행 가능한 전자 인사말 카드 만들기
우리는 종종 사진을 직접 선택할 수 있는 일부 전자 축하 카드 제작 소프트웨어를 볼 수 있으며, 그러면 EXE 실행 파일이 생성됩니다. 축하 카드를 열면 음악이 재생되면서 사진이 표시됩니다. 이제 스트림 작업을 배웠으므로 만들 수도 있습니다.
이미지를 추가하는 과정에서 기존의 Cjt_AddtoFile을 직접 사용할 수 있는데, 이제 해야 할 일은 이미지를 어떻게 읽어서 표시하느냐 하는 것입니다. 이전 Cjt_LoadFromFile을 사용하여 먼저 이미지를 읽고 파일로 저장한 다음 로드할 수 있습니다. 그러나 강력한 스트림 도구를 사용하여 파일 스트림을 직접 읽고 표시하는 더 간단한 방법이 있습니다. , 모든 것이 간단해집니다.
요즘 가장 인기 있는 사진은 BMP 형식과 JPG 형식입니다. 이제 이 두 종류의 그림에 대한 읽기 및 표시 기능을 작성하겠습니다.
함수 Cjt_BmpLoad(ImgBmp:TImage;SourceFile:String):Boolean;
var
출처:TFileStream;
MyFileSize:정수;
시작하다
소스:=TFileStream.Create(SourceFile,fmOpenRead 또는 fmShareDenyNone);
노력하다
노력하다
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//리소스 읽기
Source.Seek(-MyFileSize,soFromEnd);//리소스의 시작 위치를 찾습니다.
ImgBmp.Picture.Bitmap.LoadFromStream(소스);
마지막으로
소스.무료;
끝;
제외하고
결과:=거짓;
출구;
끝;
결과:=참;
끝;
위는 BMP 이미지를 읽는 함수이고, 아래는 JPG 이미지를 읽는 함수이다. JPG 단위를 사용하기 때문에 프로그램에 'uses jpeg'라는 문장을 추가해야 한다.
함수 Cjt_JpgLoad(JpgImg:Timage;SourceFile:String):Boolean;
var
출처:TFileStream;
MyFileSize:정수;
내jpg: TJpegImage;
시작하다
노력하다
Myjpg:= TJpegImage.Create;
소스:=TFileStream.Create(SourceFile,fmOpenRead 또는 fmShareDenyNone);
노력하다
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));
Source.Seek(-MyFileSize,soFromEnd);
Myjpg.LoadFromStream(소스);
JpgImg.Picture.Bitmap.Assign(Myjpg);
마지막으로
소스.무료;
내jpg.free;
끝;
제외하고
결과:=false;
출구;
끝;
결과:=true;
끝;
이 두 가지 기능을 사용하여 판독 프로그램을 만들 수 있습니다. BMP 사진을 예로 들어보겠습니다.
Delphi를 실행하고 새 프로젝트를 만든 다음 이미지 디스플레이 컨트롤 Image1을 배치합니다. 창의 Create 이벤트에 다음 문장을 작성하세요.
Cjt_BmpLoad(Image1,Application.ExeName);
이것이 헤더 파일이고, 이전 방법을 사용하여 head.res 리소스 파일을 생성합니다.
이제 추가 기능 프로그램 만들기를 시작할 수 있습니다. 전체 코드는 다음과 같습니다.
단위 Unit1;
인터페이스
용도
Windows, 메시지, SysUtils, 클래스, 그래픽, 컨트롤, 양식, 대화 상자,
ExtCtrls, StdCtrls, ExtDlgs;
유형
TForm1 = 클래스(TForm)
편집1: T편집;
버튼1: T버튼;
Button2: T버튼;
OpenPictureDialog1: TOpenPictureDialog;
절차 FormCreate(보내는 사람: TObject);
절차 Button1Click(Sender: TObject);
절차 Button2Click(보내는 사람: TObject);
사적인
함수 ExtractRes(ResType, ResName, ResNewName : String):boolean;
함수 Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
{비공개 선언}
공공의
{공개 선언}
끝;
var
Form1: TForm1;
구현
{$R *.DFM}
함수 TForm1.ExtractRes(ResType, ResName, ResNewName : String):boolean;
var
해상도: TResourceStream;
시작하다
노력하다
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
노력하다
Res.SavetoFile(ResNewName);
결과:=true;
마지막으로
해상도 무료;
끝;
제외하고
결과:=false;
끝;
끝;
함수 TForm1.Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
대상,소스:TFileStream;
MyFileSize:정수;
시작하다
노력하다
소스:=TFileStream.Create(SourceFile,fmOpenRead 또는 fmShareExclusive);
대상:=TFileStream.Create(TargetFile,fmOpenWrite 또는 fmShareExclusive);
노력하다
Target.Seek(0,soFromEnd);//끝에 리소스 추가
Target.CopyFrom(소스,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//리소스 크기를 계산하여 보조 프로세스 끝에 씁니다.
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
마지막으로
대상.무료;
소스.무료;
끝;
제외하고
결과:=거짓;
출구;
끝;
결과:=참;
끝;
절차 TForm1.FormCreate(Sender: TObject);
시작하다
Caption:='Bmp2Exe 데모 프로그램 작성자: Chen Jingtao';
편집1.텍스트:='';
OpenPictureDialog1.DefaultExt := GraphicExtension(TBitmap);
OpenPictureDialog1.Filter := GraphicFilter(TBitmap);
Button1.Caption:='BMP 사진 선택';
Button2.Caption:='EXE 생성';
끝;
절차 TForm1.Button1Click(Sender: TObject);
시작하다
OpenPictureDialog1.Execute이면
Edit1.Text:=OpenPictureDialog1.FileName;
끝;
절차 TForm1.Button2Click(Sender: TObject);
var
헤드온도:문자열;
시작하다
FileExists(Edit1.Text)가 아닌 경우
시작하다
Application.MessageBox('BMP 이미지 파일이 없습니다. 다시 선택하십시오!','Message',MB_ICONINFORMATION+MB_OK)
출구;
끝;
HeadTemp:=ChangeFileExt(Edit1.Text,'.exe');
if ExtractRes('exefile','head',HeadTemp) 그러면
if Cjt_AddtoFile(Edit1.Text,HeadTemp) 다음
Application.MessageBox('EXE 파일이 성공적으로 생성되었습니다!','메시지',MB_ICONINFORMATION+MB_OK)
또 다른
시작하다
if FileExists(HeadTemp) then DeleteFile(HeadTemp);
Application.MessageBox('EXE 파일 생성에 실패했습니다!','Message',MB_ICONINFORMATION+MB_OK)
끝;
끝;
끝.
어때요? 놀랍습니다 :) 프로그램 인터페이스를 더 아름답게 만들고 일부 기능을 추가하면 등록이 필요한 소프트웨어보다 크게 열등하지 않다는 것을 알게 될 것입니다.
------------------------------------- -------------
실제 적용 3: 스트림을 사용하여 자신만의 OICQ 만들기
OICQ는 Shenzhen Tencent Company가 개발한 온라인 실시간 통신 소프트웨어로 중국에 대규모 사용자 기반을 보유하고 있습니다. 단, OICQ를 사용하려면 먼저 인터넷에 연결하고 Tencent 서버에 로그인해야 합니다. 그래서 우리는 직접 작성하여 로컬 네트워크에서 사용할 수 있습니다.
OICQ는 연결 없는 프로토콜인 UDP 프로토콜을 사용합니다. 즉, 통신 당사자는 연결을 설정하지 않고도 정보를 보낼 수 있으므로 효율성이 상대적으로 높습니다. Delphi 자체에 포함된 FastNEt의 NMUDP 제어는 UDP 프로토콜의 사용자 데이터그램 제어입니다. 그러나 이 컨트롤을 사용하는 경우 TNMXXX 컨트롤에 버그가 있으므로 컴퓨터를 종료하기 전에 프로그램을 종료해야 합니다. 모든 nm 컨트롤의 기본인 PowerSocket에서 사용하는 ThreadTimer는 숨겨진 창(TmrWindowClass 클래스)을 사용하여 결함을 처리합니다.
무엇이 잘못됐나요:
Psock::TThreadTimer::WndProc(var msg:TMessage)
msg.message=WM_TIMER인 경우
그 사람이 직접 처리해요
msg.결과:=0
또 다른
msg.result:=DefWindowProc(0,...)
끝
문제는 DefWindowProc를 호출할 때 전송된 HWND 매개변수가 실제로 상수 0이므로 실제로 DefWindowProc가 작동할 수 없다는 것입니다. 모든 입력 메시지에 대한 호출은 WM_QUERYENDsession을 포함하여 0을 반환하므로 창을 종료할 수 없습니다. DefWindowProc의 비정상적인 호출로 인해 실제로 WM_TIMER를 제외하고 DefWindowProc에서 처리하는 다른 메시지는 유효하지 않습니다.
해결책은 PSock.pas에 있습니다.
TThreadTimer.Wndproc 내에서
결과 := DefWindowProc(0, Msg, WPARAM, LPARAM);
다음으로 변경:
결과 := DefWindowProc( FWindowHandle, Msg, WPARAM, LPARAM );
OICQ의 초기 저수준 버전에도 이 문제가 있었습니다. OICQ가 꺼지지 않으면 화면이 잠시 깜박인 다음 컴퓨터가 꺼지면 다시 나타납니다.
자, 더 이상 고민하지 말고 OICQ를 작성해 보겠습니다. 이것은 실제로 Delphi와 함께 제공되는 예제입니다.
새 프로젝트를 만들고 FASTNET 패널에서 NMUDP 컨트롤을 창으로 드래그한 다음 Editip, EditPort, EditMyTxt라는 3개의 EDIT, BtSend, BtClear, BtSave, MEMOMemoReceive, SaveDialog 및 StatusBar1 버튼 3개를 배치합니다. 사용자가 BtSend를 클릭하면 메모리 스트림 개체가 생성되고 보낼 텍스트 정보가 메모리 스트림에 기록된 다음 NMUDP가 스트림을 내보냅니다. NMUDP가 데이터를 수신하면 DataReceived 이벤트가 트리거됩니다. 여기에서는 수신된 스트림을 문자 정보로 변환한 다음 표시합니다.
참고: 생성 및 사용된 모든 스트림 객체를 해제(Free)해야 합니다. 그러나 스트림 생성에 실패하면 Destroy를 사용하면 프로그램에서 예외가 생성됩니다. 먼저 스트림이 성공적으로 구축되지 않았는지 여부를 확인하며, 구축된 경우에만 릴리스되므로 Free를 사용하는 것이 더 안전합니다.
이 프로그램에서는 몇 가지 중요한 속성을 가진 NMUDP 컨트롤을 사용합니다. RemoteHost는 원격 컴퓨터의 IP 또는 컴퓨터 이름을 나타내고, LocalPort는 주로 들어오는 데이터가 있는지 모니터링하는 로컬 포트입니다. RemotePort는 원격 포트로, 데이터 전송 시 이 포트를 통해 데이터가 전송됩니다. 이를 이해하면 이미 우리 프로그램을 이해할 수 있습니다.
전체 코드는 다음과 같습니다.
단위 Unit1;
인터페이스
용도
Windows, 메시지, SysUtils, 클래스, 그래픽, 컨트롤, 양식, 대화 상자, StdCtrls, ComCtrls, NMUDP;
유형
TForm1 = 클래스(TForm)
NMUDP1: TNMUDP;
편집IP: T편집;
편집포트: TEdit;
EditMyTxt: TEdit;
메모수신: TMemo;
BtSend: TButton;
BtClear: T버튼;
BtSave: T버튼;
StatusBar1: TStatusBar;
SaveDialog1: TSaveDialog;
절차 BtSendClick(Sender: TObject);
절차 NMUDP1DataReceived(발신자: TComponent; NumberBytes: 정수;
FromIP: 문자열; 포트: 정수);
절차 NMUDP1InvalidHost(var 처리됨: Boolean);
절차 NMUDP1DataSend(보내는 사람: TObject);
절차 FormCreate(보내는 사람: TObject);
절차 BtClearClick(Sender: TObject);
절차 BtSaveClick(Sender: TObject);
절차 EditMyTxtKeyPress(Sender: TObject; var Key: Char);
사적인
{비공개 선언}
공공의
{공개 선언}
끝;
var
Form1: TForm1;
구현
{$R *.DFM}
절차 TForm1.BtSendClick(Sender: TObject);
var
마이스트림: TMemoryStream;
MySendTxt: 문자열;
가져오기,icode:정수;
시작하다
Val(EditPort.Text,Iport,icode);
icode<>0이면
시작하다
Application.MessageBox('포트는 숫자여야 합니다. 다시 입력해 주세요!','메시지',MB_ICONINFORMATION+MB_OK);
출구;
끝;
NMUDP1.RemoteHost := EditIP.Text; {원격 호스트}
NMUDP1.LocalPort:={로컬 포트}
NMUDP1.RemotePort := {원격 포트}
MySendTxt := EditMyTxt.Text;
MyStream := TMemoryStream.Create {스트림 생성}
노력하다
MyStream.Write(MySendTxt[1], Length(EditMyTxt.Text));{데이터 쓰기}
NMUDP1.SendStream(MyStream) {스트림 보내기}
마지막으로
MyStream.Free {릴리스 스트림}
끝;
끝;
절차 TForm1.NMUDP1DataReceived(보내는 사람: TComponent;
NumberBytes: 정수; FromIP: 문자열: 정수);
var
마이스트림: TMemoryStream;
MyReciveTxt: 문자열;
시작하다
MyStream := TMemoryStream.Create {스트림 생성}
노력하다
NMUDP1.ReadStream(MyStream);{스트림 수신}
SetLength(MyReciveTxt,NumberBytes);{NumberBytes는 수신된 바이트 수입니다.}
MyStream.Read(MyReciveTxt[1],NumberBytes);{데이터 읽기}
MemoReceive.Lines.Add(''+FromIP+' 호스트로부터 정보를 받았습니다:'+MyReciveTxt);
마지막으로
MyStream.Free {릴리스 스트림}
끝;
끝;
절차 TForm1.NMUDP1InvalidHost(var 처리됨: Boolean);
시작하다
Application.MessageBox('상대방의 IP 주소가 올바르지 않습니다. 다시 입력해 주세요!','메시지',MB_ICONINFORMATION+MB_OK);
끝;
절차 TForm1.NMUDP1DataSend(송신자: TObject);
시작하다
StatusBar1.SimpleText:='메시지가 성공적으로 전송되었습니다!';
끝;
절차 TForm1.FormCreate(Sender: TObject);
시작하다
EditIP.Text:='127.0.0.1';
EditPort.Text:='8868';
BtSend.Caption:='보내기';
BtClear.Caption:='채팅 기록 지우기';
BtSave.Caption:='채팅 기록 저장';
MemoReceive.ScrollBars:=ssBoth;
MemoReceive.Clear;
EditMyTxt.Text:='여기에 정보를 입력하고 보내기를 클릭하세요.';
StatusBar1.SimplePanel:=true;
끝;
절차 TForm1.BtClearClick(Sender: TObject);
시작하다
MemoReceive.Clear;
끝;
절차 TForm1.BtSaveClick(Sender: TObject);
시작하다
if SaveDialog1.Execute then MemoReceive.Lines.SaveToFile(SaveDialog1.FileName);
끝;
절차 TForm1.EditMyTxtKeyPress(Sender: TObject; var Key: Char);
시작하다
Key=#13이면 BtSend.Click;
끝;
끝.
OICQ는 Socket5 통신 방식을 사용하기 때문에 위 프로그램은 확실히 OICQ보다 훨씬 뒤떨어져 있습니다. 온라인 상태가 되면 먼저 서버에서 친구 정보와 온라인 상태를 검색하고, 전송 시간이 초과되면 먼저 서버에 정보를 저장하고 다음 번에 상대방이 온라인 상태가 될 때까지 기다린 후 전송한 후 삭제합니다. 서버의 백업. 앞서 배운 개념을 바탕으로 이 프로그램을 개선할 수 있습니다. 예를 들어 온라인 상태를 관리하기 위해 NMUDP 컨트롤을 추가하면 전송된 정보가 먼저 AND 또는 연산을 위해 ASCII 코드로 변환되고 수신자가 판단합니다. 정보 헤더가 올바른지 여부에 관계없이 올바른 경우에만 정보가 해독되어 표시되므로 보안과 기밀성이 향상됩니다.
또한 UDP 프로토콜의 또 다른 큰 장점은 브로드캐스트가 가능하다는 점입니다. 이는 동일한 네트워크 세그먼트에 있는 누구나 특정 IP 주소를 지정하지 않고도 정보를 수신할 수 있음을 의미합니다. 네트워크 세그먼트는 일반적으로 A, B, C의 세 가지 범주로 나뉩니다.
1~126.XXX.XXX.XXX(클래스 A 네트워크): 브로드캐스트 주소는 XXX.255.255.255입니다.
128~191.XXX.XXX.XXX(클래스 B 네트워크): 브로드캐스트 주소는 XXX.XXX.255.255입니다.
192~254.XXX.XXX.XXX(카테고리 C 네트워크): 브로드캐스트 주소는 XXX.XXX.XXX.255입니다.
예를 들어, 세 대의 컴퓨터가 192.168.0.1, 192.168.0.10 및 192.168.0.18인 경우 정보를 보낼 때 IP 주소 192.168.0.255만 지정하면 브로드캐스트가 가능합니다. 아래는 IP를 브로드캐스트 IP로 변환하는 기능입니다. 이를 활용하여 자신만의 OICQ^-^을 개선해 보세요.
함수 Trun_ip(S:string):string;
var s1,s2,s3,ss,sss,Head: 문자열;
n,m:정수;
시작하다
sss:=S;
n:=pos('.',s);
s1:=복사(s,1,n);
m:=길이(s1);
삭제(s,1,m);
헤드:=copy(s1,1,(length(s1)-1));
n:=pos('.',s);
s2:=복사(s,1,n);
m:=길이(s2);
삭제(s,1,m);
n:=pos('.',s);
s3:=복사(s,1,n);
m:=길이(s3);
삭제(s,1,m);
ss:=sss;
if strtoint(Head) in [1..126] then ss:=s1+'255.255.255' //1~126.255.255.255 (클래스 A 네트워크)
if strtoint(Head) in [128..191] then ss:=s1+s2+'255.255';//128~191.XXX.255.255 (클래스 B 네트워크)
if strtoint(Head) in [192..254] then ss:=s1+s2+s3+'255' //192~254.XXX.XXX.255 (카테고리 네트워크)
결과:=ss;
끝;
------------------------------------- -------------
5. 실제 적용 4: 스트림을 사용하여 네트워크를 통해 화면 이미지 전송
많은 네트워크 관리 프로그램을 보셨을 것입니다. 이러한 프로그램의 기능 중 하나는 원격 컴퓨터의 화면을 모니터링하는 것입니다. 실제로 이는 스트림 작업을 사용해도 달성됩니다. 아래에서는 이 예제를 두 개의 프로그램으로 나누어 보겠습니다. 하나는 서버이고 다른 하나는 클라이언트입니다. 프로그램이 컴파일된 후에는 단일 컴퓨터, 로컬 네트워크 또는 인터넷에서 직접 사용할 수 있습니다. 해당 의견이 프로그램에 제공되었습니다. 자세한 분석은 나중에 하도록 하겠습니다.
새 프로젝트를 생성하고 ServerSocket 컨트롤을 인터넷 패널의 창으로 드래그합니다. 이 컨트롤은 주로 클라이언트를 모니터링하고 클라이언트와의 연결 및 통신을 설정하는 데 사용됩니다. 수신 포트를 설정한 후 Open 또는 Active:=True 메서드를 호출하여 작업을 시작합니다. 참고: 이전 NMUDP와 달리 소켓이 수신을 시작하면 해당 포트를 변경할 수 없습니다. 이를 변경하려면 먼저 Close를 호출하거나 Active를 False로 설정해야 합니다. 그렇지 않으면 예외가 발생합니다. 또한, 포트가 이미 열려 있는 경우에는 이 포트를 더 이상 사용할 수 없습니다. 따라서 프로그램이 종료되기 전에는 프로그램을 다시 실행할 수 없습니다. 그렇지 않으면 예외가 발생합니다. 즉, 오류 창이 나타납니다. 실제 응용 프로그램에서는 프로그램이 실행되었는지 확인하고 이미 실행 중인 경우 종료함으로써 오류를 피할 수 있습니다.
클라이언트에서 데이터가 전달되면 ServerSocket1ClientRead 이벤트가 트리거되고 여기에서 수신된 데이터를 처리할 수 있습니다. 이 프로그램에서는 주로 클라이언트가 보낸 캐릭터 정보를 수신하고 사전 합의에 따라 해당 작업을 수행합니다.
전체 프로그램 코드는 다음과 같습니다.
단위 Unit1;{서버 프로그램}
인터페이스
용도
Windows, 메시지, SysUtils, 클래스, 그래픽, 컨트롤, 양식, 대화 상자, JPEG, ExtCtrls, ScktComp;
유형
TForm1 = 클래스(TForm)
서버소켓1: TServerSocket;
절차 ServerSocket1ClientRead(Sender: TObject;소켓: TCustomWinSocket);
절차 FormCreate(보내는 사람: TObject);
절차 FormClose(Sender: TObject; var Action: TCloseAction);
사적인
절차 Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
{맞춤형 화면 캡처 기능, DrawCur는 마우스 이미지 캡처 여부를 나타냅니다.}
{비공개 선언}
공공의
{공개 선언}
끝;
var
Form1: TForm1;
MyStream: TMemorystream;{메모리 스트림 개체}
구현
{$R *.DFM}
절차 TForm1.Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
var
Cursorx, Cursory: 정수;
dc:hdc;
마이칸: Tcanvas;
R: TRect;
DrawPos: T포인트;
마이커서: TIcon;
hld: hwnd;
Threadld: dword;
mp: t포인트;
pIconInfo: TIconInfo;
시작하다
Mybmp := Tbitmap.Create {BMPMAP 생성}
Mycan := TCanvas.Create {화면 캡처}
dc := GetWindowDC(0);
노력하다
Mycan.Handle := dc;
R := Rect(0, 0, 화면.너비, 화면.높이);
Mybmp.Width := R.Right;
Mybmp.Height := R.Bottom;
Mybmp.Canvas.CopyRect(R, Mycan, R);
마지막으로
releaseDC(0, DC);
끝;
Mycan.Handle := 0;
Mycan.Free;
if DrawCur then {마우스 이미지 그리기}
시작하다
GetCursorPos(DrawPos);
MyCursor := TIcon.Create;
getcursorpos(mp);
hld := WindowFromPoint(mp);
Threadld := GetWindowThreadProcessId(hld, nil);
AttachThreadInput(GetCurrentThreadId, Threadld, True);
MyCursor.Handle := Getcursor();
AttachThreadInput(GetCurrentThreadId, threadld, False);
GetIconInfo(Mycursor.Handle, pIconInfo);
cursorx := DrawPos.x - round(pIconInfo.xHotspot);
cury := DrawPos.y - round(pIconInfo.yHotspot);
Mybmp.Canvas.Draw(cursorx, 커서y, MyCursor) {마우스로 그리기}
DeleteObject(pIconInfo.hbmColor);{GetIconInfo는 사용 시 두 개의 비트맵 개체를 생성합니다. 이 두 개체는 수동으로 해제해야 합니다.}
DeleteObject(pIconInfo.hbmMask); {그렇지 않으면 호출한 후 비트맵을 생성하고, 여러 번 호출하면 리소스가 소진될 때까지 여러 비트맵이 생성됩니다.}
Mycursor.ReleaseHandle; {배열 메모리 해제}
MyCursor.Free {마우스 포인터 해제}
끝;
끝;
절차 TForm1.FormCreate(Sender: TObject);
시작하다
ServerSocket1.Port := 3000;
ServerSocket1.Open; {소켓이 수신을 시작합니다}
끝;
절차 TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
시작하다
ServerSocket1.Active이면 ServerSocket1.Close인 경우;
끝;
프로시저 TForm1.ServerSocket1ClientRead(Sender: TObject;
소켓: TCustomWinSocket);
var
S, S1: 문자열;
MyBmp: TBitmap;
내jpg: TJpegimage;
시작하다
S := Socket.ReceiveText;
if S = 'cap' then {클라이언트가 화면 캡처 명령을 실행함}
시작하다
노력하다
MyStream := TMemorystream.Create;{메모리 스트림 생성}
MyBmp := TBitmap.Create;
Myjpg := TJpegimage.Create;
Cjt_GetScreen(MyBmp, True); {True는 마우스 이미지를 잡는 것을 의미합니다.}
Myjpg.Assign(MyBmp); {인터넷에서 쉽게 전송할 수 있도록 BMP 이미지를 JPG 형식으로 변환}
Myjpg.CompressionQuality := 10; {JPG 파일 압축 비율 설정, 숫자가 클수록 이미지는 더 선명하지만 데이터는 더 커집니다.}
Myjpg.SaveToStream(MyStream); {스트리밍에 JPG 이미지 쓰기}
내jpg.free;
MyStream.Position := 0;{참고: 이 문장을 추가해야 합니다.}
s1 := inttostr(MyStream.size);{스트림 크기}
Socket.sendtext(s1) {전송 스트림 크기}
마지막으로
MyBmp.free;
끝;
끝;
if s = 'ready'이면 {클라이언트가 이미지를 수신할 준비가 되었습니다.}
시작하다
MyStream.Position := 0;
Socket.SendStream(MyStream) {스트림 보내기}
끝;
끝;
끝.
위는 서버이고 아래는 클라이언트 프로그램을 작성해보겠습니다. 새 프로젝트를 만들고 소켓 컨트롤 ClientSocket, 이미지 디스플레이 컨트롤 Image, Panel, Edit, 두 개의 버튼 및 상태 표시줄 컨트롤 StatusBar1을 추가합니다. 참고: Panel1 위에 Edit1과 두 개의 버튼을 배치합니다. ClientSocket의 속성은 ServerSocket과 유사하지만 연결할 서버의 IP 주소를 나타내는 추가 Address 속성이 있습니다. IP 주소를 입력한 후 "Connect"를 클릭하면 서버 프로그램과 연결이 완료됩니다. "화면 캡처"를 클릭하면 문자가 서버로 전송됩니다. 프로그램은 JPEG 이미지 단위를 사용하기 때문에 Uses에 Jpeg를 추가해야 합니다.
전체 코드는 다음과 같습니다.
유닛 Unit2{클라이언트};
인터페이스
용도
Windows, 메시지, SysUtils, 클래스, 그래픽, 컨트롤, 양식, 대화 상자, StdCtrls, ScktComp, ExtCtrls, Jpeg, ComCtrls;
유형
TForm1 = 클래스(TForm)
ClientSocket1: TClientSocket;
이미지1: T이미지;
StatusBar1: TStatusBar;
패널1: T패널;
편집1: T편집;
버튼1: T버튼;
Button2: T버튼;
절차 Button1Click(Sender: TObject);
프로시저 ClientSocket1Connect(Sender: TObject;
소켓: TCustomWinSocket);
절차 Button2Click(보내는 사람: TObject);
절차 ClientSocket1Error(발신자: TObject; 소켓: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: 정수);
절차 ClientSocket1Read(Sender: TObject; 소켓: TCustomWinSocket);
절차 FormCreate(보내는 사람: TObject);
절차 FormClose(Sender: TObject; var Action: TCloseAction);
프로시저 ClientSocket1Disconnect(Sender: TObject;
소켓: TCustomWinSocket);
사적인
{비공개 선언}
공공의
{공개 선언}
끝;
var
Form1: TForm1;
MySize: Longint;
MyStream: TMemorystream;{메모리 스트림 개체}
구현
{$R *.DFM}
절차 TForm1.FormCreate(Sender: TObject);
시작하다
{--------- 다음은 윈도우 컨트롤의 모양 속성을 설정하는 것입니다------------- }
{참고: Button1, Button2 및 Edit1을 Panel1 위에 배치}
Edit1.Text := '127.0.0.1';
Button1.Caption := '호스트에 연결';
Button2.Caption := '화면 캡처';
Button2.Enabled := 거짓;
Panel1.Align := alTop;
Image1.Align := alClient;
Image1.Stretch := 참;
StatusBar1.Align:=alBottom;
StatusBar1.SimplePanel := True;
{-------------------------------------- ---------}
MyStream := TMemorystream.Create {메모리 스트림 객체 생성}
MySize := 0;
끝;
절차 TForm1.Button1Click(Sender: TObject);
시작하다
ClientSocket1.Active가 아닌 경우
시작하다
ClientSocket1.Address := Edit1.Text; {원격 IP 주소}
ClientSocket1.Port := 3000; {소켓 포트}
ClientSocket1.Open; {연결 설정}
끝;
끝;
절차 TForm1.Button2Click(Sender: TObject);
시작하다
Clientsocket1.Socket.SendText('cap'); {화면 이미지를 캡처하도록 서버에 알리는 명령을 보냅니다.}
Button2.Enabled := 거짓;
끝;
절차 TForm1.ClientSocket1Connect(Sender: TObject;
소켓: TCustomWinSocket);
시작하다
StatusBar1.SimpleText := '호스트 사용' + ClientSocket1.Address + '연결이 성공적으로 설정되었습니다!';
Button2.Enabled := 참;
끝;
프로시저 TForm1.ClientSocket1Error(Sender: TObject;
소켓: TCustomWinSocket: TErrorEvent;
varErrorCode: 정수);
시작하다
오류코드 := 0; {오류창을 띄우지 않습니다.}
StatusBar1.SimpleText := '호스트에 연결할 수 없습니다.' + ClientSocket1.Address + '연결이 설정되었습니다!';
끝;
절차 TForm1.ClientSocket1Disconnect(Sender: TObject;
소켓: TCustomWinSocket);
시작하다
statusBar1.simpletext : 'with homes' + clientsocket1.address + 'Disponnect!';
button2.enabled : = false;
끝;
프로시저 TForm1.ClientSocket1Read(Sender: TObject;
소켓: TCustomWinSocket);
var
MyBuffer : 배열 [0..10000]의 {set wearch buffer}
Myrecevicelength : 정수;
S: 문자열;
MyBMP : TBITMAP;
myjpg : tjpegimage;
시작하다
상태 bar1.simpletext : = '데이터 수신 ...';
mysize = 0이면 {mysize는 서버에서 전송 된 바이트 수입니다. 이미지 수신이 시작되지 않았 음을 의미합니다.
시작하다
s : = socket.receivetext;
mysize : = strtoint (s);
clientSocket1.socket.sendText ( 'Ready') {서버가 이미지를 보내도록 통지하기 위해 명령을 보내 됨}.
끝
또 다른
시작 {다음은 부품을 수신하는 이미지 데이터입니다}
myrecevicelength : = socket.receivelength;
statusbar1.simpletext : = '데이터 수신, 데이터 크기는 다음과 같습니다.' + inttostr (mysize);
Socket.receiveBuf (MyBuffer, MyReceViceLength) {데이터 패킷을 받고 버퍼로 읽습니다.
mystream.write (mybuffer, myrecevicelength) {스트림에 데이터 쓰기}
mystream.size> = mysize 든 {스트림 길이가 수신 할 바이트 수보다 큰 경우 수신이 완료됩니다}
시작하다
mystream.position : = 0;
mybmp : = tbitmap.create;
myjpg : = tjpegimage.create;
노력하다
myjpg.loadfromStream (mystream);
mybmp.assign (myjpg) {jpg 변환 bmp}
상태 bar1.simpletext : = '이미지 표시';
image1.picture.bitmap.assign (mybmp) {image1 요소에 할당}
마지막으로 {다음은 정리 작업입니다}
mybmp.free;
myjpg.free;
button2.enabled : = true;
{socket.sendText ( 'cap');이 문장을 추가하여 화면을 지속적으로 캡처}}
mystream.clear;
mysize : = 0;
끝;
끝;
끝;
끝;
절차 TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
시작하다
mystream.free; {메모리 스트림 객체 출시}
ClientSocket1.Active 인 경우 ClientSocket1.Close;
끝;
끝.
프로그램 원리 : 서버를 실행하여 청취를 시작한 다음 클라이언트를 실행하고 서버 IP 주소를 입력하여 연결을 설정 한 다음 문자를 보내 서버에 화면을 캡처하도록 알립니다. 서버는 사용자 정의 기능 CJT_GETSCREEN을 호출하여 화면을 캡처하고 BMP로 저장하고 BMP를 JPG로 변환 한 다음 JPG를 메모리 스트림에 쓰고 스트림을 클라이언트로 보냅니다. 스트림을 수신 한 후 클라이언트는 반대 작업을 수행하여 스트림을 JPG로 변환 한 다음 BMP로 변환 한 다음 표시합니다.
참고 : 소켓의 제한으로 인해 너무 큰 데이터를 한 번에 전송할 수는 없지만 여러 번만 전송할 수 있습니다. 따라서이 프로그램에서 스크린 캡처를 스트림으로 변환 한 후 서버는 먼저 스트림의 크기를 보냅니다 그것은 변환되고 표시됩니다.
이 프로그램과 이전 자체 제작 OICQ는 모두 메모리 스트림 객체 TMEMORYSTREAM을 사용합니다. 실제로이 스트림 객체는 프로그래밍에 가장 일반적으로 사용되는 I/O 읽기 및 쓰기 기능을 향상시킬 수 있으며 동시에 여러 가지 유형의 스트림을 작동시키고 데이터를 교환하려는 경우 사용하십시오. "중개인"은 최고입니다. 예를 들어 스트림을 압축하거나 압축 해제하면 먼저 tmemorystream 객체를 생성 한 다음 다른 데이터를 복사 한 다음 해당 작업을 수행하십시오. 메모리에서 직접 작동하기 때문에 효율은 매우 높습니다. 때로는 지연조차 눈치 채지 못합니다.
프로그램 개선 영역 : 물론 압축 장치를 추가하여 보내기 전에 압축 할 수 있습니다. 참고 : 여기에는 BMP를 JPG로 변환하는 대신 직접 압축 한 다음 압축하는 트릭도 있습니다. 실험에 따르면 위 프로그램의 이미지의 크기는 LAH 압축 알고리즘을 사용하여 처리되면 8-12KB에 불과합니다. 더 빨리 가려면이 방법을 사용할 수 있습니다. 먼저 첫 번째 이미지를 캡처 한 다음 두 번째 이미지에서 시작하여 이전 이미지와 다른 영역에서 이미지 만 보내십시오. 이 방법을 사용하는 원격 관리자라는 외국 프로그램이 있습니다. 그들이 테스트 한 데이터는 다음과 같습니다. 로컬 네트워크의 초당 100-500 프레임, 네트워크 속도가 매우 낮을 때 인터넷에서 초당 5-10 프레임. 이러한 Digressions는 단지 한 가지 진실을 설명하고 싶어합니다. 문제에 대해 생각할 때 특히 프로그램을 작성할 때 특히 매우 복잡해 보이는 프로그램은 다른 각도에서 그것에 대해 생각하지 않을 수도 있습니다. . 프로그램은 죽었고 재능은 살아 있습니다. 물론, 이것들은 경험의 축적에만 의존 할 수 있습니다. 그러나 처음부터 좋은 습관을 형성하면 평생 배당금을 지불 할 것입니다!