A modern C# wrapper for NVIDIA's NVAPI, providing easy access to NVIDIA GPU features and settings.
- IntPtr-based API surface; no custom handle types to manage
- Automatic cleanup via
IDisposable(SafeHandle-backed) - Strongly typed structs/enums matching NVAPI headers
- Helper methods for common adapter/display queries
- Facade DTOs for bool-friendly helper results, with
*Native()accessors when you need raw structs - Facade intentionally excludes Direct3D/DX-specific NVAPI entry points (native-only)
- ClangSharp-generated bindings kept in sync with the SDK
- Tests skip gracefully when hardware is absent
- Split test suites:
NVAPIWrapper.NativeTestsexercises only ClangSharp-generated APIs.NVAPIWrapper.FacadeTestsexercises the new facade helpers.
- NVIDIA GPU with NVAPI support
- Windows 10/11 x64
- .NET 10.0 SDK
- NVIDIA drivers
git clone https://github.com/terrymacdonald/NVAPIWrapper.git
cd NVAPIWrapper
./prepare_NVAPI.ps1 # pulls the NVAPI SDK
./build_NVAPI.ps1 # restores, regenerates bindings, builds, tests- Local build artifacts: after
./build_NVAPI.ps1, referenceNVAPIWrapper\bin\Debug\net10.0\NVAPIWrapper.dll(orReleaseif you build release) from your project. - Add as a project reference: add
NVAPIWrapper/NVAPIWrapper.csprojto your solution and reference it. - Requirements at runtime: Windows x64, NVIDIA GPU/driver, and NVAPI DLLs available (the wrapper dynamically loads
ControlLibfrom the installed NVIDIA drivers).
using NVAPIWrapper;
using var api = NVAPIApiHelper.Initialize();
var adapters = api.EnumerateAdapters();
Console.WriteLine($"Found {adapters.Count} NVIDIA GPU(s)");
foreach (var adapter in adapters)
{
var props = adapter.GetProperties();
Console.WriteLine($"\nGPU: {adapter.Name}");
Console.WriteLine($"Device ID: 0x{props.pci_device_id:X}");
foreach (var display in adapter.GetDisplays())
{
if (!display.IsActive()) continue;
var (width, height) = display.GetResolution();
var refresh = display.GetRefreshRateHz();
Console.WriteLine($" {width}x{height} @ {refresh:F2} Hz");
}
}try
{
using var NVAPI = NVAPIApi.Initialize();
// use the API
}
catch (NVAPIException ex)
{
Console.WriteLine($"NVAPI Error: {ex.Result} - {ex.Message}");
}
catch (DllNotFoundException)
{
Console.WriteLine("NVAPI DLL not found. Install NVIDIA Graphics drivers.");
}Use the facade helpers to avoid manual struct sizing/handle management.
DTO-returning helpers use bool properties; call *Native() variants when you need the raw structs.
Get/Set operations are split into Get*() and Set*() helpers; GetSet*Native() remains for direct NVAPI calls.
using NVAPIWrapper;
using var api = NVAPIApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
foreach (var display in adapter.GetDisplays())
{
if (!display.IsActive()) continue;
var (w, h) = display.GetResolution();
var hz = display.GetRefreshRateHz();
Console.WriteLine($"{adapter.Name}: {w}x{h} @ {hz:F2} Hz");
}
}using NVAPIWrapper;
using System.Linq;
using var api = NVAPIApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
var displayHelper = adapter.GetDisplays().First();
var result = displayHelper.GetCombinedDisplay();
if (result.IsSupported && result.NumOutputs > 0)
{
Console.WriteLine($"Adapter {adapter.Name} has a combined display with {result.NumOutputs} outputs.");
}
}using NVAPIWrapper;
using var api = NVAPIApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
var tempHelper = api.GetTemperatureHelper(adapter);
var sensor = tempHelper.EnumTemperatureSensors().FirstOrDefault();
if (sensor != IntPtr.Zero)
{
var tempC = tempHelper.TemperatureGetState(sensor);
Console.WriteLine($"{adapter.Name}: {tempC:F1} C");
}
}using NVAPIWrapper;
using var api = NVAPIApiHelper.Initialize();
var adapter = api.EnumerateAdapters().First();
var args = new ctl_wait_property_change_args_t
{
Size = (uint)sizeof(ctl_wait_property_change_args_t),
Version = 0,
PropertyType = ctl_property_type_flags_t.CTL_PROPERTY_TYPE_FLAG_DISPLAY,
TimeOutMilliSec = 5_000 // 5 seconds
};
adapter.WaitForPropertyChange(args);
Console.WriteLine("Property change observed or timeout reached.");If you prefer direct P/Invoke access, use NVAPIApi and the generated structs/functions.
The facade does not wrap Direct3D/DX-specific NVAPI functions. Those are available only via the native bindings and require D3D headers/types during generation.
Note: the following snippet is native-only and will compile only if D3D headers/types are available in your build.
using NVAPIWrapper;
using var nvapi = NVAPIApi.Initialize();
// ID3D12Device* device = ... (from your D3D12 setup)
// bool supported = false;
// var status = NVAPI.NvAPI_D3D12_QueryPresentBarrierSupport(device, &supported);
// if (status == _NvAPI_Status.NVAPI_OK && supported)
// {
// // Use additional NvAPI_D3D12_* functions from the native bindings.
// }using NVAPIWrapper;
using var NVAPI = NVAPIApi.Initialize();
foreach (var adapter in NVAPI.EnumerateAdapters())
{
var displays = NVAPI.EnumerateDisplays(adapter);
foreach (var display in displays)
{
var props = new ctl_display_properties_t { Size = (uint)sizeof(ctl_display_properties_t), Version = 0 };
if (NVAPI.ctlGetDisplayProperties((_ctl_display_output_handle_t*)display, &props) != ctl_result_t.CTL_RESULT_SUCCESS)
continue;
var timing = props.Display_Timing_Info;
if (timing.HActive == 0 || timing.VActive == 0) continue;
Console.WriteLine($"{timing.HActive}x{timing.VActive} @ {timing.RefreshRate / 1000.0:F2} Hz");
}
}using NVAPIWrapper;
using var NVAPI = NVAPIApi.Initialize();
foreach (var adapter in NVAPI.EnumerateAdapters())
{
var args = new ctl_combined_display_args_t
{
Size = (uint)sizeof(ctl_combined_display_args_t),
Version = 0,
OpType = ctl_combined_display_optype_t.CTL_COMBINED_DISPLAY_OPTYPE_QUERY_CONFIG
};
var result = NVAPI.ctlGetSetCombinedDisplay((_ctl_device_adapter_handle_t*)adapter, &args);
if (result == ctl_result_t.CTL_RESULT_SUCCESS && args.IsSupported && args.NumOutputs > 0)
{
Console.WriteLine("Combined display present.");
}
}using NVAPIWrapper;
using var NVAPI = NVAPIApi.Initialize();
foreach (var adapter in NVAPI.EnumerateAdapters())
{
uint count = 0;
if (NVAPI.ctlEnumTemperatureSensors((_ctl_device_adapter_handle_t*)adapter, &count, null) != ctl_result_t.CTL_RESULT_SUCCESS || count == 0)
continue;
var sensors = new IntPtr[count];
unsafe
{
fixed (IntPtr* pSensors = sensors)
{
if (NVAPI.ctlEnumTemperatureSensors((_ctl_device_adapter_handle_t*)adapter, &count, (_ctl_temp_handle_t**)pSensors) != ctl_result_t.CTL_RESULT_SUCCESS)
continue;
}
double temp = 0;
if (NVAPI.ctlTemperatureGetState((_ctl_temp_handle_t*)sensors[0], &temp) == ctl_result_t.CTL_RESULT_SUCCESS)
{
Console.WriteLine($"{temp:F1} C");
}
}
}Tests require NVIDIA GPU hardware and the NVAPI DLLs present. They skip gracefully if not available.
./test_NVAPI.ps1
# or
dotnet test NVAPIWrapper.NativeTests/NVAPIWrapper.NativeTests.csproj
dotnet test NVAPIWrapper.FacadeTests/NVAPIWrapper.FacadeTests.csprojThe test runner script executes only passive tests (Category=Passive). Active tests (Category=Active) can change system settings and must be run manually, for example:
dotnet test NVAPIWrapper.FacadeTests/NVAPIWrapper.FacadeTests.csproj --filter "Category=Active"When NVIDIA releases a new NVAPI:
./prepare_NVAPI.ps1 # update SDK bits
./build_NVAPI.ps1 # regenerates bindings via ClangSharp and rebuildsNVAPIWrapper/- main wrappercs_generated/- ClangSharp output (auto-generated)NVAPIApi.cs- high-level APINVAPIExtensions.cs- helpers for common ops
NVAPIWrapper.NativeTests/- native test suiteNVAPIWrapper.FacadeTests/- facade test suiteSamples/- sample appsdrivers.gpu.control-library/- NVAPI SDK payload (populated by prepare script)
- Always dispose
NVAPIApi(useusing); SafeHandle + finalizer backstops leaks. - Handles returned from enumerate calls are opaque; pass them back to NVAPI or helper methods.
- Facade helpers return DTOs with
boolproperties; use*Native()helper methods to access raw structs. - Struct
Versionfields are bytes in native structs; use(byte)0/1in native code paths.
PRs welcome-please add/keep tests passing and let the generator own cs_generated.