Move various win API calls into native code

Faster, more robust. Should fix #1728196
This commit is contained in:
Kovid Goyal 2017-10-28 13:07:38 +05:30
parent 7357c7d7da
commit b6312f1f12
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 126 additions and 32 deletions

View File

@ -163,7 +163,7 @@
"name": "winutil",
"only": "windows",
"sources": "calibre/utils/windows/winutil.c",
"libraries": "shell32 wininet",
"libraries": "shell32 wininet advapi32",
"cflags": "/X"
},
{

View File

@ -281,6 +281,10 @@ def get_portable_base():
def get_unicode_windows_env_var(name):
winutil = plugins['winutil'][0]
getenv = getattr(winutil, 'getenv', None)
if getenv is not None:
return getenv(unicode(name))
import ctypes
name = unicode(name)
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
@ -293,19 +297,26 @@ def get_unicode_windows_env_var(name):
def get_windows_username():
'''
Return the user name of the currently loggen in user as a unicode string.
Return the user name of the currently logged in user as a unicode string.
Note that usernames on windows are case insensitive, the case of the value
returned depends on what the user typed into the login box at login time.
'''
winutil = plugins['winutil'][0]
username = getattr(winutil, 'username', None)
if username is not None:
return username()
import ctypes
from ctypes import wintypes
try:
advapi32 = ctypes.windll.advapi32
GetUserName = getattr(advapi32, u'GetUserNameW')
GetUserName.argtypes = [wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD)]
GetUserName.restype = wintypes.BOOL
except AttributeError:
pass
else:
buf = ctypes.create_unicode_buffer(257)
n = ctypes.c_int(257)
n = wintypes.DWORD(257)
if GetUserName(buf, ctypes.byref(n)):
return buf.value
@ -313,6 +324,10 @@ def get_windows_username():
def get_windows_temp_path():
winutil = plugins['winutil'][0]
temp_path = getattr(winutil, 'temp_path', None)
if temp_path is not None:
return temp_path()
import ctypes
n = ctypes.windll.kernel32.GetTempPathW(0, None)
if n == 0:
@ -324,6 +339,10 @@ def get_windows_temp_path():
def get_windows_user_locale_name():
winutil = plugins['winutil'][0]
locale_name = getattr(winutil, 'locale_name', None)
if locale_name is not None:
return locale_name()
import ctypes
k32 = ctypes.windll.kernel32
n = 255
@ -334,32 +353,17 @@ def get_windows_user_locale_name():
return u'_'.join(buf.value.split(u'-')[:2])
number_formats = None
def get_windows_number_formats():
# This can be changed to use localeconv() once we switch to Visual Studio
# 2015 as localeconv() in that version has unicode variants for all strings.
global number_formats
if number_formats is None:
import ctypes
from ctypes.wintypes import DWORD
k32 = ctypes.windll.kernel32
n = 25
buf = ctypes.create_unicode_buffer(u'\0'*n)
k32.GetNumberFormatEx.argtypes = [ctypes.c_wchar_p, DWORD, ctypes.c_wchar_p, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int]
k32.GetNumberFormatEx.restype = ctypes.c_int
if k32.GetNumberFormatEx(None, 0, u'123456.7', None, buf, n) == 0:
raise ctypes.WinError()
src = buf.value
thousands_sep, decimal_point = u',.'
idx = src.find(u'6')
if idx > -1 and src[idx+1] != u'7':
decimal_point = src[idx+1]
src = src[:idx]
for c in src:
if c not in u'123456':
thousands_sep = c
break
number_formats = (thousands_sep, decimal_point)
return number_formats
ans = getattr(get_windows_number_formats, 'ans', None)
if ans is None:
winutil = plugins['winutil'][0]
localeconv = getattr(winutil, 'localeconv', None)
if localeconv is not None:
d = localeconv()
thousands_sep, decimal_point = d['thousands_sep'], d['decimal_point']
else:
from locale import localeconv
d = localeconv()
thousands_sep, decimal_point = d['thousands_sep'].decode('mbcs'), d['decimal_point'].decode('mbcs')
ans = get_windows_number_formats.ans = thousands_sep, decimal_point
return ans

