Interpreter hangs after calling my extension

Ben Hutchings ben.hutchings at roundpoint.com
Thu Jun 28 03:01:55 EDT 2001


I'm trying to write a Python extension that wraps the DLL interface to
CVS that's included with WinCvs.  This is my first Python extension so
it's not clear to me whether the error is in my use of the Python/C
API or my use of the CVS DLL.  Since the former is less obscure I'm
trying to check that first.

The CVS DLLs (there are several versions built with different options)
export a dllglue_main function, which works roughly like the main
function of a normal CVS executables, and several functions whose
names begin with 'dllglue_set', which are used to set a call-back that
replaces getenv and call-backs that replace console input and output.

My extension module 'cvsdll' has a function called main that is a
wrapper for dllglue_main.  It takes a sequence of command arguments,
copies them and passes them to dllglue_main.  It has call-back
functions for console input and output that make use of file-like
objects stored as the 'stdin', 'stdout', and 'stderr' attributes of
the module.  As the code stands now, the module user is supposed
to set these.

When I enter 'cvsdll.main([])' at the Python console, my extension
function is called; it runs and returns as expected (checked in the
VC++ debugger), but the Python interpreter never prints a new prompt
or reacts to input.  Further, if I set cvsdll.stderr = sys.stderr
first, the error message that is successfully written to sys.stderr
(checked in the VC++ debugger) never appears in the console.

So, are the errors in my use of the Python/C API, or the way I'm
compiling my code, or are they elsewhere?

--- begin code ---
#include <Python.h>

#include "cvsgui/common/dll_glue.h"
#undef getenv /* dll_glue.h #defines getenv for use in the DLL */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

PyObject * cvsdll_main(PyObject *, PyObject *);

/* Pointer to DLL's dllglue_main function */
int (*real_dllglue_main)(char const *, int, char **);

/* The call-back functions for the CVS DLL */
long consolein(char *, long);
long consoleout(char *, long);
long consoleerr(char *, long);
/* Common code for consoleout and consolerr */
long consolewrite(char * file_attr_name, char * buf, long len);

static PyObject * module_dict;

__declspec(dllexport) void initcvsdll()
{
    PyObject * module;
    HMODULE dll;
    FARPROC func;

    static PyMethodDef methods[] = {
        {"main", cvsdll_main, METH_VARARGS, "CVS main function"},
        {NULL, NULL},
    };

    dll = LoadLibrary("cvs2ntslib.dll");
    if (dll == NULL)
        PyErr_SetString(PyExc_WindowsError, "Failed to load CVS DLL");
    else
    {
        /* Omitted code to get real_dllglue_main and set call-back
           functions, since it's not Python-related.  This leaves
           func == NULL if and only if it fails. */

        if (func == NULL)
        {
            PyErr_SetString(
                PyExc_WindowsError,
                "Did not find expected functions in CVS DLL");
        }
        else
        {
            /* All OK - so go ahead and create the module */
            module = Py_InitModule("cvsdll", methods);
            module_dict = PyModule_GetDict(module);
        }
    }
}

static PyObject * cvsdll_main(PyObject * self, PyObject * args)
{
    PyObject * result = NULL;
    PyObject * main_args;

    if (PyArg_ParseTuple(args, "O", &main_args))
    {
        long argc;

        /* Add one for the "cvs" in front */
        argc = 1 + PySequence_Length(main_args);
        if (argc >= 1)
        {
            char ** argv;
            long argi;

            /* Add one for the NULL at the end */
            argv = calloc(argc + 1, sizeof(char *));

            /* Convert all arguments into C strings.  Make copies
               since C programs are allowed to modify their argv. */
            argv[0] = strdup("cvs");
            argv[argc] = NULL;
            for (argi = 1; argi < argc; ++argi)
            {
                PyObject * arg;
                PyObject * arg_str;

                arg = PySequence_GetItem(main_args, argi - 1);
                if (arg == NULL)
                    break;
                arg_str = PyObject_Str(arg);
                Py_DECREF(arg);
                if (arg_str == NULL)
                    break;
                argv[argi] = strdup(PyString_AsString(arg_str));
                Py_DECREF(arg_str);
            }

            /* Check that all arguments were successfully converted */
            if (argi == argc)
            {
                /* dllglue_main takes a path name to change the
                   current directory to.  I'm not sure why, since the
                   caller can easily do that.  For now, avoid
                   changing it. */
		result = PyInt_FromLong(real_dllglue_main(".", argc, argv));
            }

            while (--argi >= 0)
                free(argv[argi]);
	    free(argv);
        }
    }

    return result;
}

/* Omitted code for consolein since it isn't being called */

static long consoleout(char * buf, long len)
{
    /* Call cvsdll.stdout.write(buf) */
    return consolewrite("stdout", buf, len);
}

static long consoleerr(char * buf, long len)
{
    /* Call cvsdll.stderr.write(buf) */
    return consolewrite("stderr", buf, len);
}

static long consolewrite(char * file_attr_name, char * buf, long len)
{
    long result = 0;
    PyObject * file;

    file = PyDict_GetItemString(module_dict, file_attr_name);
    if (file != NULL)
    {
        PyObject * buf_obj;
        PyObject * result_obj;

        buf_obj = PyBuffer_FromMemory(buf, len);
        result_obj = PyObject_CallMethod(file, "write", "O", buf_obj);
        Py_DECREF(buf_obj);
        if (result_obj != NULL)
        {
            result = len;
            Py_DECREF(result_obj);
        }
    }

    return result;
}
--- end code ---

This is being compiled with the command line:

cl /Zi /LD /MD pycvs.c /I "c:\program files\python20\include" /I ".."
    "C:\Program Files\Python20\Libs\python20.lib"
    /link /out:c:\progra~1\python20\dlls\cvsdll.pyd /debug

The options should cause the compiler and linker to (a) build a DLL,
(b) include debugging information in it, and (c) make it use the
multi-threaded DLL run-time library.



More information about the Python-list mailing list