您正在查看: c++builder 分类下的文章

C++ Builder 研究--用游戏操纵杆摸拟鼠标

要编写一个支持游戏操纵杆的应用程序,首先必须要捕获游戏操纵杆,接着要处理Windows发送给程序窗口的操纵杆消息,最后使用完操纵杆后,还应将捕获的操纵杆资源释放。



  调用API函数joySetCapture能捕获游戏操纵杆。调用joySetCapture函数后,操纵杆产生的所有消息将会发送到指定的窗口。它的原型为:



MMRESULT joySetCapture(HWND hwnd, UINT uJoyID, UINT uPeriod, BOOL fChanged );



  其中,参数hwnd为接收操纵杆消息的窗口句柄;参数uJoyID为要捕获的操纵杆标识,它可以是JOYSTICKID1或是JOYSTICKID2,即第一、第二个游戏操纵杆;参数uPeriod为轮询的频率,单位为毫秒,它指定给应用程序发送有关操纵杆信息的间隔时间;参数fChanged为改变位置标识,可设为false。



  要释放操纵杆的捕获时,使用joyReleaseCapture函数。它只有一个参数,就是操纵杆的标识JOYSTICKID1或JOYSTICKID2。



  下面,就让我们用Borland C++ Builder 5.0来做一个用游戏操纵杆模拟鼠标的程序。

运行Borland C++ Builder 5.0,双击窗体Form1,在Form1的OnCreate事件中加入以下代码捕获一个游戏操纵杆:

void __fastcall TForm1::FormCreate(Tobject *Sender)

{

int JoyMsg;

//捕获游戏操纵杆

JoyMsg=joySetCapture(Handle,JOYSTICKID1,0,false);

if(JoyMsg==JOYERR_NOCANDO)

{

//捕获失败

ShowMessage("不能捕获游戏杆!");

}

else

{

if(JoyMsg==JOYERR_UNPLUGGED)

{

//没有连接

ShowMessage("游戏杆未与系统连接!");

}

else

{

if(JoyMsg==MMSYSERR_NODRIVER)
{
//没有安装
ShowMessage("系统没有安装游戏杆!");
}

else
{
//捕获成功
ShowMessage("捕获游戏杆成功!");
}

}

}



  在Form1的OnCloseQuery事件中加入代码,让程序关闭时释放操纵杆捕获的资源:

void __fastcall TForm1::FormCloseQuery(Tobject *Sender, bool &CanClose)

{

//释放操纵杆捕获

joyReleaseCapture(JOYSTICKID1);

}

捕获游戏操纵杆后,Windows会把所有的操纵杆消息发送给窗口Form1。当操纵杆的方向钮按被按下时,产生的是MM_JOY1MOVE消息,当功能按钮被按下时,产生MM_JOY1BUTTONDOWN消息。在程序中分别响应并处理这两个消息,就可以模拟鼠标的移动和点击。

  但是在C++ Builder中,这两条消息并不是标准的Windows消息,这就需要我们自已定义和处理消息了。在C++ Builder里响应自定义消息的步骤为:

  1.建立消息映射表

  2.声明消息处理函数

  3.编写消息处理函数



  首先在代码编辑窗口点击右键,选择弹出菜单的“Open Source/Header File”或是按热键Ctrl+F6,打开窗体Form1头文件“Uint1.h”。

  在窗体的TForm1类中的公有成员中加入代码来建立消息映射表,把消息的处理权交给自定义的消息处理函数:

public:

BEGIN_MESSAGE_MAP

MESSAGE_HANDLER(MM_JOY1BUTTONDOWN,Tmessage,OnJoyDown)

MESSAGE_HANDLER(MM_JOY1MOVE,Tmessage,OnJoyMove)

END_MESSAGE_MAP(Tform)



  然后在类的私有成员中加入代码声明消息处理函数:

private:

void __fastcall OnJoyDown(Tmessage &Message);

void __fastcall OnJoyMove(Tmessage &Message);



  最后,按Ctrl+F6键切换回“Uint1.cpp”的编辑窗口,在末尾空白处添加下面两个自定义的消息响应函数:



//自定义的MM_JOY1BUTTONDOWN消息响应函数OnJoyDown

void __fastcall TForm1::OnJoyDown(Tmessage &Message)

{

if(Message.Wparam & JOY_BUTTON1)

{

//模拟鼠标左键按下

mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);

Caption="左键按下";

}

if(Message.Wparam & JOY_BUTTON2)

{

//模拟鼠标右键按下

mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0);

Caption="右键按下";

}

