回 帖 发 新 帖 刷新版面

主题:包含了WINIO头文件之后,在连接总是出错,是怎么回事?

大下们,请多指教,我用的是BCB6,系统WIN2000,连接时出现[Linker Error] Unresolved external 'InitializeWinIo' referenced from D:\LTP\UNIT1.OBJ
  [Linker Error] Unresolved external 'GetPortVal' referenced from D:\LTP\UNIT1.OBJ
  [Linker Error] Unresolved external 'SetPortVal' referenced from D:\LTP\UNIT1.OBJ
  [Linker Error] Unresolved external 'MapPhysToLin' referenced from D:\LTP\UNIT1.OBJ
  [Linker Error] Unresolved external 'UnmapPhysicalMemory' referenced from D:\LTP\UNIT1.OBJ
  [Linker Error] Unresolved external 'GetPhysLong' referenced from D:\LTP\UNIT1.OBJ
  [Linker Error] Unresolved external 'SetPhysLong' referenced from D:\LTP\UNIT1.OBJ
  [Linker Error] Unresolved external 'ShutdownWinIo' referenced from D:\LTP\UNIT1.OBJ
的错误,是怎么回事?谢了!

回复列表 (共10个回复)

沙发

没听说有这个头文件啊
winio是你自己定义的?

板凳

网上有下,用于WIN2000下直接控制硬件I/O输入输出的

3 楼

网上有下,用于WIN2000下直接控制硬件I/O输入输出的

4 楼

网上有下,用于WIN2000下直接控制硬件I/O输入输出的

5 楼

哦 那没用过,不清楚了

6 楼

你好:
    你的问题我也出现过,可能是因为没有正确的连接winio.dll,我直接把它包含到了工程里,调试通过,但是读写好象不正确。你再试一下。
    我的问题通过别的方式解决了,没用它。

7 楼

别的方式,能教教我吗?我只需要读取并口的数据就行,我用的是WIN2000系统

8 楼

用户模式程序在Windows NT操作系统下使用IN或者OUT指令来存取I/O端口的话会被系统终止,但是我们却可以,这怎么可能呢?这是因为我运行程序前刚喝了脑x金!广告里面天天说,脑x金无所不能!~~~~~~呵呵,开个玩笑,这当然是因为有了Giveio驱动程序。

Giveio驱动程序的源代码

;@echo off
;goto make

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;  giveio - Kernel Mode Driver
;  Demonstrate direct port I/O access from a user mode
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.386
.model flat, stdcall
option casemap:none
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                              I N C L U D E   F I L E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
include \masm32\include\w2k\ntstatus.inc
include \masm32\include\w2k\ntddk.inc
include \masm32\include\w2k\ntoskrnl.inc
includelib \masm32\lib\w2k\ntoskrnl.lib
include \masm32\Macros\Strings.mac
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                     E Q U A T E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
IOPM_SIZE equ 2000h     ; sizeof I/O permission map
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                        C O D E
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       DriverEntry
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING

