Introduction
Windows Phone 8 (WP8) supports native code. Developers can write applications using C/C++ directly, and port existing achievements to Windows Phone. But there are many differences between APIs on WP8 and APIs on Win32. These differences exist in many categories, such as native thread, synchronization method, file find, library load, etc., which brings a lot of workload for code porting.
Python is a dynamic script language and has many function modules. It is easy to learn and use. Python can improve the flexibility of the application and existing Python modules can also be used to speed up the development procedure. As Python is a dynamic language, apps can use this feature to create logic or control some application functions dynamically. The Python interpreter is written in C. Because WP8 supports native code, we can compile Python source code on WP8. Thus, it makes it possible to use Python on WP8.
Because of the limitations of APIs on WP8, changes have to be made in some source code of Python to be compiled successfully. Furthermore, some APIs such as thread, Windows registry are not supported, not all modules or features can be ported to WP8.
This article talks about how to compile Python source code on WP8, the changes made on the source code, and gives an example of using Python in a WP8 native app.
Main Changes in Source Code
Thread Related Functions
As WP8 does not support native threading, thread related functions cannot be compiled on WP8. So we remove these source code files from the project and change pyconfig.h.
#undef WITH_THREAD
These source files are removed from the project:
modules\posixmodule
modules\mmapmodule
modules\threadmodule
Python\thread.c
Changes in timemodule.c:
#if !defined(ENV_WP)
static PyObject *
time_sleep(PyObject *self, PyObject *args)
{
double secs;
if (!PyArg_ParseTuple(args, "d:sleep", &secs))
return NULL;
if (floatsleep(secs) != 0)
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
#endif
#if !defined(ENV_WP)
{"sleep", time_sleep, METH_VARARGS, sleep_doc},
#endif
Changes in config.c:
#if !defined(ENV_WP)
{"nt", initnt},
#endif
#if !defined(ENV_WP)
{"mmap", initmmap},
#endif
#if !defined(ENV_WP)
{"_winreg", init_winreg},
#endif
#if !defined(ENV_WP)
{"_subprocess", init_subprocess},
#endif
Because threads are not supported, the OS Python module cannot work correctly, which causes site.py to not import correctly. So site.py should be imported by default.
In pythonrun.c:
#if !defined(ENV_WP)
if (!Py_NoSiteFlag)
initsite();
#endif
Changes to the getenv Function
The getenv
function on WP8 returns no environment value, so we replace this function with a new one called getenv_win8
which returns variables for PYTHONPATH
and PYTHONHOME
.
First, change pydebug.h:
extern char *getenv_win8(char *name);
#define Py_GETENV(s) (Py_IgnoreEnvironmentFlag ? NULL : getenv_win8(s))
Create a new source file named Python_wp8.cpp, and add the function getenv_win8
.
bool GetInstall_Dir_win8(char *Buf,int BufSize)
{
if( BufSize == 0 || Buf == NULL )
return false;
WideCharToMultiByte( CP_ACP, 0, Windows::ApplicationModel::Package::Current->InstalledLocation ->
Path->Data(), -1, Buf, BufSize-1, NULL, NULL );
Buf[BufSize-1] = 0;
return true;
}
char *getenv_win8(char *name)
{
char Buf[1024];
static char RetBuf[1024];
GetInstall_Dir_win8(Buf,1024);
if( stricmp(name,"PYTHONPATH") == 0 ){
sprintf(RetBuf,"%s\\python\\lib",Buf);
return RetBuf;
}else if( stricmp(name,"PYTHONHOME") == 0 ){
sprintf(RetBuf,"%s\\python",Buf);
return RetBuf;
}else
return NULL;
}
Note: The Python_wp8.cpp file should be compiled with the Windows Runtime.
Changes to the loadlibraryex Function
First, LoadLibraryEx
cannot load the library with an absolute path, so GetFullPathName
should not be called before the LoadLibraryEx
function. Second, LoadPackagedLibrary
should be used other than LoadLibraryEx
.
Changes in dynload_win.c.
#if !defined(ENV_WP)
old_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
if (GetFullPathName(pathname,
sizeof(pathbuf),
pathbuf,
&dummy)) {
ULONG_PTR cookie = _Py_ActivateActCtx();
hDLL = LoadLibraryEx(pathname, NULL,
LOAD_WITH_ALTERED_SEARCH_PATH);
_Py_DeactivateActCtx(cookie);
}
SetErrorMode(old_mode);
#endif
#if defined(ENV_WP)
{
ULONG_PTR cookie = _Py_ActivateActCtx();
MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, pathname, -1,wacModuleName, 512 );
hDLL = (HINSTANCE)SRP_LoadPackage(wacModuleName);
_Py_DeactivateActCtx(cookie);
}
#endif
void *SRP_LoadPackage(wchar_t *wacModuleName)
{
return (void *)LoadPackagedLibrary(wacModuleName,0);
}
Changes to the FindFirstFile Function
FindFirstFile
is not supported, FindFirstFileEx
should be used with a Unicode string. Therefore the function in import.c has to be changed.
static int
case_ok(char *buf, Py_ssize_t len, Py_ssize_t namelen, char *name)
{
#if defined(MS_WINDOWS)
#if !defined(ENV_WP)
WIN32_FIND_DATA data;
#else
wchar_t wacFileName[512] ;
char acFileName[512] ;
WIN32_FIND_DATAW data;
#endif
HANDLE h;
if (Py_GETENV("PYTHONCASEOK") != NULL)
return 1;
#if !defined(ENV_WP)
h = FindFirstFile(buf, &data);
#else
MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, buf, -1,wacFileName, 512 );
h = FindFirstFileExW(wacFileName,FindExInfoStandard,&data,FindExSearchNameMatch,NULL, 0);
#endif
if (h == INVALID_HANDLE_VALUE) {
PyErr_Format(PyExc_NameError,
"Can't find file for module %.100s\n(filename %.300s)",
name, buf);
return 0;
}
FindClose(h);
#if !defined(ENV_WP)
return strncmp(data.cFileName, name, namelen) == 0;
#else
WideCharToMultiByte( CP_ACP, 0, data.cFileName, -1, acFileName, 511, NULL, NULL );
return strncmp(acFileName, name, namelen) == 0;
#endif
…
Changes to Other Files or Functions
There are some other changes in the source code, please check the source code.
The full source change with the WP8 project can be downloaded from http://code.google.com/p/c-python-for-windows-phone8.
Example of Using Python in Native Apps on WP8
1. Create Project
Open VS2012 and create a native project for Windows Phone 8 as follows:
2. Add python27.dll to the Project, Set its Property "content" to Yes
3. Set Include Directories and Library Search Path
4. Add python27.lib
5. Create pytest.py, and Add It to the Project, Set its Property Content to Yes
def add(a,b):
return a + b
The Python code to be tested is simple. It counts the sum of two numbers.
6. Create C++ Code to Call Python
#include <windows.h>
#if defined(_DEBUG)
#undef _DEBUG
#include "python.h"
#define _DEBUG
#else
#include "python.h"
#endif
void test()
{
PyObject *pName, *pModule, *pDict, *pFunc, *pArgs, *pRetVal;
Py_Initialize();
if ( !Py_IsInitialized() ){
return;
}
pName = PyString_FromString("pytest");
pModule = PyImport_Import(pName);
if ( !pModule ){
OutputDebugString(L"can't find pytest.py\n");
return;
}
pDict = PyModule_GetDict(pModule);
if ( !pDict ){
return;
}
pFunc = PyDict_GetItemString(pDict, "add");
if ( !pFunc || !PyCallable_Check(pFunc) ) {
OutputDebugString(L"can't find function [add]\n");
return;
}
pArgs = PyTuple_New(2);
PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",3));
PyTuple_SetItem(pArgs, 1, Py_BuildValue("l",4));
pRetVal = PyObject_CallObject(pFunc, pArgs);
char ResultBuf[512];
wchar_t ResultBufW[512];
sprintf_s(ResultBuf,512,"function return value : %ld\r\n", PyInt_AsLong(pRetVal));
MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, ResultBuf, -1,ResultBufW, 512 );
OutputDebugString(ResultBufW);
Py_DECREF(pName);
Py_DECREF(pArgs);
Py_DECREF(pModule);
Py_DECREF(pRetVal);
Py_Finalize();
return;
}
Compile and run, the result will be printed in the output window.