倾家荡产200分,如何列举ftp下所有文件目录,并输入数据库(access) (200分)

  • 主题发起人 主题发起人 hyne
  • 开始时间 开始时间
H

hyne

Unregistered / Unconfirmed
GUEST, unregistred user!
如题,给出源代码,送全部的分,呵呵。
就200,大家不要嫌少啊。
 
大家快快解答啊.
我很需要这个问题的.
 
远程还是本地?有权限吗?
 
远程啦,如果是本地我可以解决的.
权限是anonymous
本来是想编一个ftp-web的搜索引擎
 

让我先来对您在FTP部分使用WININET DLL时需要编写的代码作一个做一个概括的了解。这并不是一个详尽的学习,但却能够让您进门。为了知晓这项技术,您要做的第一件事情是明白 WININET.PAS 中的一些函数返回的是一个叫做 HINTERNET 类的指针变量:


var
HINTERNET: Pointer;

这个指针扮演一个您正在使用的不同的因特网服务的句柄的角色。获得了这个句柄之后,你应当把它作为第一个参数传递给在这个进程周期[注:指FTP的整个存在时间(译者)]中调用的其他WININET函数。

您要记住的适当您在使用它的时间内要把句柄返回给系统,通常是通过调用 WININET 函数 InternetCloseHandle 来实现:


function InternetCloseHandle(hInet: HINTERNET): BOOL; stdcall;

为了让一个WININET进程开始,您调用 InternetOpen :

function InternetOpen(lpszCallerName: PChar; dwAccessType: DWORD;
lpszServerName: PChar; nServerPort: INTERNET_PORT;
dwFlags: DWORD): HINTERNET; stdcall;


第一个参数时打开这个进程的应用程序的名字。您可以在这个参数中传递任何您所要的任意符串。微软公司的文献声称"这个名字作为HTTP协议中的用户代理器的名字而被使用"。这个保留的参数可以设为0或空。


var
MyHandle: HINTERNET;

begin
MyHandle := InternetOpen('MyApp', 0, nil, 0, 0);
end;

如果您想要关于这个函数的更多信息,从 www.microsoft.com 那里下载 WININET.HLP 。

打开了这这个进程之后,下一步是通过 InternetConnect 函数来连接到服务器上。


function InternetConnect(
hInet: HINTERNET; // Handle from InternetOpen
lpszServerName: PChar; // Server: i.e., www.borland.com
nServerPort: INTERNET_PORT; // Usually 0
lpszUsername: PChar; // usually anonymous
lpszPassword: PChar; // usually your email address
dwService: DWORD; // FTP, HTTP, or Gopher?
dwFlags: DWORD; // Usually 0
dwContext: DWORD): // User defined number for callback
HINTERNET; stdcall;

这里有三个可能的可以通过 dwService 参数传递的自说明旗标,它们是互斥的:


INTERNET_SERVICE_FTP
INTERNET_SERVICE_GOPHER
INTERNET_SERVICE_HTTP

下面是 dwFlags 参数的选择:


INTERNET_CONNECT_FLAG_PASSIVE

这个选项仅当您在前一个参数中传递了 INTERNET_SERVER_FTP 才有效。这时候这个参数没有其他有效的选项。


如果这个进程成功的话会返回一个有效的指针,否则它返回空。


连接上之后

当您连接上之后,您可以调用来 GetCurrentDirectory 获得当前的路径的名字:


function TMyFtp.GetCurrentDirectory: string;
var
Len: Integer;
S: string;
begin
Len := 0;
ftpGetCurrentDirectory(FFTPHandle, PChar(S), Len);
SetLength(S, Len);
ftpGetCurrentDirectory(FFTPHandle, PChar(S), Len);
Result := S;
end;

这个函数声明如下:


function FtpGetCurrentDirectory(
hFtpSession: HINTERNET; // handle from InternetConnect
lpszCurrentDirectory: PChar; // directory returned here
var lpdwCurrentDirectory: DWORD): // buf size of 2nd parameter
BOOL; stdcall; // True on success

