一个端口访问器的编写 — Windows XP驱动程序举例 VS.NET+WIN XP DDK+DriverStudio3.2开发环境版 东南大学计算机科学与工程学院 杨全胜
本讲义假设阅读者已经熟悉VC++. NET和VS 本讲义假设阅读者已经熟悉VC++.NET和VS.NET(2002)的使用。如果对Window XP驱动程序的编写尚不熟悉,请参阅本人所编写的《Windows XP驱动程序编写方法——Step by Step》电子讲义。 注意:程序中暗红色显示的部分是我们添加或修改过的语句,其他是DriverWorks自动生成的。蓝色显示的部分是要删除的语句。省略号的部分是不变的。语句中T.Trace(TraceInfo, __FUNCTION__“xxxx”)这样的语句是向调试软件输出信息,该信息可在DriverMonitor或其他调试监视器中看到。
2次
由于一个可能是DriverStudio 3.2中的BUG,所以及时生成的一个空工程项目也无法编译通过,需要对生成的工程文件做以下手工修改: 把MyIOPort项目中的sources文件中的: TARGETLIBS=$ (DDK_LIB_PATH)\ntstrsafe.lib $ (DDK_LIB_PATH)\csq.lib 这一行去掉就可以编译通过了
在“MyIOPortDevice.h”文件的 class MyIOPortDevice : public KPnpDevice定义中添加下面的变量定义。 protected: // Member data KIoRange m_ParPortIos; 注意:程序中暗红色显示的部分是我们添加或修改过的语句,其他是DriverWorks自动生成的。蓝色显示的部分是要删除的语句。省略号的部分是不变的。语句中T.Trace(TraceInfo, __FUNCTION__“xxxx”)这样的语句是向调试软件输出信息,该信息可在DriverMonitor或其他调试监视器中看到。
修改下面函数代码: NTSTATUS MyIOPortDevice::OnStartDevice(KIrp I) { T.Trace(TraceInfo, __FUNCTION__"++. IRP %p\n", I); NTSTATUS status = STATUS_SUCCESS; I.Information() = 0; // Get the list of raw resources from the IRP PCM_RESOURCE_LIST pResListRaw = I.AllocatedResources(); // Get the list of translated resources from the IRP PCM_RESOURCE_LIST pResListTranslated = I.TranslatedResources(); // TODO: Add device-specific code to initialize/start your hardware device. // The base class will handle completion of the IRP status = m_ParPortIos.Initialize( 0x00, // PC机I/O地址空间的首地址是00H TRUE, // 在CPU I/O空间内 8, // 设备读写数据的字节宽度 TRUE // 映射到系统空间 ); T.Trace(TraceInfo, __FUNCTION__"--. IRP %p, STATUS %x\n", I, status); return status; }
下面的函数保留和添加下述语句,其他全部删除 NTSTATUS MyIOPortDevice::MYIOPORT_IOCTL_Read_Handler(KIrp I) { T.Trace(TraceInfo, __FUNCTION__"++. IRP %p\n", I); NTSTATUS status = STATUS_SUCCESS; ULONG outputSize = I.IoctlOutputBufferSize(); char buff1[50],buff2[50]; struct ioport { int port; UCHAR data; } *iopt; ULONG fwLength=0; iopt=(ioport *)I.IoctlBuffer(); // 指针直接指向IRP的BUFF区域这里进 //来的时候有用户程序的信息,出去的时候放返回信息 //显示从应用程序得到的要读的端口号。 T.Trace(TraceInfo, __FUNCTION__"Read port is 0x%d\n", iopt->port );
// 从端口读一个字节的数据 iopt->data= (UCHAR)m_ParPortIos.inb(iopt->port); fwLength = 8; if (outputSize >= fwLength) // 如果读入缓冲够长 { I.Information() = fwLength; // 返回信息长度 T.Trace(TraceInfo, __FUNCTION__"Read Data is 0x%d\n", iopt->data); // 显示从应用程序得到的命令串。 } else { I.Information() = 0; // 否则信息长度为0 T.Trace(TraceInfo, __FUNCTION__"buff size too small\n"); T.Trace(NT_SUCCESS(status)?TraceInfo:TraceWarning, __FUNCTION__"--. IRP %p, STATUS %x\n", I, status); return status;
NTSTATUS MyIOPortDevice::MYIOPORT_IOCTL_Write_Handler(KIrp I) { T.Trace(TraceInfo, __FUNCTION__"++. IRP %p\n", I); NTSTATUS status = STATUS_SUCCESS; char buff1[50],buff2[50]; struct ioport { int port; UCHAR data; } *iopt; ULONG fwLength=0; iopt=(ioport *)I.IoctlBuffer(); // 指针直接指向IRP的BUFF区域这里进来的时候有用户程序的信息,出去的时候放返回信息 T.Trace(TraceInfo, __FUNCTION__"Write port is 0x%d, Write data is 0x%d\n", iopt->port , iopt->data ); // 显示从应用程序得到的命令串。 m_ParPortIos.outb(iopt->port,iopt->data); // 向端口写一个字节的数据 I.Information() = 0; T.Trace(NT_SUCCESS(status)?TraceInfo:TraceWarning, __FUNCTION__"--. IRP %p, STATUS %x\n", I, status); return status; }
下面我们来修改应用程序,该程序访问硬件端口来获得CMOS中的数据以及让主板小喇叭发声。首先要修改一下应用程序项目的属性中的字符集。缺省的字符集是“使用 Unicode 字符集”,把它改成“未设置”。 右键点击
接下来我们修改资源文件。下图是系统自动生成的应用程序界面,这并不适合我们的需要。删除这些控件,换上下页显示的控件。 全部删除
Static Text List Control Button 在对话框中分别建立如图的三个按钮,一个列表控件(List Control)和一个静态文本框。其中,列表控件和按钮的属性如下页的图设置。
在“MyIOPortApp.h”文件中增加下列函数声明 UCHAR ReadOneByte(int port); // 从port读一个字节 void WriteOneByte(int port, UCHAR value); // 向端口port写一个字节 BOOL OpenMyDevice(); // 打开设备 void Silence( void ); // 静音 void Sound(DWORD freq ); // 发频率为freq的声音 void OnReadcmos(HWND hDlg); // 读CMOS
在“MyIOPortApp.cpp”文件中增加下列函数: UCHAR ReadOneByte(int port) { char buff[200]; ULONG nOutput; // Count written to bufOutput struct ioport { int port; UCHAR data; } iopt,iopt2; iopt.port=port; if (!DeviceIoControl(g_hDevice, MYIOPORT_IOCTL_Read, &iopt, //输出到驱动程序 sizeof(iopt), //IOCTL_INBUF_SIZE, &iopt2, // 从驱动程序得到返回值 8, &nOutput, NULL) )
{ sprintf(buff,"ERROR: DeviceIoControl returns %0x.", GetLastError()); ::MessageBox(NULL,buff,"错误", MB_OK|MB_ICONSTOP); return 0; } else return iopt2.data;
void WriteOneByte(int port, UCHAR value) { char buff[200]; ULONG nOutput; struct ioport { int port; UCHAR data; } iopt,iopt2; iopt.port=port; iopt.data=value; if (!DeviceIoControl(g_hDevice, MYIOPORT_IOCTL_Write, &iopt, //输出到驱动程序 sizeof(iopt), //IOCTL_INBUF_SIZE, &iopt2, // 从驱动程序得到返回值 8, &nOutput, NULL) )
{ sprintf(buff,"ERROR: DeviceIoControl returns %0x.", GetLastError()); ::MessageBox(NULL,buff,"错误", MB_OK|MB_ICONSTOP); return ; }
BOOL OpenMyDevice() { DWORD lastError; HDEVINFO hDeviceInfo; DWORD bufferSize; SP_DEVICE_INTERFACE_DATA interfaceData; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceDetail; // Find devices that have our interface hDeviceInfo = SetupDiGetClassDevs( (LPGUID)&GUID_DEVINTERFACE_MYIOPORT, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE );
if (hDeviceInfo == INVALID_HANDLE_VALUE) { lastError = GetLastError(); MyIOPortOutputText(_T("SetupDiGetClassDevs failed, GetLastError() = %d"), lastError); return FALSE; } // Setup the interface data struct interfaceData.cbSize = sizeof(interfaceData); if(SetupDiEnumDeviceInterfaces(hDeviceInfo,NULL, (LPGUID)&GUID_DEVINTERFACE_MYIOPORT, 0,&interfaceData)) if (!SetupDiGetDeviceInterfaceDetail( hDeviceInfo,&interfaceData, NULL,0,&bufferSize,NULL))
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { MyIOPortOutputText(_T("Error: couldn't get interface detail, (%d)"), GetLastError()); return FALSE; } // Allocate a big enough buffer to get detail data deviceDetail= (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(bufferSize); if (deviceDetail == NULL) MyIOPortOutputText(_T("Error: Buffer allocation failed")); // Setup the device interface struct deviceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
// Try again to get the device interface detail info if (!SetupDiGetDeviceInterfaceDetail( hDeviceInfo, &interfaceData, deviceDetail, bufferSize, NULL, NULL)) { MyIOPortOutputText(_T("Error: SetupDiGetDeviceInterfaceDetail failed (%d)"), GetLastError()); free(deviceDetail); return FALSE; } SetupDiDestroyDeviceInfoList(hDeviceInfo);
g_hDevice = CreateFile( (LPCTSTR)deviceDetail->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,0); if (g_hDevice == INVALID_HANDLE_VALUE) { MyIOPortOutputText(_T("Error: CreateFile failed for device %s (%d)\n"), deviceDetail->DevicePath, GetLastError()); return FALSE; } MyIOPortOutputText(_T("Opened device %s"), deviceDetail->DevicePath); return TRUE; MyIOPortOutputText(_T("No devices found"));
void Sound(DWORD freq ) { UCHAR data; if(freq>=20 && freq<=20000) freq = 1193181 / freq; data = ReadOneByte(0x61); if((data & 3) == 0) WriteOneByte(0x61, data | 3); WriteOneByte(0x43, 0xb6); } WriteOneByte(0x42, (UCHAR)(freq%256)); WriteOneByte(0x42, (UCHAR)(freq/256));
void Silence( void ) { UCHAR data; data = ReadOneByte(0x61); WriteOneByte(0x61, data & 0xfc); }
void OnReadcmos(HWND hDlg) { // TODO: Add your control notification handler code here int i,it; char buff[23]; char cmosram[128][200]={ // CMOS每个字节的含义 "目前系统时间的秒", "报警时间秒值", "目前系统时间的分", "报警时间分值", "目前系统时间的小时", "报警时间小时值", "目前星期几", "目前系统日期", "目前系统月份", "目前系统年的后两位",
"状态寄存器A", "状态寄存器B", "状态寄存器C", "状态寄存器D", "诊断状态记录值", "当机复位指示字节", "磁盘驱动器类型:xxxx.... 软驱0类型 0001=360K 0010=1.2M ....xxxx 软驱1类型 0011=720K 0100=1.44M 0110=2.88M", "(海洋板)..x..... 硬盘0Translate 1=Yes 0=No ...x.... 硬盘1Translate 1=Yes 0=No .....x.. 1=Step rate fast 0=Step rate slow ......xx 软驱个数00=1个 01=2个 10=三个 11=四个 ", "硬盘类型:xxxx.... 硬盘驱动器0的类型 1111=使用19h单元 ....xxxx 硬盘驱动器1的类型 1111=使用1Ah单元", "字节 x....... 1=Anti-Virus 硬盘Boot区写保护 0=disable .xxx.... 软驱2类型 ....xxxx 软驱3类型",
"所安装设备的类型:xx. 00=1个软驱,01=2个软驱,. xx "所安装设备的类型:xx...... 00=1个软驱,01=2个软驱,..xx.... 00=单显 01=CGA 10=CGA 11=VGA/EGA,后四位高到低是显示、键盘、协处理器与软件机使能(=1)", "基本内存容量低字节,单位KB", "基本内存容量高字节,单位KB", "扩充内存容量低字节,单位KB", "扩充内存容量高字节,单位KB", "(海洋板)硬盘驱动器0的类型", "(海洋板)硬盘驱动器1的类型", "(海洋板)显示卡类型 VGA/monochrome", "(海洋板)....xxxx 启动顺序0=A:C: 1=C:A: 2=Screen prompt 3=Auto search 4=Network .x...... 486-CPU Cache 0=disable 1= enable ", "(海洋板)x....... 1=键盘使用缺省参数 0=使用本单元值 .xx..... 键盘延时00=0.25秒01=0.5秒10=0.75秒11=1秒 ...xxxxx 键盘重发速率,单位cps",
"(海洋板)硬盘1的柱面数", "(海洋板)硬盘1的磁头数", "(海洋板)硬盘1的扇区数", "(海洋板)硬盘0的柱面数", "(海洋板)硬盘0的磁头数", "(海洋板)硬盘0的扇区数", "(海洋板)AT-Bus clock 0=16.7Mhz 1=13.3Mhz 2=11.1Mhz 3=8.3Mhz 4=6.7Mhz 5=5.6Mhz 6=4.2Mhz", "(海洋板)memory type 00h=60nS 20h=70nS", "串口配置", "并口配置", "未使用",
"标准CMOS校验和", "扩充内存容量低字节,单位KB", "BCD码的世纪值(年的高2位,如19,20等)", "信息标志", "xxxx.... Shadow of D000 0=Vacant ....xxxx Shadow of C000 0=ROM ", "xxxx.... Shadow of F000 0=ROM ....xxxx Shadow of E000 0=Vacant ", "xxxx.... Shadow of D000 1=WP 0=Read/Write ....xxxx Shadow of C000 1=WP 0=Read/Write ", "xxxx.... Shadow of F000 1=WP 0=Read/Write ....xxxx Shadow of E000 1=WP 0=Read/Write ", "内存大小,单位兆", "(内存大小有关=160/前一单元 )", "口令代码Security Code",
"xx. 口令检测方式 0=Disable 1=Setup only 2=Powerup&Setup 3=Bootup&Setup "xx...... 口令检测方式 0=Disable 1=Setup only 2=Powerup&Setup 3=Bootup&Setup ..xxxxxx Cold-Boot Delay 冷启动延时(单位秒)*2 ", "xxxx.... 4=Full test 5=Quick scan 7=Skip test ....xx.. Xfer-Mode of 硬盘0 0=Standard 1=Poll ......xx Xfer-Mode of 硬盘1 2=Block 3=32-Bit Block ", "10h-3Dh部分单元的按字节检查和,不包括17h,18h,19h,1Ah,26h, 27h,30h,31h,32h,38h,3Ah,3Ch,3Dh单元 ", "保留",
"保留",
"保留",
"保留",
"保留", "保留" }; LVITEM lvitem; HWND hWnd; hWnd = GetDlgItem(hDlg, IDC_CMOSLIST); BOOL qu; UCHAR index,value,tmp; for(i=0;i<128;i++) { lvitem.mask = LVIF_TEXT|LVIF_STATE; lvitem.iItem = i; lvitem.iSubItem = 0; sprintf(buff,"%02XH",i); lvitem.pszText = buff;
SendMessage(hWnd,LVM_INSERTITEM,0,(LPARAM)&lvitem); lvitem.mask = LVIF_TEXT; lvitem.iItem = i; lvitem.iSubItem = 1; tmp = (UCHAR)i; index = 0x80 | tmp; WriteOneByte(0x70,index); value = ReadOneByte(0x71); sprintf(buff,"%02XH",value); SendMessage(hWnd,LVM_SETITEM,0,(LPARAM)&lvitem); lvitem.iSubItem = 2; lvitem.pszText = cmosram[i]; }
下面增加关键的消息处理: LRESULT CALLBACK MyIOPortMainDlgProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) { …… switch (uMsg) case WM_INITDIALOG: g_hDevice = INVALID_HANDLE_VALUE; // 初始化List Control hWnd = GetDlgItem(hDlg, IDC_CMOSLIST); RECT rect; GetWindowRect(hWnd,&rect); LVCOLUMN lvm;
strcpy(lvm.pszText,"偏移"); lvm.cx = (rect.right - rect.left)/10; lvm.iSubItem = 0; lvm.mask = LVCF_TEXT|LVCF_WIDTH|LVCF_SUBITEM; lvm.fmt = LVCFMT_CENTER; SendMessage(hWnd,LVM_INSERTCOLUMN,0,(LPARAM)&lvm); strcpy(lvm.pszText,"值"); lvm.iSubItem = 1; SendMessage(hWnd,LVM_INSERTCOLUMN,1,(LPARAM)&lvm); lvm.cx = (rect.right - rect.left)*4/5; lvm.iSubItem = 2; SendMessage(hWnd,LVM_INSERTCOLUMN,2,(LPARAM)&lvm);
if(OpenMyDevice()) { EnableWindow(GetDlgItem(hDlg, IDC_READCMOS), TRUE); EnableWindow(GetDlgItem(hDlg, IDC_SPEAKER), TRUE); } else EnableWindow(GetDlgItem(hDlg, IDC_READCMOS), FALSE); EnableWindow(GetDlgItem(hDlg, IDC_SPEAKER), FALSE); // 此处还有DS自动生成的程序段,需全部删除 return 1; case WM_COMMAND: switch (LOWORD(wParam)) case IDOK: if (g_hDevice != INVALID_HANDLE_VALUE)
CloseHandle(g_hDevice); g_hDevice = INVALID_HANDLE_VALUE; } // Terminate our I/O completion thread SetEvent(g_hIoCompletionThreadTerminationEvent); WaitForSingleObject(g_hIoCompletionThread, INFINITE); UnregisterDeviceNotification(g_hInterfaceNotification); EndDialog(hDlg, 0); PostQuitMessage(0); break; case IDC_SPEAKER: // C调 Sound(262); // 1 = 262 Hz Sleep(200); Silence(); Sound(288); // 2 = 294 Hz
Sound(330); // 3 = 330 Hz Sleep(200); Silence(); break; case IDC_READCMOS: OnReadcmos(hDlg); default: } ……
做完上述添加后,将下列在MyIOPortApp.cpp文件中DS自动生成的部分删除: DWORD MyIOPortEnumerateDevices(HWND hDlg) { …… // 该函数全部删除 } HANDLE MyIOPortOpenDevice(HWND hDlg) 删除下列消息的处理程序: WM_DEVICECHANGE,IDC_OPEN_BUTTON,IDC_CLOSE_BUTTON,IDC_OP_TYPE_COMBO IDC_DEVICE_INSTANCE_LIST,IDC_EXECUTE_BUTTON,IDC_CANCEL_IO_BUTTON
1)右键点击 2)选择该项目
1)右键点击 2)选择该项目