Fun with SWIG, wxPython, and the Windows event system
One of the things that I do at the day job is write support for various models of check scanners into our scanning application. It’s a python app, running on Windows using wxPython for the interface. And the one constant in the whole thing is that no two scanners work alike, connect similarly, or have remotely similar APIs. So far, I’ve done:
– Serial
– Serial protocol over Ethernet
– XML over HTTP
– COM Based
– COM Based with Event Callbacks
– SWIGified dll interface
– SWIGified dll interface, with Event Messages
The last scanner that I worked on (which shall go unnamed due to NDA (and really, it’s a published API. Why an NDA? (It’s not like people developing for your scanner wouldn’t like it if everyone copied the same interface))) was the most involved so far, at least to the point of getting the first image out of it. After that, it actually was pretty simple. (And to be fair, there is something called the Ranger API, which does support a good number of scanners, but not all of the ones that we’re interested in. It’s up there, under COM with event callbacks).
Step 1: Getting a SWIG wrapper of the dll to compile using Visual Studio .NET 2003. I’m using VS.net 2003 since I have to stay with what compiled Python 2.4. So, it’s not modern, but it works for what I need it to do. There are a lot of little bits to getting a working SWIG wrapper, especially with debug mode. I gave up on a full debug version of this one due to the difficulty of building a debug version of wxWindows/wxPython on top of all the other python modules that I’d need. First thing is to download SWIG and any other dependencies. I’m on the 1.3 tree of SWIG, later versions may differ. You need to start with a dll project, add the appropriate headers and whatnot. The python libraries and the driver libraries need to be available for linking, the output needs to be put in a \_{module}.pyd file (or
\_{module}\_d.pyd for the debug version). There needs to be a SWIG .i interface file with a custom build step of:
swig.exe -python -o “$(ProjectDir)\$(InputName)_wrap.cxx” “$(InputPath)”
with an output of “$(ProjectDir)\$(InputName)_wrap.cxx”
A minimal .i file for wrapping a windows dll:
%module XVX
%{
/* Includes the header in the wrapper code */
#include “stdafx.h”
#include “XXinterface.h”
%}
/* Parse the header file to generate wrappers */
%include
%include “XXinterface.h”
This .i should swigify and compile, but is not the most useful thing in the world. I had trouble linking, even though all the files to be linked were available. I finally tracked it down to the calling convention — where the last SWIG dll wrapper I made used fastcall, this one required cdecl.
Step 2: Getting a usable hWnd reference from wxPython to a SWIG wrapper so that it worked. The window handle as returned from wxPython’s window.GetHandle() is a python int, which SWIG doesn’t like to automatically convert to a hWnd. So, there’s a little typemap magic in the swig interface file to take that Python integer and turn it into something usable.
/* handle hwnds */
%typemap(in) (HWND Handle) {
$1 = (HWND) ((int)PyInt_AsLong($input));
}
It’s a little ganky in that I’m taking a random integer and making it a window handle, but looking at all the typedefs, I’m pretty sure that it’s a valid approach. What I’m not 100% sure of is if this will work on 64bit machines. I reserve the right to revisit this when I have a 64bit test machine with dev tools.
Step 3: Hooking the Event system so that we could catch ON_MESSAGE events.
The sample code I have is for a MFC C++ app, and it has a message map:
BEGIN_MESSAGE_MAP()
ON_MESSAGE(CUSTOM_EVENT,myHandler),
…
END_MESSAGE_MAP()
It turns out that while wxWindows/wxPython does get most events (mouse, command, menu, etc) and allow them to be handled by registering a handler, the ON_MESSAGE event is not one of them. The messages come into the application through a WndProc. The only reference I found in google for this was a faq page which references a MSWWindowProc that you have to override. That didn’t appear to work, which led to this list message (which is archived at a few places on the web, but on devshed.com, it doesn’t have the all important link to the answer) that links to code for overriding the WndProc and getting all the messages that you need. Once you have that, it’s a simple matter of filtering for the message type sent by the driver.
Step 4: Fleshing out the SWIG interface so that it’s not quite as painful to use. There are a lot of methods that pass back short strings in a buffer that you provide. That’s easily handled using a typemap pair in the .i file. This fragment will take the char* outString, BYTE MaxLen pair, wherever they appear, and drop them from the required python arguments of the function, and append them to the result. It could be a bit better, in that it returns all 255 characters from the buffer and it returns an array of [boolean, string] to python. I’m still working on a fix for that.
/* handle the strings */
%typemap(in,numinputs=0) (char* outString, BYTE MaxLen)(char s[255]) {
$1 = s;
$2 = 255;
}
%typemap(argout) (char* outString, BYTE MaxLen){
$result = SWIG_Python_AppendOutput($result, SWIG_FromCharPtrAndSize($1,$2));
}
I tend to target named parameters, rather than target any type combination like char*, BYTE. It’s easy enough to apply this to other names:
%apply (char* outString, BYTE MaxLen) { (char* ErrorString, BYTE MaxLen) };
Next, the ON_MESSAGE event has two parameters, a message and one that’s either a pointer or an int. Since that pointer is delivered directly to python without being able to typemap it to something else, I’ve added a couple of convenience methods for getting the contents of that pointer. (without pulling ctypes into it) The typemap does something similar to the hWnd typemap above, and the inline function just returns my input, but SWIG will take that pointer and turn it into the python representation of that structure.
%typemap(in) (ImagesStruct *pImageStruct) {
$1 = (ImagesStruct *) PyInt_AsLong($input);
}
%inline %{
ImagesStruct * WINAPI _GetImageStruct(ImagesStruct *pImageStruct ) {
return pImageStruct;
}
%}