分享
 
 
 

Win32汇编教程十二 管道操作

王朝system·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

--------------------------------------------------------------------------------

在这儿下载本节的所有源程序

概述

Windows 引入了多进程和多线程机制。同时也提供了多个进程之间的通信手段,包括剪贴板、DDE、OLE、管道等,和其他通信手段相比,管道有它自己的限制和特点,管道实际上是一段共享内存区,进程把共享消息放在那里。并通过一些 API 提供信息交换。

管道是两个头的东西,每个头各连接一个进程或者同一个进程的不同代码,按照管道的类别分有两种管道,匿名的和命名的;按照管道的传输方向分也可以分成两种,单向的双向的。根据管道的特点,命名管道通常用在网络环境下不同计算机上运行的进程之间的通信(当然也可以用在同一台机的不同进程中)它可以是单向或双向的;而匿名管道只能用在同一台计算机中,它只能是单向的。匿名管道其实是通过用给了一个指定名字的有名管道来实现的。

使用管道的好处在于:读写它使用的是对文件操作的 api,结果操作管道就和操作文件一样。即使你在不同的计算机之间用命名管道来通信,你也不必了解和自己去实现网络间通信的具体细节。

我们简单的介绍一下命名管道的使用。

命名管道是由服务器端的进程建立的,管道的命名必须遵循特定的命名方法,就是 "\\.\pipe\管道名",当作为客户端的进程要使用时,使用"\\计算机名\\pipe\管道名" 来打开使用,具体步骤如下:

服务端通过函数 CreateNamedPipe 创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。

服务端侦听来自客户端的连接请求,该功能通过 ConnectNamedPipe 函数实现。

客户端通过函数 WaitNamedPipe 来等待管道的出现,如果在超时值变为零以前,有一个管道可以使用,则 WaitNamedPipe 将返回 True,并通过调用 CreateFile 或 CallNamedPipe 来呼叫对服务端的连接。

此时服务端将接受客户端的连接请求,成功建立连接,服务端 ConnectNamedPipe 返回 True

建立连接之后,客户端与服务器端即可通过 ReadFile 和 WriteFile,利用得到的管道文件句柄,彼此间进行信息交换。

当客户端与服务端的通信结束,客户端调用 CloseFile,服务端接着调用 DisconnectNamedPipe。最后调用函数CloseHandle来关闭该管道。

由于命名管道使用时作为客户端的程序必须知道管道的名称,所以更多的用在同一“作者”编写的服务器/工作站程序中,你不可能随便找出一个程序来要求它和你写的程序来通过命名管道通信。而匿名管道的使用则完全不同,它允许你和完全不相干的进程通信,条件是这个进程通过控制台“console”来输入输出,典型的例子是老的 Dos 应用程序,它们在运行时 Windows 为它们开了个 Dos 窗口,它们的输入输出就是 console 方式的。还有一些标准的 Win32 程序也使用控制台输入输出,如果在 Win32 编程中不想使用图形界面,你照样可以使用 AllocConsole 得到一个控制台,然后通过 GetStdHandle 得到输入或输出句柄,再通过 WriteConsole 或 WriteFile 把结果输出到控制台(通常是一个象 Dos 窗口)的屏幕上。虽然这些程序看起来象 Dos 程序,但它们是不折不扣的 Win32 程序,如果你在纯 Dos 下使用,就会显示“The program must run under Windows!”。

一个控制台有三个句柄:标准输入、标准输出和和标准错误句柄,标准输入、标准输出句柄是可以重新定向的,你可以用匿名管道来代替它,这样一来,你可以在管道的另一端用别的进程来接收或输入,而控制台一方并没有感到什么不同,就象 Dos 下的 > 或者 < 可以重新定向输出或输入一样。通常控制台程序的输入输出如下:

(控制台进程output) write ----> 标准输出设备(一般是屏幕)

(控制台进程input) read <---- 标准输入设备(一般是键盘)

而用管道代替后:

(作为子进程的控制台进程output) write ----> 管道1 ----> read (父进程)

(作为子进程的控制台进程input) read <----> 管道2 <---- write (父进程)

使用匿名管道的步骤如下:

