microsoft / windows-rs

Rust for Windows
https://kennykerr.ca/rust-getting-started/
Apache License 2.0
10.35k stars 486 forks source link

Access violation when using DirectInput API #3128

Closed flaamjab closed 3 months ago

flaamjab commented 3 months ago

Summary

Hello!

I'm attempting to use the DirectInput API through the windows crate (on Windows 10, Rust version 1.79) but have run into an issue with the program crashing when any method is called on the object returned from DirectInput8Create.

The equivalent C++ code works though, and I don't know what I am missing, so I assume it might be an issue with the crate.

#include <stdio.h>

#include <dinput.h>

int main() {
  HINSTANCE hinstance = GetModuleHandle(nullptr);

  IDirectInput8W* di8 = nullptr;
  HRESULT res = DirectInput8Create(hinstance, DIRECTINPUT_VERSION, IID_IDirectInput8W, (LPVOID*)&di8, nullptr);
  if (FAILED(res)) {
    printf("DirectInput8Create: error\n");
    return -1;
  }

  res = di8->EnumDevices(DI8DEVCLASS_GAMECTRL, di8_enumerate, di8, DIEDFL_ATTACHEDONLY);
  if (FAILED(res)) {
    printf("EnumDevices: error\n");
    return -1;
  }

  return 0;
}

Crate manifest

[package]
name = "directinput-demo"
version = "0.0.0"
edition = "2021"

[dependencies]
windows = { version = "0.57.0", features = [
    "Win32_Devices_HumanInterfaceDevice",
    "Win32_System_LibraryLoader"
]}

Crate code

use std::{
    ffi::c_void,
    ptr::{self},
    thread,
    time::Duration,
};

use windows::{
    core::Interface,
    Win32::{
        Devices::HumanInterfaceDevice::{
            DirectInput8Create, IDirectInput8W, DI8DEVCLASS_GAMECTRL, DIDEVICEINSTANCEW,
            DIEDFL_ATTACHEDONLY, DIRECTINPUT_VERSION,
        },
        Foundation::{BOOL, HINSTANCE},
        System::LibraryLoader::GetModuleHandleW,
    },
};

fn main() {
    unsafe {
        let hinstance: HINSTANCE = GetModuleHandleW(None).unwrap().into();
        assert_ne!(hinstance.0, 0);
        let mut di8: *mut IDirectInput8W = ptr::null_mut();
        let iid = IDirectInput8W::IID;
        DirectInput8Create(
            hinstance,
            DIRECTINPUT_VERSION,
            ptr::addr_of!(iid),
            ptr::addr_of_mut!(di8) as _,
            None,
        )
        .unwrap();
        assert!(!di8.is_null());
        (*di8)
            .EnumDevices(
                DI8DEVCLASS_GAMECTRL,
                Some(di8_enumerate),
                ptr::null_mut(),
                DIEDFL_ATTACHEDONLY,
            )
            .unwrap();
    }
}

unsafe extern "system" fn di8_enumerate(dev: *mut DIDEVICEINSTANCEW, param1: *mut c_void) -> BOOL {
    true.into()
}
Nerixyz commented 3 months ago

You have one reference too many:

DirectInput8Create takes a *mut *mut c_void (C++: void**) as ppvout while you're passing a *mut *mut IUnknown = *mut *mut *mut c_void (C++: void*** - remember that IUnknown is a pointer already).

The following code doesn't segfault:

use std::{
    ffi::c_void,
    ptr::{self},
    thread,
    time::Duration,
};

use windows::{
    core::{IUnknown, Interface},
    Win32::{
        Devices::HumanInterfaceDevice::{
            DirectInput8Create, IDirectInput8W, DI8DEVCLASS_GAMECTRL, DIDEVICEINSTANCEW,
            DIEDFL_ATTACHEDONLY, DIRECTINPUT_VERSION,
        },
        Foundation::{BOOL, HINSTANCE},
        System::LibraryLoader::GetModuleHandleW,
    },
};

fn main() {
    unsafe {
        let hinstance: HINSTANCE = GetModuleHandleW(None).unwrap().into();
        assert_ne!(hinstance.0, 0);
        let mut di8 = ptr::null_mut();
        DirectInput8Create(
            hinstance,
            DIRECTINPUT_VERSION,
            &IDirectInput8W::IID,
            &mut di8,
            None,
        )
        .unwrap();
        assert!(!di8.is_null());
        let di8 = IDirectInput8W::from_raw(di8);
        di8.EnumDevices(
            DI8DEVCLASS_GAMECTRL,
            Some(di8_enumerate),
            ptr::null_mut(),
            DIEDFL_ATTACHEDONLY,
        )
        .unwrap();
    }
}

unsafe extern "system" fn di8_enumerate(dev: *mut DIDEVICEINSTANCEW, param1: *mut c_void) -> BOOL {
    true.into()
}

I think the metadata could be annotated here to make the generated code a bit nicer and harder to misuse.

flaamjab commented 3 months ago

That's illuminating. Thanks!

kennykerr commented 3 months ago

The metadata for DirectInput8Create is not great. It might be possible to improve the bindings if you bring it up on the Win32 metadata repo.