is a device really there? lazarus / 67-Windows ---------------------------------------------------------------------- dieselnutjob · 2021-03-27 15:21 Hi I have some code that needs to behave differently depending on whether a certain device is present on the PCI bus or not. I already have this code working:- Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses classes, sysutils, strutils, registry;var  Registry: TRegistry;  Key: String;  strl1: TStringList;  Vendor: string;  Device: string;  i: integer;  j: integer;begin  Result:=0;  Registry := TRegistry.Create;  Key:='\SYSTEM\CurrentControlSet\Enum\PCI';  strl1:=TStringList.Create;  strl1.Clear;  try    // Navigate to proper "directory":    Registry.RootKey := HKEY_LOCAL_MACHINE;    if Registry.OpenKeyReadOnly(Key) then    begin      Registry.GetKeyNames(strl1);    end;  finally    Registry.Free;  end;   It gives me a stringlist strl1 full of all the PCI devices in the registry, and it works without admin rights. The problem is that I am getting false positives because it returns both devices that are on the bus right now, and also devices that have been on the bus before (so called "hidden" devices). So I need to find a way to tell the difference. I have been playing with a program called "Device Remover" which is free download on MajorGeeks. One piece of information that this gives you about a PCI device is DeviceNodeStatus. It seems that if the PCI device is really there you get a CR_SUCCESS and if not a CR_NO_SUCH_DEVNODE. I believe that it is deducing this using this function:- https://docs.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf- cfgmgr32-cm_get_devnode_status I found references to cfgmgr in the JwaWindows unit but ifdefed out on my installation. I found references to the CR codes here https://github.com/msysgit/msysgit/blob/master/mingw/include/ddk/cfgmg r32.h I think that basically if CM_Get_DevNode_Status returns a zero then the device is present and working and if not, then it isn't. Am I headed in the right direction here?  This is what I am thinking:- 1. Look in the registry using the code I already have, search for the device ID that I am looking for (already working). 2. Somehow convert to a dnDevInst handle that that PCI device (I don't know how to do this) 3. Invoke CM_Get_DevNode_Status and if it returns a zero then it's there (I don't know how to do this). If anyone can help me over my mental block then it would be appreciated. thanks, DNJ ---------------------------------------------------------------------- PascalDragon · 2021-03-27 16:26 You could alternatively use the SetupApi which can be used without administrative rights for this as well. For this you can use the SetupApi unit from JVCL with the attached patch to make it work correctly with both FPC and Win64. Here you have an example: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program tpci; uses  SetupApi, Windows; var  di: HDEVINFO;  data: SP_DEVINFO_DATA;  size, i, proptype: DWord;  buf: array of Byte;  ws: UnicodeString;begin  di := SetupDiGetClassDevsW(Nil, 'PCI', 0, DIGCF_PRESENT or DIGCF_ALLCLASSES);  if di = HDEVINFO(INVALID_HANDLE_VALUE) then    Writeln('Failed to retrieve device information; error: ', GetLastError)  else begin    SetLength(buf, 1024);    data.cbSize := SizeOf(data);    i := 0;    while SetupDiEnumDeviceInfo(di, i, data) do begin      if not SetupDiGetDeviceRegistryPropertyW(di, data, SPDRP_HARDWAREID, proptype, @buf[0], Length(buf), size) then begin        Writeln('Failed to retrieve device property; error: ', GetLastError);        Break;      end;      SetLength(ws, size div 2);      Move(buf[0], ws[1], Length(ws) * SizeOf(WideChar));      Writeln(ws);      Inc(i);    end;    SetupDiDestroyDeviceInfoList(di);  end;end. You should check sites like StackOverflow (e.g. here) for more information on using the API. ---------------------------------------------------------------------- dieselnutjob · 2021-03-28 14:52 this looks interesting https://github.com/project- jedi/jvcl/blob/master/jvcl/run/CfgMgr32.pas ---------------------------------------------------------------------- dieselnutjob · 2021-03-28 18:49 concerning definition of TCM_Locate_DevNodeA at line 3106 of https://github.com/project-jedi/jvcl/blob/master/jvcl/run/CfgMgr32.pas it says:- Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i TCM_Locate_DevNodeA = function(var dnDevInst: DEVINST;    pDeviceID: DEVINSTID_A;        // OPTIONAL    ulFlags: ULONG): CONFIGRET; stdcall; Why is the first input to the function a DEVINST ? Shouldn't it be a PDEVINST ? here https://docs.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf- cfgmgr32-cm_locate_devnodea it says "pdnDevInst A pointer to a device instance handle that CM_Locate_DevNode retrieves. The retrieved handle is bound to the local machine." I think that I have seen this before though, definitions of functions that pass a variable where Microsoft says a pointer to a variable but the person that wrote the function (clearly more experienced than me) has it working. ---------------------------------------------------------------------- engkin · 2021-03-28 19:02 Quote from: dieselnutjob on March 28, 2021, 08:49:27 pmconcerning definition of TCM_Locate_DevNodeA at line 3106 of https://github.com/project-jedi/jvcl/blob/master/jvcl/run/CfgMgr32.pas it says:- Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i TCM_Locate_DevNodeA = function(var dnDevInst: DEVINST;    pDeviceID: DEVINSTID_A;        // OPTIONAL    ulFlags: ULONG): CONFIGRET; stdcall; Why is the first input to the function a DEVINST ? Shouldn't it be a PDEVINST ? See the usage of var: function(var dnDevInst: DEVINST ... It is "almost" identical, so it should work. From the compiler point of view, you "must" use a variable of type DEVINST. In many cases you'll see both versions declared as two overloaded functions. ---------------------------------------------------------------------- dieselnutjob · 2021-03-28 20:01 I can invoke the function but it is returning error code 30 ( #define CR_INVALID_DEVICE_ID              0x0000001E ). The definition of the device ID is here https://docs.microsoft.com/en- us/windows-hardware/drivers/install/device-instance-ids I think that the problem is that my previously shared code is returning the Device ID but not the Instance ID.  I am aware that my Device IDs have to have "PCI\" appended to the front. ---------------------------------------------------------------------- dieselnutjob · 2021-03-28 20:28 ok maybe this is the answer from https://docs.microsoft.com/it- it/windows-hardware/drivers/install/instance-ids QuoteTo obtain the bus-supplied instance ID for a device, use an IRP_MN_QUERY_ID request and set the Parameters.QueryId.IdType member to BusQueryInstanceID. ---------------------------------------------------------------------- dieselnutjob · 2021-03-30 13:24 I'm getting close. If I start up Windows Device Manager, open up my Intel Chipset Damily PCI Express Root Port, Right click properties, Details, Select Device instance path. Copy the result. Send it to the this function:- Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type   { TForm1 }  ULONG  = cardinal;  RETURN_TYPE = DWORD;  {$EXTERNALSYM RETURN_TYPE}  // Handle to a loaded DLL  //TModuleHandle = HINST;  CONFIGRET = RETURN_TYPE;  {$EXTERNALSYM CONFIGRET}  DEVINST = DWORD;  {$EXTERNALSYM DEVINST}  PDEVINST = ^DEVINST;  {$EXTERNALSYM PDEVINST}  DEVINSTID_A = PAnsiChar; // Device ID ANSI name.  {$EXTERNALSYM DEVINSTID_A}   TCM_Locate_DevNodeA = function(var pdnDevInst: PDEVINST;    pDeviceID: DEVINSTID_A;        // OPTIONAL    ulFlags: ULONG): CONFIGRET; stdcall;const  CfgMgrDllName = 'cfgmgr32.dll';  CM_LOCATE_DEVNODE_NORMAL       = $00000000;  {$EXTERNALSYM CM_LOCATE_DEVNODE_NORMAL}  CM_LOCATE_DEVNODE_PHANTOM      = $00000001;  {$EXTERNALSYM CM_LOCATE_DEVNODE_PHANTOM}  CM_LOCATE_DEVNODE_CANCELREMOVE = $00000002;  {$EXTERNALSYM CM_LOCATE_DEVNODE_CANCELREMOVE}  CM_LOCATE_DEVNODE_NOVALIDATION = $00000004;  {$EXTERNALSYM CM_LOCATE_DEVNODE_NOVALIDATION}  CM_LOCATE_DEVNODE_BITS         = $00000007;  {$EXTERNALSYM CM_LOCATE_DEVNODE_BITS}     function TForm1.DeviceToHandle(ADevice:AnsiString):  PDEVINST;var  CM_Locate_DevNodeA: TCM_Locate_DevNodeA;  CfgMgrApiLib: TLibHandle;  Flags: ULong;  pDevice: pChar;begin  CfgMgrApiLib:=Loadlibrary(CfgMgrDllName);  if CfgMgrApiLib<>dynlibs.NilHandle then  begin    CM_Locate_DevNodeA:=TCM _Locate_DevNodeA(GetProcedureAddress(CfgMgrApiLib,'CM_Locate_DevNodeA' ));    pDevice:=@ADevice[1];    Flags:=CM_LOCATE_DEVNODE_NORMAL;    memo1.Append(inttostr(CM_Locate_DevNodeA(Result,pDevice,Flags)));  end;end;   It prints a zero into the Memo1, in other words it's happy. ---------------------------------------------------------------------- dieselnutjob · 2021-03-30 13:44 and, if I tell Device Manager to display "hidden" devices and find a USB device that's greyed out then send the Device instance path I get a 13 USB\VID_0CE9&PID_1012\6&1CEBE3FB&0&3 13 #define CR_NO_SUCH_DEVNODE                0x0000000D So this API works, and does tell me whether something is there or not. I just need to find a way to pull the Device instance out of the registry (or somewhere) programatically for a Device ID that I'm interesting in. ---------------------------------------------------------------------- dieselnutjob · 2021-03-30 15:00 Hooray I got it working  Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Registry, Dynlibs; type   { TForm1 }  ULONG  = cardinal;  RETURN_TYPE = DWORD;  {$EXTERNALSYM RETURN_TYPE}  // Handle to a loaded DLL  //TModuleHandle = HINST;  CONFIGRET = RETURN_TYPE;  {$EXTERNALSYM CONFIGRET}  DEVINST = DWORD;  {$EXTERNALSYM DEVINST}  PDEVINST = ^DEVINST;  {$EXTERNALSYM PDEVINST}  DEVINSTID_A = PAnsiChar; // Device ID ANSI name.  {$EXTERNALSYM DEVINSTID_A}   TCM_Locate_DevNodeA = function(var pdnDevInst: PDEVINST;    pDeviceID: DEVINSTID_A;        // OPTIONAL  ulFlags: ULONG): CONFIGRET; stdcall;   TForm1 = class(TForm)    Button1: TButton;    Memo1: TMemo;    procedure Button1Click(Sender: TObject);  private    function DeviceToCRcode(ADevice:pChar):  CONFIGRET;  public   end; var  Form1: TForm1; implementation {$R *.lfm} { TForm1 }const  CfgMgrDllName = 'cfgmgr32.dll';  CM_LOCATE_DEVNODE_NORMAL       = $00000000;  {$EXTERNALSYM CM_LOCATE_DEVNODE_NORMAL}  CM_LOCATE_DEVNODE_PHANTOM      = $00000001;  {$EXTERNALSYM CM_LOCATE_DEVNODE_PHANTOM}  CM_LOCATE_DEVNODE_CANCELREMOVE = $00000002;  {$EXTERNALSYM CM_LOCATE_DEVNODE_CANCELREMOVE}  CM_LOCATE_DEVNODE_NOVALIDATION = $00000004;  {$EXTERNALSYM CM_LOCATE_DEVNODE_NOVALIDATION}  CM_LOCATE_DEVNODE_BITS         = $00000007;  {$EXTERNALSYM CM_LOCATE_DEVNODE_BITS} procedure TForm1.Button1Click(Sender: TObject);var  Registry: TRegistry;  Key: String;  DeviceList: TStringList;  InstanceList: TStringList;  i: integer;  j: integer;  DeviceID: AnsiString;  DeviceInstanceID: AnsiString; begin  DeviceList:=TStringList.Create;  InstanceList:=TStringList.Create;  memo1.Clear;  Key:='\SYSTEM\CurrentControlSet\Enum\PCI';  memo1.Append(key);  DeviceList.Clear;  Registry := TRegistry.Create;  try    Registry.RootKey := HKEY_LOCAL_MACHINE;    if Registry.OpenKeyReadOnly(Key) then    begin      Registry.GetKeyNames(DeviceList);    end;  finally    Registry.Free;  end;  for i:=0 to DeviceList.Count-1 do  begin    DeviceID:='PCI\'+DeviceList[i];    memo1.Append(DeviceID);    Key:='\SYSTEM\CurrentControlSet\Enum\PCI\'+DeviceList[i];    memo1.Append(key);    InstanceList.Clear;    Registry := TRegistry.Create;    try      Registry.RootKey := HKEY_LOCAL_MACHINE;      if Registry.OpenKeyReadOnly(Key) then      begin        Registry.GetKeyNames(InstanceList);      end;    finally      Registry.Free;    end;    for j:=0 to InstanceList.Count-1 do    begin      DeviceInstanceID:=DeviceID+'\'+InstanceList[j];      memo1.Append(DeviceInstanceID);      if length(DeviceInstanceID)>0 then        memo1.Append(IntToStr(DeviceToCRcode(@DeviceInstanceID[1])));    end;    end;end; function TForm1.DeviceToCRcode(ADevice:pChar):  CONFIGRET;var  CM_Locate_DevNodeA: TCM_Locate_DevNodeA;  CfgMgrApiLib: TLibHandle;  Flags: ULong;  ADevInst: PDEVINST;begin  CfgMgrApiLib:=Loadlibrary(CfgMgrDllName);  if CfgMgrApiLib<>dynlibs.NilHandle then  begin    CM_Locate_DevNodeA:=TCM _Locate_DevNodeA(GetProcedureAddress(CfgMgrApiLib,'CM_Locate_DevNodeA' ));    Flags:=CM_LOCATE_DEVNODE_NORMAL;    Result:=CM_Locate_DevNodeA(ADevInst,ADevice,Flags);  end;end; end. ---------------------------------------------------------------------- dieselnutjob · 2021-03-30 17:24 compared to my previous post is this better, worse or no different? :- Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function TForm1.DeviceToCRcode(ADevice:pChar):  CONFIGRET;var  CM_Locate_DevNodeA: TCM_Locate_DevNodeA;  CfgMgrApiLib: TLibHandle;  Flags: ULong;  ADevInst: PDEVINST;begin  Result:=$FFFFFFFF;  CfgMgrApiLib:=dynlibs.NilHandle;  try    CfgMgrApiLib:=Loadlibrary(CfgMgrDllName);    if CfgMgrApiLib<>dynlibs.NilHandle then    begin      CM_Locate_DevNodeA: =TCM_Locate_DevNodeA(GetProcedureAddress(CfgMgrApiLib,'CM_Locate_DevNo deA'));      Flags:=CM_LOCATE_DEVNODE_NORMAL;      Result:=CM_Locate_DevNodeA(ADevInst,ADevice,Flags);    end;  finally  if CfgMgrApiLib<>dynlibs.NilHandle then Freelibrary(CfgMgrApiLib);  end;end; otherwise it would be doing a loadlibrary over and over again without ever freeing it. Does that matter? or maybe the loadlibrary should be in the main code so it is just invoked one, pass the handle to the function each time, and then unload at the end of the main code? As you can tell I'm not a professional coder. ---------------------------------------------------------------------- dieselnutjob · 2021-03-30 21:43 sorry more questions. I have other code where I use the windows function GetIfTable https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf- iphlpapi-getiftable To use this function I didn't bother with dynlibs, I just defined it as an external function like this Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function GetIfTable(pIfTable:Pointer; pdwSize: Pointer; bOrder:LongWord):LongWord; stdcall; external 'IPHLPAPI.DLL'; When I try the same with CM_Locate_DevNodeA https://docs.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf- cfgmgr32-cm_locate_devnodea like this Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function CM_Locate_DevNodeA(pdnDevInst:Pointer; pDeviceID: Pointer; ulFlags: LongWord): LongWord; stdcall; external 'CFGMGR32.DLL'; The program runs but the function always returns a 3 which means CR_INVALID_POINTER The value of the pointer I pass in is exactly the same as with my previous code loading the DLL using Dynlibs. Is it possible that GetIfTable can be invoked as an external function without dynlibs, but CM_Locate_DevNodeA cannot ? I notice that the MSDN definitions of these functions is worded slightly differently:- For GetIfTable Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i IPHLPAPI_DLL_LINKAGE DWORD GetIfTable(  PMIB_IFTABLE pIfTable,  PULONG       pdwSize,  BOOL         bOrder); For CM_Locate_DevNodeA Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i CMAPI CONFIGRET CM_Locate_DevNodeA(  PDEVINST    pdnDevInst,  DEVINSTID_A pDeviceID,  ULONG       ulFlags); Is this DLL_Linkage vs CMAPI wording significant? or is there some other factor that I don't understand? ---------------------------------------------------------------------- dieselnutjob · 2021-03-31 15:21 OK finally got it working without dynlibs. It was a stupid mistake. I was sending a pointer to CM_Locate_DevNodeA that didn't point to anything. If instead I define a longword and send it @longword, then it works. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Registry; type   { TForm1 }    TForm1 = class(TForm)    Button1: TButton;    Memo1: TMemo;    procedure Button1Click(Sender: TObject);  private    function DeviceToCRcode(ADevice:pansichar):  LongWord;  public   end; var  Form1: TForm1; implementation {$R *.lfm}  { TForm1 }const  CM_LOCATE_DEVNODE_NORMAL       = $00000000;  {$EXTERNALSYM CM_LOCATE_DEVNODE_NORMAL}  CM_LOCATE_DEVNODE_PHANTOM      = $00000001;  {$EXTERNALSYM CM_LOCATE_DEVNODE_PHANTOM}  CM_LOCATE_DEVNODE_CANCELREMOVE = $00000002;  {$EXTERNALSYM CM_LOCATE_DEVNODE_CANCELREMOVE}  CM_LOCATE_DEVNODE_NOVALIDATION = $00000004;  {$EXTERNALSYM CM_LOCATE_DEVNODE_NOVALIDATION}  CM_LOCATE_DEVNODE_BITS         = $00000007;  {$EXTERNALSYM CM_LOCATE_DEVNODE_BITS} function CM_Locate_DevNodeA(pdnDevInst:pointer; pDeviceID: pointer; ulFlags: Longword): Longword; stdcall; external 'cfgmgr32.dll'; procedure TForm1.Button1Click(Sender: TObject);var  Registry: TRegistry;  Key: AnsiString;  DeviceList: TStringList;  InstanceList: TStringList;  i: integer;  j: integer;  DeviceID: AnsiString;  DeviceInstanceID: AnsiString; begin  DeviceList:=TStringList.Create;  InstanceList:=TStringList.Create;  memo1.Clear;  Key:='\SYSTEM\CurrentControlSet\Enum\PCI';  memo1.Append(key);  DeviceList.Clear;  Registry := TRegistry.Create;  try    Registry.RootKey := HKEY_LOCAL_MACHINE;    if Registry.OpenKeyReadOnly(Key) then    begin      Registry.GetKeyNames(DeviceList);    end;  finally    Registry.Free;  end;  for i:=0 to DeviceList.Count-1 do  begin    DeviceID:='PCI\'+DeviceList[i];    memo1.Append(DeviceID);    Key:='\SYSTEM\CurrentControlSet\Enum\PCI\'+DeviceList[i];    memo1.Append(key);    InstanceList.Clear;    Registry := TRegistry.Create;    try      Registry.RootKey := HKEY_LOCAL_MACHINE;      if Registry.OpenKeyReadOnly(Key) then      begin        Registry.GetKeyNames(InstanceList);      end;    finally      Registry.Free;    end;    for j:=0 to InstanceList.Count-1 do    begin      DeviceInstanceID:=DeviceID+'\'+InstanceList[j];      memo1.Append(DeviceInstanceID);      if length(DeviceInstanceID)>0 then        memo1.Append(IntToStr(DeviceToCRcode(@DeviceInstanceID[1])));    end;    end;end; function TForm1.DeviceToCRcode(ADevice:pansichar):  LongWord;var  Flags: LongWord;  ADevInst: LongWord;begin  ADevInst:=0;  Flags:=CM_LOCATE_DEVNODE_NORMAL;  Result:=CM_Locate_DevNodeA(@ADevInst,ADevice,Flags);end; end. ---------------------------------------------------------------------- dieselnutjob · 2021-03-31 17:12 a final word of warning with my code. It tells you if a device is really there, but not if it is really working. As an example look at this device on the PCI bus of my laptop https://pscan.uk/images/devicehasproblem.png If you look at "Device Node Status" it returned CR_SUCCESS, so it's there, but the driver hasn't loaded. If you want to know if it's working then you need to look for maybe DeviceStatus or something like that. At least my code will give you a handle to pass to CM_Get_DevNode_Status