使用 CreatePipe 建立两个管道,得到管道句柄,一个用来输入,一个用来输出

准备执行控制台子进程,首先使用 GetStartupInfo 得到 StartupInfo

使用第一个管道句柄代替 StartupInfo 中的 hStdInput,第二个代替 hStdOutput、hStdError,即标准输入、输出、错误句柄

使用 CreateProcess 执行子进程,这样建立的子进程输入和输出就被定向到管道中

父进程通过 ReadFile 读第二个管道来获得子进程的输出,通过 WriteFile 写第一个管道来将输入写到子进程

父进程可以通过 PeekNamedPipe 来查询子进程有没有输出

子进程结束后,要通过 CloseHandle 来关闭两个管道。

下面是具体的说明和定义:

1. 建立匿名管道使用 CreatePipe 原形如下:

BOOL CreatePipe(

PHANDLE hReadPipe, // address of variable for read handle

PHANDLE hWritePipe, // address of variable for write handle

LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes

DWORD nSize // number of bytes reserved for pipe

);

当管道建立后,结构中指向的 hReadPipe 和 hWritePipe 可用来读写管道,当然由于匿名管道是单向的,你只能使用其中的一个句柄,参数中的 SECURITY_ATTRIBUTES 的结构必须填写,定义如下:

typedef struct_SECURITY_ATTRIBUTES{

DWORD nLength: //定义以字节为单位的此结构的长度

LPVOID lpSecurityDescriptor; //指向控制这个对象共享的安全描述符,如果为NULL这个对象将被分配一个缺省的安全描述

BOOL bInheritHandle; //当一个新过程被创建时,定义其返回是否是继承的.供系统API函数使用.

}SECURITY_ATTRIBUTES;

2. 填写创建子进程用的 STARTUPINFO 结构,一般我们可以先用 GetStartupInfo 来填写一个缺省的结构,然后改动我们用得到的地方,它们是:

hStdInput -- 用其中一个管道的 hWritePipe 代替

hStdOutput、hStdError -- 用另一个管道的 hReadPipe 代替

dwFlags -- 设置为 STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW 表示输入输出句柄及 wShowWindow 字段有效

wShowWindow -- 设置为 SW_HIDE,这样子进程执行时不显示窗口。

填写好以后,就可以用 CreateProcess 来执行子进程了,具体有关执行子进程的操作可以参考上一篇教程《进程控制》

3. 在程序中可以用 PeekNamedPipe 查询子进程有没有输出,原形如下:

BOOL PeekNamedPipe(

HANDLE hNamedPipe, // handle to pipe to copy from

LPVOID lpBuffer, // pointer to data buffer

DWORD nBufferSize, // size, in bytes, of data buffer

LPDWORD lpBytesRead, // pointer to number of bytes read

LPDWORD lpTotalBytesAvail, // pointer to total number of bytes available

LPDWORD lpBytesLeftThisMessage // pointer to unread bytes in this message

);

我们可以将尝试读取 nBuffersize 大小的数据,然后可以通过返回的 BytesRead 得到管道中有多少数据,如果不等于零,则表示有数据可以读取。

4. 用 ReadFile 和 WriteFile 来读写管道,它们的参数是完全一样的,原形如下:

ReadFile or WriteFile(

HANDLE hFile, // handle of file to read 在这里使用管道句柄

LPVOID lpBuffer, // address of buffer that receives data 缓冲区地址

DWORD nNumberOfBytesToRead, // number of bytes to read 准备读写的字节数

LPDWORD lpNumberOfBytesRead, // address of number of bytes read,实际读到的或写入的字节数

LPOVERLAPPED lpOverlapped // address of structure for data 在这里用 NULL

);

5. 用 CloseHandle 关闭管道一和管道二的 hReadPipe和 hWritePipe 这四个句柄。

下面给出了一个例子程序,这个程序是上篇教程《进程控制》的例子的扩充,如果你对有的 api 感到陌生的话,请先阅读上一篇教程。

源程序 - 汇编源文件

DEBUG equ 0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Programmed by 罗云彬, bigluo@telekbird.com.cn

; Website: http://asm.yeah.net