if(Message.Wparam & JOY_BUTTON3)

{

//模拟鼠标左键抬起

mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);

Caption="左键抬起";

}

if(Message.Wparam & JOY_BUTTON4)

{

//模拟鼠标右键抬起

mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0);

Caption="右键抬起";

}

//继续传递消息

Tform::Dispatch(&Message);

}



//自定义的MM_JOY1MOVE消息响应函数OnJoyDown

void __fastcall TForm1::OnJoyMove(Tmessage &Message)

{

int x,y;

POINT pt;

//取得鼠标当前坐标

GetCursorPos(&pt);

x=LOWORD(Message.Lparam);

y=HIWORD(Message.Lparam);

if(x!=32678)

{

if(x)
{
//向右
pt.x+=10;
}

else
{
//向左
pt.x-=10;
}

}

if(y!=32678)

{

if(y)
{
//向下
pt.y+=10;
}

else
{
//向上
pt.y-=10;
}

}

//设置鼠标坐标

SetCursorPos(pt.x,pt.y);

//继续传递消息

Tform::Dispatch(&Message);

}



  注意:调试运行这个程序,系统必须要安装有游戏操纵杆。自定义的消息处理函数末尾最好加一句 TForm1::Dispatch(&Message),这条语句的作用是让消息继续传递下去。Windows是使用用消息处理机制的,如果没有这一句语句,消息将完全被拦截,Windows程序可能由于得不到消息而无法实现正常的功能。




C++ Builder 研究--一种计算CD标识的算法



北 京 市167 信 箱(100036) 王 亚 军



---- 本 文 介 绍 了 关 于 音 乐CD 的 红 皮 书 格 式。 文 章 从 如 何 根 据 数 据 格 式, 调 用Windows MCI 接 口 函 数, 利 用 一 定 算 法 计 算CD 之ID 号, 用 来 唯 一 标 识CD 等 方 面 阐 述 和 说 明, 具 有 一 定 的 应 用 价 值。



一、 红 皮 书 格 式

----在 光 盘 格 式 家 族 中, 大 家 较 熟 悉 的 有 红 皮 书、 黄 皮 书、 白 皮 书 以 及 桔 皮 书 等 多 种 标 准。 这 些 格 式 有 个 共 同 的 特 点, 就 是 光 盘 最 小 分 配 单 位 是 长 度 为2352 个 字 节 的 扇 区; 当 然, 并 不 是 所 有 的2352 个 字 节 都 可 为 用 户 所 用, 这 要 看 具 体 是 哪 个 标 准。 例 如, 黄 皮 书 格 式 中2352 个 字 节 要 扣 除 一 些 检 错 和 纠 错 的 开 销, 只 有2048 个 字 节 是 真 正 存 放 用 户 数 据 的。



----红 皮 书 格 式 是1980 年PHILIP 和SONY 联 合 颁 布 的, 又 称CD -DA 格 式, 是 为 控 制 音 频CD 建 立 的 标 准。 它 是 国 际 标 准ISO10149。 红 皮 书 标 准 规 范 了 光 盘 尺 寸、 塑 料 的 光 参 数、 录 放 环 境、 信 号 测 量、 保 真 度 以 及 音 乐 波 形 数 字 文 件 结 构。 红 皮 书 音 频 的 数 字 化 方 式 用44.1kHz 的 采 样 频 率、16 级 量 化 电 平 而 且 是 双 通 道 记 录 和 线 形 格 式。 现 已 成 为 音 乐 工 业 中 的 事 实 标 准。 主 要 有 以 下 内 容:



最 多99 首 歌 曲(track, 即 音 轨);

每 扇 区2352 字 节 均 用 来 存 放 声 音 数 据;

声 音 数 据 为 采 样 率44.1K、16 比 特 量 化 的PCM 信 号;

立 体 声;

