Win32::API and Inline::C should be able to help out here. Here's an Inline::C demo (based on some C code I found in my msdn documentation):
use warnings;
use Inline C => Config =>
LIBS => '-lSetupAPI',
BUILD_NOISY => 1;
use Inline C => <<'EOC';
#include <windows.h>
#include <setupapi.h>
#include <stdio.h>
#include <devguid.h>
#include <regstr.h>
int wrap_SetupDiGetClassDevs() {
HDEVINFO hDevInfo;
// Create a HDEVINFO with all present devices.
hDevInfo = SetupDiGetClassDevs(NULL,
0, // Enumerator
0,
DIGCF_PRESENT | DIGCF_ALLCLASSES );
if (hDevInfo == INVALID_HANDLE_VALUE)
croak("INVALID_HANDLE_VALUE returned");
return hDevInfo;
}
void wrap_SetupDiDestroyDeviceInfoList(int hDevInfo) {
SetupDiDestroyDeviceInfoList(hDevInfo);
}
void foo(int hDevInfo) {
INLINE_STACK_VARS;
SP_DEVINFO_DATA DeviceInfoData;
DWORD i, DataT, buffersize;
LPTSTR buffer;
// Enumerate through all devices in Set.
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
INLINE_STACK_RESET;
for (i=0;SetupDiEnumDeviceInfo(hDevInfo,i,
&DeviceInfoData);i++)
{
buffer = NULL;
buffersize = 0;
//
// Call function with null to begin with,
// then use the returned buffer size
// to Alloc the buffer. Keep calling until
// success or an unknown failure.
//
while (!SetupDiGetDeviceRegistryProperty(
hDevInfo,
&DeviceInfoData,
SPDRP_DEVICEDESC,
&DataT,
(PBYTE)buffer,
buffersize,
&buffersize))
{
if (GetLastError() ==
ERROR_INSUFFICIENT_BUFFER)
{
// Change the buffer size.
if (buffer) LocalFree(buffer);
buffer = LocalAlloc(LPTR,buffersize);
}
else croak("ERROR 1 in sub foo");
}
INLINE_STACK_PUSH(sv_2mortal(newSVpv(buffer, 0)));
if (buffer) LocalFree(buffer);
}
if ( GetLastError()!=NO_ERROR &&
GetLastError()!=ERROR_NO_MORE_ITEMS )
croak("ERROR 2 in sub foo");
INLINE_STACK_DONE;
INLINE_STACK_RETURN(i);
}
EOC
# perl code starts here:
my $handle = wrap_SetupDiGetClassDevs();
my @devices = foo($handle);
# clean up
wrap_SetupDiDestroyDeviceInfoList($handle);
print $_, "\n" for @devices;
Feel free to modify the code so that it does what you want. I don't think MinGW contains the requisite C header files, so you would need an MS compiler to run that code. And because the code passes a handle around, the compiler probably needs to be VC 6 (not VC 7 or 8) - assuming that your perl was built with VC 6.
So ... it may not be all that useful to you ... but it kept me occupied for a while :-)
Cheers, Rob | [reply] [d/l] |
This code was very helpful. It does have some drawbacks, such as casting pointers to and from int, and not enumerating interfaces.
Below is a require file that builds on the ideas and exports a single function, which I use to provide a sensible UI for CDROM devices. Of course, it can be modified for other uses. I don't claim it's perfect, but I thought that in exchange for the excellent starting point, I should give back. Note that it is customized for CDROMs; for other disks, you'll want to retrieve the partition number and for non-storage, do something else...
The code may be freely used and modified - no warranty, but please follow the usual rules: credit the source, add a comment if you make any modifications, and do not remove the copyright. Enjoy.
# CD drive information
# Copyright (C) 2018 Timothe Litt litt _at acm ddot org
#
# This code may be freely used and modified. There is no
# warranty - use at your own risk. You may not remove
# the copyright. If you make any changes, add a comment
# identifying yourself and the change.
#
use 5.10.0;
use warnings;
use strict;
package CdInfo;
our $VERSION = '1.001';
my $gitid = '$Format:%H$';
our $cdate = '$Format:%cD$';
$VERSION .= ", commit $gitid" if( $gitid !~ /^\044Format:.*\$$/ );
use Exporter;
our @ISA = qw/Exporter/;
our @EXPORT_OK = qw/cdinfo/;
use File::Find;
my $libpath;
my $incpath;
$libpath = '' unless( $libpath );
$incpath = '' unless( $incpath );
BEGIN {
find( { wanted => sub { /^libsetupapi\.a$/i &&
($libpath ||= $File::Find::dir);
/^setupapi\.h$/i &&
($incpath ||= $File::Find::dir); },
follow => 1, follow_skip => 2 }, @INC );
die( "No MinGW libsetupapi $libpath $incpath\n" )
unless( $libpath && $incpath );
}
use Inline( C => Config =>
libs => "-L$libpath -lSetupAPI",
INC => "-I$incpath",
# print_info => 1,
# BUILD_NOISY => 1,
);
use Inline 'C';
Inline->init;
1;
__DATA__
__C__
#include <windows.h>
#include <stdio.h>
#include <initguid.h>
#include <devguid.h>
#include <regstr.h>
#include <setupapi.h>
#include <WinioCtl.h>
/* Error codes can be looked-up at
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v
+=vs.85).aspx
*/
/* Returns an array that may be suitable for initializing a hash.
* Each CDROM device returns two elements.
* The second is always the "Friendly" name of the device.
* item selects the first:
* 0 - The storage device number (note: not persisted across reboots)
* 1 - The device path (suitable for calling CreateFile)
*
* '' is returned if he value can't be obtained or item is out of rang
+e.
* Multiple errors will, of course, be replaced by the last entry if
* used as a hash initializer. In that case, to get all the errors,
* inspect the array.
*/
void cdinfo( int item ) {
INLINE_STACK_VARS;
HDEVINFO hDevInfo;
SP_DEVINFO_DATA DeviceInfoData;
DWORD i, n = 0, DataT;
INLINE_STACK_RESET;
static GUID GUID_DEVINTERFACE_CDROM = {
0x53F56308, 0xB6BF, 0x11D0,
{0x94, 0xF2,
0x00, 0xA0, 0xC9, 0x1E, 0xFB, 0x8B } };
/* Create a set of all present CDROM devices. */
hDevInfo = SetupDiGetClassDevs( &GUID_DEVINTERFACE_CDROM, NULL, NU
+LL,
DIGCF_PRESENT | DIGCF_DEVICEINTERF
+ACE );
if( hDevInfo == INVALID_HANDLE_VALUE) {
char buf[132];
sprintf( buf, "cdinfo: SetupDiGetClassDevs error %08x", GetLast
+Error() );
croak( buf );
}
/* Enumerate all devices in set. */
memset( &DeviceInfoData, 0, sizeof(DeviceInfoData ) );
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
i = 0;
while( SetupDiEnumDeviceInfo( hDevInfo, i++, &DeviceInfoData ) ) {
SP_DEVICE_INTERFACE_DATA ifdata;
DWORD j = 0;
memset( &ifdata, 0, sizeof( ifdata ));
ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
while( SetupDiEnumDeviceInterfaces( hDevInfo, &DeviceInfoData,
&GUID_DEVINTERFACE_CDROM,
+j++,
&ifdata ) ) {
PSP_DEVICE_INTERFACE_DETAIL_DATA detail = NULL;
LPTSTR buffer = NULL;
DWORD buffersize = 0;
SP_DEVINFO_DATA DevInfoData;
HANDLE devhandle;
STORAGE_DEVICE_NUMBER devnum;
DevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
/* Obtain device path name (\\?\bus...) */
if( !SetupDiGetDeviceInterfaceDetail( hDevInfo, &ifdata,
NULL, 0, &buffersize
+,
NULL ) &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER ) {
char buf[132];
sprintf( buf,
"cdinfo: SetupDiGetDeviceInterfaceDetail erro
+r %08x",
GetLastError() );
croak( buf );
}
detail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)
LocalAlloc( LPTR, buffersize );
detail->cbSize = sizeof( SP_DEVICE_INTERFACE_DETAIL_DATA
+);
if( !SetupDiGetDeviceInterfaceDetail( hDevInfo, &ifdata,
detail, buffersize,
+ NULL,
&DevInfoData ) ) {
char buf[132];
sprintf( buf,
"cdinfo: SetupDiGetDeviceInterfaceDetail err
+or %08x",
GetLastError() );
if( detail ) LocalFree( detail );
croak( buf );
}
switch( item ) {
case 0:
devhandle = CreateFile( detail->DevicePath, 0,
FILE_SHARE_DELETE|FILE_SHARE_
+READ|
FILE_SHARE_
+WRITE,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL )
+;
if( devhandle == INVALID_HANDLE_VALUE ||
! DeviceIoControl( devhandle,
IOCTL_STORAGE_GET_DEVICE_NUMBE
+R,
NULL, 0, &devnum, sizeof( devn
+um ),
&buffersize, NULL ) ) {
INLINE_STACK_PUSH(sv_2mortal(newSVpv("", 0)));
} else {
INLINE_STACK_PUSH(sv_2mortal(newSVuv(devnum.Devic
+eNumber)));
}
if( devhandle != INVALID_HANDLE_VALUE )
CloseHandle( devhandle );
break;
case 1:
INLINE_STACK_PUSH(sv_2mortal(newSVpv(detail->DevicePa
+th, 0)));
break;
default:
INLINE_STACK_PUSH(sv_2mortal(newSVpv("", 0)));
break;
}
++n;
LocalFree( detail );
detail = NULL;
buffersize = 0;
while( !SetupDiGetDeviceRegistryProperty(
hDevInfo,
&DevInfoData,
SPDRP_FRIENDLYNAME,
&DataT,
(PBYTE)buffer,
buffersize,
&buffersize)) {
if( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) {
/* Increase the buffer size. */
if( buffer) LocalFree(buffer);
buffer = (LPTSTR)LocalAlloc( LPTR, buffersize );
} else {
char buf[132];
sprintf( buf,
"cdinfo: SetupDiGetDeviceRegistryPropert
+y error %08x",
GetLastError() );
if( buffer) LocalFree(buffer);
croak(buf);
}
}
SetLastError( 0 );
INLINE_STACK_PUSH(sv_2mortal(newSVpv(buffer, 0)));
++n;
if( buffer) LocalFree(buffer);
buffer = NULL;
buffersize = 0;
}
if( GetLastError() != NO_ERROR &&
GetLastError() != ERROR_NO_MORE_ITEMS ) {
char buf[132];
sprintf( buf,
"cdinfo: SetupDiEnumDeviceInterfaces error %08x",
GetLastError() );
croak(buf);
}
}
if( GetLastError() != NO_ERROR &&
GetLastError() != ERROR_NO_MORE_ITEMS ) {
char buf[132];
sprintf( buf,
"cdinfo: SetupDiEnumDeviceInfo error %08x", GetLastEr
+ror() );
croak(buf);
}
SetupDiDestroyDeviceInfoList(hDevInfo);
INLINE_STACK_DONE;
INLINE_STACK_RETURN(n);
}
| [reply] [d/l] |
Rob-
Wow! Great response. I've never knew about Inline before -- pretty cool stuff. Thanks for the time and effort!
I can see me playing around with this for a while, but I wonder if there isn't already some windows command that either does this, or gets me a bit closer!?! If not, it may be most efficient just to build one, and call that from the perl script.
Either way, I sure am learning a lot!
-Craig
| [reply] |