Skip to content

Commit 830d7a3

Browse files
committed
RF: find TCL / Tk symbols correctly on Windows
As Christoph G found, we can't use the tkinter extension module to find the TCL / Tk symbols on Windows, because Windows DLLs do not return the addresses of symbols they import from GetProcAddress. Instead, iterate through the modules loaded in the current process to find the TCL and Tk symbols. See: * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682621(v=vs.85).aspx * https://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx
1 parent 1c85968 commit 830d7a3

File tree

2 files changed

+120
-30
lines changed

2 files changed

+120
-30
lines changed

setupext.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,6 +1732,8 @@ def add_flags(self, ext):
17321732
ext.include_dirs.extend(['win32_static/include/tcl85'])
17331733
else:
17341734
ext.include_dirs.extend(['win32_static/include/tcl86'])
1735+
# PSAPI library needed for finding TCL / Tk at run time
1736+
ext.libraries.extend(['psapi'])
17351737

17361738
elif sys.platform == 'darwin':
17371739
# this config section lifted directly from Imaging - thanks to

src/_tkagg.cpp

Lines changed: 118 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,119 @@ static PyMethodDef functions[] = {
252252
{ NULL, NULL } /* sentinel */
253253
};
254254

255-
// Functions to fill global TCL / Tk function pointers from tkinter module.
255+
// Functions to fill global TCL / Tk function pointers by dynamic loading
256+
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
256257

258+
/*
259+
* On Windows, we can't load the tkinter module to get the TCL or Tk symbols,
260+
* because Windows does not load symbols into the library name-space of
261+
* importing modules. So, knowing that tkinter has already been imported by
262+
* Python, we scan all modules in the running process for the TCL and Tk
263+
* function names.
264+
*/
265+
#include <windows.h>
266+
#define PSAPI_VERSION 1
267+
#include <psapi.h>
268+
// Must be linked with 'psapi' library
269+
270+
FARPROC _dfunc(HMODULE lib_handle, const char *func_name)
271+
{
272+
// Load function `func_name` from `lib_handle`.
273+
// Set Python exception if we can't find `func_name` in `lib_handle`.
274+
// Returns function pointer or NULL if not present.
275+
276+
char message[100];
277+
278+
FARPROC func = GetProcAddress(lib_handle, func_name);
279+
if (func == NULL) {
280+
sprintf(message, "Cannot load function %s", func_name);
281+
PyErr_SetString(PyExc_RuntimeError, message);
282+
}
283+
return func;
284+
}
285+
286+
int get_tcl(HMODULE hMod)
287+
{
288+
// Try to fill TCL global vars with function pointers. Return 0 for no
289+
// functions found, 1 for all functions found, -1 for some but not all
290+
// functions found.
291+
TCL_CREATE_COMMAND = (tcl_cc) GetProcAddress(hMod, "Tcl_CreateCommand");
292+
if (TCL_CREATE_COMMAND == NULL) { // Maybe not TCL module
293+
return 0;
294+
}
295+
TCL_APPEND_RESULT = (tcl_app_res) _dfunc(hMod, "Tcl_AppendResult");
296+
return (TCL_APPEND_RESULT == NULL) ? -1 : 1;
297+
}
298+
299+
int get_tk(HMODULE hMod)
300+
{
301+
// Try to fill Tk global vars with function pointers. Return 0 for no
302+
// functions found, 1 for all functions found, -1 for some but not all
303+
// functions found.
304+
TK_MAIN_WINDOW = (tk_mw) GetProcAddress(hMod, "Tk_MainWindow");
305+
if (TK_MAIN_WINDOW == NULL) { // Maybe not Tk module
306+
return 0;
307+
}
308+
TK_FIND_PHOTO = (tk_fp) _dfunc(hMod, "Tk_FindPhoto");
309+
TK_PHOTO_PUTBLOCK = (tk_ppb_nc) _dfunc(hMod,
310+
"Tk_PhotoPutBlock_NoComposite");
311+
TK_PHOTO_BLANK = (tk_pb) _dfunc(hMod, "Tk_PhotoBlank");
312+
return ((TK_FIND_PHOTO == NULL) ||
313+
(TK_PHOTO_PUTBLOCK == NULL) ||
314+
(TK_PHOTO_BLANK == NULL)) ? -1 : 1;
315+
}
316+
317+
int load_tkinter_funcs(void)
318+
{
319+
// Load TCL and Tk functions by searching all modules in current process.
320+
// Return 0 for success, non-zero for failure.
321+
322+
HMODULE hMods[1024];
323+
HANDLE hProcess;
324+
DWORD cbNeeded;
325+
unsigned int i;
326+
int found_tcl = 0;
327+
int found_tk = 0;
328+
329+
// Returns pseudo-handle that does not need to be closed
330+
hProcess = GetCurrentProcess();
331+
332+
// Iterate through modules in this process looking for TCL / Tk names
333+
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
334+
for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
335+
if (!found_tcl) {
336+
found_tcl = get_tcl(hMods[i]);
337+
if (found_tcl == -1) {
338+
return 1;
339+
}
340+
}
341+
if (!found_tk) {
342+
found_tk = get_tk(hMods[i]);
343+
if (found_tk == -1) {
344+
return 1;
345+
}
346+
}
347+
if (found_tcl && found_tk) {
348+
return 0;
349+
}
350+
}
351+
}
352+
353+
if (found_tcl == 0) {
354+
PyErr_SetString(PyExc_RuntimeError, "Could not find TCL routines");
355+
} else {
356+
PyErr_SetString(PyExc_RuntimeError, "Could not find Tk routines");
357+
}
358+
return 1;
359+
}
360+
361+
#else // not Windows
362+
363+
/*
364+
* On Unix, we can get the TCL and Tk synbols from the tkinter module, because
365+
* tkinter uses these symbols, and the symbols are therefore visible in the
366+
* tkinter dynamic library (module).
367+
*/
257368
#if PY3K
258369
#define TKINTER_PKG "tkinter"
259370
#define TKINTER_MOD "_tkinter"
@@ -273,31 +384,8 @@ char *fname2char(PyObject *fname)
273384
#define fname2char(s) (PyString_AsString(s))
274385
#endif
275386