如果您把最后一个参数设为0,那么WININET会使用这个参数来返回路径字符串的长度。接着您可以为您的字符串分配内存,也可以在调用一次这个函数来获得路径的名字。这个过程在上面的方法中已经演示过了。(注意到*设定长度*的那个调用,Delphi 要求您在类似这样的情况下为新的长字符串分配内存!这是因为这个字符串必须在操作系统中指定值,而不是在 Delphi 应用程序中指定。结果就是 Delphi 不能在类似的情况下像它通常那样悄悄地为字符串分配内存)

下面是返回在特定路径下当前可用的文件的一系列函数:


function GetFindDataStr(FindData: TWin32FindData): string;
var
S: string;
Temp: string;
begin
case FindData.dwFileAttributes of
FILE_ATTRIBUTE_ARCHIVE: S := 'A';
// FILE_ATTRIBUTE_COMPRESSED: S := 'C';
FILE_ATTRIBUTE_DIRECTORY: S := 'D';
FILE_ATTRIBUTE_HIDDEN: S := 'H';
FILE_ATTRIBUTE_NORMAL: S := 'N';
FILE_ATTRIBUTE_READONLY: S := 'R';
FILE_ATTRIBUTE_SYSTEM: S := 'S';
FILE_ATTRIBUTE_TEMPORARY: S := 'T';
else
S := IntToStr(FindData.dwFileAttributes);
end;
S := S + GetDots(75);
Move(FindData.CFilename[0], S[6], StrLen(FindData.CFileName));
Temp := IntToStr(FindData.nFileSizeLow);
Move(Temp[1], S[25], Length(Temp));
Result := S;
end;

function TMyFtp.FindFiles: TStringList;
var
FindData: TWin32FindData;
FindHandle: HInternet;
begin
FindHandle := FtpFindFirstFile(FFtphandle, '*.*',
FindData, 0, 0);
if FindHandle = nil then begin
Result := nil;
Exit;
end;
FCurFiles.Clear;
FCurFiles.Add(GetFindDataStr(FindData));
while InternetFindnextFile(FindHandle, @FindData) do
FCurFiles.Add(GetFindDataStr(FindData));
InternetCloseHandle(Findhandle);
GetCurrentDirectory;
Result := FCurFiles;
end;

这里需要注意的关键函数是 ftpFindFirstFile, InternetFindNextFile & InternetCloseHandle 。您可以像调用 Delphi 函数 FindFirst、FindNext & FinClose 一样调用这些函数。特别的是,您使用函数 ftpFindFirstFile 来取得这个路径下的第一个函数。您可以不断地调用 InternetFindNextFile ,直到函数返回"False"为止。当这个进程结束时,调用 InternetCloseHandle 来通知操作系统回收与这个进程相关的内存。

I'm not going to explain this process further in this newsletter. If you want more information, you might look up FindFirst in the Delphi help. One final note: Unlike the functions mentioned in the previous paragraph, TWin32FindData is not defined in WININET.PAS, but instead can be found in the WIN32 help file that ships with Delphi. It is declared in the WINDOWS.PAS file that ships with Delphi.
我不准备在这里进一步解析这个进程。如果您想要更多的信息,您可以在 Delphi 帮助中查找 FindFirst 。最后提醒一句:并不向前文提及的函数,TWin32FindData 并不是在 WININET.PAS 中定义的, 但可以在随 Delphi 分发的 WIN32 帮助文件中找到它。它在随 Delphi 分发的 WINDOWS.PAS 文件中被定义。
==================================================================================

接受一个文件

您可以使用 WININET.PAS 文件中的 ftpGetFile 函数来从FTP取回一个文件:

function FtpGetFile(
hFtpSession: HINTERNET; // Returned by InternetConnect
lpszRemoteFile: PChar; // File to get
lpszNewFile: PChar; // Where to put it on your PC
fFailIfExists: BOOL; // Overwrite existing files?
dwFlagsAndAttributes: DWORD; // File attribute-See CreateFile.
dwFlags: DWORD; // Binary or ASCII transfer
dwContext: DWORD): // Usually zero
BOOL stdcall; // True on success

下面是一个如何使用该函数的例子:


function TMyFtp.GetFile(FTPFile, NewFile: string): Boolean;
begin
Result := FtpGetFile(FFTPHandle, PChar(FTPFile), PChar(NewFile),
False, File_Attribute_Normal,
Ftp_Transfer_Type_Binary, 0);
end;

如果要知道 dwFlagsAndAttributes 参数中的变量是怎样传递的,请查阅随 Delphi 附送的 WIN32 帮助文件。


