IOCTL (I/O Control Codes)

The IOCTL mechanism provides a way for user‑mode applications to send device‑specific commands to a driver. An IOCTL request is composed of a 32‑bit control code that encodes the device type, function, method, and access rights.

Windows defines a set of standard IOCTL codes; drivers can also define custom codes using the CTL_CODE macro.

IOCTL Syntax

The macro used to define an IOCTL code is:

#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
    ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) )

Key components:

ComponentSizeDescription
DeviceType16 bitsIdentifies the class of device.
Access2 bitsSpecifies required access (FILE_ANY_ACCESS, FILE_READ_ACCESS, FILE_WRITE_ACCESS).
Function12 bitsDriver‑specific function number (0‑4095).
Method2 bitsHow data is passed (METHOD_BUFFERED, METHOD_IN_DIRECT, METHOD_OUT_DIRECT, METHOD_NEITHER).

Defining Custom IOCTL Codes

Typical driver header:

#include <winternl.h>
#define FILE_DEVICE_EXAMPLE 0x00008000

#define IOCTL_EXAMPLE_GET_INFO CTL_CODE(FILE_DEVICE_EXAMPLE, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_EXAMPLE_SET_DATA CTL_CODE(FILE_DEVICE_EXAMPLE, 0x801, METHOD_IN_DIRECT, FILE_WRITE_ACCESS)

Example Usage (C++)

Sending an IOCTL from user mode:

#include <windows.h>
#include <iostream>

int main()
{
    HANDLE hDevice = CreateFileW(L"\\\\.\\ExampleDevice",
                                 GENERIC_READ | GENERIC_WRITE,
                                 0, nullptr,
                                 OPEN_EXISTING,
                                 FILE_ATTRIBUTE_NORMAL,
                                 nullptr);
    if (hDevice == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to open device: " << GetLastError() << std::endl;
        return 1;
    }

    DWORD dummy = 0;
    BOOL result = DeviceIoControl(hDevice,
                                  IOCTL_EXAMPLE_GET_INFO,
                                  nullptr, 0,
                                  &dummy, sizeof(dummy),
                                  nullptr,
                                  nullptr);
    if (!result) {
        std::cerr << "IOCTL failed: " << GetLastError() << std::endl;
    } else {
        std::cout << "IOCTL succeeded, value: " << dummy << std::endl;
    }

    CloseHandle(hDevice);
    return 0;
}

Remarks

  • Always validate input buffers in kernel mode to prevent security vulnerabilities.
  • Prefer METHOD_BUFFERED for simple data exchange; use direct I/O for large data transfers.
  • Use IOCTL_CODE macros consistently to avoid overlapping function numbers.

See Also