无 目 录 和 文 件 结 构, 连 续 存 放PCM 信 号;

播 放 时 按 每 秒75 扇 区 的 速 度;

每 张 光 盘 最 多74 分 钟。

----音 频 数 据 不 像 其 他 计 算 机 数 据( 如 程 序 文 件) 那 样 对 错 误 具 有 极 大 的 敏 感 性( 一 般 对 误 码 具 敏 感 性 的 数 据 不 能 容 忍1 比 特 的 错 误), 因 此 每 扇 区 的2352 个 字 节 无 需 额 外 的 检 错 纠 错 的 开 销, 均 可 用 来 存 放 音 频 数 据。 这 样, 每 个 扇 区 能 存 放 的 音 乐 时 间 为2352/(16/8 ×2 ×44100)=1/75 秒, 因 此 播 放 速 度 为75 扇 区 / 秒。 扇 区 地 址 与 播 放 时 间 具 有 一 一 对 应 的 关 系; 在CD -DA 格 式 中, 用 时 间MSF 格 式 表 示, 标 记 为 00:00:00 格 式:

----M: 分 钟 数, 以 一 个 字 节 表 示;

----S: 秒 数, 范 围0 ~59, 以 一 个 字 节 表 示;

----F: 扇 区 数, 范 围0 ~74, 以 一 个 字 节 表 示, 代 表 有 多 少 个1/75 秒。



----在ISO 9660 标 准 下, 单 张 的 CD -ROM 被 定 义 为 一 个" 卷", 前150 个 扇 区 用 于 保 存 各 个 音 轨 的 起 始 时 间 以 及 光 盘 本 身 的 卷 标、 版 权 等 信 息。 所 以, 从MSF 到 绝 对 扇 区 地 址 的 转 换 关 系 为:



---- Address = M * 60 * 75 + S * 75 + F + 150 ;



----本 人 在 开 发 一 个 个 人CD 收 藏 数 据 库 时 发 现, 各 个CD 出 版 公 司 都 有 自 己 的CD 编 号 规 范, 无 法 统 一, 如: DG 437 667 -2。 因 此, 有 必 要 采 用 一 种 算 法 唯 一 标 识 单 张CD。 基 于 以 上 分 析, 采 用 如 下 算 法, 事 实 证 明 是 可 行 的。



二、 算 法 说 明( 程 序 框 架)

struct toc {

int min;

int sec;

int frame;

};// 单 个 音 轨 内 容 表



struct toc cdtoc[100];

// 结 构 数 组, 用 来 存 放 各 个 音 轨 的 内 容



int read_cdtoc_from_drive(void)

{

/ * 将CD 的 内 容 表 读 入cdtoc[] 结 构 数 组 */

return (tot_trks);

}



int cddb_sum(int n)

{

int ret;

/ * 将 传 入 参 数 的 每 位 数 据 累 加 */

ret = 0;

while (n > 0) {
ret = ret + (n % 10);
n = n / 10;

}

return (ret);

}

unsigned long cddb_discid(int tot_trks)