典型控制

下面的 Delphi 控制给了你一个通过 WININET FTP 部分建立可视工具的起点。只是因为,这个控制可以让您是用 Object Inspector 来定义远程服务器(RemoteServer)、用户身份(UserID)和密码(Password)。


unit Ftp1;

{ FTP example using WININET.PAS rather than
an ACTIVEX control. Requires WININET.PAS and
WININET.DLL. WININET.DLL you can get from
Microsoft, WININET.PAS is available from
www.borland.com, or with some versions of
Delphi 2.0.

You might Respond to OnNewDir events as follows:

procedure TForm1.FTP1NewDir(Sender: TObject);
begin
ListBox1.Items := MyFtp1.FindFiles; // Get the directory list
end;
}

interface

uses
Windows, Classes, WinINet,
SysUtils;

type
TMyFtp = class(TComponent)
private
FContext: Integer;
FINet: HInternet;
FFtpHandle: HInternet;
FCurFiles: TStringList;
FServer: string;
FOnNewDir: TNotifyEvent;
FCurDir: string;
FUserID: string;
FPassword: string;
function GetCurrentDirectory: string;
procedure SetUpNewDir;
protected
destructor Destroy; override;
public
constructor Create(AOwner: TComponent); override;
function Connect: Boolean;
function FindFiles: TStringList;
function ChangeDirExact(S: string): Boolean;
function ChangeDirCustom(S: string): Boolean;
function BackOneDir: Boolean;
function GetFile(FTPFile, NewFile: string): Boolean;
function SendFile1(FTPFile, NewFile: string): Boolean;
function SendFile2(FTPFile, NewFile: string): Boolean;
function CustomToFileName(S: string): string;
published
property CurFiles: TStringList read FCurFiles;
property CurDir: string read FCurDir;
property UserID: string read FUserID write FUserID;
property Password: string read FPassword write FPassword;
property Server: string read FServer write FServer;
property OnNewDir: TNotifyEvent read FOnNewDir
write FOnNewDir;
end;

procedure Register;

implementation

uses
Dialogs;

// A few utility functions

function GetFirstToken(S: string; Token: Char): string;
var
Temp: string;
Index: INteger;
begin
Index := Pos(Token, S);
if Index < 1 then begin
GetFirstToken := '';
Exit;
end;
Dec(Index);
SetLength(Temp, Index);
Move(S[1], Temp[1], Index);
GetFirstToken := Temp;
end;

function StripFirstToken(S: string; Ch: Char): string;
var
i, Size: Integer;
begin
i := Pos(Ch, S);
if i = 0 then begin
StripFirstToken := S;
Exit;
end;
Size := (Length(S) - i);
Move(S[i + 1], S[1], Size);
SetLength(S, Size);
StripFirstToken := S;
end;

function ReverseStr(S: string): string;
var
Len: Integer;
Temp: String;
i,j: Integer;
begin
Len := Length(S);
SetLength(Temp, Len);
j := Len;
for i := 1 to Len do begin
Temp := S[j];
dec(j);
end;
ReverseStr := Temp;
end;

function StripLastToken(S: string; Token: Char): string;
var
Temp: string;
Index: INteger;
begin
SetLength(Temp, Length(S));
S := ReverseStr(S);
Index := Pos(Token, S);
Inc(Index);
Move(S[Index], Temp[1], Length(S) - (Index - 1));
SetLength(Temp, Length(S) - (Index - 1));
StripLastToken := ReverseStr(Temp);
end;


procedure Register;
begin
RegisterComponents('Unleash', [TMyFtp]);
end;

constructor TMyFtp.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FCurFiles := TStringList.Create;
FINet := InternetOpen('WinINet1', 0, nil, 0, 0);
end;

destructor TMyFtp.Destroy;
begin
if FINet <> nil then
InternetCloseHandle(FINet);
if FFtpHandle <> nil then
InternetCloseHandle(FFtpHandle);
inherited Destroy;
end;

function TMyFtp.Connect: Boolean;
begin
FContext := 255;
FftpHandle := InternetConnect(FINet, PChar(FServer), 0,
PChar(FUserID), PChar(FPassWord),
Internet_Service_Ftp, 0, FContext);
if FFtpHandle = nil then
Result := False
else begin
SetUpNewDir;
Result := True;
end;
end;