local status:NTSTATUS
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
local kvpi:KEY_VALUE_PARTIAL_INFORMATION
local pIopm:PVOID
local pProcess:LPVOID

    invoke DbgPrint, $CTA0("giveio: Entering DriverEntry")
    mov status, STATUS_DEVICE_CONFIGURATION_ERROR
    lea ecx, oa
    InitializeObjectAttributes ecx, pusRegistryPath, 0, NULL, NULL
    invoke ZwOpenKey, addr hKey, KEY_READ, ecx
    .if eax == STATUS_SUCCESS
        push eax
        invoke ZwQueryValueKey, hKey, $CCOUNTED_UNICODE_STRING("ProcessId", 4), \
                               KeyValuePartialInformation, addr kvpi, sizeof kvpi, esp
        pop ecx
        .if ( eax != STATUS_OBJECT_NAME_NOT_FOUND ) && ( ecx != 0 )
            invoke DbgPrint, $CTA0("giveio: Process ID: %X"), \
                                dword ptr (KEY_VALUE_PARTIAL_INFORMATION PTR [kvpi]).Data
            ; Allocate a buffer for the I/O permission map
            invoke MmAllocateNonCachedMemory, IOPM_SIZE
            .if eax != NULL
                mov pIopm, eax
                lea ecx, kvpi
                invoke PsLookupProcessByProcessId, \
                        dword ptr (KEY_VALUE_PARTIAL_INFORMATION PTR [ecx]).Data, addr pProcess
                .if eax == STATUS_SUCCESS
                    invoke DbgPrint, $CTA0("giveio: PTR KPROCESS: %08X"), pProcess
                    invoke Ke386QueryIoAccessMap, 0, pIopm
                    .if al != 0
                        ; I/O access for 70h port
                        mov ecx, pIopm
                        add ecx, 70h / 8
                        mov eax, [ecx]
                        btr eax, 70h MOD 8
                        mov [ecx], eax

                       ; I/O access for 71h port
                        mov ecx, pIopm
                        add ecx, 71h / 8
                        mov eax, [ecx]
                        btr eax, 71h MOD 8
                        mov [ecx], eax

                        invoke Ke386SetIoAccessMap, 1, pIopm
                        .if al != 0
                            invoke Ke386IoSetAccessProcess, pProcess, 1
                            .if al != 0
                                invoke DbgPrint, $CTA0("giveio: I/O permission is successfully given")
                            .else
                                invoke DbgPrint, $CTA0("giveio: I/O permission is failed")
                                mov status, STATUS_IO_PRIVILEGE_FAILED
                            .endif
                        .else
                            mov status, STATUS_IO_PRIVILEGE_FAILED
                        .endif
                    .else
                        mov status, STATUS_IO_PRIVILEGE_FAILED
                    .endif
                    invoke ObDereferenceObject, pProcess
                .else
                    mov status, STATUS_OBJECT_TYPE_MISMATCH
                .endif
                invoke MmFreeNonCachedMemory, pIopm, IOPM_SIZE
            .else
                invoke DbgPrint, $CTA0("giveio: Call to MmAllocateNonCachedMemory failed")
                mov status, STATUS_INSUFFICIENT_RESOURCES
            .endif
        .endif
        invoke ZwClose, hKey
    .endif

    invoke DbgPrint, $CTA0("giveio: Leaving DriverEntry")
    mov eax, status
    ret

DriverEntry endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
end DriverEntry


:make
set drv=giveio
\masm32\bin\ml /nologo /c /coff %drv%.bat
\masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj
del %drv%.obj
echo.
pause


    这个驱动的代码是基于Dale Roberts写的一个著名的例子(giveio)改编的。

I/O许可位图

    谜底揭晓了:我们的驱动程序修改了I/O许可位图(I/O permission bit map,IOPM),这样进程就被允许自由地存取I/O端口,这方面详细资料见http://www.intel.com/design/intarch/techinfo/pentium/PDF/inout.pdf
    每个进程都有自己的I/O许可位图,每个单独的I/O端口的访问权限都可以对每个进程进行单独授权,如果相关的位被设置的话,对对应端口的访问就是被禁止的,如果相关的位被清除,那么进程就可以访问对应的端口。

下面是一个C代码实现的例子:
#include <ntddk.h>
#include "MyPort.h"

// 设备类型定义
// 0-32767被Microsoft占用,用户自定义可用32768-65535
#define FILE_DEVICE_MYPORT    0x0000f000

// I/O控制码定义
// 0-2047被Microsoft占用,用户自定义可用2048-4095
#define MYPORT_IOCTL_BASE 0xf00

#define IOCTL_MYPORT_READ_BYTE   CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_MYPORT_WRITE_BYTE  CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE+1, METHOD_BUFFERED, FILE_ANY_ACCESS)

// IOPM是65536个端口的位屏蔽矩阵,包含8192字节(8192 x 8 = 65536)
// 0 bit: 允许应用程序访问对应端口
// 1 bit: 禁止应用程序访问对应端口

#define IOPM_SIZE    8192

typedef UCHAR IOPM[IOPM_SIZE];

IOPM *pIOPM = NULL;

// 设备名(要求以UNICODE表示)
const WCHAR NameBuffer[] = L"\\Device\\MyPort";
const WCHAR DOSNameBuffer[] = L"\\DosDevices\\MyPort";