{

int i, t = 0, n = 0;

i = 0;

while (i



三、 使 用

----假 设 你 的 编 译 器 支 持32 位 整 数, cddb_discid 函 数 计 算discid, 基 于CD 的 内 容 表, 按 照MSF 格 式, 故 意 忽 略 了 扇 区 数, 函 数 接 收tot_trks 参 数(CD 的 音 轨 总 数), 返 回discid 这 个 整 数。 假 设cdtoc[] 是 一 个 结 构 数 组, 每 个 结 构 包 含 分 钟 数、 秒 数 和 扇 区 数 三 个 域, 它 们 实 际 上 是 每 个 音 轨 的 分 钟 数、 秒 数 和 扇 区 数 的 偏 移 量。 这 些 信 息 从CD 的 内 容 表 中 读 出, 数 组 中 实 际 上 有tot_trks + 1 个 元 素, 最 后 一 个 是 个 虚 拟 音 轨, 即 最 外 一 个 音 轨 末 端 的 偏 移 量, 或 称 为 顶 头 音 轨( 也 称 音 轨0xAA), 函 数 循 环 检 测 内 容 表 中 的 每 个 音 轨, 计 算 每 个 音 轨 的(M * 60) + S( 按 秒 计 算 偏 移 量), 并 把 结 果 给cddb_sum() 函 数, 该 函 数 把 数 据 中 的 每 个 数 字 累 加, 每 个 音 轨 的 结 果 保 存 在 变 量n 中。



----循 环 结 束 时



用 顶 头 音 轨 的 偏 移 量(M *60 +S) 减 去 第 一 个 音 轨 的 偏 移 量 (M *60 +S), 按 秒 计 算disk 的 长 度。

用n 对0xff 取 余, 将 结 果 左 移24 位。

把t 左 移8 位。

----把2、3 所 得 结 果 连 同tot_trks 进 行 或 运 算, 结 果 便 是diskid。



----为 了 建 库 方 便,diskid 用16 进 制 代 表, 如 果 少 于8 位 字 符 串, 前 面 补0,( 如3a8f07 变 为003a8f07), 为 方 便 起 见, 其 中 的 字 母 用 小 写。



----使 用MS -Windows MCI 接 口 的 用 户 应 注 意:



----Windows MCI 接 口 不 提 供 顶 头 音 轨 的MSF 位 置, 因 此, 你 必 须 通 过 计 算 最 后 一 个 音 轨 的 起 始 位 置 加 上 最 后 一 个 音 轨 的 长 度 来 得 到 它。 然 而,MCI 接 口 返 回 最 后 一 个 音 轨 的 长 度 比CD 的Toc 中 所 存 的 长 度 少 一 桢, 大 多 数 情 况 下, 这 不 影 响 产 生 的ID, 因 为, 计 算disk ID 时, 总 要 截 掉 桢 数。 然 而, 如 果 顶 头 音 轨 的 桢 数 为0, 秒 数 会 减 一, 桢 数 成 为74, 例 如: 一 张CD 最 后 一 个 音 轨 在 偏 移 量48m 32s 12f 处, 该 音 轨 长 度 为2m 50s 63f, 顶 头 偏 移 量51m 23s 0f, Windows MCI 错 误 报 告 长 度 为2m 50s 62f, 这 会 产 生51m 22s 74f 的 顶 头 偏 移 量, 这 样 导 致 长 度 被 截 断 一 秒, 从 而 使 得disk ID 不 正 确。 这 样, 当 你 计 算 顶 头 位 置 的 时 候, 在 最 后 一 个 音 轨 的 长 度 上 加 上 一 桢。



----对 于Windows 客 户 来 说, 按 照MSF 格 式 计 算 顶 头 的 最 简 便 的 方 法 是:

(offset_minutes * 60 * 75) +(offset_seconds * 75) +offset_frames + (length_minutes * 60 * 75) +(length_seconds * 75) + length_frames +1=X

X 是 按 桢 计 算 的 顶 头 偏 移 量, 若 按 秒 计 算, 只 需 简 单 地 用75 除, 消 掉 余 数 即 可。

C++ Builder 研究--用CB动态改变显示器分辨率

void __fastcall TForm1::btnGetClick(TObject *Sender)

{

int x,y;

x = GetSystemMetrics(SM_CXSCREEN);

y = GetSystemMetrics(SM_CYSCREEN);

ShowMessage("显示器水平分辨率:" + AnsiString(x) + "\n\n显示器垂直分辨率:" + AnsiString(y));



}

//---------------------------------------------------------------------------

void __fastcall TForm1::DynamicResolution(int x,int y)

{

TDeviceMode lpDevMode;

bool Result;



Result = EnumDisplaySettings(NULL,0,&lpDevMode);

if (Result)

{
lpDevMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
lpDevMode.dmPelsWidth = x;
lpDevMode.dmPelsHeight = y;
Result = ChangeDisplaySettings(&lpDevMode,0);
// = DISP_CHANGE_SUCCESSFUL;

}

}

void __fastcall TForm1::BitBtn1Click(TObject *Sender)

{

DynamicResolution(800,600);

}

//---------------------------------------------------------------------------



void __fastcall TForm1::BitBtn2Click(TObject *Sender)

{

DynamicResolution(1024,768);

}

C++ Builder 研究--通过端口获取IDE硬盘ID

#include

#include

#include

#include

#include

char GetAscii(unsigned int inData[], int offStart, int offEnd);

int main(void)

{

unsigned int diskData[256]; /
Disk data /

unsigned int offset; /
Disk data offset /

int loop;

int numDrv; /
Number of IDE hard drives /

union REGSregisters;

unsigned int biosCyl[2]; /
Cylinders, Heads, Sectors /

unsigned int biosHead[2];

unsigned int biosSec [2];

numDrv = peekb(0x40, 0x75); /
BIOS Data area, Number of Hard disks /

for (loop = 0; loop < numDrv; loop++)

{
while (inp(0x01f7) != 0x50); /
Wait for controller not busy /
outp(0x01f6, (loop == 0 ? 0xa0 : 0xb0)); /
Get first/second drive /
outp(0x01f7, 0xec); /
Get drive info data /
while (inp(0x1f7) != 0x58); /
Wait for data ready /
for (offset = 0; offset != 256; offset++) /
Read "sector" /
diskData[offset] = inpw(0x1f0);
/
Get BIOS drive info /
registers.h.ah = 0x08; /
Get drive info /
registers.h.dl = 0x80 + loop; /
Drive is 80H for Disk 0, 81H for Disk 1 /
int86(0x13, ?isters, ?isters);
if (!registers.x.cflag) /
All OK if carry not set /
{
biosHead[loop] = registers.h.dh + 1; /
Heads are from 0 /
biosSec[loop] = registers.h.cl & 0x3f; /
sec is bits 5 - 0 /
/
+1 because starts from 0 and +1 for FDISK leaving one out /
biosCyl[loop] = ((registers.h.cl & 0xc0) << 2) + registers.h.ch + 2;
} /
end of if /
printf("DRIVE %d:\n", loop);
printf("Model Number______________________: %s\n", GetAscii(diskData, 27, 46));
printf("Serial Number_____________________: %s\n", GetAscii(diskData, 10, 19));
printf("Controller Revision Number________: %s\n\n", GetAscii(diskData, 23, 26));
printf("Able to do Double Word Transfer___: %6s\n", (diskData[48] == 0 ? "No" : "Yes"));
printf("Controller type___________________: %04X\n", diskData[20]);
printf("Controller buffer size (bytes)____: %6u\n", diskData[21] * 512);
printf("Number of ECC bytes transferred___: %6u\n", diskData[22]);
printf("Number of sectors per interrupt___: %6u\n\n", diskData[47]);
printf("Hard Disk Reports\n");
printf("Number of Cylinders (Fixed)_______: %6u\n", diskData[1]);
printf("Number of Heads___________________: %6u\n", diskData[3]);
printf("Number of Sectors per Track_______: %6u\n\n", diskData[6]);
printf("BIOS Reports\n");
printf("Number of Cylinders_______________: %6u\n", biosCyl[loop]);
printf("Number of Heads___________________: %6u\n", biosHead[loop]);
printf("Number of Sectors per Track_______: %6u\n\n", biosSec[loop]);
printf("Press any key to continue...\n\n");
getch();

} /
end of for /

return 0;

} /
main() */

char GetAscii(unsigned int inData[], int offStart, int offEnd)

{

static char retVal[255];

int loop, loop1;

for (loop = offStart, loop1 = 0; loop <= offEnd; loop++)

{
retVal[loop1++] = (char )(inData[loop] / 256); /
Get High byte /
retVal[loop1++] = (char )(inData[loop] % 256); /
Get Low byte /

} /
end of for /

retVal[loop1] = '\0'; /
Make sure it ends in a NULL character /

return retVal;

} /
GetAscii() */

C++ Builder 研究--虚拟设备驱动程序的设计与实现

由于Windows对系统底层操作采取了屏蔽的策略,因而对用户而言,系统变得更为安全,但这却给众多的硬件或者系统软件开发人员带来了不小的困难,因为只要应用中涉及到底层的操作,开发人员就不得不深入到Windows的内核去编写属于系统级的虚拟设备驱动程序。Win 98与Win 95设备驱动程序的机理不尽相同,Win 98不仅支持与Windows NT 5.0兼容的WDM(win32 Driver Mode)模式驱动程序,而且还支持与Win 95兼容的虚拟设备驱动程序VxD(Virtual Device Driver)。下面介绍了基于Windows 9x平台的虚拟环境、虚拟设备驱动程序VxD的基本原理和设计方法,并结合开发工具VToolsD给出了一个为可视电话音频卡配套的虚拟设备驱动程序VxD的设计实例。

  1.Windows 9x的虚拟环境

  Windows 9x作为一个完整的32位多任务操作系统,它不像Window 3.x那样依赖于MS-DOS,但为了保证软件的兼容性,Windows 9x除了支持Win16应用程序和win32应用程序之外,还得支持MS-DOS应用程序的运行。Windows 9x是通过虚拟机VM(Virtual Machine)环境来确保其兼容和多任务特性的。

  所谓Windows虚拟机(通常简称为Windows VM)就是指执行应用程序的虚拟环境,它包括MS-DOS VM和System VM两种虚拟机环境。在每一个MS-DOS VM中都只运行一个MS-DOS进程,而System VM能为所有的Windows应用程序和动态链接库DLL(Dynamic Link Libraries)提供运行环境。每个虚拟机都有独立的地址空间、寄存器状态、堆栈、局部描述符表、中断表状态和执行优先权。虽然Win16、Win32应用程序都运行在System VM环境下,但Win16应用程序共享同一地址空间,而Win32应用程序却有自己独立的地址空间。

  在编写应用程序时,编程人员经常忽略虚拟环境和实环境之间的差异,一般认为虚拟环境也就是实环境。但是,在编写虚拟设备驱动程序VxD时却不能这样做,因为VxD的工作是向应用程序代码提供一个与硬件接口的环境,为每一个客户虚拟机管理虚设备的状态,透明地仲裁多个应用程序,同时对底层硬件进行访问。这就是所谓虚拟化的概念。

  VxD在虚拟机管理器VMM(Virtual Machine Manager)的监控下运行,而VMM实际上是一个特殊的VxD。VMM执行与系统资源有关的工作,提供虚拟机环境(能产生、调度、卸载VM)、负责调度多线程占先时间片及管理虚拟内存等工作。VxD与VMM运行在其他任何虚拟机之外,VxD事实上就是实现虚拟机的软件的一部分。

  与大多数操作系统一样,Windows也是采用层次式体系结构。VMM和VxDs构成了Win 95的ring0级的系统核心(应用程序运行在ring3级,ring1、ring2级未被使用),具有系统的最高优先权。Windows还提供一些以"drv"为后缀名的驱动程序,主要是指串行口的通信程序和并行口的打印机程序。这些程序与VxD不同,它们是运行在ring3级上的。

  2.深入理解VMM和VxD

  如前所述,VxD是Virtual Device Driver的缩写,但有人将它理解为虚拟任何驱动程序。实际上,VxD并非仅指那些虚拟化的某一具体硬件的设备驱动程序。比如某些VxD能够虚拟化设备,而某些VxD作为设备驱动程序却并不虚拟化设备,还有些VxD与设备并没有什么关系,它仅向其他的VxD或是应用程序提供服务。

  VxD可以随VMM一起静态加载,也可以根据需要动态加载或卸载。正是由于VxD与VMM之间的紧密协作,才使得VxD具有了应用程序所不具备的能力,诸如可以不受限制地访问硬件设备、任意查看操作系统数据结构(如描述符表、页表等)、访问任何内存区域、捕获软件中断、捕获I/O端口操作和内存访问等,甚至还可以截取硬件中断。

  尽管VxD使用32位平面存储模式(flat memory model),但它的代码和数据仍使用分段管理,段有六种类型,即实模式初始化、保护模式初始化、可分页、不可分页、静态和只调试(debug only),每种类型又有代码段和数据段之分,所以VxD共有12个段。实模式代码段和数据段为16位(分段模式),其他段则是32位(平面模式)。“实模式初始化”段包含了在Windows初始化过程的最初阶段VMM变为保护模式之前要执行的代码。静态加载的VxD此时可以查看Windows启动前的实模式环境,决定是否继续加载,并通知VMM。加载完毕后,VMM进入保护模式并执行保护模式初始化代码,同样将执行结果再通知VMM。初始化完成后,“实模式初始化”段和“保护模式初始化”段即被遗弃。VxD的大部分代码都在其他的某一段中,“可分页”段允许虚拟存储管理器(Virtual Memory Manager)进行分页管理,大多数的VxD代码都应当在“可分页”段。“不可分页”段的内容主要包括:VxD的主入口点、硬件中断处理函数、所访问的数据以及能被另一个VxD中断处理函数调用的异步服务。“静态”段仅用于可以动态加载的VxD,当VxD卸载后,静态代码段和数据段都保留在内存中。“只调试”段只是VMM在Soft-ICE for Win 95等调试环境下才将其载入。

  VMM是通过VxD的设备描述符块DDB(Device Descriptor Block)来识别的。DDB向VMM提供了VxD的主入口点,还向应用程序和其他的VxD提供了入口点。VMM利用这个主入口点将VM及Windows自身的状态通知给VxD,然后VxD通过相应的工作来响应这些事件。由于VxD不仅仅服务于一个物理设备(比如多个串口)或仅与一个VM发生联系,所以VxD需要产生自己支持的数据结构(Supporting Data Structures)来保存每一个设备、每一个VM的配置和状态信息。VxD用一个或多个设备上下文结构来保存设备信息,如I/O端口基地址、中断向量等,VxD将自己的每个VM的状态信息保存在VMM的VM控制块中。

  VMM提供的服务包括:事件服务、内存管理服务、兼容执行和保护模式执行的服务、登录表服务、调度程序服务、同步服务、调试服务、I/O捕获服务、处理错误和中断服务、VM中断和回调服务、配置管理程序服务以及其他杂项服务。

  以上内容仅涉及到VxD设计的一小部分,作为VxD的开发人员必须掌握更多的知识。首先是操作系统的知识,如地址空间、执行上下文、资源加锁、进程间通信和异步事件处理等方面的知识;其次,对Intel处理器应有较深入的理解,包括寄存器、机器指令集、保护机制、分页机制,以及虚拟8086模式;最后,还必须熟悉VMM提供的各类服务和接口,熟悉Windows其他的系统VxD。

  3.开发工具VToolsD简介

  VToolsD是专门用于开发VxD程序的一种工具软件,它包括VxD框架代码生成器QuickVxD、C运行库、VMM/VxD服务库、VxD的C++类库、VxDLoad和VxDView等实用工具以及大量的C、C++例程。由vc++、BC++的32位编译器编译生成的VxD程序可以脱离VToolsD环境运行。

  利用QuickVxD可以方便、快捷地生成VxD的框架,即生成后缀名为h、cpp和mak的三个文件。源文件包含了运行VxD的基本组件,其中包含控制消息处理、API入口点、以及VxD服务等函数框架,并且还定义了标志,设置了编译参数,声明了类,然后在C++环境下,向生成的各个处理函数体内添加自己的代码,最后使用编译器NMAKE生成标准的VxD程序。

  由于VxD运行在ring0级,所以调试程序相当困难。我使用的调试工具是Soft-ICE for Win 95。

  目前VToolsD的最新版本为3.0,它支持设备访问体系结构DAA(Device Access Architecture),所编写的程序代码将可以在所有Windows平台(包括Win 95、Win 98以及Windows NT)上共享。当然也可以使用Microsoft公司的DDK(Device Developer Kit)来开发VxD,但DDK不能像VToolsD那样通过屏蔽系统及VxD的底层技术细节提供丰富的C运行库和C++类库,而是让开发人员充分享用面向对象编程方法的方便与快捷,因此仅就该点而言,使用DDK是不方便的。

  4.VxD程序设计实例

  我在开发可视电话音频卡的设计过程中,用VToolsD 2.03、vc++ 5.0为自制的PC/XT总线扩展卡开发了虚拟设备驱动程序Audcard.vxd。该卡每20ms申请一次中断,中断由应用程序动态载入系统的Audcard.vxd响应并加以处理。中断服务程序ISR(Interrupt Service Routine)结束后,调用函数shell_PostMessage( )向应用程序窗口发送自定义消息。应用程序接受消息后,再通过函数DeviceIoControl( )与VxD的接口函数OnW32DeviceIoControl( )互传缓冲区数据。程序结束即可动态卸载VxD。

  当中断发生时,处理器转换为ring0级保护模式。Windows系统并不像DOS那样通过中断描述符表IDT(Interrupt Descriptor Table)直接指向中断处理过程,而是由IDT入口指向VMM中的程序。该程序将判断是否为中断调用,如果是,则把中断控制权交给虚拟可编程中断控制器VPICD(Virtual Programmable Interrupt Controller Device),VPICD实际上是一个重要的VxD。VPICD再将其交给另一个注册了该中断的VxD(如Audcard.vxd)来处理。VxD程序是通过调用VPICD服务VPICD_Virtualize_IRQ来注册中断的。

  虚拟设备驱动程序Audcard.vxd的部分源代码Audcard.h和Audcard.cpp在网上,网址为:www.pccomputing.com.cn。此应用程序使用了下列函数:CreateFile()动态加载VxD、CloseHandle()并动态卸载VxD、PreTranslateMessage()截获消息、DeviceIoControl()与VxD互传缓冲区数据。虚拟设备驱动程序Audcard.vxd经调试后工作正常,未发生过任何丢失数据或死机的现象。

  下面是虚拟设备驱动程序Audcard.vxd的部分源代码Audcard.h和Audcard.cpp,限于篇幅,由QuickVxD自动生成的Audcard.mak未列出。

  ①Audcard.h

  //AUDCARD.h - include file for VxD AUDCARD

  #include

  #define DEVICE_CLASS AudcardDevice

  #define AUDCARD_DeviceID UNDEFINED_DEVICE_ID

  #define AUDCARD_Init_Order UNDEFINED_INIT_ORDER#define AUDCARD_Major

  #define AUDCARD_Minor 0

  #define MY_IRQ 5 //定义5号中断

  class MyHwInt:public VHardwareInt

  {

  public:

   MyHwInt():VHardwareInt(MY_IRQ,0,0,0){}

   virtual VOID OnHardwareInt(VMHANDLE);

  };

  class AudcardDevice : public Vdevice

  {

  public:

   virtual BOOL OnSysDynamicDeviceInit();

   virtual BOOL OnSysDynamicDeviceExit();

   virtual DWORD OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams);

   MyHwInt* pMyIRQ;

  };

  class AudcardVM : public VVirtualMachine

  {

  public:

   AudcardVM(VMHANDLE hVM);

  };

  class AudcardThread : public Vthread

  {

   public:

   AudcardThread(THREADHANDLE hThread);

  };

  

  ②Audcard.cpp

  //AUDCARD.cpp - main module for VxD AUDCARD

  #define DEVICE_MAIN

  #include "audcard.h"

  Declare_Virtual_Device(AUDCARD)

  #define WM_USER_POSTVXD 0x1000

  //自定义消息

  #undef DEVICE_MAIN

  AudcardVM::AudcardVM(VMHANDLE hVM) : VVirtualMachine(hVM) {}

  AudcardThread::AudcardThread(THREADHANDLE hThread) : Vthread(hThread) {}

  BOOL AudcardDevice::OnSysDynamicDeviceInit() //动态加载时初始化

  {

   ……//硬件初始化

   pMyIRQ=new MyHwInt();

  if(pMyIRQ&&pMyIRQ->hook()) //挂接中断

  {

   pMyIRQ->physicalUnmask(); //允许中断

   return TRUE;

   }

   else return FALSE;

  }

  BOOL AudcardDevice::OnSysDynamicDeviceExit()

  //动态卸载过程

  {

   delete pMyIRQ;

   return TRUE;

  }

  DWORD AudcardDevice::OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams)

  //与Win32应用程序的接口函数

  {

  ……

  }

  VOID MyHwInt::OnHardwareInt(VMHANDLE hVM)

  {

   …… // 中断处理

   shell_PostMessage(AppWnd,WM_USER_POSTVXD ,0,0,0,NULL);

   //向应用程序窗口发送消息

   sendPhysicalEOI(); //通知VPICD中断结束

  }