; LuoYunBin's Win32 ASM page (罗云彬的编程乐园)

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 版本信息

; 汇编教程附带例子程序 - 管道例子

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.386

.model flat, stdcall

option casemap :none ; case sensitive

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Include 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

include windows.inc

include user32.inc

include kernel32.inc

include comctl32.inc

include comdlg32.inc

include gdi32.inc

includelib user32.lib

includelib kernel32.lib

includelib comctl32.lib

includelib comdlg32.lib

includelib gdi32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Equ 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ICO_MAIN equ 1000

MENU_MAIN equ 2000

IDM_EXEC equ 2001

IDM_EXIT equ 2002

F_RUNNING equ 0001h ;进程在运行中

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data?

stStartUp STARTUPINFO <?>

hInstance dd ?

hMenu dd ?

hWinMain dd ?

hWinText dd ?

hFont dd ?

hRunThread dd ?

hRead1 dd ?

hWrite1 dd ?

hRead2 dd ?

hWrite2 dd ?

szBuffer db 512 dup (?)

dwFlag dd ?

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data

szMenuExecute db '连接 MS-&DOS 方式',0

szExcuteError db '启动应用程序错误!',0

szCaption db '管道示例程序 ... http://asm.yeah.net',0

szClassName db 'PipeExample',0

;szDllName db 'riched32.dll',0

;szClassNameRedit db 'RichEdit',0

szDllName db 'riched20.dll',0

szClassNameRedit db 'richedit20a',0

szCommand db 'c:\command.com',0

stLogFont LOGFONT <24,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_STROKE_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH or FF_SWISS,"Fixedsys">

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.code

if DEBUG

include Debug.asm

endif

include Win.asm

;********************************************************************

; 执行程序用的线程

; 1. 用 CreateProcess 建立进程

; 2. 用 WaitForSingleOject 等待进程结束

;********************************************************************

_RunThread proc uses ebx ecx edx esi edi,dwParam:DWORD

local @stSecurity:SECURITY_ATTRIBUTES

local @dwExitCode

local @dwBytesRead

local @stRange:CHARRANGE

or dwFlag,F_RUNNING

;********************************************************************

; “执行”菜单改为“结束”

;********************************************************************

invoke EnableMenuItem,hMenu,IDM_EXEC,MF_GRAYED

invoke EnableMenuItem,hMenu,IDM_EXIT,MF_GRAYED

;********************************************************************

; 建立管道

;********************************************************************

mov @stSecurity.nLength,sizeof SECURITY_ATTRIBUTES

mov @stSecurity.lpSecurityDescriptor,NULL

mov @stSecurity.bInheritHandle,TRUE

invoke CreatePipe,addr hRead1,addr hWrite1,addr @stSecurity,NULL

invoke CreatePipe,addr hRead2,addr hWrite2,addr @stSecurity,NULL

;********************************************************************

; 执行文件,如果成功则等待程序结束

;********************************************************************

invoke GetStartupInfo,addr stStartUp

mov eax,hRead1

mov stStartUp.hStdInput,eax

mov eax,hWrite2

mov stStartUp.hStdOutput,eax

mov stStartUp.hStdError,eax

mov stStartUp.dwFlags,STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW

mov stStartUp.wShowWindow,SW_HIDE

invoke CreateProcess,NULL,addr szCommand,NULL,NULL,NULL,NORMAL_PRIORITY_CLASS,NULL,NULL,offset stStartUp,offset stProcInfo

.if eax != 0

.while TRUE

invoke GetExitCodeProcess,stProcInfo.hProcess,addr @dwExitCode

.break .if @dwExitCode != STILL_ACTIVE

invoke PeekNamedPipe,hRead2,addr szBuffer,511,addr @dwBytesRead,NULL,NULL

.if @dwBytesRead != 0

invoke RtlZeroMemory,addr szBuffer,512

invoke ReadFile,hRead2,addr szBuffer,@dwBytesRead,addr @dwBytesRead,NULL

mov @stRange.cpMin,-1

mov @stRange.cpMax,-1

invoke SendMessage,hWinText,EM_EXSETSEL,0,addr @stRange