// 这是两个在ntoskrnl.exe中的未见文档的服务例程
// 没有现成的已经说明它们原型的头文件,我们自己声明
void Ke386SetIoAccessMap(int, IOPM *);
void Ke386IoSetAccessProcess(PEPROCESS, int);

// 函数原型预先说明
NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
void MyPortUnload(IN PDRIVER_OBJECT DriverObject);

// 驱动程序入口,由系统自动调用,就像WIN32应用程序的WinMain
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
    PDEVICE_OBJECT deviceObject;
    NTSTATUS status;
    UNICODE_STRING uniNameString, uniDOSString;

    // 为IOPM分配内存
    pIOPM = MmAllocateNonCachedMemory(sizeof(IOPM));
    if (pIOPM == 0)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    // IOPM全部初始化为0(允许访问所有端口)
    RtlZeroMemory(pIOPM, sizeof(IOPM));

    // 将IOPM加载到当前进程
    Ke386IoSetAccessProcess(PsGetCurrentProcess(), 1);
    Ke386SetIoAccessMap(1, pIOPM);

    // 指定驱动名字
    RtlInitUnicodeString(&uniNameString, NameBuffer);
    RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);

    // 创建设备
    status = IoCreateDevice(DriverObject, 0,
            &uniNameString,
            FILE_DEVICE_MYPORT,
            0, FALSE, &deviceObject);

    if (!NT_SUCCESS(status))
    {
        return status;
    }

    // 创建WIN32应用程序需要的符号连接
    status = IoCreateSymbolicLink (&uniDOSString, &uniNameString);

    if (!NT_SUCCESS(status))
    {
        return status;
    }

    // 指定驱动程序有关操作的模块入口(函数指针)
    // 涉及以下两个模块:MyPortDispatch和MyPortUnload
    DriverObject->MajorFunction[IRP_MJ_CREATE]         =
    DriverObject->MajorFunction[IRP_MJ_CLOSE]          =
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyPortDispatch;
    DriverObject->DriverUnload = MyPortUnload;

    return STATUS_SUCCESS;
}

// IRP处理模块
NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PIO_STACK_LOCATION IrpStack;
    ULONG              dwInputBufferLength;
    ULONG              dwOutputBufferLength;
    ULONG              dwIoControlCode;
    PULONG             pvIOBuffer;
    NTSTATUS           ntStatus;

    // 填充几个默认值
    Irp->IoStatus.Status = STATUS_SUCCESS;    // 返回状态
    Irp->IoStatus.Information = 0;            // 输出长度

    IrpStack = IoGetCurrentIrpStackLocation(Irp);

    // Get the pointer to the input/output buffer and it's length

    // 输入输出共用的缓冲区
    // 因为我们在IOCTL中指定了METHOD_BUFFERED,
    pvIOBuffer = Irp->AssociatedIrp.SystemBuffer;

    switch (IrpStack->MajorFunction)
    {
        case IRP_MJ_CREATE:        // 与WIN32应用程序中的CreateFile对应
            break;

        case IRP_MJ_CLOSE:        // 与WIN32应用程序中的CloseHandle对应
            break;

        case IRP_MJ_DEVICE_CONTROL:        // 与WIN32应用程序中的DeviceIoControl对应
            dwIoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
            switch (dwIoControlCode)
            {
                // 我们约定,缓冲区共两个DWORD,第一个DWORD为端口,第二个DWORD为数据
                // 一般做法是专门定义一个结构,此处简单化处理了
                case IOCTL_MYPORT_READ_BYTE:        // 从端口读字节
                    pvIOBuffer[1] = _inp(pvIOBuffer[0]);
                    Irp->IoStatus.Information = 8;  // 输出长度为8
                    break;
                case IOCTL_MYPORT_WRITE_BYTE:       // 写字节到端口
                    _outp(pvIOBuffer[0], pvIOBuffer[1]);
                    break;
                default:        // 不支持的IOCTL
                    Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
            }
    }

    ntStatus = Irp->IoStatus.Status;

    IoCompleteRequest (Irp, IO_NO_INCREMENT);

    return ntStatus;
}