function TMyFtp.GetCurrentDirectory: string;
var
Len: Integer;
S: string;
begin
Len := 0;
ftpGetCurrentDirectory(FFTPHandle, PChar(S), Len);
SetLength(S, Len);
ftpGetCurrentDirectory(FFTPHandle, PChar(S), Len);
Result := S;
end;

procedure TMyFtp.SetUpNewDir;
begin
FCurDir := GetCurrentDirectory;
if Assigned(FOnNewDir) then
FOnNewDir(Self);
end;

function GetDots(NumDots: Integer): string;
var
S: string;
i: Integer;
begin
S := '';
for i := 1 to NumDots do
S := S + ' ';
Result := S;
end;

function GetFindDataStr(FindData: TWin32FindData): string;
var
S: string;
Temp: string;
begin
case FindData.dwFileAttributes of
FILE_ATTRIBUTE_ARCHIVE: S := 'A';
// FILE_ATTRIBUTE_COMPRESSED: S := 'C';
FILE_ATTRIBUTE_DIRECTORY: S := 'D';
FILE_ATTRIBUTE_HIDDEN: S := 'H';
FILE_ATTRIBUTE_NORMAL: S := 'N';
FILE_ATTRIBUTE_READONLY: S := 'R';
FILE_ATTRIBUTE_SYSTEM: S := 'S';
FILE_ATTRIBUTE_TEMPORARY: S := 'T';
else
S := IntToStr(FindData.dwFileAttributes);
end;
S := S + GetDots(75);
Move(FindData.CFilename[0], S[6], StrLen(FindData.CFileName));
Temp := IntToStr(FindData.nFileSizeLow);
Move(Temp[1], S[25], Length(Temp));
Result := S;
end;

function TMyFtp.FindFiles: TStringList;
var
FindData: TWin32FindData;
FindHandle: HInternet;
begin
FindHandle := FtpFindFirstFile(FFtphandle, '*.*',
FindData, 0, 0);
if FindHandle = nil then begin
Result := nil;
Exit;
end;
FCurFiles.Clear;
FCurFiles.Add(GetFindDataStr(FindData));
while InternetFindnextFile(FindHandle, @FindData) do
FCurFiles.Add(GetFindDataStr(FindData));
InternetCloseHandle(Findhandle);
GetCurrentDirectory;
Result := FCurFiles;
end;

function TMyFtp.CustomToFileName(S: string): string;
const
PreSize = 6;
var
Temp: string;
TempSize: Integer;
begin
Temp := '';
TempSize := Length(S) - PreSize;
SetLength(Temp, TempSize);
Move(S[PreSize], Temp[1], TempSize);
Temp := GetFirstToken(Temp, ' ');
Result := Temp;
end;

function TMyFtp.BackOneDir: Boolean;
var
S: string;
begin
S := FCurDir;
S := StripLastToken(S, '/');
if S = '/' then begin
Result := False;
Exit;
end;

if S <> '' then begin
ChangeDirExact(S);
Result := True;
end else begin
ChangeDirExact('/');
Result := True;
end;

end;

// Changes to specific directory in S
function TMyFtp.ChangeDirExact(S: string): Boolean;
begin
if S <> '' then
FtpSetCurrentDirectory(FFTPHandle, PChar(S));
Result := True;
FindFiles;
SetUpNewDir;
end;

// Assumes S has been returned by GetFindDataString;
function TMyFtp.ChangeDirCustom(S: string): Boolean;
begin
S := CustomToFileName(S);
if S <> '' then
FtpSetCurrentDirectory(FFTPHandle, PChar(S));
Result := True;
FindFiles;
SetUpNewDir;
end;

function TMyFtp.GetFile(FTPFile, NewFile: string): Boolean;
begin
Result := FtpGetFile(FFTPHandle, PChar(FTPFile), PChar(NewFile),
False, File_Attribute_Normal,
Ftp_Transfer_Type_Binary, 0);
end;

function TMyFtp.SendFile1(FTPFile, NewFile: string): Boolean;
const
Size:DWord = 3000;
var
Transfer: Bool;
Error: DWord;
S: string;
begin
Transfer := FtpPutFile(FFTPHandle, PChar(FTPFile),
PChar(NewFile),
Ftp_Transfer_Type_Binary, 0);

