* Gertjan Klein:
What I've been thinking about is to write a single [Windows] executable that
gets associated with .py and .pyw (instead of python.exe itself).
Well, you need two: one for console subsystem, and one for GUI subsystem.
Happily you can use the same source code.
This
executable would parse the #! line to look for a specific python
version, or use a configured default if none found (or a parsing error
occurs). It would then invoke the appropriate python version (with
whatever arguments, if any, are supplied).
As far as I can see, this allows both typing the script name and
arguments (i.e., without python31 before it) from a command prompt, and
doubleclicking on a .py or .pyw file from windows explorer. In both
cases, the proper python executable would be used to run the script.
What's been holding me back so far is that probably needs to be written
in C, to prevent the Python runtime's startup overhead. I haven't
written any significant amount of C code in years, if not decades, so
that seems like a daunting task to me at the moment. ;-)
If it's OK with C++, I just sat down and wrote this.
It's not especially well tested (or almost not at all), and it's limited.
It handles or is meant to handle Unicode script file paths, but the path to the
Python interpreter, specified in a "#!" comment in the script's first line, must
be single byte per character. And if that path contains spaces must be quoted.
<code file="run_script.cpp">
// Note: in order to handle Unicode paths needs to use Windows API command line.
//
// If this code works then it was written (but not tested) by Alf P. Steinbach.
// Otherwise it's someone impersonating me.
#include <string> // std::wstring
#include <vector> // std::vector
#include <stdexcept>
#include <stdio.h>
#include <stddef.h>
#undef STRICT
#undef NOMINMAX
#undef UNICODE
#define STRICT
#define NOMINMAX
#define UNICODE
#include <windows.h> // CommandLineToArgvW, GetCommandLine
using namespace std;
//------------------------------ Various things ordinarily from libraries...
bool throwX( char const s[] ) { throw std::runtime_error( s ); }
typedef ptrdiff_t Size;
template< typename Container >
Size n_elements( Container const& c ) { return c.size(); }
// The C++98 standard library doesn't offer Unicode filename functionality.
// Using library extension that works with GNU g++ and Microsoft Visual C++.
class TextFileReader
{
private:
FILE* f;
TextFileReader( TextFileReader const& ); // No copy constructor.
TextFileReader& operator=( TextFileReader const& ); // No assignment.
public:
TextFileReader( wstring const& path )
: f( _wfopen( path.c_str(), L"r" ) )
{
(f != 0) || throwX( "Unable to open file for reading" );
}
~TextFileReader() { fclose( f ); }
wstring line()
{
wstring s;
for( ;; )
{
int const c = fgetc( f );
if( c == EOF || c == L'\n' ) { break; }
s.push_back( wchar_t( c ) );
}
return s;
}
};
wstring substring( wstring const& s, Size const i, Size const n = -1 )
{
wstring::size_type const count = (n == -1? wstring::npos : n);
return (i >= n_elements( s )? L"" : s.substr( i, count ));
}
//------------------------------ Main
typedef wstring String;
typedef vector<String> StringVector;
StringVector cmdArguments()
{
struct Args
{
wchar_t** p;
int n;
Args()
{
p = CommandLineToArgvW( GetCommandLine(), &n );
(p != 0) || throwX( "Unable to obtain command line arguments" );
}
~Args() { GlobalFree( p ); }
};
Args const args;
return StringVector( args.p, args.p + args.n );
}
int run( wstring const& prog_path, wstring const& args )
{
wstring cmd_line = prog_path + L" " + args;
cmd_line.c_str();
PROCESS_INFORMATION process_info = {};
STARTUPINFO startup_info = { sizeof( startup_info ) };
bool const ok = !!CreateProcess(
0, // application name
&cmd_line[0], // command line
0, // process security attributes
0, // thread security attributes
TRUE, // inherit handles
0, // creation flags
0, // environment, 0 => inherit parent process env.
0, // current directory
&startup_info,
&process_info
);
(ok)
|| throwX( "Unable to run the interpreter" );
bool const wait_ok =
(WaitForSingleObject( process_info.hProcess, INFINITE ) != WAIT_FAILED);
DWORD exit_code = EXIT_FAILURE;
GetExitCodeProcess( process_info.hProcess, &exit_code );
CloseHandle( process_info.hProcess );
CloseHandle( process_info.hThread );
(wait_ok) || throwX( "Waiting for the program to end failed" );
return exit_code;
}
int cppMain()
{
StringVector const args = cmdArguments();
(n_elements( args ) == 2)
|| throwX( "Usage: run_script QUOTED_PATH_TO_SCRIPT_FILE" );
wstring const script_path = args[1];
wstring const first_line = TextFileReader( script_path ).line();
wstring const prefix = substring( first_line, 0, 2 );
wstring const prog_path = substring( first_line, 2 );
(prefix == L"#!" && prog_path.length() > 0)
|| throwX( "Unable to determine interpreter" );
return run( prog_path, script_path );
}
int main()
{
try
{
return cppMain();
}
catch( exception const& x )
{
fprintf( stderr, "!run_script: %s\n", x.what() );
return EXIT_FAILURE;
}
}
<code>
<build compiler="g++">
g++ -std=c++98 -pedantic run_script.cpp -o run_script.exe
g++ -std=c++98 -pedantic run_script.cpp -Wl,-subsystem,windows -o run_scriptw.exe
</build>
<build compiler="msvc">
cl /nologo /GX /GR run_script.cpp shell32.lib
cl /nologo /GX /GR run_script.cpp shell32.lib /Fe:run_scriptw.exe /link
/subsystem:windows /entry:mainCRTStartup
run_script.cpp
</build>
<code file="test.py">
#!python
print( "Hello" )
exit( 123 )
</code>
Cheers & hth.,
- Alf