// 删除驱动
void MyPortUnload(IN PDRIVER_OBJECT DriverObject)
{
    UNICODE_STRING uniDOSString;

    if(pIOPM)
    {
        // 释放IOPM占用的空间
        MmFreeNonCachedMemory(pIOPM, sizeof(IOPM));
    }

    RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);

    // 删除符号连接和设备
    IoDeleteSymbolicLink (&uniDOSString);
    IoDeleteDevice(DriverObject->DeviceObject);
}

下面给出实现设备驱动程序的动态加载的源码。动态加载的好处是,你不用做任何添加新硬件的操作,也不用编辑注册表,更不用重新启动计算机。

// 安装驱动并启动服务
// lpszDriverPath:  驱动程序路径
// lpszServiceName: 服务名
BOOL StartDriver(LPCTSTR lpszDriverPath, LPCTSTR lpszServiceName)
{
    SC_HANDLE hSCManager;        // 服务控制管理器句柄
    SC_HANDLE hService;          // 服务句柄
    DWORD dwLastError;           // 错误码
    BOOL bResult = FALSE;        // 返回值

    // 打开服务控制管理器
    hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (hSCManager)
    {
        // 创建服务
        hService = CreateService(hSCManager,
                    lpszServiceName,
                    lpszServiceName,
                    SERVICE_ALL_ACCESS,
                    SERVICE_KERNEL_DRIVER,
                    SERVICE_DEMAND_START,
                    SERVICE_ERROR_NORMAL,
                    lpszDriverPath,
                    NULL,
                    NULL,
                    NULL,
                    NULL,
                    NULL);

        if (hService == NULL)
        {
            if (::GetLastError() == ERROR_SERVICE_EXISTS)
            {
                hService = ::OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);
            }
        }

        if (hService)
        {
            // 启动服务
            bResult = StartService(hService, 0, NULL);

            // 关闭服务句柄
            CloseServiceHandle(hService);
        }

        // 关闭服务控制管理器句柄
        CloseServiceHandle(hSCManager);
    }

    return bResult;
}

// 停止服务并卸下驱动
// lpszServiceName: 服务名
BOOL StopDriver(LPCTSTR lpszServiceName)
{
    SC_HANDLE hSCManager;        // 服务控制管理器句柄
    SC_HANDLE hService;          // 服务句柄
    BOOL bResult;                // 返回值
    SERVICE_STATUS ServiceStatus;

    bResult = FALSE;

    // 打开服务控制管理器
    hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (hSCManager)
    {
        // 打开服务
        hService = OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);

        if (hService)
        {
            // 停止服务
            bResult = ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus);

            // 删除服务
            bResult = bResult && DeleteService(hService);

            // 关闭服务句柄
            CloseServiceHandle(hService);
        }

        // 关闭服务控制管理器句柄
        CloseServiceHandle(hSCManager);
    }

    return bResult;
}

应用程序实现端口I/O的接口如下:

// 全局的设备句柄
HANDLE hMyPort;

// 打开设备
// lpszDevicePath: 设备的路径
HANDLE OpenDevice(LPCTSTR lpszDevicePath)
{
    HANDLE hDevice;

    // 打开设备
    hDevice = ::CreateFile(lpszDevicePath,    // 设备路径
        GENERIC_READ | GENERIC_WRITE,        // 读写方式
        FILE_SHARE_READ | FILE_SHARE_WRITE,  // 共享方式
        NULL,                    // 默认的安全描述符
        OPEN_EXISTING,           // 创建方式
        0,                       // 不需设置文件属性
        NULL);                   // 不需参照模板文件

    return hDevice;
}

// 打开端口驱动
BOOL OpenMyPort()
{
    BOOL bResult;

    // 设备名为"MyPort",驱动程序位于Windows的"system32\drivers"目录中
    bResult = StartDriver("system32\\drivers\\MyPort.sys", "MyPort");

    // 设备路径为"\\.\MyPort"
    if (bResult)
    {
        hMyPort = OpenDevice("\\\\.\\MyPort");
    }

    return (bResult && (hMyPort != INVALID_HANDLE_VALUE));
}

// 关闭端口驱动
BOOL CloseMyPort()
{
    return (CloseHandle(hMyPort) && StopDriver("MyPort"));
}

