halildurmus / win32

Access common Win32 APIs directly from Dart using FFI — no C required!
https://win32.pub
BSD 3-Clause "New" or "Revised" License
762 stars 123 forks source link

fix: Audio Endpoint crash #932

Open lucaantonelli opened 3 days ago

lucaantonelli commented 3 days ago

Description

I'm not actually sure if it's my fault, but when i try to call the audio endpoints (like enumAudioEndpoints, getDefaultAudioEndpoint, getDevice), my app crash.

Steps To Reproduce

I made a small code to reproduce the issue:

        import 'dart:ffi';

        import 'package:ffi/ffi.dart';
        import 'package:win32/win32.dart';
        import 'package:flutter/material.dart';

        void main() {
          runApp(const MyApp());
        }

        class MyApp extends StatefulWidget {
          const MyApp({super.key});

          @override
          State<MyApp> createState() => _MyAppState();
        }

        class _MyAppState extends State<MyApp> {
          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              home: Scaffold(
                body: Center(
                  child: ElevatedButton(
                    onPressed: () {
                      final audioService = AudioService();
                      audioService.enumAudioDevices(EDataFlow.eRender, ERole.eMultimedia);
                    },
                    child: const Text('Enumerate audio devices'),
                  ),
                ),
              ),
            );
          }
        }

        class AudioService {
          void enumAudioDevices(int deviceType, int role) {
            final pEnumerator = calloc<Pointer<COMObject>>();
            final pCollection = calloc<Pointer<COMObject>>();

            try {
              // Initialize COM library
              var hr = CoInitializeEx(nullptr, COINIT.COINIT_APARTMENTTHREADED);
              if (FAILED(hr)) throw WindowsException(hr);

              // Create device enumerator
              hr = CoCreateInstance(convertToCLSID(CLSID_MMDeviceEnumerator), nullptr, CLSCTX.CLSCTX_ALL,
                  convertToIID(IID_IMMDeviceEnumerator), pEnumerator.cast());
              if (FAILED(hr)) throw WindowsException(hr);

              final enumerator = IMMDeviceEnumerator(pEnumerator.value);

              // Enumerate audio endpoints
              hr = enumerator.enumAudioEndpoints(deviceType, DEVICE_STATE.DEVICE_STATE_ACTIVE, pCollection);
              if (FAILED(hr)) throw WindowsException(hr);
            } finally {
              calloc.free(pEnumerator);
              calloc.free(pCollection);
              CoUninitialize();
            }
          }
        }

Expected Behavior

The app should get the List of Devices for specified type without crashing on function call.

Additional Context

No response

halildurmus commented 3 days ago

It's a pointer bug. You can fix it by making the following changes:

- final pEnumerator = calloc<Pointer<COMObject>>();
+ final pEnumerator = calloc<COMObject>();
- final pCollection = calloc<Pointer<COMObject>>();
+ final pCollection = calloc<COMObject>();

...

- final enumerator = IMMDeviceEnumerator(pEnumerator.value);
+ final enumerator = IMMDeviceEnumerator(pEnumerator);

...

- hr = enumerator.enumAudioEndpoints(deviceType, DEVICE_STATE.DEVICE_STATE_ACTIVE, pCollection);
+ hr = enumerator.enumAudioEndpoints(deviceType, DEVICE_STATE.DEVICE_STATE_ACTIVE, pCollection.cast());

Also, you can easily create device enumerator by calling MMDeviceEnumerator.createInstance();:

class AudioService {
  void enumAudioDevices(int deviceType, int role) {
    final pCollection = calloc<COMObject>();

    try {
      // Initialize COM library
      var hr = CoInitializeEx(nullptr, COINIT.COINIT_APARTMENTTHREADED);
      if (FAILED(hr)) throw WindowsException(hr);

      // Create device enumerator
      final enumerator = MMDeviceEnumerator.createInstance();

      // Enumerate audio endpoints
      hr = enumerator.enumAudioEndpoints(
          deviceType, DEVICE_STATE.DEVICE_STATE_ACTIVE, pCollection.cast());
      if (FAILED(hr)) throw WindowsException(hr);

      final collection = IMMDeviceCollection(pCollection);
      final pcDevices = calloc<Uint32>();
      hr = collection.getCount(pcDevices);
      if (FAILED(hr)) throw WindowsException(hr);

      print('Found ${pcDevices.value} audio devices:');

      for (var i = 0; i < pcDevices.value; i++) {
        final ppDevice = calloc<COMObject>();
        hr = collection.item(i, ppDevice.cast());
        if (FAILED(hr)) throw WindowsException(hr);

        final device = IMMDevice(ppDevice);
        final ppstrId = calloc<PWSTR>();
        hr = device.getId(ppstrId);
        if (FAILED(hr)) throw WindowsException(hr);
        final deviceId = ppstrId.value.toDartString();
        free(ppstrId);
        print('Device ID: $deviceId');
        free(ppDevice);
      }
    } finally {
      free(pCollection);
      CoUninitialize();
    }
  }
}
lucaantonelli commented 3 days ago

Oh many thanks! I've just started learning this, where i can find a wiki or a documentation to start with to understand better? Thanks again for your answer!

halildurmus commented 2 days ago

Oh many thanks! I've just started learning this, where i can find a wiki or a documentation to start with to understand better? Thanks again for your answer!

You're very welcome!

You can find the documentation for the package at https://win32.pub/docs, and the COM-related documentation is available here. However, the COM documentation isn't exhaustive. For practical examples of using COM APIs, you might want to check out the COM examples listed here.

Currently, working with COM APIs can be a bit cumbersome. However, I'm actively working on v6 of the package, which will introduce significant improvements to both the Win32 and COM APIs. The overall developer experience is going to be much more streamlined and user-friendly. I plan to release v6 sometime next year.

To give you an idea of what’s coming, here’s how the example I shared earlier will (roughly) look in v6:

class AudioService {
  void enumAudioDevices(EDataFlow deviceType, DEVICE_STATE role) {
    // Initialize COM library
    CoInitializeEx(COINIT.COINIT_APARTMENTTHREADED);

    // Create device enumerator
    final clsid = MMDeviceEnumerator;
    final enumerator = createInstance<IMMDeviceEnumerator>(clsid);
    free(clsid);

    // Enumerate audio endpoints
    final collection = enumerator.enumAudioEndpoints(deviceType, role);
    final devicesCount = collection.getCount();
    print('Found $devicesCount audio devices:');

    for (var i = 0; i < devicesCount; i++) {
      final device = collection.item(i);
      final pDeviceId = device.getId();
      final deviceId = pDeviceId.toDartString();
      pDeviceId.free();
      print('Device ID: $deviceId');
    }
  }
}

I also would like to thank you for sponsoring me -- it means a lot. If you have more questions as you learn, feel free to reach out! I'll be happy to help.