exemaker / exemaker.c

/*
 * $Id: exemaker.c 290 2004-10-11 22:46:26Z fredrik $
 *
 * python script loader
 *
 * history:
 * 2002-01-25 fl  created (based on scriptToolbox sample)
 * 2004-10-10 fl  minor tweaks for the effbot.org release
 * 2004-10-11 fl  added #!exe support
 *
 * Copyright (c) 2002-2004 by Fredrik Lundh
 * Copyright (c) 2000-2004 by Secret Labs AB
 */

char copyright[] = " Copyright (c) Secret Labs AB 2000-2004 ";

#if defined(WINDOWS)
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

#include <stdio.h>
#include <stdlib.h>

/* TODO: add pythonw support! */

/* -------------------------------------------------------------------- */
/* The following function is taken from Python's Python/dynload_win.c
   module, where it is used to check what Python a given extension DLL
   is linked against (see that module for more information on how this
   works) */

#define DWORD_AT(mem) (*(DWORD *)(mem))
#define WORD_AT(mem)  (*(WORD *)(mem))

const char *
GetPythonImport(HINSTANCE hModule)
{
    unsigned char *dllbase, *import_data, *import_name;
    DWORD pe_offset, opt_offset;
    WORD opt_magic;
    int num_dict_off, import_off;

    if (!hModule)
        return NULL;

    dllbase = (unsigned char *) hModule;

    pe_offset = DWORD_AT(dllbase + 0x3C);

    if (memcmp(dllbase + pe_offset, "PE\0\0", 4))
        return NULL;

    opt_offset = pe_offset + 4 + 20;
    opt_magic = WORD_AT(dllbase+opt_offset);
    if (opt_magic == 0x10B) {
        /* PE32 */
        num_dict_off = 92;
        import_off   = 104;
    } else if (opt_magic == 0x20B) {
        /* PE32+ */
        num_dict_off = 108;
        import_off   = 120;
    } else {
        /* Unsupported */
        return NULL;
    }

    if (DWORD_AT(dllbase + opt_offset + num_dict_off) >= 2) {
        import_data = dllbase + DWORD_AT(dllbase + opt_offset + import_off);
        while (DWORD_AT(import_data)) {
            import_name = dllbase + DWORD_AT(import_data + 12);
            if (strlen(import_name) >= 6 && !memcmp(import_name,"python",6)) {
                char *pch;
                pch = import_name + 6;
                while (*pch && *pch != '.') {
                    if (*pch >= '0' && *pch <= '9')
                        pch++;
                    else {
                        pch = NULL;
                        break;
                    }
                }
	    
                if (pch)
                    return import_name;
            }
            import_data += 20;
        }
    }

    return NULL;
}

DWORD rfindx(char* scope, char token) {
    DWORD i;
    for (i = strlen(scope); i > 0 && scope[i] != token; i--)
        ;
    return i;
}

DWORD finddir(char* dllfile) {
    return rfindx(dllfile, '\\');
}

DWORD slashtobs(char *path, DWORD length) {
    DWORD i;
    for (i = 0; i < length; i++) {
        if (path[i] == '/')
            path[i] = '\\';
        else if (path[i] == '\r' || path[i] == '\n')
            break;
    }
    return i;
}

/* -------------------------------------------------------------------- */
const char cannot_open[] = "Cannot open ";
const char cannot_find[] = "Cannot find ";
const char bang_python[] = "#!python.exe";
const char lib_os_py[] = "\\lib\\os.py";
const char cannot_find_dll_in[] = "Cannot find DLL in ";

