Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

loading TCL / Tk symbols dynamically#6442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
efiring merged 14 commits intomatplotlib:masterfrommatthew-brett:dynamic-tkagg
May 24, 2016
Merged
Changes from1 commit
Commits
Show all changes
14 commits
Select commitHold shift + click to select a range
edc80a3
WIP: loading TCL / Tk symbols dynamically
matthew-brettMay 17, 2016
64f01dd
RF: refactor C code to Michael D's commments
matthew-brettMay 17, 2016
89535be
NF: add Windows support for TCL dynamic loading
matthew-brettMay 18, 2016
fc5d060
RF: make dynamic loading of TCL the one true way
matthew-brettMay 18, 2016
1c85968
RF: disable build-time link against TCL/Tk
matthew-brettMay 18, 2016
830d7a3
RF: find TCL / Tk symbols correctly on Windows
matthew-brettMay 19, 2016
4c5a3ab
RF: short-circuit the Tk function tests
matthew-brettMay 20, 2016
94bb457
WIP: Add excerpts from TCL / Tk header
matthew-brettMay 20, 2016
3f5407a
RF: check, edit, refactor Tcl / tk mini header
matthew-brettMay 21, 2016
573aeb5
RF: remove Tcl / Tk header / lib discovery
matthew-brettMay 21, 2016
626a1d2
RF: remove build-time check of Tkinter
matthew-brettMay 22, 2016
0c2c5a0
RF: force TkAgg / Agg extensions with messages
matthew-brettMay 23, 2016
de99a76
BF: fix goofy double "installing" message
matthew-brettMay 23, 2016
aa275c6
TST: add test of tkagg backend import
matthew-brettMay 24, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
NextNext commit
WIP: loading TCL / Tk symbols dynamically
This is an attempt to load the symbols we need from the Tkinter.tkintermodule at run time, rather than by linking at build time.It is one way of building a manylinux wheel that can build on a basicManylinux docker image, and then run with the TCL / Tk library installedon the installing user's machine.  It would also make the situationbetter on OSX, where we have to build against ActiveState TCL forcompatibility with Python.org Python, but we would like to allowrun-time TCL from e.g. homebrew.I have tested this on Debian Jessie Python 2.7 and 3.5, and on OSX 10.9with Python 2.7.Questions:* Would y'all consider carrying something like this approach in  the matplotlib source, but not enabled by default, to help building  binary wheels?* Do you have any better suggestions about how to do this?* My C fu is weak; is there a way of collecting the typedefs I need from  the TCL / Tk headers rather than copying them into the _tkagg.cpp  source file (typdefs starting around line 52)?* My fu for Python C extension modules is also weak; did I configure  exceptions and handle references correctly?
  • Loading branch information
@matthew-brett
matthew-brett committedMay 17, 2016
commitedc80a38e76e6cf61a49a4311b0c5c3101835a44
159 changes: 141 additions & 18 deletionssrc/_tkagg.cpp
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,6 +44,38 @@ typedef struct
Tcl_Interp *interp;
} TkappObject;

#define DYNAMIC_TKINTER

#ifdef DYNAMIC_TKINTER
// Load TCL / Tk symbols from tkinter extension module at run-time.
// Typedefs, global vars for TCL / Tk library functions.
typedef Tcl_Command (*tcl_cc)(Tcl_Interp *interp,
const char *cmdName, Tcl_CmdProc *proc,
ClientData clientData,
Tcl_CmdDeleteProc *deleteProc);
static tcl_cc TCL_CREATE_COMMAND;
typedef void (*tcl_app_res) (Tcl_Interp *interp, ...);
static tcl_app_res TCL_APPEND_RESULT;
typedef Tk_Window (*tk_mw) (Tcl_Interp *interp);
static tk_mw TK_MAIN_WINDOW;
typedef Tk_PhotoHandle (*tk_fp) (Tcl_Interp *interp, const char *imageName);
static tk_fp TK_FIND_PHOTO;
typedef void (*tk_ppb_nc) (Tk_PhotoHandle handle,
Tk_PhotoImageBlock *blockPtr, int x, int y,
int width, int height);
static tk_ppb_nc TK_PHOTO_PUTBLOCK;
typedef void (*tk_pb) (Tk_PhotoHandle handle);
static tk_pb TK_PHOTO_BLANK;
#else
// Build-time linking against system TCL / Tk functions.
#define TCL_CREATE_COMMAND Tcl_CreateCommand
#define TCL_APPEND_RESULT Tcl_AppendResult
#define TK_MAIN_WINDOW Tk_MainWindow
#define TK_FIND_PHOTO Tk_FindPhoto
#define TK_PHOTO_PUTBLOCK Tk_PhotoPutBlock
#define TK_PHOTO_BLANK Tk_PhotoBlank
#endif