276-
#if defined(_MSC_VER)
277-
#include <windows.h>
278-
#define LIB_PTR_TYPE HMODULE
279-
#define LOAD_LIB(name) LoadLibrary(name)
280-
#define CLOSE_LIB(name) FreeLibrary(name)
281-
FARPROC _dfunc(LIB_PTR_TYPE lib_handle, const char *func_name)
282-
{
283-
// Load function `func_name` from `lib_handle`.
284-
// Set Python exception if we can't find `func_name` in `lib_handle`.
285-
// Returns function pointer or NULL if not present.
286-
287-
char message[100];
288-
289-
FARPROC func = GetProcAddress(lib_handle, func_name);
290-
if (func == NULL) {
291-
sprintf(message, "Cannot load function %s", func_name);
292-
PyErr_SetString(PyExc_RuntimeError, message);
293-
}
294-
return func;
295-
}
296-
#else
297387
#include <dlfcn.h>
298-
#define LIB_PTR_TYPE void*
299-
#define LOAD_LIB(name) dlopen(name, RTLD_LAZY)
300-
#define CLOSE_LIB(name) dlclose(name)
388+
301389
void *_dfunc(void *lib_handle, const char *func_name)
302390
{
303391
// Load function `func_name` from `lib_handle`.
@@ -313,9 +401,8 @@ void *_dfunc(void *lib_handle, const char *func_name)
313401
}
314402
return func;
315403
}
316-
#endif
317404

318-
int _func_loader(LIB_PTR_TYPE lib)
405+
int _func_loader(void *lib)
319406
{
320407
// Fill global function pointers from dynamic lib.
321408
// Return 1 if any pointer is NULL, 0 otherwise.
@@ -339,7 +426,7 @@ int load_tkinter_funcs(void)
339426
// Load tkinter global funcs from tkinter compiled module.
340427
// Return 0 for success, non-zero for failure.
341428
int ret = -1;
342-
LIB_PTR_TYPE tkinter_lib;
429+
void *tkinter_lib;
343430
char *tkinter_libname;
344431
PyObject *pModule = NULL, *pSubmodule = NULL, *pString = NULL;
345432

@@ -359,21 +446,22 @@ int load_tkinter_funcs(void)
359446
if (tkinter_libname == NULL) {
360447
goto exit;
361448
}
362-
tkinter_lib = LOAD_LIB(tkinter_libname);
449+
tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY);
363450
if (tkinter_lib == NULL) {
364451
PyErr_SetString(PyExc_RuntimeError,
365452
"Cannot dlopen tkinter module file");
366453
goto exit;
367454
}
368455
ret = _func_loader(tkinter_lib);
369456
// dlclose probably safe because tkinter has been imported.
370-
CLOSE_LIB(tkinter_lib);
457+
dlclose(tkinter_lib);
371458
exit:
372459
Py_XDECREF(pModule);
373460
Py_XDECREF(pSubmodule);
374461
Py_XDECREF(pString);
375462
return ret;
376463
}
464+
#endif // end not Windows
377465

378466
#if PY3K
379467
static PyModuleDef _tkagg_module = { PyModuleDef_HEAD_INIT, "_tkagg", "", -1, functions,

0 commit comments

Comments
 (0)