int __cdecl
main(int ac, char **av)
{
    HINSTANCE mainDll;
    DWORD n;
    char* argv[100];
    int (__cdecl * Py_Main)(int argc, char *argv[]);
    void (__cdecl * Py_SetPythonHome)(char* home);
    char fullfile[512];
    char workingdir[512];
    char requestedapplication[256];
    char exefile[256];
    char scriptfile[256];
    char message[512];
    char* application;
    DWORD isExe;
    DWORD i;

    /* calculate script name: our .exe name with .exe replaced by .py */
    GetModuleFileName(NULL, scriptfile, sizeof(scriptfile));
    i = rfindx(scriptfile, '.');
    scriptfile[i++] = '.';
    scriptfile[i++] = 'p';
    scriptfile[i++] = 'y';
    scriptfile[i] = '\0';

    n = finddir(scriptfile);
    strncpy_s(workingdir, sizeof(workingdir), scriptfile, n);
    workingdir[n] = 0;

    {
        /* read first line from script */
        HANDLE script = CreateFile(scriptfile, GENERIC_READ, FILE_SHARE_READ, NULL,
                            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if (script == (HANDLE) -1) {
            strncpy_s(message, sizeof message, cannot_open, sizeof cannot_open);
            strcat_s(message, sizeof message, scriptfile);
            goto error;
        }
        ReadFile(script, requestedapplication, sizeof(requestedapplication)-1, &n, NULL);
        CloseHandle(script);
    }

    i = slashtobs(requestedapplication, n);
    requestedapplication[i] = '\0';

    /* look for #! header; if not present, assume #!python.exe */
    if (requestedapplication[0] == '#' && requestedapplication[1] == '!') {
        isExe = !memcmp(requestedapplication+i-4, ".exe", 4);
    } else {
        strncpy_s(requestedapplication, sizeof requestedapplication, bang_python, sizeof bang_python);
        isExe = 1;
    }
    /* skip past the #! */
    application = requestedapplication + 2;

    /* search the exe directory first */
    if (requestedapplication[0] == '\\' || requestedapplication[1] == ':') {
        /* absolute path, we can use LoadLibraryEx with LOAD_WITH_ALTERED_SEARCH_PATH */
        application = requestedapplication;
    } else {
        DWORD slash = finddir(scriptfile) + 1;
        strncpy_s(fullfile, sizeof(fullfile), scriptfile, slash);
        fullfile[slash] = 0;
        strcat_s(fullfile, sizeof(fullfile), application);
        application = fullfile;
    }

    mainDll = LoadLibraryEx(application, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
    if (!mainDll) {
        strncpy_s(message, sizeof message, cannot_find, sizeof cannot_find);
        strcat_s(message, sizeof message, requestedapplication);
        goto error;
    }

    if (isExe) {
        HINSTANCE pythonDll;
        /* it's an EXE file; look for a Python DLL */
        /* this finds the name of the python library that would be loaded by `application` if run */
        const char *realpythondllfile = GetPythonImport(mainDll);
        if (!realpythondllfile) {
            strncpy_s(message, sizeof message, cannot_find_dll_in, sizeof cannot_find_dll_in);
            strcat_s(message, sizeof message, requestedapplication);
            goto error;
        }
        {
            char loadPythonDll[512];
            /* try loading the DLL file from the main EXE file's directory */
            strncpy_s(loadPythonDll, sizeof loadPythonDll, workingdir, sizeof workingdir);
            strcat_s(loadPythonDll, sizeof loadPythonDll, "\\");
            strcat_s(loadPythonDll, sizeof loadPythonDll, realpythondllfile);
            /*** try loading the python dll found near `application` using the path from `workingdir` */
            pythonDll = LoadLibraryEx(
                workingdir, NULL, LOAD_WITH_ALTERED_SEARCH_PATH
            );
        }
        if (!pythonDll) {
            DWORD pos;
            char loadPythonDll[512];
            /* if that doesn't work, search for the DLL */
            pos = finddir(application);
            strncpy_s(loadPythonDll, sizeof loadPythonDll, application, pos);
            loadPythonDll[pos] = 0;
            strcat_s(loadPythonDll, sizeof loadPythonDll, "\\");
            strcat_s(loadPythonDll, sizeof loadPythonDll, realpythondllfile);
            /*** try loading the python dll found near `application` using the path for `application` */
            pythonDll = LoadLibraryEx(
                loadPythonDll, NULL, LOAD_WITH_ALTERED_SEARCH_PATH
                );
        }
        if (!pythonDll) {
            strncpy_s(message, sizeof message, cannot_find, sizeof cannot_find);
            strcat_s(message, sizeof message, realpythondllfile);
            goto error;
        }
        FreeLibrary(mainDll);
        mainDll = pythonDll;
    }

    /* get entry points */
    Py_Main = (void*) GetProcAddress(mainDll, "Py_Main");
    if (!Py_Main)
        ExitProcess(1);

    /* get paths */
    GetModuleFileName(mainDll, requestedapplication, sizeof(requestedapplication));
    GetModuleFileName(mainDll, exefile, sizeof(exefile));

    /* check if we have a Python library relative to the DLL home */
    i = finddir(requestedapplication);
    strncpy_s(requestedapplication + i, sizeof requestedapplication - i, lib_os_py, sizeof lib_os_py);

    {
        HANDLE ospy = CreateFile(requestedapplication, GENERIC_READ, FILE_SHARE_READ, NULL,
                                 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

        if (ospy != (HANDLE) -1) {
            /* lib/os.py exists; set PYTHONHOME to the DLL directory */
            Py_SetPythonHome = (void*) GetProcAddress(mainDll, "Py_SetPythonHome");
            if (Py_SetPythonHome) {
                requestedapplication[i] = '\0';
                Py_SetPythonHome(requestedapplication); /* SetPythonHome keeps a reference! */
            }
            CloseHandle(ospy);
        }
    }

    /* zap environment variables (is this necessary?) */
    SetEnvironmentVariable("PYTHONDEBUG", NULL);
    SetEnvironmentVariable("PYTHONHOME", NULL);
    SetEnvironmentVariable("PYTHONOPTIMIZE", NULL);
    SetEnvironmentVariable("PYTHONPATH", NULL);
    SetEnvironmentVariable("PYTHONVERBOSE", NULL);

    argv[0] = exefile;
    argv[1] = "-S"; /* don't import site just yet */
    argv[2] = scriptfile;
    for (i = 1; i < (DWORD) ac; i++)
        argv[2+i] = av[i];
    return Py_Main(2+i, argv);

error:
    MessageBox(NULL, message, "Internal error", MB_ICONERROR | MB_OK);
    return 1;
}

#if 0
int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpCmdLine, int nCmdShow)
{
    return main(__argc, __argv);
}
#endif
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.