pashanoid has asked for the wisdom of the Perl Monks concerning the following question:

Dear Bretheren, I'm unable to tweak eval so that my super creative piece of code may work on both Linux and Win32 without me commenting out the Win32 lines every time I compile it to work in enemy territory. This code detects COM ports via the Registry and attempts to open/close them to figure out if they are usable or not. On pure Win32 it works, however I want it in my giant perl all-in-one program, which will complaint about certain adversarial modules if asked to be run on Linux. Long story short: the $string in eval statent returns nothing. When run without eval, it works. Please help.

#!/usr/bin/perl use strict; my %RegHash; my $PortObj; my $Registry; my (@keys, @values, $computer, $string); my $os = $^O; my @ok_ports; if ($os =~ /Win/i){ eval { $string = " use Win32; use Win32::TieRegistry ( TiedHash => \'\%RegHa +sh\' ); my \$computer; my \$Registry= \\%RegHash; \@keys = keys( \%{ \$Registry->{\'HKEY_LOCAL_M +ACHINE\\\\HARDWARE\\\\DEVICEMAP\\\\SERIALCOMM\'}}); \@values = values( \%{ \$Registry->{\'HKEY_LOC +AL_MACHINE\\\\HARDWARE\\\\DEVICEMAP\\\\SERIALCOMM\'}}); print \" inside evAl values=\@values keys=\@ke +ys \\n\"; "; print "$string\n"; eval "$string; 1" or warn "inside eval fails! with: $@ +"; eval "use Win32::SerialPort; 1"; my $computer; my $Registry= \%RegHash; print "values =@values keys =@keys \n"; 1 } or die $@; for (my $i=0; $i<@keys; $i++){ my $port_name = $keys[$i]; $port_name =~ s/\\//g; $port_name =~ s/\.//g; next if ($port_name =~ /SmSrl/g); my $port = $values[$i]; print "trying port $port_name which is: $port\n"; eval { if ($PortObj = new Win32::SerialPort($port, 1) +) { #1 is quiet $PortObj->close; push(@ok_ports, $port) } else { print "$port not opening!\n"; } }; sleep (1); } } else { eval "use Device::SerialPort; 1" or die $@; print "I love Linux!\n"; #all ttyS* and ttyUSB* @ok_ports = ('ttyUSB0', 'ttyUSB1'); } print "ports opening are: @ok_ports \n";

Replies are listed 'Best First'.
Re: code that runs (and works) on both Linux and Win32
by ikegami (Patriarch) on Sep 02, 2011 at 08:47 UTC
    There's no reason to have all that code in a string. Create two modules with the same interface, one that works on Windows, one that works elsewhere. Load the appropriate module.
    my $device_pkg = $^O =~ /Win32/ ? 'My::SerialPort::Win32' : 'My::SerialPort::Linux'; eval "require $device_pkg" or die $@; my $device = $device_pkg->new(); $device->do_something();

      In a way the Anomymous monk pointed me in the right direction!, this works:

      #!/usr/bin/perl use strict; use File::Find; my $PortObj; my $Registry; my %RegHash; my (@keys, @values); my $os = $^O; my (@ports, @ok_ports); if ($os =~ /Win/i){ #comment out for linux! eval { require Win32; require Win32::TieRegistry; Win32::TieRegistry->import(TiedHash=>\%RegHash); require Win32::SerialPort; my $computer; my $Registry= \%RegHash; my @keys = keys( %{ $Registry->{"HKEY_LOCAL_MACHINE\\H +ARDWARE\\DEVICEMAP\\SERIALCOMM"} } ); my @values = values( %{ $Registry->{"HKEY_LOCAL_MACHIN +E\\HARDWARE\\DEVICEMAP\\SERIALCOMM"} } ); for (my $i=0; $i<@keys; $i++){ my $port_name = $keys[$i]; $port_name =~ s/\\/ +/g; $port_name =~ s/\.//g; next if ($port_name =~ /SmSrl/g); my $port = $ +values[$i]; eval { if ($PortObj = new Win32::SerialPort($p +ort, 1)) { $PortObj->close; push(@ok_ports, $port); } else { print "$port not opening!\n"; + }}; sleep (1); }}; } else { eval "use Device::SerialPort; 1" or die $@; my $directory = '/dev'; opendir (DIR, $directory) or die $!; while (my $file = readdir(DIR)) { #print "$file\n"; push (@ports, "$directory/$file") if ($file =~ /ttyS/g +); push (@ports, "$directory/$file") if ($file =~ /ttyUSB +/g); } closedir(DIR); foreach my $port (@ports){ $PortObj = new Device::SerialPort($port, 1) or next; $PortObj->close; push(@ok_ports, $port); sleep (1); } } print "ports opening are: @ok_ports \n";
