Back
How to extract device information with Dart FFI on Windows
Learn how to extract device information on Windows using Dart FFI. Follow code examples to effectively access and utilize device data in your Dart applications.
I’m Rob Vrooman, a software engineer here at Pieces for Developers. My main responsibilities are to develop and maintain plugins and integrations that interface with our core application. In my recent projects, I’ve been exploring the capabilities of Dart’s Foreign Function Interface (FFI) to interact with native Windows libraries. This journey has led me to delve into obtaining detailed device information using dxgi.dll. These tools have proven invaluable for accessing low-level system details and understanding development with Dart FFI.
Setting the stage
The primary goal of this project is to gather device information so that the Pieces application can recommend the most powerful local Large Language Model (LLM) able to run effectively on the user's system. Much of our existing codebase is written in Dart, and by leveraging Dart’s FFI, I can call native C functions and interact with system libraries.
Extracting device information with Dart FFI on Windows
Setup
First, in your Dart project's pubspec.yaml file, be sure to include the latest 'ffi' package. If you will be working with Windows APIs, it is also recommended that you use the 'win32' package.
Using dxgi.dll for device information
dxgi.dll (DirectX Graphics Infrastructure) provides a set of APIs to enumerate graphics adapters and outputs. By interfacing with this DLL, I am able to retrieve detailed information about the video controllers installed on the system, such as the adapter description, memory size, and driver version.
Implementation details
Often, when I read blogs, I think to myself, "Where is the code??". To not bury the lead, here is what the dxgi.dll query ended up looking like:
Let's go into more detail about what exactly is going on here.
GPUInfo
A plain old Dart object meant to hold the extracted data.
Arena
The Arena class in dart FFI is basically a helper utility that ensures allocated memory is freed upon exiting the scope. Behind the scenes, it can use malloc or calloc to perform the allocation. The default allocator is calloc.
CreateDXGIFactory1
First, we allocate the necessary memory for a pointer to a win32 COMObject as required by CreateDXGIFactory1.
The CreateDXGIFactory1 function creates a factory that can be used to generate other DXGI objects. In our case, we're looking to create an IDXGIFactory object to be able to iterate the available graphics adapters with the EnumAdapters function.
Setting up dart functions to call these functions through FFI looks something like this:
With the factory function call setup, we are able to enumerate the adapters:
Enumerating adapters
Again, we start by allocating the required memory buffer. Next, we create our IDXGIFactory1Object from the Void Pointer received from the original create factory call.
Where'd that come from? Here's where it gets a bit more complex: to call functions that are part of an object and class layout, we need to understand which memory location to point to:
VTables and the like deserve a blog unto themselves, but for this case, I can simply say that the layout can be dumped by your compiler. In my case, I used the command:
For a clang class layout dump of:
Notice how the EnumAdapters1 function has an offset number of '12', the same as used in the code above. The Dart FFI library handles offsets with memory size calculations, so you can use this number directly.
Now, starting from 0 and incrementing the device index until failure, we can retrieve information about the adapters.
Adapter descriptions
What's new here? Now that we have a Void Pointer that's meant to be an IDXGIAdapter1, we create a new object from that pointer. Since the pattern of how this is accomplished has already been discussed, I will leave it out for brevity.
GPUInfo!
We made it! We have the data!
There's one more important piece here to go over. How is DXGI_ADAPTER_DESC defined in our Dart code? This structure must match the expected memory layout as shown in the documentation:
Conclusion
This project has been an incredible learning experience, providing deep insights into both Dart’s capabilities and the intricacies of Windows system programming. By combining Dart with FFI and leveraging dxgi.dll, I was able to unlock powerful functionalities to obtain device information, which in turn lets us provide the most well-optimized generative AI experience for our users within Pieces.