Using SIP on extended wx

I am using SIP to make a python wrapper for a C++ lib than extends wxWidgets. As an example I have a C++ function that takes a wxWindow* as an argument:

void foo(wxWindow* w);

On the python side the wxWindow type needs to recognized as a wx.Window (asside: in the spydor variable explorer I actually see _core.Window as the python type for a wx.Window). This is proving to be difficult. If in my .sip file I try

class wxWindow /PyName=Window/;

then on the python side it expects a Window, not a wx.Window as the argument type. i.e. I need to get the module name prefix wx. in there somehow.

If I try class wxWindow /PyName=wx.Window/;

I get ‘Annotation has a value of the wrong type’ from sip-build.

Possibly the correct solution is in my .sip file to include a .sip file from the wxPyhon source tree, but which one? I tried

%Import(name=…/…/…/Phoenix-master/src/wxpy_api.sip)

then sip-build tells me ‘No %Module has been specified for module defined in …/…/…/Phoenix-master/src/wxpy_api.sip’

Any guidance on how to reconcile these types would be much appreciated.
Also if there is more approriate list for this question, I am all ears.
Regards
Jan

I haven’t actually done this yet, but I’ll share what I do know or can guess with confidence.

First, you’ll want to work from a wxPython source tarfile, not a checkout from git. This is because most of the .sip files are generated as part of the build, and are not committed to git. If you really need to work from the master branch, then fetching a source archive from https://wxpython.org/Phoenix/snapshot-builds/ will give you the most up to date version of the source code. If you really, really need to use a checkout from git then you will need to run some build.py commands in that source tree to generate those files for yourself.

In those source archives the generated sip files are located at .../sip/gen and you probably want to %Import the _core.sip file from there.

The dropping of the leading wx from the names is handled in the %Module directive, like this:

%Module( name=wx._core,
         keyword_arguments="All",
         use_argument_names=True,
         all_raise_py_exception=True,
         language="C++")
{
    %AutoPyName(remove_leading="wx")
};

There are also some answers for questions you haven’t run into yet :wink:

You’ll likely need to ensure that you are using the same instance of the SIP runtime library that wxPython is using. wxPython provides the library in the wx.siplib module. The headers for that version of the siplib are located in .../sip/siplib in the source tarball, so you’ll want to ensure that the C++ compile flags for your extension module include a -I flag pointing to that folder.

Also, you will need to change the name that your module uses to locate the siplib extension module. You can do that with a -n flag passed on the sip command line. Here is the list of sip command line flags that I use for the wxPython builds:

        self.SIPOPTS  = ' '.join([
                         '-w',    # enable warnings
                         '-o',    # turn on auto-docstrings
                         '-g',    # turn on acquire/release of GIL for everything
                         '-n', 'wx.siplib', # name of the module containing the siplib
                         '-I', os.path.join(phoenixDir(), 'src'),
                         '-I', os.path.join(phoenixDir(), 'sip', 'gen'),
                         ])

Hello Robin, Thanks so much for your help. I have got the first parts that you suggested implemented. At run time when python imports my wrapper lib I am getting

python3 -c "import wx;import PyPlotting;"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
RuntimeError: the wx._core module failed to register with the sip module

which is probably what you were anticipating. I have an early -I compiler include flag into the …/sip/siplib folder. However sip (5.1) is also pulling in those .c files from its own folder which may or may not be a problem. I have tried this with the latest tartball wxPython-4.1.0a1.dev4539+c51cdd23 and with the tartball for the exact version of my wxPython installation wxPython-4.0.7.post2, same result for both.

Regarding the last item: My sip command line doesn’t have an -n flag:

usage: sip-install [-h] [-V] [--quiet] [--verbose] [--build-dir DIR]
                   [--target-dir DIR] [--api-dir DIR] [--concatenate N]
                   [--debug] [--no-docstrings] [--pep484-pyi]
                   [--protected-is-public] [--no-protected-is-public]
                   [--tracing]

I am not sure what wx.siplib is and why or how to hook it into my build.
I am using sip 5.1.0 in case that matters.

If I can get this working I will follow up with a summary of what I learned.
Thanks
Jan

I’m still on sip version 4.19.19 on master, and 4.19.16 on the 4.0.x branch. You can get the exact binary that the wxPython build downloads for itself from https://wxpython.org/Phoenix/tools/.

OK Apparently new users are not allowed to upload attachments! So I have pasted my configure.py for running sip4 below. The only change I needed to make to my .sip file for my wrapper was to add one line at the start:

%Import _core.sip

That’s it. The _core.sip comes from the wxPython source tree. The secret sauce to make things work is the -n wx.siplib passed to the sip command line. I might be possible do to this without downloading the source tree, as the wxPython binary install does include sip.h and wx.siplib. But I could not find _core.sip in the binary tree. So for now you need the source tree for _core.sip and all its many dependencies.

If anyone needs more info I will be happy to provide it.

configure.py:
import os
import sipconfig

# The name of the SIP build file generated by SIP and used by the build
# system.
build_file = “PyPlotting.sbf”

# Get the SIP configuration information.
config = sipconfig.Configuration()

# Run SIP to generate the code.
# Add includes to pick .sip from the WxPython build tree at /home/jan/wxPython-4.1
# Note: we are not picking C headers (*.h) YET, that comes below
# The secret sauce seems to be the the -n wx.siplib without that the module won’t load into python3
# I think this tells sip to use Robin’s build of siblib (c files in wxPython-4.1/src) instead of building a local copy
os.system(" “.join([config.sip_bin, “-c”, “.”, “-I/home/jan/wxPython-4.1/sip/gen/”,”-I/home/jan/wxPython-4.1/src", “-n wx.siplib”,"-b", build_file, “PyPlotting.sip”]))

# Create the Makefile.
makefile = sipconfig.SIPModuleMakefile(config, build_file)

#
# Here is where we tell the build where to find sip.h and any other headers in wxPython-4.1 source tree
#
makefile.extra_include_dirs=["/home/jan/wxPython-4.1/sip/siplib","…/" ,"…/…/","/usr/lib/x86_64-linux-gnu/wx/include/gtk3-unicode-3.0","/usr/include/wx-3.0"]
#
# Some compile flags required by wxWidgets
#
makefile.extra_defines=[“HAVE_CONFIG_H”,"_FILE_OFFSET_BITS=64",“WXUSINGDLL”,"__WXGTK__"]
#
# replace …/bin/Debug with a path to your extension lib
# /home/jan/.local/lib/python3.7/site-packages/wx is the location of the shared wx libs used by wxPython
# You will see that these are sent to the linker with -rpath, which should set the runtime path as well.
# This may be important because its easy to wxWidget .so’s sprinkled in various places in a linux dist.
#
makefile.extra_lib_dirs=["…/bin/Debug","/home/jan/.local/lib/python3.7/site-packages/wx"]
# Add the library we are wrapping. The name doesn’t include any platform
# specific prefixes or extensions (e.g. the “lib” prefix on UNIX, or the
# “.dll” extension on Windows).
makefile.extra_libs = [“PyPlotting”]

# Generate the Makefile itself.
makefile.generate()