Windows 驱动程序的发展演变
我们在学习开发驱动程序时有必要弄清楚Windows设备驱动程序的发展演变过程(为了简便起见,以下简称驱动程序),以便明白我们将要开发什么样的驱动程序。这就象你开发一个应用程序时必须弄清楚它是运行在WINDOWS平台下还是在DOS平台下,否则我们能写出什么样的应用程序就可想而知了。
驱动程序开发者的各项任务之中,有许多是为特定的硬件编写驱动程序。由于WINDOWS的发展,这样的工作在 Windows 9X 下要比在前一版Windows(windows3.x 、Windows Workgroup) 中容易得多。先了解一些历史演变,可能会对驱动程序的编写有所帮助。
实模式Windows(Real-Mode Windows)
从一开始,MS-DOS 和系统基本输入输出系统(BIOS) 就已经提供了许多硬件设备的驱动程序。BIOS 通过一些常用的软件中断,开放出驱动程序的服务 ,像INT 10h 是显示系靳中断,INT 13h是磁盘子系靳中断,INT 16h 是键盘中断等等。BIOS 也处理硬件中断,并承担对“可编程中断控制器”(Programmable Interrupt Controller ,PIC )的管理任务。MS-DOS 也通过软件中断(如 INT 21h 、INT 25h 、INT 26h )提供了系统服务 ,并提供一个机制(CONFIG.SYS 中的 device= 语句),让新的或强化后的驱动程序能?蛟谙到y启动时被加载进操作系统内核。
标准模式Windows(Standard-Mode Windows)
早期的 Windows 中,MS-DOS 和 BIOS 是最重要的。Windows运行在实模式状态中,这时的Windows充其量不过是一个强化后的MS-DOS图形用户界面而已。从系统角度看,Windows只不过是个大的图形应用程序。Intel 80286 的出现,使 Windows能?蛟诒;つJ街性诵胁⒒竦酶叽? 16MB 实际内存空间。依靠保护模式和实模式的转换,Windows 仍然继续使用MS-DOS 和 BIOS 提供的服务来完成所有的系统需求。这种运作模式被称为 Windows标准模式(Windows standard mode) 。在 80286 机器上切换实模式和保护模式,系统开销很大。Intel 于是提供了一个快又有效率的指令,让你从实模式切换到保护模式。但Intel 认为没有什么人还需要再从保护模式切换回实模式。结果,唯一能?蛉帽;つJ匠绦颍ㄈ? Windows standard mode )存取实模式软件(如 MS-DOS )的方法就是复位CPU(reset CPU) 。在人们开发出来的各种复位方法中,最普遍的一种就是触发键盘控制器,提供由 Ctrl-Alt-Delete 键所发出的外部信号。于是引发所谓的三键失效(triple fault,即三键热启动),这是 CPU 先天无法处理的一种“失效“。事实上无论哪一种作法,代价都很昂贵,因为它们至少都得经过 BIOS 的引导程序 。事实上,在某些 286 机器,模式的切换要花掉好几毫秒。显然 Windows 需要一种方法,避免每次一有事件发生,像是键盘被按下或鼠标移动等等,就得切换到实模式。解?Q方法就是写一个保护模式驱动程序,可以在保护模式中处理 I/O 中断。这些驱动程序直到今天我们都还在使用,你在 SYSTEM 子目录中看到的扩展名为 .DRV 的文件都是!包括 MOUSE.DRV 、COMM.DRV 等等。我把它们称为 ring3 DLL 驱动程序,因为它们实质上都是 16 位 Windows 动态链接库(DLLs ),在 ring3层 (Intel CPU 最不受保护的层,一般应用程序运行在ring3层,核心态的驱动程序动行在ring0层)执行。它们的任务是在不离开 CPU保护模式的前提下,和 Windows KERNEL 、USER 、GDI 模块之间形成接口。
增强模式Windows(Enhanced-Mode Windows )
Intel 80386 CPU 使 Windows的第三种操作模式(所谓的 enhanced mode)成为可能。在此模式中 Windows 采用分页(paging) 和虚拟86(V86) 特性,创造出??拟机器(VirtualMachines ,VMs )。对一个应用程序而言,VM 就像一独立的的个人电脑,独自拥有自己的键盘、鼠标、显示器等等硬件。而实际上,经过所谓的??拟化(virtualization ),数个 VMs 共享相同硬件。对最终用户而言,最大的好处是他现在能?蛟诖翱谧刺?中(而非全屏幕)运行MS-DOS程序 。"??拟化"是 VxDs 的工作。VxD 的名称来自于 "virtual x device",意思是此驱动程序用来??拟化某个(x )设备。例如:VKD用来??拟化键盘,使Windows 和任何一个MS-DOS程序都自认为独立拥有属于自己的键盘。VMD 用来??拟化鼠标。某些 VxDs 并不是为了??拟化某些硬件,而是为了提供各种底层系统服务。页面交换(PAGESWAP) 和 页面文件(PAGEFILE)就属于这种非设备VxD ,它们共同管理交换文件(swap file ),使 增强模式Windows (enhanced-modeWindows) 得以将磁盘空间分配成为??拟内存的一部份。尽管基础技术令人耳目一新,但增强模式Windows (enhanced-mode Windows )还是继续在磁盘和文件 I/O 方面使用 MS-DOS 和 BIOS 。需要交换(swap )一个文件时,它还是把 CPU 切换到 V86 模式,让 MS-DOS 和 BIOS 来处理 I/O 操作。在保护模式、真实模式、V86 模式之间的所有切换动作都使得 Windows 慢下来。更多
的延时则来自于MS-DOS 和 BIOS 不可重入这一问题(即不能两个程序同时使用相同的服务)。Windows 必须强迫所有应用程序在同一个队列等待实模式服务。
Windows95
Windows 95 将终结这一份对历史的回忆。Windows 95 使用数种不同的驱动程序模型,大部份是使用 32 位 ring0层的虚拟设备驱动程序(VxDs) ,而非 rin3层的 DLLs 。所有的设备驱动程序都有一个具有管理功能的核心虚拟机VMM(虚拟机管理器)管理。
Windows对中断的处理与MS-DOS大不一样。当中断发生时,处理器转换为ring0级保护模式。Windows系统并不像MS-DOS那样通过中断描述符表IDT(Interrupt Descriptor Table)直接指向中断处理过程,而是由IDT入口指向VMM中的程序。该程序将判断是否为中断调用,如果是,则把中断控制权交给虚拟可编程中断控制器VPICD(Virtual Programmable Interrupt Controller Device),VPICD实际上是一个重要的VxD。VPICD再将其交给另一个注册了该中断的VxD(如Audcard.vxd)来处理。VxD程序是通过调用VPICD服务VPICD_Virtualize_IRQ来注册中断的。
Windows 95 对于设备 (device) 的处理,一般的模型是:由一个 VxD 掌管所有中断并执行所有数据传输,应用程序则使用函数调用 (function calls) 的方式对 VxDs 发出服务请求。这种VxD 为主的设备规划模型的一个好例子就是:Windows 95 的串行通信(serial communications) 。从前 Windows的串行通讯是使用一个 ring3 驱动程序(COMM.DRV ),?群?硬件中断处理程序以及驱动一个通用异步收发蕊片(universal asynchronous receiver-transmitter (UART )蕊片)所需的全部逻辑功能代码。在未让此驱动程序知道的情?r下,两个 VxDs (VCD 和COMBUFF )拦截了硬件中断和软件 IN/OUT 指令,为的是??拟化每一个 port ,并且改善因多任务而引起的问题。Windows 95 也有一个 ring3 组件名为 COMM.DRV ,但这个组件已经成为新的VxD (VCOMM )的一个简单的外层程序,只用来提供 16 位程序和 VCOMM之间的接口。VCOMM 则处于底层,联结一般应用程序、VxD clients 、 VxD 端口驱动程序和实际的硬件。端口驱动程序现在负责处理所有中断,并执行真正与硬件起作用的 IN/OUT 指令。
Windows 95 文件系统是另一个好例子。过去,对文件系统服务的请求(requests ),源自于16 位保护模式程序所发出的 INT 21h 。有一个 VxD 用来处理这些 INT 21h ,并将它们切换到 V86 模式,以便让MS-DOS 处理。MS-DOS 发出 INT 13h中断 ,以请求使用 BIOS 的磁盘 I/O 功能;发出 INT 2Fh ,允许网络的 "redirector modules"(重新定向模块)将此请求通过网络传输出去。Windows 95 提供给应用程序的,仍是向上兼容的接口,INT 21h 仍旧是导至文件系统的动作,但是底层基础却大不一样。
在 Windows 95 之中,一个名为“可安砚文件系统“(Installable File System ,IFS )的管理器会处理所有 INT 21h ,甚至是来自于 V86 模式的。然后它把控制权交斤一个文件系统驱动程序(File System Driver ,FSD )。有一个 FSD 名为 VFAT ,是针对 MS-DOS
文件系统(所谓 File Allocation Table ,FAT )而设计;另一个 FSD 名为 CDFSD ,可以解析 CD-ROM 格式;此外还有其他 FSDs ,知道如何经由各种网络彼此通讯。针对本机(local 端)FSD (如VFAT )的磁盘操作,会经过被I/O管理器(Input/Output Supervisor ,IOS)监视管理的一堆VxDs处理。甚至 V86 模式的 INT 13h 中断调用最终也是由 IOS 处理。换句??真,实模式和保护模式所发出的对文件系靳的请求(request ),不论是针对本地(local )或远程(remote )磁盘,有可能完全(或几乎完全)由 VxDs 来处理。Windows 95 这种以 VxD 为中心的驱动程序模型,好处之一是,系统程序员不一定要是 MS-DOS 和 BIOS 的专家,就可以写驱动程序。那些准备提供系统扩展组件的程序员,也同享这个好处;原本你必须了解DOS保护模式接口(DPMI)以及 Windows 核心模块的许多神秘特性或未公开特性,现在只需了解 Win32 的 DeviceIoControl API 函数,以及那些支持所谓 "alertable waits”(即时唤醒,大意是那些可以在VXD中调用的Windows 32位 API函数,但数量极其有限,)的 Win32 API 即可。这两个接口可以让你把 VxD 当做 32 位应用程序的扩展组件。尽管Windows系统驱动程序设计的任务主要是在系统底层上扩展 Windows 的功能,但Windows 95 还是保留了令人印象深刻的向上兼容能力(对上层程序,如dos程序来说,它们的调用接口没变,但底层实际操作却大不一样了)。DPMI 还是存在(有些16 位程序还是需要它),你还是可以运行实模式的网络驱动程序或文件系统驱动程序--如果这是你的必要选择。事实上,你往往可以把 Windows 3.1 的一整组硬件设备、网络驱动程序、16 位应用程序及其必要的 VxDs 整个搬到 Windows 95 ,不至于遭遇什么大问题。Windows98&2k&NT
1996年的Windows Hardware Engineering Conference(WinHEC)会议上,Microsoft宣布了一种新的Windows设备驱动程序模型――Win32 Driver Model(WDM)。这种新的设备驱动程序模型将成为Windows 2000(即Windows NT 5.0)的核心。
这个消息令从事Windows设备驱动程序(VxD)开发的程序员感到沮丧(虽然大家早已预料到Windows系列与Windows NT系列最终将走到一起)。WDM将vxd的开发人员带到了一个新的起点上,什么都是新的:新的模式,新的观点。如果你曾看过DDK的汇编代码的话,你一定可以体会这个消息对VxD开发者是个沉重的打击,而对于Windows NT设备驱动程序(Kernel Mode Driver)的开发者来说,却是另一番心情――因为WDM基本等于Kernel Mode Driver+Plug and Play。 VxD将让位于WDM,现在令我们欣慰的是Microsoft宣布Windows 98(Windows 98支持VxD,推荐使用WDM方式驱动,但有些设备,如打印机等还不能用它,微软预先设想的是Windows98和Windows 2k x86版在WDM驱动上可以二进制码兼容,但实际上没有完全实现)可能会坚持到200X年(天知道,估计也就是三两年)。在这期间,掌握VxD技术的你还是可以主动要求老板给你加薪的。即使到了WDM一统天下之时,也不用灰心,因为无论是VxD还是WDM,都要求开发人员对计算机硬件有着全面而细致的了解。通过VxD的锻炼,你至少熟悉了计算机的硬件资源并对保护模式有了比较深刻的认识,这些东西都是将来从事WDM开发的硬功夫。 好了,该说说Windows NT了。在Windows NT中,80386保护模式的“保护”比Windows 95中更坚固,这个“镀金的笼子”更加结实,更加难以打破。在Windows 95中,至少应用程序I/O操作是不受限制的,而在Windows NT中,我们的应用程序连这点权限都被剥夺了。在NT中几乎不太可能进入真正的ring0层。
在Windows NT中,存在三种Device Driver: 1.“Virtual device Driver” (VDD)。通过VDD,16位应用程序,如DOS 和Win16应用程序可以访问特定的I/O端口(注意,不是直接访问,而是要通过VDD来实现访问)。 2.“GDI Driver”,提供显示和打印所需的GDI函数。 3.“Kernel Mode Driver”,实现对特定硬件的操作,比如说CreateFile, CloseHandle (对于文件对象而言), ReadFile, WriteFile, DeviceIoControl 等操作。“Kernel Mode Driver”还是Windows NT中唯一可以对硬件中断和DMA进行操作的Driver。SCSI 小端口驱动和 网卡NDIS 驱动都是Kernel Mode Driver的一种特殊形式。
Visual studio11与Windows8带来格外不同的新体验
1.启动Vs11 ![]()
2.看见满目的驱动开发模板 ![]()
3.选择一个驱动模式,有内核模式与用户模式两种的驱动 ![]()
4.创建一个驱动程序,UMDF Driver2 ![]()
5.可以看见驱动工程与驱动安装工程
![]()
6.编译驱动 ![]()
7.选择继续调试 ![]()
8.选择用户模式进行调试 ![]()
9.设备代码描述如下 [cpp] view plaincopy
- /*++
-
- Module Name:
-
- Device.cpp
-
- Abstract:
-
- This module contains the implementation of the
- device driver.
-
- Environment:
-
- Windows User-Mode Driver Framework (WUDF)
-
- --*/
- #include "internal.h"
- #include "device.tmh"
-
- HRESULT
- CMyDevice::CreateInstanceAndInitialize(
- __in IWDFDriver *FxDriver,
- __in IWDFDeviceInitialize * FxDeviceInit,
- __out CMyDevice **Device
- )
- /*++
-
- Routine Description:
-
- This method creates and initializs an instance of the driver's
- device callback object.
-
- Arguments:
-
- FxDeviceInit - the settings for the device.
-
- Device - a location to store the referenced pointer to the device object.
-
- Return Value:
-
- Status
-
- --*/
- {
- //
- // Create a new instance of the device class
- //
- CComObject<CMyDevice> *pMyDevice = NULL;
- HRESULT hr = S_OK;
-
- TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
-
- hr = CComObject<CMyDevice>::CreateInstance( &pMyDevice );
- if (FAILED(hr))
- {
- TraceEvents(TRACE_LEVEL_ERROR,
- TRACE_DEVICE,
- "%!FUNC! Failed to create device instance %!hresult!",
- hr);
- goto Exit;
- }
-
- //
- // Initialize the instance. This calls the WUDF framework,
- // which keeps a reference to the device interface for the lifespan
- // of the device.
- //
- hr = pMyDevice->Initialize(FxDriver, FxDeviceInit);
- if (FAILED(hr))
- {
- TraceEvents(TRACE_LEVEL_ERROR,
- TRACE_DEVICE,
- "%!FUNC! Failed to initialize device %!hresult!",
- hr);
- goto Exit;
- }
-
- *Device = pMyDevice;
-
- Exit:
-
- TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");
-
- return hr;
- }
-
- HRESULT
- CMyDevice::Initialize(
- __in IWDFDriver * FxDriver,
- __in IWDFDeviceInitialize * FxDeviceInit
- )
- /*++
-
- Routine Description:
-
- This method initializes the device callback object and creates the
- partner device object.
-
- The method should perform any device-specific configuration that:
- * could fail (these can't be done in the constructor)
- * must be done before the partner object is created -or-
- * can be done after the partner object is created and which aren't
- influenced by any device-level parameters the parent (the driver
- in this case) might set.
-
- Arguments:
-
- FxDeviceInit - the settings for this device.
-
- Return Value:
-
- status.
-
- --*/
- {
- IWDFDevice *fxDevice = NULL;
- HRESULT hr = S_OK;
- IUnknown *unknown = NULL;
-
- TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
-
- //
- // Configure things like the locking model before we go to create our
- // partner device.
- //
-
- //
- // Set the locking model.
- //
-
- FxDeviceInit->SetLockingConstraint(None);
-
- //
- // Set power policy ownership.
- //
- FxDeviceInit->SetPowerPolicyOwnership(TRUE);
-
- //
- // Any per-device initialization which must be done before
- // creating the partner object.
- //
-
- //
- // Create a new FX device object and assign the new callback object to
- // handle any device level events that occur.
- //
-
- //
- // We pass an IUnknown reference to CreateDevice, which takes its own
- // reference if everything works.
- //
-
- hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
- if (FAILED(hr))
- {
- TraceEvents(TRACE_LEVEL_ERROR,
- TRACE_DEVICE,
- "%!FUNC! Failed to get IUnknown %!hresult!",
- hr);
- goto Exit;
- }
-
- hr = FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice);
- DriverSafeRelease(unknown);
- if (FAILED(hr))
- {
- TraceEvents(TRACE_LEVEL_ERROR,
- TRACE_DEVICE,
- "%!FUNC! Failed to create a framework device %!hresult!",
- hr);
- goto Exit;
- }
-
- //
- // Set our FxDevice member variable.
- //
- m_FxDevice = fxDevice;
-
- //
- // Drop the reference we got from CreateDevice. Since this object
- // is partnered with the framework object they have the same
- // lifespan - there is no need for an additional reference.
- //
-
- DriverSafeRelease(fxDevice);
-
- Exit:
-
- TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");
-
- return hr;
- }
-
- HRESULT
- CMyDevice::Configure(
- VOID
- )
- /*++
-
- Routine Description:
-
- This method is called after the device callback object has been initialized
- and returned to the driver. It would setup the device's queues and their
- corresponding callback objects.
-
- Arguments:
-
- FxDevice - the framework device object for which we're handling events.
-
- Return Value:
-
- status
-
- --*/
- {
-
- HRESULT hr = S_OK;
-
- TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
-
- //
- // Create and configure the I/O queue
- //
- hr = CMyIoQueue::CreateInstanceAndInitialize(m_FxDevice, this, &m_IoQueue);
- if (FAILED(hr))
- {
- TraceEvents(TRACE_LEVEL_ERROR,
- TRACE_DEVICE,
- "%!FUNC! Failed to create and initialize queue %!hresult!",
- hr);
- goto Exit;
- }
-
- hr = m_IoQueue->Configure();
- if (FAILED(hr))
- {
- TraceEvents(TRACE_LEVEL_ERROR,
- TRACE_DEVICE,
- "%!FUNC! Failed to configure queue %!hresult!",
- hr);
- goto Exit;
- }
-
- //
- // Create Device Interface
- //
- hr = m_FxDevice->CreateDeviceInterface(&GUID_DEVINTERFACE_UMDFDriverMVP,NULL);
- if (FAILED(hr))
- {
- TraceEvents(TRACE_LEVEL_ERROR,
- TRACE_DEVICE,
- "%!FUNC! Failed to create device interface %!hresult!",
- hr);
- goto Exit;
- }
-
- Exit:
-
- TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");
-
- return hr;
- }
-
- HRESULT
- CMyDevice::OnPrepareHardware(
- __in IWDFDevice * /* FxDevice */
- )
- /*++
-
- Routine Description:
-
- This routine is invoked to ready the driver
- to talk to hardware.
-
- Arguments:
-
- FxDevice : Pointer to the WDF device interface
-
- Return Value:
-
- HRESULT
-
- --*/
- {
- TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
-
- TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");
-
- return S_OK;
- }
-
- HRESULT
- CMyDevice::OnReleaseHardware(
- __in IWDFDevice * /* FxDevice */
- )
- /*++
-
- Routine Description:
-
- This routine is invoked when the device is being removed or stopped
- It releases all resources allocated for this device.
-
- Arguments:
-
- FxDevice - Pointer to the Device object.
-
- Return Value:
-
- HRESULT - Always succeeds.
-
- --*/
- {
- TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");
-
- TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");
-
- return S_OK;
- }
10.驱动描述代码如下 [cpp] view plaincopy
- /*++
-
- Module Name:
-
- Driver.cpp
-
- Abstract:
-
- This module contains the implementation of the
- core driver callback object.
-
- Environment:
-
- Windows User-Mode Driver Framework (WUDF)
-
- --*/
-
- #include "internal.h"
- #include "driver.tmh"
-
- HRESULT
- CMyDriver::OnDeviceAdd(
- __in IWDFDriver *FxWdfDriver,
- __in IWDFDeviceInitialize *FxDeviceInit
- )
- /*++
-
- Routine Description:
-
- The FX invokes this method when it wants to install our driver on a device
- stack. This method creates a device callback object, then calls the Fx
- to create an Fx device object and associate the new callback object with
- it.
-
- Arguments:
-
- FxWdfDriver - the Fx driver object.
-
- FxDeviceInit - the initialization information for the device.
-
- Return Value:
-
- status
-
- --*/
- {
- HRESULT hr = S_OK;
- CMyDevice *device = NULL;
-
- //
- // Create device callback object
- //
- hr = CMyDevice::CreateInstanceAndInitialize(FxWdfDriver,
- FxDeviceInit,
- &device);
-
- //
- // Call the device's construct method. This
- // allows the device to create any queues or other structures that it
- // needs now that the corresponding fx device object has been created.
- //
- if (SUCCEEDED(hr))
- {
- hr = device->Configure();
- }
-
- return hr;
- }
11.用户模式的Dl代码如下 [cpp] view plaincopy
- /*++
-
- Module Name:
-
- Dllsup.cpp
-
- Abstract:
-
- This module contains the implementation of the Driver DLL entry point.
-
- Environment:
-
- Windows User-Mode Driver Framework (WUDF)
-
- --*/
-
- #include "internal.h"
- #include "dllsup.tmh"
-
- //
- // This GUID must match the DriverCLSID value for the service binary in the INF.
- // For more information see http://msdn.microsoft.com/en-us/library/ff560526(VS.85).aspx
- // 650f6cd9-7f41-4e8c-b344-85385c57d632
- //
- const CLSID CLSID_Driver =
- {0x650f6cd9,0x7f41,0x4e8c,{0xb3,0x44,0x85,0x38,0x5c,0x57,0xd6,0x32}};
-
- HINSTANCE g_hInstance = NULL;
-
- class CMyDriverModule :
- public CAtlDllModuleT< CMyDriverModule >
- {
- };
-
- CMyDriverModule _AtlModule;
-
- //
- // DLL Entry Point
- //
-
- extern "C"
- BOOL
- WINAPI
- DllMain(
- HINSTANCE hInstance,
- DWORD dwReason,
- LPVOID lpReserved
- )
- {
- if (dwReason == DLL_PROCESS_ATTACH) {
- WPP_INIT_TRACING(MYDRIVER_TRACING_ID);
-
- g_hInstance = hInstance;
- DisableThreadLibraryCalls(hInstance);
-
- } else if (dwReason == DLL_PROCESS_DETACH) {
- WPP_CLEANUP();
- }
-
- return _AtlModule.DllMain(dwReason, lpReserved);
- }
-
-
- //
- // Returns a class factory to create an object of the requested type
- //
-
- STDAPI
- DllGetClassObject(
- __in REFCLSID rclsid,
- __in REFIID riid,
- __deref_out LPVOID FAR* ppv
- )
- {
- return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
- }
|