View File

@ -121,8 +121,20 @@ class BuildTest(unittest.TestCase):
def test_winutil(self):
from calibre.constants import plugins
winutil = plugins['winutil'][0]
def au(x, name):
self.assertTrue(isinstance(x, unicode), name + '() did not return a unicode string')
for x in winutil.argv():
self.assertTrue(isinstance(x, unicode), 'argv() not returning unicode string')
au(x, 'argv')
for x in 'username temp_path locale_name'.split():
au(getattr(winutil, x)(), x)
d = winutil.localeconv()
au(d['thousands_sep'], 'localeconv')
au(d['decimal_point'], 'localeconv')
for k, v in d.iteritems():
au(v, k)
for k in os.environ.keys():
au(winutil.getenv(unicode(k)), 'getenv-' + k)
def test_sqlite(self):
import sqlite3

View File

@ -42,6 +42,8 @@ wherever possible in this module.
#define UNICODE
#include <Windows.h>
#include <Wininet.h>
#include <LMcons.h>
#include <locale.h>
#include <Python.h>
#include <structseq.h>
#include <timefuncs.h>
@ -228,6 +230,62 @@ winutil_set_max_stdio(PyObject *self, PyObject *args) {
Py_RETURN_NONE;
}
static PyObject *
winutil_getenv(PyObject *self, PyObject *args) {
const wchar_t *q;
if (!PyArg_ParseTuple(args, "u", &q)) return NULL;
wchar_t *ans = _wgetenv(q);
if (ans == NULL) Py_RETURN_NONE;
return PyUnicode_FromWideChar(ans, wcslen(ans));
}
static PyObject *
winutil_username(PyObject *self) {
wchar_t buf[UNLEN + 1] = {0};
DWORD sz = sizeof(buf)/sizeof(buf[0]);
if (!GetUserName(buf, &sz)) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
return PyUnicode_FromWideChar(buf, wcslen(buf));
}
static PyObject *
winutil_temp_path(PyObject *self) {
wchar_t buf[MAX_PATH + 1] = {0};
DWORD sz = sizeof(buf)/sizeof(buf[0]);
if (!GetTempPath(sz, buf)) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
return PyUnicode_FromWideChar(buf, wcslen(buf));
}
static PyObject *
winutil_locale_name(PyObject *self) {
wchar_t buf[LOCALE_NAME_MAX_LENGTH + 1] = {0};
if (!GetUserDefaultLocaleName(buf, sizeof(buf)/sizeof(buf[0]))) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
return PyUnicode_FromWideChar(buf, wcslen(buf));
}
static PyObject *
winutil_localeconv(PyObject *self) {
struct lconv *d = localeconv();
#define W(name) #name, d->_W_##name
return Py_BuildValue(
"{su su su su su su su su}",
W(decimal_point), W(thousands_sep), W(int_curr_symbol), W(currency_symbol),
W(mon_decimal_point), W(mon_thousands_sep), W(positive_sign), W(negative_sign)
);
#undef W
}
static PyObject *
winutil_strftime(PyObject *self, PyObject *args)
{
@ -381,6 +439,26 @@ be a unicode string. Returns unicode strings."
"setmaxstdio(num)\n\nSet the maximum number of open file handles."
},
{"getenv", (PyCFunction)winutil_getenv, METH_VARARGS,
"getenv(name)\n\nGet the value of the specified env var as a unicode string."
},
{"username", (PyCFunction)winutil_username, METH_NOARGS,
"username()\n\nGet the current username as a unicode string."
},
{"temp_path", (PyCFunction)winutil_temp_path, METH_NOARGS,
"temp_path()\n\nGet the current temporary dir as a unicode string."
},
{"locale_name", (PyCFunction)winutil_locale_name, METH_NOARGS,
"locale_name()\n\nGet the current locale name as a unicode string."
},
{"localeconv", (PyCFunction)winutil_localeconv, METH_NOARGS,
"localeconv()\n\nGet the locale conventions as unicode strings."
},
{NULL, NULL, 0, NULL}
};