invoke SendMessage,hWinText,EM_REPLACESEL,FALSE,addr szBuffer

invoke SendMessage,hWinText,EM_SCROLLCARET,NULL,NULL

invoke SendMessage,hWinText,WM_SETFONT,hFont,0

.endif

.endw

invoke CloseHandle,stProcInfo.hProcess

invoke CloseHandle,stProcInfo.hThread

.else

invoke MessageBox,hWinMain,addr szExcuteError,NULL,MB_OK or MB_ICONERROR

.endif

;********************************************************************

; 关闭管道

;********************************************************************

invoke CloseHandle,hRead1

invoke CloseHandle,hWrite1

invoke CloseHandle,hRead2

invoke CloseHandle,hWrite2

;********************************************************************

; 把“结束”菜单改为“执行”

;********************************************************************

invoke EnableMenuItem,hMenu,IDM_EXEC,MF_ENABLED

invoke EnableMenuItem,hMenu,IDM_EXIT,MF_ENABLED

invoke EnableWindow,hWinText,FALSE

and dwFlag,not F_RUNNING

ret

_RunThread endp

;********************************************************************

; 窗口程序

;********************************************************************

WndMainProc proc uses ebx edi esi, hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD

mov eax,wMsg

;********************************************************************

.if eax == WM_CREATE

mov eax,hWnd

mov hWinMain,eax

call _Init

;********************************************************************

.elseif eax == WM_SIZE

mov edx,lParam

mov ecx,edx

shr ecx,16

and edx,0ffffh

invoke MoveWindow,hWinText,0,0,edx,ecx,TRUE

invoke PostMessage,hWinText,WM_SIZE,wParam,lParam

;********************************************************************

.elseif eax == WM_CLOSE

test dwFlag,F_RUNNING

.if ZERO?

invoke DestroyWindow,hWinMain

invoke PostQuitMessage,NULL

.endif

;********************************************************************

.elseif eax == WM_COMMAND

mov eax,wParam

.if ax == IDM_EXEC

;********************************************************************

; 如果没有在执行中(dwFlag 没有置位) 则建立线程,在线程中执行程序

; 如果已经在执行中,则用 TerminateProcess 终止执行

;********************************************************************

test dwFlag,F_RUNNING

.if ZERO?

invoke EnableWindow,hWinText,TRUE

invoke SetFocus,hWinText

invoke CreateThread,NULL,NULL,offset _RunThread,NULL,NULL,offset hRunThread

.else

invoke TerminateProcess,stProcInfo.hProcess,-1

.endif

.elseif ax == IDM_EXIT

invoke DestroyWindow,hWinMain

invoke PostQuitMessage,NULL

.endif

.else

invoke DefWindowProc,hWnd,wMsg,wParam,lParam

ret

.endif

xor eax,eax

ret

WndMainProc endp

;********************************************************************

; 程序入口

;********************************************************************

start:

call _WinMain

invoke ExitProcess,NULL

;********************************************************************

_WinMain proc

local @stWcMain:WNDCLASSEX

local @stMsg:MSG

local @hRichEdit

invoke LoadLibrary,offset szDllName

mov @hRichEdit,eax

invoke InitCommonControls

invoke GetModuleHandle,NULL

mov hInstance,eax

invoke LoadMenu,hInstance,MENU_MAIN

mov hMenu,eax

;***************** 注册窗口类 ***************************************

invoke LoadCursor,0,IDC_ARROW

mov @stWcMain.hCursor,eax

mov @stWcMain.cbSize,sizeof WNDCLASSEX

mov @stWcMain.hIconSm,0

mov @stWcMain.style,CS_HREDRAW or CS_VREDRAW

mov @stWcMain.lpfnWndProc,offset WndMainProc

mov @stWcMain.cbClsExtra,0

mov @stWcMain.cbWndExtra,0

mov eax,hInstance

mov @stWcMain.hInstance,eax

invoke LoadIcon,hInstance,ICO_MAIN

mov @stWcMain.hIcon,eax

mov @stWcMain.hbrBackground,COLOR_BTNFACE+1

mov @stWcMain.lpszClassName,offset szClassName

mov @stWcMain.lpszMenuName,0

