Dual-license your content for inclusion in The Perl 5 Wiki using this HOWTO, or join us for a chat on irc.freenode.net#PerlNet.
Melbourne Perl Mongers/Meeting History 2008 07
From PerlNet
[edit]
Preparing for a possible Win32 port - David Dick
[edit]
Reasons for a Win32 port
- Writing unix style applications, wondering what happens when customer wants it on windows
- I don't want to write for Win32, but i wouldn't mind if eventually porting my application to Win32 involved minimal effort and suffering
- I'm writing some some of application may have at least a partial Win32 component
[edit]
General porting preparation
- perldoc perlport - First port of call for differences between platforms
- virtual machines - easy rollback after altering the operating system
[edit]
This talk will cover
- Filesystem Locations
- Logging
- Process Creation
- Daemons
- Security
- Build Process
[edit]
Filesystem Locations (Binaries)
- Win32 root paths are unknown until runtime
my ($base);
if ($^O eq 'MSWin32') {
# usually 'C:\Program Files';
# due to L10n 'Program Files' can be anything
# due to installer choices 'C:\' can also be anything
$base = Win32::GetFolderPath(Win32::CSIDL_PROGRAM_FILES());
} else {
$base = '/opt';
}
return File::Spec->catdir($base, $organisation, $product);
[edit]
Filesystem Locations (Data)
my ($base);
if ($^O eq 'MSWin32') {
# XP is 'C:\Documents and Settings\All Users\Application Data'
# Vista is 'C:\ProgramData';
$base = Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA());
} else {
$base = '/var/opt';
}
return File::Spec->catdir($base, $organisation, $product);
[edit]
Filesystem Locations (Config)
- All /etc is stored in the Registry
- Win32API::Registry && Win32::TieRegistry
- /etc/opt is in the Registry in HKEY_LOCAL_MACHINE\SOFTWARE
[edit]
Filesystem Locations (Config)
my ($key, $type, $value);
unless (Win32API::Registry::RegOpenKeyEx(
Win32API::Registry::HKEY_LOCAL_MACHINE(),
"SOFTWARE\\$organisation\\$product",
0,
Win32API::Registry::KEY_READ(),
$key
))
{
die("Failed to open registry:$^E");
}
unless (Win32API::Registry::RegQueryValueEx(
$key,
$name,
[],
$type,
$value
))
{
die("Failed to read from registry:$^E");
}
unless (Win32API::Registry::RegCloseKey($key)) {
die("Failed to close registry:$^E");
}
[edit]
Filesystem Locations (Recommendations)
- Abstract all references to file system paths
- Abstract Config access
- Probably need a initialise method, and a get/cache method
my ($config) = Config->initialise(); my ($value) = $config->get($name);
[edit]
Log Messages
- Logging with Win32 looks similar, but...
my ($event_log) = Win32::EventLog->new('product');
$event_log->Report({
EventType => Win32::EventLog::EVENTLOG_ERROR_TYPE(),
EventID => 11439,
Strings => "$path\0$^E",
});
[edit]
Log Messages
- is a little bit more complicated
- Win32 logging involves an EventId (numeric) with parameters
- To show the user the event in the event viewer ...
[edit]
Log Messages
- ... you need to create a message definition file ...
open (MESSAGES, ">product.mc"); print MESSAGES <<_MC_FILE_; MessageId=11439 Language=English Failed to open %1 for writing:%2 . _MC_FILE_ # must have a trailing empty line close(MESSAGES);
[edit]
Log Messages
- ... and compile it into a (resource) DLL ...
# set PATH to include mc & rc
system('mc product.mc'); # requires platform sdk
system('rc -r -fo product.res product.rc'); # requires platform sdk
# remember to call vcvars32.bat, vcvarsall.bat or whatever before linking
# link requires visual c++ express edition
system('link -dll -noentry -out:product.dll product.res');
[edit]
Log Messages
- ... and register the DLL in the registry ...
Root = HKEY_LOCAL_MACHINE Key = SYSTEM\CurrentControlSet\Services\EventLog\Application\product Name = EventMessageFile Value = $path_of_product_dll
[edit]
Log Messages (Problems)
- Unix localizes and logs
- Win32 logs and localizes
- Disaster for Error Messages to change on a new version
[edit]
Log Messages (Cheating)
- Could use '%1'
- use Sys::Syslog::Win32();
[edit]
Process Creation
- There is no fork or exec
- perlfork describes how fork without exec is simulated via threads.
- Cygwin is just awesomely evil by having a real fork for windows.
- Win32::Process describes the native process creation interface
[edit]
Process Creation - Win32::Process::Create
- Easy to make mistakes here
my ($process);
Win32::Process::Create(
$process,
'C:\Perl\bin\perl.exe', # fully qualified + suffix
"perl -w $full_path_to_child_perl_script",
0,
Win32::Process::NORMAL_PRIORITY_CLASS(),
$ENV{windir},
);
[edit]
Process Creation
- perl creates a window
- wperl does not create a window
- system("$program $parameter1 $parameter2") works
- as does open("$program $parameter1 $parameter2 |")
- kill(0, $pid) works. Sort of. No EPERM support.
- for backgrounding, launch with Win32::Process
- Monitor processes with Win32::Process::Info
[edit]
Process Creation - waitpid
- to monitor a Win32::Process process
my ($exit_code);
$process->GetExitCode($exit_code);
if ($exit_code == Win32::Process::STILL_ACTIVE()) {
return undef;
} else {
return $exit_code;
}
[edit]
Process Creation - Monitoring
- Getting a complete process listing
my ($pi) = Win32::Process::Info->new();
foreach my $info ($pi->GetProcInfo()) {
print $info->{ProcessId} . ":" . $info->{CommandLine} . "\n";
}
[edit]
Process Creation - Multi-tasking
- perldoc perlfork
- use threads;
- which seem to come down to the same thing
- Single threaded / Non blocking. Okay for exclusive network operations.
*POSIX::EWOULDBLOCK = sub() { return 10035; };
*POSIX::EINPROGRESS = sub() { return 10036; };
[edit]
Process Creation Recommedations
- abstract process creation to only allow an executable + parameters type interface
- abstract existance of a pid
my ($pid) = Process->create('program.pl', @arguments);
if (Process->alive($pid)) {
...
} else {
...
}
[edit]
Daemons
- Win32 Daemons are called Services
- Services start/stop via API calls, NOT signals
- Must respond via API to instructions in 30 secs
- Win32::Daemon allows access to this API
- written by Dave Roth
- stored at roth.net
[edit]
Services (Initialising)
if ($^O eq 'MSWin32') {
require Win32::Daemon;
STARTING_SERVICE: {
Win32::Daemon::StartService();
my ($state) = Win32::Daemon::State();
unless ($state) {
sleep $sleep_time;
redo STARTING_SERVICE;
}
}
} else {
require Proc::Daemon;
Proc::Daemon::Init();
}
[edit]
Services (Status Checking)
my $state;
STATE_CHECK: {
$state = Win32::Daemon::State();
if ($state == Win32::Daemon::SERVICE_STOP_PENDING()) {
Win32::Daemon::State(Win32::Daemon::SERVICE_STOPPED());
} elsif ($state == Win32::Daemon::SERVICE_PAUSE_PENDING()) {
Win32::Daemon::State(Win32::Daemon::SERVICE_PAUSED());
sleep $sleep_time;
redo STATE_CHECK;
... etc ...
} elsif ($state == Win32::Daemon::SERVICE_RUNNING()) {
}
}
return $state;
[edit]
Services (Starting and Stopping)
- command line access via 'net start|stop <servicename>
- GUI access via 'Services' in 'Administrative Tools'
[edit]
Services (Environment Variables)
- Custom Environment variables are awkward
- With Services, Environment variables are only updated after a reboot
- Custom environment variables == reboot after every installation
- Think PATH, ORACLE_ROOT, etc
[edit]
Services (Background Processes)
- think of a background user process interacting with the desktop
- launched at login time
- ssh-agent???
- Create a name at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
- with a value of the command to run
- must run as that process. Do not attempt to background this process
[edit]
Security (users)
- Root/LOCAL_SYSTEM on Win32 is not all-powerful
- difficult for LOCAL_SYSTEM to interact with the Desktop
- Therefore there is no easy way to "drop privileges"
- "drop privileges" means logging on as that user
- which requires a username AND password
[edit]
Security (SIDs)
- Stands for Security Identifiers
- Like a gid AND uid
- Always the same, across all versions Win32/L10n
- S-1-1-0 = Everyone
- S-1-5-18 = Local System
[edit]
Security (who am i?)
- Win32 equivalent of (getpwuid($>))[0])
my ($process_info) = Win32::Process::Info->new();
my ($current_process) = ${$process_info->GetProcInfo($$)}[0];
$user_name = $current_process->{'Owner'};
[edit]
Security (filesystem)
- Powerful/complex permissions system involving multiple groups
- Very difficult to manually audit effectively
- Very easy to audit programmatically
- use Win32::FileSecurity();
- sort of combines chown and chmod into one call
[edit]
Security (filesystem)
my (%permissions);
my (@array_of_permissions) = (
'STANDARD_RIGHTS_READ',
'R',
'READ',
'GENERIC_READ'
);
my (@another_array_of_permissions) = ( .. );
my ($user_mask) = Win32::FileSecurity::MakeMask(@array_of_permissions);
my ($group_mask) = Win32::FileSecurity::MakeMask(@another_array);
$permissions{$user_name} = $user_mask;
$permissions{$group_name} = $group_mask;
unless (Win32::FileSecurity::Set($file_path, \%permissions)) {
die("Failed to chmod/chown $path");
}
[edit]
Security (Temp Files)
- Each windows user has it's own temp directory
- no /dev/fd/ equivalent - Roll your own??
#
# NOTE : This section of code has never been used to
# accomplish anything useful. It's place here
# is to serve as a possible jumping off point
# for anyone interested in pursuing this idea.
# You should also need to use the inherit flag
# in Win32::Process::Create
#
my $tmp_handle = File::Temp::tempfile();
my $os_handle = Win32API::File::GetOsFHandle($tmp_handle);
unless ($os_handle) {
die("Failed to get OS File Handle:$^E");
}
unless (Win32API::File::SetHandleInformation(
$os_handle,
Win32API::File::HANDLE_FLAG_INHERIT(),
1
))
{
die("Failed to set handle information:$^E");
}
[edit]
Build Process (Wix)
- open source project at sourceforge
- builds native windows packages
- fulls much the same role as rpm/dpkg
- That is, no mechanism for retrieving dependencies
[edit]
Build Process (Wix)
- Version 2 is stable/documented
- Version 3 is development/undocumented
[edit]
Build Process (Wix)
- build an XML describing your product
- Very good documentation (at least for stable)
- use wix binaries to create msi package from XML
- building an entire perl distribution increases package size by about 8mb
- Vista seems to have toughest security model
[edit]
Build Process (Dependencies)
- Remember to depend on Win32 capable libraries
- Check dependencies for a Win32 Makefile
- for example 'win32/makefile.mk' in perl source
- Estimate time required to port it youself if required
- Some projects advertise Win32 compatibility when they mean Cygwin