if not Transfer then begin
Error := GetLastError;
ShowMessage(Format('Error Number: %d. Hex: %x',
[Error, Error]));
SetLength(S, Size);
if not InternetGetLastResponseInfo(Error, PChar(S), Size) then
begin
Error := GetLastError;
ShowMessage(Format('Error Number: %d. Hex: %x',
[Error, Error]));
end;
ShowMessage(Format('Error Number: %d. Hex: %x Info: %s',
[Error, Error, S]));
end else
ShowMessage('Success');
Result := Transfer;
end;

function TMyFtp.SendFile2(FTPFile, NewFile: string): Boolean;
var
FHandle: HInternet;
begin
FHandle := FtpOpenFile(FFTPHandle, 'sam.txt', GENERIC_READ,
FTP_TRANSFER_TYPE_BINARY, 0);
if FHandle <> nil then
InternetCloseHandle(FHandle)
else
ShowMessage('Failed');
Result := True;
end;

end.
这只能起到指导性的作用,你再看了这个以后希望对你有启发
 
示例:将目录添加至一个listbox中。
使用NMFTP控件,在其OnListItem事件中添加如下程序:
procedure TForm1.NMFTP1ListItem(Listing: String);
begin
//add list item
ListBox1.Items.Add(Listing);
end;

执行 NMFTP1.list 即可。

[:)]
代码:
如果只要目录名,可以对Listing进行判断,符合目录形式的添加至数据库中即可。
 
天啦,哥们儿,有分也不要这么浪费啊,
delphi自带的demo中的源代码很多的,随便找找就能看到你想要的东西。
 
同意 小猪!
富翁就是富翁,没说的。
 
大家还没有解决问题啊,列出当前目录的所有东西我是知道,但是递归那部分我不会了.
大家快快解决啊,不然得到10月1日以后了.
 
非得要现成的?自己看看demo再看看帮助一定可以搞定。
反正当时我是这样搞定的。主要现在源码不再手上,过了这么久
也记不清了,要不我也不介意给你贴出来。
 
access部分可以不要,只要能够列出所有的文件目录即可,
简单点的代码行不行。
最好还是用memo
和delphi自带的ftp控件
 
delphi demo里的代码,显示如何判断list回来的目录信息里的一行是否目录及得到子目录(或文件)名
你只要针对每个目录反复changedir、list及判断就能得到所有的目录名
不要告诉我你需要所有的代码

function GetNameFromDirLine(Line: String; Var IsDirectory: Boolean): String;
Var
i: Integer;
DosListing: Boolean;
begin
IsDirectory := Line[1] = 'd';
DosListing := false;
for i := 0 to 7 do begin
if (i = 2) and not IsDirectory then begin
IsDirectory := Copy(Line, 1, Pos(' ', Line) - 1) = '<DIR>';
if not IsDirectory then
DosListing := Line[1] in ['0'..'9']
else DosListing := true;
end;
Delete(Line, 1, Pos(' ', Line));
While Line[1] = ' ' do Delete(Line, 1, 1);
if DosListing and (i = 2) then break;
end;
Result := Line;
end;
 
有点用,但是关键是递归一项,谁能解决啊.
"蟑螂"算是回答得有点到位了,但是关键是递归的严密逻辑本人不会.
容易进去出不来,也就不能完全显示所有目录文件了
 
我给个递归列举本地指定目录下的所有目录的例子,
至于FTP上的,我想在相应的函数前头加个ftp就差不多了。
“授人鱼不如授人渔!”
procedure tform1.getdirs(path:string);
var
sr:tsearchrec;
begin
if findfirst(path+'*.*',faanyfile,sr)=0 then
if sr.attr=fadirectory then
begin
listbox1.items.add(sr.name);//记下目录名
getdirs(path+sr.name+'/');//递归找这个刚找到的目录下的所有子目录名
end;
while findnext(sr)=0 do
begin
if sr.attr=fadirectory then
begin
listbox1.items.add(sr.name);
getdirs(path+sr.name+'/');
end;
end;
end;
注意,其中path最后一个字符必须为'/',即你查找
d:/a/目录下的所有目录,如果找到b这个目录,就找
d:/a/b/目录下的所有目录,如此递归到在一个目录下
找不出目录为止。其实递归也还简单的,请你细细想
一下,有无道理?





 
hyne, 你还在网上吗?
 
