Search code examples
delphiwinapiwindows-7delphi-7

Need to resolve HMONITOR --> deviceName (or deviceName --> HMONITOR) in windows


EDIT - See Update at end

This is for Delphi 7.0 Build 4.453

Summary

I need to be able to take the Handle property from a TMonitor object (an element in the Monitors array in the TScreen component) which is a HMONITOR, and turn it into the string you would use in calls to EnumDisplaySettings as the lpszDeviceName parameter.

(my end goal is to get a list of device settings from a given HMONITOR value, by passing the resolved lpszDeviceName into calls to EnumDisplaySettings).

Detailed Information

As mentioned above, the Screen.Monitors[x].Handle property is of type HMONITOR and is normally used to pass into the GetMonitorInfo function, which returns, geometry information, but no lpszDeviceName. (note: there is a TMonitorInfoEx structure that has a szDevice field, but it does not seem to get filled in on my system, even though i am setting the cbSize field to the appropriate size).

Alternatively, if i can use a szDeviceName to get the equivalent HMONITOR value, i could plug it into the following function, which would use it in a comparison (I have inserted a call to fictitious function called hMonitorFromDeviceName in the code below) to indicate how it would be used.

function GetMonitorDeviceName(hmon : HMONITOR) : string;
var
  DispDev : TDisplayDevice;
  deviceName : string;
  nDeviceIndex : integer;
begin
  Result := '';

  FillChar(DispDev, sizeof(DispDev),0);
  DispDev.cb := sizeof(DispDev);

  nDeviceIndex := 0;
  while (EnumDisplayDevices(nil, nDeviceIndex, DispDev, 0)) do
  begin

     if ( hMonitorFromDeviceName(DispDev.DeviceString) = hmon ) then
     begin
        Result := StrPas(DispDev.DeviceString);
        exit;
     end;

     inc(nDeviceIndex);

  end;
end;

Update

Thanks to David Heffernan, I have tested his solution, and here is a sample function to get the monitor name from a given handle:

function GetMonitorName(hmon : HMONITOR) : string;
type
  TMonitorInfoEx = record
    cbSize: DWORD;
    rcMonitor: TRect;
    rcWork: TRect;
    dwFlags: DWORD;
    szDevice: array[0..CCHDEVICENAME - 1] of AnsiChar;
end;
var
  DispDev : TDisplayDevice;
  deviceName : string;
   monInfo : TMonitorInfoEx;
begin
  Result := '';

  monInfo.cbSize := sizeof(monInfo);
  if GetMonitorInfo(hmon,@monInfo) then
  begin

    DispDev.cb := sizeof(DispDev);
     EnumDisplayDevices(@monInfo.szDevice, 0, DispDev, 0);
     Result := StrPas(DispDev.DeviceString);

  end;
end;

Solution

  • I think that you must be calling GetMonitorInfo incorrectly. This code:

    {$APPTYPE CONSOLE}
    
    uses
      SysUtils, MultiMon, Windows, Forms;
    
    var
      i: Integer;
      MonitorInfo: TMonitorInfoEx;
    begin
      MonitorInfo.cbSize := SizeOf(MonitorInfo);
      for i := 0 to Screen.MonitorCount-1 do
      begin
        if not GetMonitorInfo(Screen.Monitors[i].Handle, @MonitorInfo) then
          RaiseLastOSError;
        Writeln(MonitorInfo.szDevice);
      end;
      Readln;
    end.
    

    produces this output on my machine:

    \\.\DISPLAY1
    \\.\DISPLAY2
    

    I suspect that your call to GetMonitorInfo is failing in some way and perhaps you are not checking the return value for errors.


    Having searched QualityCentral I suspect you have fallen victim to a known bug in older versions of Delphi: QC#3239. This is reported fixed in version 10.0.2124.6661 which is Delphi 2006.


    Your comments confirm this diagnosis. To fix the problem you'll need a new TMonitorInfoEx definition. Here's one that will work on your pre-Unicode Delphi:

    type
      TMonitorInfoEx = record
        cbSize: DWORD;
        rcMonitor: TRect;
        rcWork: TRect;
        dwFlags: DWORD;
        szDevice: array[0..CCHDEVICENAME - 1] of AnsiChar;
      end;
    

    If you add that to the code above (before you declare the variables of course) then I believe it will resolve your problem.


    As an interesting aside, even in XE3, these structs have not been translated correctly: QC#114460. Admittedly the error is rather benign as it only affects PMonitorInfoExA and TMonitorInfoExA, but the error caught me out whilst trying to solve the problem in this question!