// 从指定端口读一个字节
// port: 端口
BYTE ReadPortByte(WORD port)
{
    DWORD buf[2];            // 输入输出缓冲区            
    DWORD dwOutBytes;        // IOCTL输出数据长度

    buf[0] = port;           // 第一个DWORD是端口
//  buf[1] = 0;              // 第二个DWORD是数据

    // 用IOCTL_MYPORT_READ_BYTE读端口
    ::DeviceIoControl(hMyPort,   // 设备句柄
        IOCTL_MYPORT_READ_BYTE,  // 取设备属性信息
        buf, sizeof(buf),        // 输入数据缓冲区
        buf, sizeof(buf),        // 输出数据缓冲区
        &dwOutBytes,             // 输出数据长度
        (LPOVERLAPPED)NULL);     // 用同步I/O    

    return (BYTE)buf[1];
}
// 将一个字节写到指定端口
// port: 端口
// data: 字节数据
void WritePortByte(WORD port, BYTE data)
{
    DWORD buf[2];            // 输入输出缓冲区            
    DWORD dwOutBytes;        // IOCTL输出数据长度

    buf[0] = port;           // 第一个DWORD是端口
    buf[1] = data;           // 第二个DWORD是数据

    // 用IOCTL_MYPORT_WRITE_BYTE写端口
    ::DeviceIoControl(hMyPort,   // 设备句柄
        IOCTL_MYPORT_WRITE_BYTE, // 取设备属性信息
        buf, sizeof(buf),        // 输入数据缓冲区
        buf, sizeof(buf),        // 输出数据缓冲区
        &dwOutBytes,             // 输出数据长度
        (LPOVERLAPPED)NULL);     // 用同步I/O
}

有了ReadPortByte和WritePortByte这两个函数,我们就能很容易地操纵CMOS和speaker了(关于CMOS值的含义以及定时器寄存器定义,请参考相应的硬件资料):

// 0x70是CMOS索引端口(只写)
// 0x71是CMOS数据端口
BYTE ReadCmos(BYTE index)
{
    BYTE data;

    ::WritePortByte(0x70, index);

    data = ::ReadPortByte(0x71);

    return data;
}

// 0x61是speaker控制端口
// 0x43是8253/8254定时器控制端口
// 0x42是8253/8254定时器通道2的端口
void Sound(DWORD freq)
{
    BYTE data;
    if ((freq >= 20) && (freq <= 20000))
    {
        freq = 1193181 / freq;

        data = ::ReadPortByte(0x61);

        if ((data & 3) == 0)
        {
            ::WritePortByte(0x61, data | 3);
            ::WritePortByte(0x43, 0xb6);
        }

        ::WritePortByte(0x42, (BYTE)(freq % 256));
        ::WritePortByte(0x42, (BYTE)(freq / 256));
    }
}

void NoSound(void)
{
    BYTE data;
    data = ::ReadPortByte(0x61);
    ::WritePortByte(0x61, data & 0xfc);
}


    // 以下读出CMOS 128个字节
    for (int i = 0; i < 128; i++)
    {
        BYTE data = ::ReadCmos(i);
        ... ...
    }

    // 以下用C调演奏“多-来-米”

    // 1 = 262 Hz
    ::Sound(262);
    ::Sleep(200);
    ::NoSound();

    // 2 = 288 Hz
    ::Sound(288);
    ::Sleep(200);
    ::NoSound();

    // 3 = 320 Hz
    ::Sound(320);
    ::Sleep(200);
    ::NoSound();

楼上的网友使用的是Yariv Kaplan写过一个WinIO的例子,能实现对物理端口和内存的访问,提供了DRV、DLL、APP三方面的源码,有兴趣的话可以深入研究一下。

9 楼

这个问题非常值得研究,我以前开发只能在98系统下,而且inportb() outportb()C++ Builder 5.5是不提供的!只有自己嵌入一段汇编实现,现在听君一席话,启发很大。可以把以前的软件移植到2000系统下了!建议斑竹开一个I/O读写在2000系统下实现的专题讲座!

10 楼

我也遇到同样的问题。你们是怎么解决的?

我来回复

您尚未登录,请登录后再回复。点此登录或注册