Skip to content
Pete Batard edited this page Jan 1, 2018 · 7 revisions

Reusable parts of the project / solutions to common problems

Libwdi implements reusable solutions to the following problems:

Table of Contents

Creating an application that uses the same codebase for MSVC, WDK/DDK, MinGW and cygwin

3 sets of files are being used for that:

  • For MinGW/cygwin, autotools scripts, that automatically generate the config.h and Makefiles used during compilation
  • For MSVC/Visual Studio, a regular set of project files, depending on the manually edited /msvc/config.h
  • For DDK/WDK, a batch script, depending on the manually edited /msvc/config.h
File(s): (MinGW/cygwin) /autogen.sh, /configure.ac, /Makefile.am, /libwdi/Makefile.am, /examples/Makefile.am
File(s): (MSVC) *.sln, .msvc/*.vcproj .msvc/*.vcxproj
File(s): (DDK) ddk_build.cmd, .msvc/*_sources

Creating an application that is compatible with all versions of Windows from Windows 2000

The trick is to use MinGW as the compiler along with the -DWINVER=0x500 flag.

File(s): /configure.ac

Creating a GUI application that uses the latest Windows visual enhancements (Aero Glass look on Vista/Windows 7), even if compiled from MinGW/cygwin

The default from all compilers, including Microsoft ones, is NOT to use the default look and feel from each platform, but to force the XP/2k one.
To enable a more up to date look and feel in standard GUI applications, you need to manually embed the common-controls manifest, as well as include <commctrl.h> in your source.

  • For MSVC projects, just adding the manifest to your resources files in the project will do.
  • For DDK/WDK, as documented here, you need to add an SXS_APPLICATION_MANIFEST line with the name of your manifest
  • For MinGW/cywgin, it's a bit more tricky. What you want to do is link to the manifest in your .rc file, with something like CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "common_controls.manifest". The problem is that, this needs to appear at the end, else MinGW will ignore it, and you want to be able to edit your .rc in Visual Studio, without destroying the additional data you add to the .rc. The solution is to have the following at the beginning of your .rc
3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "// Must reference a manifest for visual styles\r\n"
    "// Oh, and it must happen at the end, or MinGW will ignore it!\r\n"
    "#if defined(__GNUC__)\r\n"
    "CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST ""common_controls.manifest""\r\n"
    "#endif\r\n"
    "\0"
END
And of course, you need a matching section at the end.
File(s): common-controls manifest, example DDK sources (Zadig), example MinGW manifest from .rc (Zadig)

Creating an application that will request elevation, using MSVC, WDK/DDK, MinGW or cygwin

The solution to that is similar to the above one, in that you need to use a manifest for elevation.
In our project, you will see that we combine multiple manifests were needed (this is the case for the Zadig example above for instance), as DDK is not able to merge separate manifests files. Also, recent versions of Visual Studio don't require an explicit manifest file for elevation, as this can be taken care of in Linker → Manifest File → UAC Execution Level.

File(s): /examples/elevation.manifest

Detecting if an application is running on a 32 or 64 bit Windows OS

Depending on the OS being run, libwdi requires a 32 or 64 bit version of the driver installer to be run. The following section of code can be used to detect if a process is running as 32 or 64 bit code:

	BOOL is_x64 = FALSE;
	// Detect whether if we should run the 64 bit installer, without
	// relying on external libs
	if (sizeof(uintptr_t) < 8) {
		// This application is not 64 bit, but it might be 32 bit
		// running in WOW64
		pIsWow64Process = (BOOL (__stdcall *)(HANDLE, PBOOL))
			GetProcAddress(GetModuleHandle("KERNEL32"), "IsWow64Process");
		if (pIsWow64Process != NULL) {
			(*pIsWow64Process)(GetCurrentProcess(), &is_x64);
		}
	} else {
		is_x64 = TRUE;
	}
File(s): /libwdi/libwdi.c → install_driver_internal()

Compiling a project for both 32 and 64 bit

When possible, because of the need for an embedded driver installer that matches the bitsize of the Windows OS, we need to compile both 32 bit and a 64 bit installer at the same time. This is how we addressed that problem:

  • Visual Studio is pretty straightforward, so we won't document it.
  • For DDK, switching between 32 and 64 bit can be done on the fly in the batch file. The environment variables you need to modify to switch between 32 and 64 bit are: _BUILDARCH, 386, AMD64 and BUILD_DEFAULT_TARGETS, as well as the PATH.
  • For MinGW, provided that you use a multilib MinGW-w64 for compilation, switching between 32 and 64 bit can be easily achieved on the fly as well with the -m32/-m64 CFLAGS/LDFLAGS options. You can also do configure time 32/64 bit support detection or enabling/disabling through configure.ac, or to set the default to be 32 bit, with only the required files as 64.
File(s): (MinGW) /Makefile.am /configure.ac
File(s): (DDK) /ddk_build.cmd

Folder/File selection dialogs that uses the new enhanced Vista or Windows 7 controls when available

This is all done in the file_dialog() and browse_for_folder() calls from zadig_stdlg.c.

File(s): (DDK) /examples/zadig_stdlg.c

USB hotplug detection

  1. When not using RegisterDeviceNotification(), Windows sends an undefined number of WM_DEVICECHANGE events in rapid sequence, all with the exact same wParam/lParam so that we cannot differentiate between them. Notifying on each of those would bother the user too much.
  2. When using RegisterDeviceNotification(), it is possible to get unique WM_DEVICECHANGE events but only for devices that already have a driver, because there is no device interface class for unknown/driverless devices and Microsoft has not publicized any way of doing so, it is NOT possible to get a single notification event for insertion/removal of devices that don't have a driver.
    Our solution then is to initiate delayed notification thread on the first WM_DEVICECHANGE message we receive, and wait for this thread to send a user defined event back to our main callback.
File(s): /examples/zadig.c → main_callback() & notification_delay_thread()

USB device listing, including driverless devices

File(s): /libwdi/libwdi.c → wdi_create_list()

Automatic incrementation of executable/DLL version numbers, using git tags

If you are using versioning in .rc files, it would obviously be nice to bump the version minor according to major commits that you push into your git repository.
In libwdi's case, we use incremental git tags to identify minor versions ("w123", "w124", ...) and we use the numeric part of that tag as the minor for the exe/DLL (eg. "1, 0, 0, 123"). The shell script linked below will:

  1. retrieve and increment the current git tag
  2. update the version in the .rc files using sed
  3. commit the changes in git
Additionally, the script also updates the USB VID → Vendor ID string source (see below).
File(s): /_bump.sh

Windows GUI MouseOver/Hover Tooltip

A simple call that creates and displays a tooltip when hovering over a dialog item (eg. text field)

File(s): /examples/zadig_stdlg.c → create_tooltip()

Sed script to convert the USB VIDs into a source that resolves the Vendor ID strings

In our applications, this is used to display the Vendor name of a specific USB device when hovering over the VID hex string.
http://www.linux-usb.org/usb.ids does a great job at maintaining known Vendor IDs, but the list still needs to be converted into something a bit more C-friendly. For that purpose we built a shell script that, when executed:

  1. uses wget to retrieve the latest version of the list
  2. uses sed to convert it to a C source with a function call that will take a VID and return the matching string.
File(s): /libwdi/vid_data.sh

UTF-8 wrappers for the WideChar (UTF-16) MS API

This is what Microsoft should have done a long time ago. Along with the A (Ansi) and W (!WideChar/Unicode) versions of an API call, it should have implemented U (UTF-8) as well, as it usually makes more sense to use UTF-8 than UTF-16 in an application, especially one that is not localized but still wants to support localized user input. For instance, wouldn't it be nice if there existed a CreateFile version that accepted an UTF-8 char * for the file name?
Our UTF-8 wrapper API provides just that, for a selected subset of the Windows API calls, with:

  • FormatMessage (FormatMessageU)
  • SendMessage (SendMessageLU: lParam as UTF-8)
  • SHGetPathFromIDList (SHGetPathFromIDListU)
  • CreateWindow (CreateWindowU)
  • GetWindowText/SetWindowText (GetWindowTextU/SetWindowTextU)
  • GetWindowTextLength (GetWindowTextLengthU)
  • GetDlgItemText/SetDlgItemText (GetDlgItemTextU/SetDlgItemTextU)
  • GetTextExtentPoint (GetTextExtentPointU)
  • CreateFile (CreateFileU)
  • GetCurrentDirectory/GetFullPathName/GetFileAttributes (GetCurrentDirectoryU/GetFullPathNameU/GetFileAttributesU)
  • SHCreateDirectoryEx (SHCreateDirectoryExU)
  • ShellExecuteEx/CreateProcess (ShellExecuteExU/CreateProcessU)
  • GetOpenFileName/GetSaveFileName (GetOpenSaveFileNameU)
  • UpdateDriverForPlugAndPlayDevices/SetupCopyOEMInf (UpdateDriverForPlugAndPlayDevicesU/SetupCopyOEMInfU)
File(s): /libwdi/msapi_utf8.h

Installing a certificate into the Trusted Publisher repository

If you want your signed drivers to be installed without security prompts, but can't go through WHQL, you will need to install your signed driver certificate in the Trusted Publisher repository. The following code does just that, through the use of the Crypt32.dll, which is available on Windows 2000 and later.
The code will also detect whether the certificate is about to be effectively written to the store, so as to let end-users with the possibility to refuse the certificate installation if they so choose.
The code must be executed from a process running in elevated mode.

File(s): /libwdi/pki.c → AddCertToTrustedPublisher()

WDK's MakeCat/Inf2Cat, MakeCert, CertMgr, SignTool, with source

Because none of the utilities above are redistributable and we needed their basic functionality to disable that last security prompt during the driver installation, we pretty much had to rewrite them all. That's right, if you want a basic MakeCat, that creates a driver cat file from a file list, a basic MakeCert, that creates self-signed certificates, a basic CertMgr, to install/delete certificates in the system Stores, or a SignTool to sign PE executables or cat files, with their sources, it's all there in our pki.c - now isn't that nice?
The only thing we ask, which seems like a fair request considering that Microsoft annoyed countless people by neither publishing the source for these WDK tool, nor making them redistributable, is that you conform to the LGPL and publish your modified sources, should you reuse this code in any way. One should never have had to rewrite these basic PKI tools. But we did, and spent a fair amount of time doing so. Therefore it sounds fair to us to try to ensure, through the LGPL, that any improvements that are made that could further help the developer community finds its way back to the community.

MakeCert:

File(s): /libwdi/pki.c → CreateSelfSignedCert()

CertMgr:

File(s): /libwdi/pki.c → AddCertToStore(), RemoveCertFromStore()

MakeCat:

File(s): /libwdi/pki.c → CreateCat(), AddFileHash(), etc.

SignTool:

File(s): /libwdi/pki.c → SelfSignFile()

Programmatically setting a Local Group Policy on Vista and Windows 7

On Vista and later, the default is to create a restore point when installing a driver. This can be a very lengthy operation, and, for non system devices that can easily be unplugged from the system, overall unnecessary.

If you use gpedit, you find that there exists a "Prevent creation of a system restore point during device activity..." local policy setting that users can toggle, in Administrative Templates ? System ? Device Installation. This translates to a Software\Policies\Microsoft\Windows\DeviceInstall\Settings\DisableSystemRestore DWORD being set to 1 in the machine Group Policy repository. To do the same in a C program, you can then proceed as with something like this:

#include <gpedit.h>
 
DWORD val, val_size=sizeof(DWORD);
HRESULT hr;
IGroupPolicyObject* pLGPO;
HKEY machine_key, dsrkey;
// MSVC is finicky about these ones => redefine them
const IID my_IID_IGroupPolicyObject =
 { 0xea502723, 0xa23d, 0x11d1, {0xa7, 0xd3, 0x0, 0x0, 0xf8, 0x75, 0x71, 0xe3} };
const IID my_CLSID_GroupPolicyObject =
 { 0xea502722, 0xa23d, 0x11d1, {0xa7, 0xd3, 0x0, 0x0, 0xf8, 0x75, 0x71, 0xe3} };
GUID ext_guid = REGISTRY_EXTENSION_GUID;
// This next one can be any GUID you want
GUID snap_guid = { 0x3d271cfc, 0x2bc6, 0x4ac2, {0xb6, 0x33, 0x3b, 0xdf, 0xf5, 0xbd, 0xab, 0x2a} };
 
// Create an instance of the IGroupPolicyObject class
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
CoCreateInstance(&my_CLSID_GroupPolicyObject, NULL, CLSCTX_INPROC_SERVER,
 &my_IID_IGroupPolicyObject, (LPVOID*)&pLGPO);
 
// We need the machine LGPO (if C++, no need to go through the lpVtbl table)
pLGPO->lpVtbl->OpenLocalMachineGPO(pLGPO, GPO_OPEN_LOAD_REGISTRY);
pLGPO->lpVtbl->GetRegistryKey(pLGPO, GPO_SECTION_MACHINE, &machine_key);
 
// The disable System Restore is a DWORD value of Policies\Microsoft\Windows\DeviceInstall\Settings
RegCreateKeyEx(machine_key, "Software\\Policies\\Microsoft\\Windows\\DeviceInstall\\Settings",
 0, NULL, 0, KEY_SET_VALUE | KEY_QUERY_VALUE, NULL, &dsrkey, NULL);
  
// Create the value
val = 1;
RegSetKeyValue(dsrkey, NULL, "DisableSystemRestore", REG_DWORD, &val, sizeof(val));
RegCloseKey(dsrkey);
 
// Apply policy and free resources
pLGPO->lpVtbl->Save(pLGPO, TRUE, TRUE, &ext_guid, &snap_guid);
RegCloseKey(machine_key);
pLGPO->lpVtbl->Release(pLGPO);
File(s): /libwdi/installer.c → disable_system_restore()