wiki:libwdi/reuse

Version 16 (modified by pbatard, 3 years ago) (diff)

--

Reusable parts of the project / solutions to common problems

Libwdi implements reusable solutions to the following problems:

Creating a Windows application that uses the exact 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, *.vcproj
File(s): (DDK) /ddk_build.cmd, *_sources

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

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

File(s): /configure.ac

Creating a Windows GUI application that use the latest Windows visual enhancements (Aero Glass look on Vista or 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
        "
    "
        "// Must reference a manifest for visual styles
    "
        "// Oh, and it must happen at the end, or MinGW will ignore it!
    "
        "#if defined(__GNUC__)
    "
        "CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST ""common_controls.manifest""
    "
        "#endif
    "
        ""
    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 Windows application from MSVC, WDK/DDK, MinGW and cygwin that require UAC elevation, even if compiled from MinGW/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 an 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 selection / File open/save Windows dialogs that uses to new enhanced Vista or Windows 7 controls when available (and default to the regular version on older platforms)

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()

Bumping the executable/DLL version number automatically 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 C source that will resolve then to 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 t 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 to Wide Char (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