VAC sources =========== Contents ======== This source package contains a Virtual Audio Cable 3.0x software source code. It includes the kernel-mode driver, user-mode wave driver and a control panel application. The Audio Repeater and Setup applications containing in the VAC binary distribution package are not included into this source package because they are not parts of the VAC project. Audio Repeater is an independent audio transfer utility and Setup is an universal installer shared by several different packages. Structure ========= There are five different projects sharing almost the same files: KMD9x - 32-bit kernel mode driver for Win98/ME (VxD) UMD9x - 16-bit user mode driver for Win98/ME (DRV) KMDNT - kernel mode driver for WinNT/2000/XP (SYS) UMDNT - user mode driver for WinNT/2000/XP (DLL) CtlPan - Win32 control panel application KMDxx projects contain: kmdriver.cpp - main kernel mode driver module kmutils.cpp - kernel mode platform-independent helper utilities unidrv.cpp - unified sound driver module wave.cpp - wave buffer and client support cable.cpp - cable manipulation KMD9x additionally contain: dsound.cpp - DirectSound interface vxd.cpp - 98/ME kernel interface KMDNT additionally contain: sys.cpp - NT kernel interface UMDxx projects contain: umdriver.cpp - main user mode driver module umutils.cpp - user mode platform-independent helper utilities UMDNT additionally contain: umdntutl.cpp - NT-specific user-mode routines All projects additionally contain: utils.cpp - mode/platform-independent helper utilities Compilation/linking notes ========================= Because the WinNT kernel-mode RTL, NTOSKRNL.LIB, is generally compiled with "stdcall" calling conventions, sys.cpp file must be compiled with "stdcall" option (/Gz for MS VC++). All interface functions exported from and imported by sys.cpp must have _cdecl modifier. 32-bit projects are tuned for MS VC++ 6.0. 16-bit project is tuned for MS VC++ 1.52. All projects are united to a single VC++ Win32 workspace (.dsw) as a subprojects. VAC project don't use any MS DDK build techniques, it must be build using standard VC++ 6.0 and 1.52 tools. 16-bit projects are build using make16.bat and build16.mak files. Make16.bat calls GNU Make utility (gmake.exe) version 3.80. 32-bit compiler include directory order: "inc" directory of DirectX 5 DDK "mmedia\inc" directory of Win 95 DDK "inc32" directory of Win 95 DDK "inc" directory of Win NT DDK "src\mmedia\inc" directory of Win NT DDK 16-bit compiler include directory order: "mmedia\inc" directory of Win 95 DDK "inc16" directory of Win 95 DDK 32-bit compiler must be supplied with the library paths to Win 95 DDK "lib" directory and to Win NT DDK "lib\free" directory. Entry point functions must be defined in linker settings for NT projects: KMDNT - DrvEntry UMDNT - DllEntryPoint The "/subsystem:native" linker option is used to build the NT kernel mode driver properly. Additional software components ============================== The addpack.zip archive contains common utility files that are used in different projects. These files must be placed into directory which is included into executable, include and library paths. The addpack.zip also contains a "UniLib" object library files. UniLib is a small universal utility library and its sources are freely available by a request. The "help" subdirectory contains the manual in the HTML Help format. Brief functionality description =============================== VAC consists of a kernel-mode and user-mode driver pair. Kernel-mode driver is used as a central part to minimize overhead and maximize timing resolution and precision. The user-mode driver implements a standard Windows audio/wave driver model by exporting the widMessage/wodMessage functions and processing the WIDM_XXX/WODM_XXX messages. By maintaining a list of the clients, the driver supports a multi-client operations. Upon receiving a WxDM_XXX message, the user-mode driver prepares it into request data packet and passes the packet to the kernel-mode driver which performs main processing tasks. The communication protocol is similar to the waveXXX API function set. To communicate with kernel-mode driver, the user-mode driver uses a protected-mode API (int 2F) under 98/ME or the ReadFile/WriteFile/DeviceIoControl functions under 2k/XP. To receive asynchronous notifications, a direct call from VxD is used under 98/ME, and the completion port technique is used under 2k/XP. The kernel-mode driver performs the most tasks to serve client requests. It receives a client request prepared by the user-mode driver into a data packet, processes it and returns a result in same packet. While processing buffer messages (KmiFunc_SendBuffer), the kernel-mode driver saves a buffer description in its own list. Loop support is implemented at kernel-mode driver side. Under 98/ME, the kernel-mode driver is implemented as a PnP VxD. Since there is no physical device, DevNode is not used. To enable dynamically install and uninstall the user-mode driver, the VxD processes the system PnP and Configuration Manager messages. To be called from the user-mode driver which is a 16-bit DLL, the VxD exports a PM API entry point which is directly called from the DLL using a int 2F command. To map client process addresses into 32-bit kernel address space, the VxD uses the SelectorMapFlat VMM service. But this service is suitable for "short-time mapping" until returning control back to user-mode driver. To keep a user-mode address space accessible at interrupt time, the VxD uses a free page allocation services, copying a mapped page table entries to a newly allocated ones. To schedule periodic events, VxD uses a global timeout event VMM service. To notify user-mode driver that buffer is finished, the VxD uses an appy time event shell service. When appears in appy time, the VxD calls the user-mode driver using Shell_CallDll service. Under 2k/XP systems, the kernel-mode driver is a native NT KMD. To be called by the user-mode driver which is a plain Win32 DLL acting behalf a client process, the KMD creates several device objects and symbolic links to them. The user-mode driver uses CreateFile to open the particular device and CreateCompletionPort to receive asynchronous notifications. To schedule periodic events, KMD uses a Timer DPC technique. All control changes including change number of cables are performed by the kernel-mode driver dynamically, without restarting it. Although there is no need to have a mixer for the mirror devices but some applications such as MS Messenger refuse the device if it has no mixer associated with it. Thus, the user-mode driver implements a fake mixer that shows a simple mixer structure to application and simulates a control change. Mixer messages take no effect on device operations. The Control Panel application uses a waveXXX function set to communicate with user-mode driver. To obtain data from the drivers and to modify driver states, the Control Panel uses a WaveOutOpen function specifying a proprietary WAVE_FORMAT_PROPERTY constant in the wFormatTag field of WAVEFORMATEX structure. Since the format structure can have variable size, it can be filled with any appropriate data. The user-mode driver passes this structure to the kernel-mode driver which performs a specified action. Brief source description ======================== VAC is written in basic C++ subset (classes, inheritance and syntax extensions over the plain C). No SEH, RTTI, virtual inheritance and other modern C++ techniques are used, excluding the native NT kernel-mode exceptions used in memory address probing. The kmdriver.cpp is a main platform independent kernel-mode driver module. It initializes a KMD, and creates a cable list. The KmDriverKmi function receives a data packet from the OS interface module (sys.cpp or vxd.cpp) and passes it to WaveHandler function. This function analyzes the function code and performs requested action, filling the data packet with result information. The kmdriver.cpp contains also the DoneQueue list. Here are collected all data buffers that are finished processing. The ReturnDoneBuffers function is called at each return from the kernel mode. It processes this list and notifies appropriate clients. The sys.cpp and vxd.cpp are interface modules to the VxD and KMD driver model under 98/ME and 2k/XP, respectively. They exports some utility functions to control an execution state and called a functions exported by kmdriver.cpp to perform tasks requested by the user-mode driver. The unidrv.cpp implements a small unified driver class to join functionality of the Wave and DirectSound drivers. It exports some virtual methods that are superseded by the WaveDriver and DsPlayDriver classes. The wave.cpp supports wave clients at KMD side. The WaveDriver class based in the UniDrv class describes a wave driver specific data and states. The WaveClient class describes a wave device client (start/stop state, current position, buffer list). Each client buffer is described by the WaveBuffer class. The dsound.cpp supports a DirectSound operations. It implements the DsPlayDriver and DsPlayBuffer classes that are based on appropriate DirectSound classes. DsPlayDriver class is also based on UniDrv class to unify driver selection. The cable.cpp implements entire cable operations: creating/deleting, client counting and data transfer. The umdriver.cpp is a main user-mode driver module. It initializes the driver and exports the DriverProc, widMessage and wodMessage functions. widMessage and wodMessage are joined to the common WaveMessage function which performs WIDM_XXX/WODM_XXX message processing. The umclient.cpp supports a driver-side client operations. The UmClient class maintains a list of buffers received from the client through WIDM_ADDBUFFER and WODM_WRITE messages. During processing these messages, the buffer data is passed to the kernel-mode driver which maintains its own buffer list in WaveClient class. Upon buffer completion, the KMD notifies the driver with the WAVEHDR address and driver callbacks its client with appropriate buffer. The ummixer.cpp implements a fake mixer by exporting the mxdMessage function and processing MXDM_XXX messages. The kmi.h defines a user-to-kernel mode protocol details: function codes, packet structures etc. The ctlpan.cpp implements the Control Panel application used to control the cables. Under 98/ME this application uses SetupAPI to reload user-mode driver by stopping and restarting the device.