static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, char **argv)
{
Tk_PhotoHandle photo;
Expand All@@ -61,25 +93,25 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc,

long mode;
long nval;
if (Tk_MainWindow(interp) == NULL) {
if (TK_MAIN_WINDOW(interp) == NULL) {
// Will throw a _tkinter.TclError with "this isn't a Tk application"
return TCL_ERROR;
}

if (argc != 5) {
Tcl_AppendResult(interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL);
TCL_APPEND_RESULT(interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL);
return TCL_ERROR;
}

/* get Tcl PhotoImage handle */
photo =Tk_FindPhoto(interp, argv[1]);
photo =TK_FIND_PHOTO(interp, argv[1]);
if (photo == NULL) {
Tcl_AppendResult(interp, "destination photo must exist", (char *)NULL);
TCL_APPEND_RESULT(interp, "destination photo must exist", (char *)NULL);
return TCL_ERROR;
}
/* get array (or object that can be converted to array) pointer */
if (sscanf(argv[2], SIZE_T_FORMAT, &aggl) != 1) {
Tcl_AppendResult(interp, "error casting pointer", (char *)NULL);
TCL_APPEND_RESULT(interp, "error casting pointer", (char *)NULL);
return TCL_ERROR;
}
bufferobj = (PyObject *)aggl;
Expand All@@ -88,7 +120,7 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc,
try {
buffer = numpy::array_view<uint8_t, 3>(bufferobj);
} catch (...) {
Tcl_AppendResult(interp, "buffer is of wrong type", (char *)NULL);
TCL_APPEND_RESULT(interp, "buffer is of wrong type", (char *)NULL);
PyErr_Clear();
return TCL_ERROR;
}
Expand All@@ -99,13 +131,13 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc,
/* get array mode (0=mono, 1=rgb, 2=rgba) */
mode = atol(argv[3]);
if ((mode != 0) && (mode != 1) && (mode != 2)) {
Tcl_AppendResult(interp, "illegal image mode", (char *)NULL);
TCL_APPEND_RESULT(interp, "illegal image mode", (char *)NULL);
return TCL_ERROR;
}

/* check for bbox/blitting */
if (sscanf(argv[4], SIZE_T_FORMAT, &bboxl) != 1) {
Tcl_AppendResult(interp, "error casting pointer", (char *)NULL);
TCL_APPEND_RESULT(interp, "error casting pointer", (char *)NULL);
return TCL_ERROR;
}
bboxo = (PyObject *)bboxl;
Expand All@@ -126,7 +158,7 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc,

destbuffer = new agg::int8u[deststride * destheight];
if (destbuffer == NULL) {
Tcl_AppendResult(interp, "could not allocate memory", (char *)NULL);
TCL_APPEND_RESULT(interp, "could not allocate memory", (char *)NULL);
return TCL_ERROR;
}

Expand DownExpand Up@@ -167,7 +199,7 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc,
block.pitch = deststride;
block.pixelPtr = destbuffer;

Tk_PhotoPutBlock(photo, &block, destx, desty, destwidth, destheight);
TK_PHOTO_PUTBLOCK(photo, &block, destx, desty, destwidth, destheight);
delete[] destbuffer;

} else {
Expand All@@ -177,9 +209,9 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc,
block.pixelPtr = buffer.data();

/* Clear current contents */
Tk_PhotoBlank(photo);
TK_PHOTO_BLANK(photo);
/* Copy opaque block to photo image, and leave the rest to TK */
Tk_PhotoPutBlock(photo, &block, 0, 0, block.width, block.height);
TK_PHOTO_PUTBLOCK(photo, &block, 0, 0, block.width, block.height);
}

return TCL_OK;
Expand DownExpand Up@@ -216,11 +248,11 @@ static PyObject *_tkinit(PyObject *self, PyObject *args)

/* This will bomb if interp is invalid... */

Tcl_CreateCommand(interp,
"PyAggImagePhoto",
(Tcl_CmdProc *)PyAggImagePhoto,
(ClientData)0,
(Tcl_CmdDeleteProc *)NULL);
TCL_CREATE_COMMAND(interp,
"PyAggImagePhoto",
(Tcl_CmdProc *)PyAggImagePhoto,
(ClientData)0,
(Tcl_CmdDeleteProc *)NULL);

Py_INCREF(Py_None);
return Py_None;
Expand All@@ -232,6 +264,90 @@ static PyMethodDef functions[] = {
{ NULL, NULL } /* sentinel */
};

#ifdef DYNAMIC_TKINTER
// Functions to fill global TCL / Tk function pointers from tkinter module.

#include <dlfcn.h>
Copy link

@ogriselogriselMay 18, 2016
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This include fails when using Visual C++ for Python 2.7:

src/_tkagg.cpp(270) : fatal error C1083: Cannot open include file: 'dlfcn.h': No such file or directory

https://ci.appveyor.com/project/mdboom/matplotlib/build/1.0.1545/job/724qv5hcfngqa2r9#L1033

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Actually more recent versions of Visual C++ also fail, e.g. in the Python 3.5 Windows build on AppVeyor:

src/_tkagg.cpp(270): fatal error C1083: Cannot open include file: 'dlfcn.h': No such file or directory error: command 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\BIN\\amd64\\cl.exe' failed with exit status 2Command exited with code 1

https://ci.appveyor.com/project/mdboom/matplotlib/build/1.0.1545/job/rk6safsaid63i7ch#L1053


#if PY3K
#define TKINTER_PKG "tkinter"
#define TKINTER_MOD "_tkinter"
// From module __file__ attribute to char *string for dlopen.
#define FNAME2CHAR(s) (PyBytes_AsString(PyUnicode_EncodeFSDefault(s)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This isn't exception proof --PyUnicode_EncodeFSDefault could raise an exception here. Maybe just make a regular old function for this purpose?

#else
#define TKINTER_PKG "Tkinter"
#define TKINTER_MOD "tkinter"
// From module __file__ attribute to char *string for dlopen
#define FNAME2CHAR(s) (PyString_AsString(s))
#endif

void *_dfunc(void *lib_handle, const char *func_name)
{
// Load function, unless there has been a previous error. If so, then
// return NULL. If there is an error loading the function, return NULL
// and set error flag.
static int have_error = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This is fine, but is a sort of unidiomatic pattern. Rather than using a static variablehas_error here, why not just return NULL if there was an error and then call it differently from_func_loader, e.g.:

if (!((TCL_CREATE_COMMAND = (tcl_cc) _dfunc(tkinter_lib, "Tcl_CreateCommand")) &&      (TCL_APPEND_RESULT = (tcl_cc) _dfunc(tkinter_lib, "Tcl_AppendResult")) &&    ... etc ..

void *func = NULL;
if (have_error == 0) {
// reset errors
dlerror();
func = dlsym(lib_handle, func_name);
const char *error = dlerror();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think this only needs to be called iffunc == NULL.

if (error != NULL) {
PyErr_SetString(PyExc_RuntimeError, error);
have_error = 1;
}
}
return func;
}

int _func_loader(void *tkinter_lib)
{
// Fill global function pointers from dynamic lib.
// Return 0 fur success; 1 otherwise.
TCL_CREATE_COMMAND = (tcl_cc) _dfunc(tkinter_lib, "Tcl_CreateCommand");
TCL_APPEND_RESULT = (tcl_app_res) _dfunc(tkinter_lib, "Tcl_AppendResult");
TK_MAIN_WINDOW = (tk_mw) _dfunc(tkinter_lib, "Tk_MainWindow");
TK_FIND_PHOTO = (tk_fp) _dfunc(tkinter_lib, "Tk_FindPhoto");
TK_PHOTO_PUTBLOCK = (tk_ppb_nc) _dfunc(tkinter_lib, "Tk_PhotoPutBlock_NoComposite");
TK_PHOTO_BLANK = (tk_pb) _dfunc(tkinter_lib, "Tk_PhotoBlank");
return (TK_PHOTO_BLANK == NULL);
}

int load_tkinter_funcs(void)
{
// Load tkinter global funcs from tkinter compiled module.
// Return 0 for success, non-zero for failure.
int ret = -1;
PyObject *pModule, *pSubmodule, *pString;

pModule = PyImport_ImportModule(TKINTER_PKG);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It's a matter of personal preference, but I generally like to avoid the excessive nesting by:

  1. setting allPyObject * to NULL in their declaration
  2. doing:
if (pModule == NULL) {  goto exit;}
  1. having
exit:   Py_XDECREF(pString);   Py_XDECREF(pSubmodule);   Py_XDECREF(pModule);   return ret;

at the bottom

if (pModule != NULL) {
pSubmodule = PyObject_GetAttrString(pModule, TKINTER_MOD);
if (pSubmodule != NULL) {
pString = PyObject_GetAttrString(pSubmodule, "__file__");
if (pString != NULL) {
char *tkinter_libname = FNAME2CHAR(pString);
void *tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY);
if (tkinter_lib == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"Cannot dlopen tkinter module file");
} else {
ret = _func_loader(tkinter_lib);
// dlclose probably safe because tkinter has been
// imported.
dlclose(tkinter_lib);
}
Py_DECREF(pString);
}
Py_DECREF(pSubmodule);
}
Py_DECREF(pModule);
}
return ret;
}
#endif

#if PY3K
static PyModuleDef _tkagg_module = { PyModuleDef_HEAD_INIT, "_tkagg", "", -1, functions,
NULL, NULL, NULL, NULL };
Expand All@@ -244,13 +360,20 @@ PyMODINIT_FUNC PyInit__tkagg(void)

import_array();

return m;
#ifdef DYNAMIC_TKINTER
return (load_tkinter_funcs() == 0) ? m : NULL;
#else
return m
#endif
}
#else
PyMODINIT_FUNC init_tkagg(void)
{
import_array();

Py_InitModule("_tkagg", functions);
#ifdef DYNAMIC_TKINTER
load_tkinter_funcs();
#endif
}
#endif

[8]ページ先頭

©2009-2025 Movatter.jp