Re: code that runs (and works) on both Linux and Win32
by cdarke (Prior) on Sep 02, 2011 at 10:46 UTC
    Perosnally I use the if pragma for loading modules. I put platform specific code into subroutines, and call via a dispatch table:
    # simplified my %g_dt = (MsWin32 => \&WinStuff, linux => \&LinuxStuff, # and so on ); ... # The call $g_dt{$^O}->(args);
    (With suitable checks at load time to ensure $^O is a key to %g_dt)
Re: code that runs (and works) on both Linux and Win32
by Anonymous Monk on Sep 02, 2011 at 07:52 UTC
      I have tried that, and it does not work. The script executes without warnings, but produces no com ports. However if I use the same modules outside of eval, they work fine. But this way the script will produce errors in Linux.

        I have tried that, and it does not work.

        But you said Thank you, worked like a charm!, so which is it?

        Seriously, don't ever write code in string eval, seeing \\\\ \$ ..... should make your eyes bleed.

        Start making subroutines, make them into modules, use require .... basically follow the advice you get, read the tutorials linked, things like that

        #!/usr/bin/perl -- use strict; use warnings; use Pashnoid::Fire; my @ok_ports = Pashnoid::Fire->get_ports(); ...
        lib/Pashnoid/Fire.pm
        package Pashnoid::Fire; use Perl::OSType qw' is_os_type '; if( is_os_type('Windows') ){ eval q{use parent 'Pashnoid::Fire::Win32'; 1} or die $@; } else { eval q{use parent 'Pashnoid::Fire::Linux'; 1} or die $@; } 1;
        lib/Pashnoid/Fire/Win32.pm
        use Win32; ... sub get_ports { my( $selfOrClass ) = shift; ...
        lib/Pashnoid/Fire/Linux.pm
        use Device::SerialPort; ... sub get_ports { my( $selfOrClass ) = shift; ...
Re: code that runs (and works) on both Linux and Win32
by JavaFan (Canon) on Sep 02, 2011 at 10:38 UTC
    Untested code below. The trick? Pretend modules you don't want to load under certain conditions are already loaded.
    BEGIN { if ($^O =~ /Win32/) { $INC{"Device/SerialPort.pm"} = 1; } else { $INC{"Win32.pm"} = 1; $INC{"Win32/TieRegistry.pm"} = 1; $INC{"Win32/SerialPort.pm"} = 1; } } use Device::SerialPort; use Win32; use Win32::TieRegistry; use Win32::SerialPort; .. rest of your logic ...
    Alternatively, use the if module:
    use if $^O =~ /Win32/, "Win32"; use if $^O =~ /Win32/, "Win32::TieRegistry"; use if $^O =~ /Win32/, "Win32::SerialPort"; use if $^O !~ /Win32/, "Device::SerialPort";
Re: code that runs (and works) on both Linux and Win32
by locked_user sundialsvc4 (Abbot) on Sep 02, 2011 at 14:58 UTC

    This is a situation where I really like to see the use of a class hierarchy:   an abstract class, with one or more OS-specific implementations, and a “class factory” that knows how to instantiate just exactly the flavor that you need.   All of the OS-type detection occurs in the class-factory.   The OS-specific routines don’t have to:   “I exist, so it must be Win32, so I must be in hell ...”   ;-)