本地的代码我看过,
不过如果和ftp结合还是不行,那位高手再来解决一下
今天回去研究研究。
明天来看看大家有没有什么结果。
 
我还在啊
我的QQ17309466,你呢?
 
bool __fastcall TFMain::SendftpTemp()//рftp ア毖??ンftp??
{
String ftpTemp=ExtractFilePath(Application->ExeName) + "ftpTemp";
if(!DirectoryExists(ftpTemp))
{
return false;
}
TSearchRec sr;
int iAttributes = 0;
iAttributes = faDirectory ;
TSearchRec Asr;
int AAttributes = faAnyFile;
if (FindFirst(ftpTemp+"//*.*", iAttributes, sr) == 0)//琩т?ヘ魁
{
do

if (((sr.Attr & iAttributes) ==sr.Attr)&&(sr.Name!=".")&&(sr.Name!=".."))
{
//琩т?ヘ魁??┮Τゅン

if(DirectoryExists(ftpTemp+"//"+sr.Name +"//Main"))
{
//ftp?郎㎝Pdf郎
if (FindFirst(ftpTemp+"//"+sr.Name +"//Main//*.*", AAttributes, Asr) == 0)
{
NMFTP1->MakeDirectory("/"+sr.Name);
NMFTP1->MakeDirectory("/"+sr.Name+"/Main");
do
if (((Asr.Attr & AAttributes) ==Asr.Attr)&&(Asr.Name!=".")&&(Asr.Name!=".."))
{
NMFTP1->ChangeDir ("/"+sr.Name+"/Main");

int FileHandle,FSize;
String LocalFile=ftpTemp+"//"+sr.Name +"//Main//"+Asr.Name;
String Remotefile=Asr.Name;
RE->Lines->Add(DateTimeToStr(Now())+":FTP:"+LocalFile);
FileHandle=FileOpen(LocalFile,fmOpenRead);
FSize=FileSeek(FileHandle,0,2);
FileClose(FileHandle);
NMFTP1->Allocate (FSize);
NMFTP1->Upload (LocalFile,Remotefile);
CGPress->AddProgress(2);
Application->ProcessMessages();

}
while (FindNext(Asr)==0);
FindClose(Asr);
}
}
//ftp?ン郎
if(DirectoryExists(ftpTemp+"//"+sr.Name +"//Attach"))
{

if (FindFirst(ftpTemp+"//"+sr.Name +"//Attach//*.*", AAttributes, Asr) == 0)
{
NMFTP1->MakeDirectory("/"+sr.Name+"/Attach");
do
if (((Asr.Attr & AAttributes) ==Asr.Attr)&&(Asr.Name!=".")&&(Asr.Name!=".."))
{
NMFTP1->ChangeDir ("/"+sr.Name+"/Attach");

int FileHandle,FSize;
String LocalFile=ftpTemp+"//"+sr.Name +"//Attach//"+Asr.Name;
String Remotefile=Asr.Name;
RE->Lines->Add(DateTimeToStr(Now())+":FTP:"+LocalFile);
FileHandle=FileOpen(LocalFile,fmOpenRead);
FSize=FileSeek(FileHandle,0,2);
FileClose(FileHandle);
NMFTP1->Allocate (FSize);
NMFTP1->Upload (LocalFile,Remotefile);
CGPress->AddProgress(2);
Application->ProcessMessages();

}
while (FindNext(Asr)==0);
FindClose(Asr);
}
}


}


while (FindNext(sr)==0);
FindClose(sr);
}
MyDeleteTree(ftpTemp);
NMFTP1->Disconnect();
return true;
}
 
给个递归的结构,不写具体的代码了
procedure dir;
begin
list;
for dirlist的每一行
begin
if isdir then
begin
记入数据库
changedir //进入当前行所表示的目录
dir; //递归调用
end;
end;
end;
 
goddy的是什么代码,看不明白的说
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
D
回复
0
查看
2K
DelphiTeacher的专栏
D
D
回复
0
查看
2K
DelphiTeacher的专栏
D
D
回复
0
查看
1K
DelphiTeacher的专栏
D
后退
顶部