invoke RegisterClassEx,addr @stWcMain

;***************** 建立输出窗口 *****************************************

invoke CreateWindowEx,NULL,offset szClassName,offset szCaption,WS_OVERLAPPEDWINDOW,0,0,680,420,NULL,hMenu,hInstance,NULL

invoke ShowWindow,hWinMain,SW_SHOWNORMAL

invoke UpdateWindow,hWinMain

;********************************************************************

.while TRUE

invoke GetMessage,addr @stMsg,NULL,0,0

.break .if eax == 0

invoke TranslateMessage,addr @stMsg

invoke DispatchMessage,addr @stMsg

.endw

invoke FreeLibrary,@hRichEdit

invoke DeleteObject,hFont

ret

_WinMain endp

;********************************************************************

; 输入程序

;********************************************************************

_InputProc proc uses ebx edi esi, hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

local @szBuffer[4]:BYTE

local @dwBytesWrite

mov eax,uMsg

.if eax == WM_CHAR

mov eax,wParam

movzx eax,al

mov dword ptr @szBuffer,eax

test dwFlag,F_RUNNING

.if !ZERO?

invoke WriteFile,hWrite1,addr @szBuffer,1,addr @dwBytesWrite,NULL

.endif

xor eax,eax

ret

.endif

invoke GetWindowLong,hWnd,GWL_USERDATA

invoke CallWindowProc,eax,hWnd,uMsg,wParam,lParam

ret

_InputProc endp

;********************************************************************

_Init proc

;*************** 建立输出 RICHEDIT 窗口 ***********************************

invoke CreateWindowEx,WS_EX_CLIENTEDGE,offset szClassNameRedit,NULL,WS_CHILD OR WS_VISIBLE OR WS_VSCROLL OR WS_HSCROLLOR ES_MULTILINE OR ES_AUTOHSCROLL OR ES_AUTOVSCROLL,0,0,0,0,hWinMain,NULL,hInstance,NULL

mov hWinText,eax

;*************** 设置字体 ***********************************************

invoke CreateFontIndirect,offset stLogFont

mov hFont,eax

invoke SendMessage,hWinText,WM_SETFONT,hFont,0

invoke SendMessage,hWinText,EM_SETREADONLY,TRUE,NULL

invoke SetWindowLong,hWinText,GWL_WNDPROC,offset _InputProc

invoke SetWindowLong,hWinText,GWL_USERDATA,eax

invoke EnableWindow,hWinText,FALSE

invoke _CenterWindow,hWinMain

invoke SetFocus,hWinText

ret

_Init endp

;********************************************************************

end start

程序的分析和要点

在程序中,我先建立了一个 Richedit 控件用来显示子进程的输出,同时将 RichEdit 子类化,截取它的键盘输入以便把它发给子进程

invoke SetWindowLong,hWinText,GWL_WNDPROC,offset _InputProc

这条语句将 RichEdit 的过程指到了 _InputProc 中,然后在 _InputProc 的 WM_CHAR 中将键入的字符 WriteFile 到管道中,我在程序中先建立了两个管道,然后执行 c:\command.com,这样就得到了一个 dos 的命令行进程,然后在循环中通过 PeekNamedPipe 检测子进程有无输出,如果有的话则通过 ReadFile 读出,在显示到 RichEdit 中。

在运行例子程序的时候要注意,你可以在这个“Command.com” 中执行几乎所有的别的程序,但是不要执行如 ucdos,pctools 之类不使用标准输入输出的程序(就是在 dos 下用不了“>”或者“<”重定向的程序),由于我们在装载子进程的时候用了 WS_HIDE,所以原来的 command.com 的窗口是隐藏的,如果你执行了这种程序那就意味着你失去的对子进程的控制,因为它们不使用标准输入来接收键盘,你也就无法通过管道让它们退出。

在这里还可以引申出匿名管道的另一个用法,如果你执行的不是 command.com 而是类似于 arj.exe 的程序,然后也不用把它的输出显示到 RichEdit 中,而是在程序中处理,那么,你就可以编写一个 winarj,当然你只需编写窗口界面和 arj.exe 之间的配合而已。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有