采用VXD技术实现实的通信
typedef struct VPICD_IRQ_Descriptor{
USHORT VID_IRQ_Number; //IRQ号(0~15)
USHORT VID_Options; //标志位选项
ULONG VID_Hw_Int_Proc; //硬件中断服务程序的地址
ULONG VID_Virt_Int_Proc; //虚拟中断服务程序
ULONG VID_Mask_Change_Proc //Mask Change调用例程
ULONG VID_IRET_Proc; //IRET调用例程
ULONG VID_IRET_Time_Out; //在Vm的进程优先级提升之前的最大等待时间
ULONG VID_Hw_Int_Ref; //硬件中断服务程序的数据存放地址
}VID;
其中只用到三位。在本例中需要声明一个名为irq4的全局变量为VID结构,并付给如下初值:VID irq4={4,0,hwproc,0,0,0,0,500,0},表示将要虚拟化IRQ4,改变其中断处理函数为void hwproc(void),该函数的原型如下:
void hwproc(void){
_asm{
mov dx,0x3f8
in al,dx
mov byte ptr [readin],al
clc
}
return;
}
在这个中断处理中,仅仅从COM1的数据寄存器(地址为3F8h)中读取接收到的数值,并把该数值存放在一个类型为BYTE、名为readin的内存中。
(2)OnSysDynamicDeviceExit()函数
BOOL OnSysDynamicDeviceExit()
{
VPICD_Force_Default_Behavior(irqhandle);
//解除IRQ4虚拟化
return TRUE;
} //OnSysDynamicDeviceExit
该数提供了用于善后处理VXD在卸载时需要完成的事件。在本例中,和VXD初始化对应,需要解除对COM1的中断IRQ4的虚拟化。作者也是用98DDK在vpicd.h中提供的外包函数void static_inline VPICD_Force_Default_Behavior(HIRQ hirp)。该函数唯一需要的参数便是使用VPICD_Virtualize_IRQ函数传回的IRQ句柄。
(3)OnDeviceIoControl()函数
DWORD OnDeviceIoControl(PDIOCPARAMETERS p){
Switch (p->dwIoControlCode)
{
case 1: //端口写功能
if(!p->lpvOutBufferp->cbOutBuffer<1)
{ //输出缓存的有效性检查
return ERROR_INVALID_PARAMETER;
}
if(serial_out((DWORD)(p->lpvInBuffer)))
{ //数据发送
*(BYTE*)(p->lpvOutBuffer)=*(BYTE*)(p->lpvInBuffer);
}
else{
*(BYTE*)(p->lpvOutBuffer)=0;
}
open_int(); //打开com1中断
return 0;
case 2: //端口读功能
if(*(BYTE*)reading= =0x00)
{ //数据读入
*(BYTE*)(p->lpvOutBuffer)=0x00;
return 0;
}
*(BTYE*)(p->lpvOutBuffer)=*(BYTE*)(readin);
return 0;
}
return 0;
}
return 0;
}
OnDeviceIoControl函数用来处理Win32应用程序对VXD的呼叫。Win32应用程序的呼叫会让VMM32送给该VXD一个系统信息,并传递进一个DIOCPARAMETERS结构的指针。该结构里包含Win32应用程序呼叫时传递进来的各个参数。这个结构的组成如下:
Typedef stunct DIOCParams{
DWORD Internall; //指向客户寄存器的指针
DWORD VMHande; //该VM的句柄
DWORD Internal2; //指向DDB结构的指针
DWORD dwIoConrolCode; //DeviceIoControl例程中呼叫的控制码
DWOD lpvInBuffer; //DeviceIoControl例程呼叫所传递进来的输入缓冲区地址
DWORD cbInBuffer; //输入缓冲区的大小
DWORD lpvOutBuffer; //DeviceIoControl例程呼叫所传递进来的输出缓冲区地址
DWORD cbOutBuffer; //输出缓冲区的大小
DWORD lpcbBytesReturned; //拷贝到输出缓冲区中的字节数(可以为NULL)
DWORD lpOverlapped; //DeviceIoControl例程呼叫所传递进来的重叠I/O块结构
DWORD hDevice; //Ring3层呼叫应用程序句柄
DWORD tagProcess; //例程标签
}
DIOPARAMETERS;
其中,dwIoControlCode指明了Win32应用程序需要VXD提供的哪一项服务。在本例中采用一个switch-case语句作为服务入口,如下所示。其中服务1为让串口送出一个字节,服务2为读取一个已经由串口接收的字节。函数open_int()是用来初始化串口以便接收字节数据;函数BOOL serial_out(DWORD pBuffer)是让串口发出一个字节。它们的函数体分别如下:
BOOL serial_out(DWORD pBuffer){
if(pBuffer= =NULL){
return FALSE;
}
_asm {
pushfd
cli
push eax
push edx
mov dx,0x3fb ;设置COM1的波特率
mov al,0x83
out dx,al
mov dx,0x3f8
mov al,12
out dx,al
mov dx,0x3f9
mov al,0
out dx,al
mov dx,0x3fb ;设置COM1的线控项
mov al,3
out dx,al
mov dx,0x3f9 ;CMM1关中断
mov al,0
out dx,al
mov dx,0x3fa ;关闭com1的FIFO功能
mov al,0
out dx,al
mov dx,0x3f8 ;字节发送
mov al,byte ptr [pBuffer]
out dx,al
pop edx
pop eax
popfd
sti
}
return TRUE;
}
serial_out这个函数体的实现是用汇编语言实现的。因为涉及到很多的端口提供以及CPU的标志(flag)和压栈操作,因此考虑到用汇编语言编写会简化代码。因为此串口传输中,用到了关闭中断的指令(cli),所以,当写操作所要求完成的任务很多时,此关中断指令会让程序的实时性很好地体现出来,但cli指令有效时间过长会导致系统问题,所以还是要谨慎使用。
Void open_int(void){
_asm{
mov dx,0x3f9 ;COM1开中断
mov al,0x05
out dx,al
}
return;
}
open_int函数用来把PC串口的中断设备按照需要设立起来。函数体很简单,仅改变了地址为3F9h的内容,意为设置Rx data ready和