Improving swig to wrap pointers as PyLong_AsVoidPtr - to avoid Android memory addresses overflowing as an int.

This commit is contained in:
Jonathan Thomas
2026-02-02 21:14:01 -06:00
parent 23b4e748ea
commit 44f92a0387
10 changed files with 201 additions and 13 deletions
+4
View File
@@ -27,6 +27,10 @@
%include "std_vector.i"
%include "std_map.i"
%include <stdint.i>
%apply uint64_t { uintptr_t };
// Ignore QWidget overloads (Qt types are not wrapped in Java bindings)
%ignore openshot::QtPlayer::SetQWidget(QWidget *);
/* Unhandled STL Exception Handling */
%include <std_except.i>
+106
View File
@@ -27,6 +27,9 @@
%include "std_vector.i"
%include "std_map.i"
%include <stdint.i>
%apply uint64_t { uintptr_t };
class QWidget;
/* Unhandled STL Exception Handling */
%include <std_except.i>
@@ -34,6 +37,8 @@
/* Include shared pointer code */
%include <std_shared_ptr.i>
%typemap(in) QWidget *;
/* Mark these classes as shared_ptr classes */
#ifdef USE_IMAGEMAGICK
%shared_ptr(Magick::Image)
@@ -98,6 +103,91 @@
#include "Timeline.h"
#include "Qt/VideoCacheThread.h"
#include "ZmqLogger.h"
#include <QtWidgets/QWidget>
static void *openshot_swig_pylong_as_ptr(PyObject *obj) {
if (!obj) {
return nullptr;
}
void *ptr = PyLong_AsVoidPtr(obj);
if (PyErr_Occurred()) {
PyErr_Clear();
return nullptr;
}
return ptr;
}
static void *openshot_swig_get_qwidget_ptr(PyObject *obj) {
if (!obj || obj == Py_None) {
return nullptr;
}
if (PyLong_Check(obj)) {
return openshot_swig_pylong_as_ptr(obj);
}
const char *sip_modules[] = {"sip", "PyQt6.sip", "PyQt5.sip"};
for (size_t i = 0; i < (sizeof(sip_modules) / sizeof(sip_modules[0])); ++i) {
PyObject *mod = PyImport_ImportModule(sip_modules[i]);
if (!mod) {
PyErr_Clear();
continue;
}
PyObject *unwrap = PyObject_GetAttrString(mod, "unwrapinstance");
if (unwrap && PyCallable_Check(unwrap)) {
PyObject *addr = PyObject_CallFunctionObjArgs(unwrap, obj, NULL);
if (addr) {
void *ptr = openshot_swig_pylong_as_ptr(addr);
Py_DECREF(addr);
if (ptr) {
Py_DECREF(unwrap);
Py_DECREF(mod);
return ptr;
}
}
}
Py_XDECREF(unwrap);
Py_DECREF(mod);
}
const char *shiboken_modules[] = {"shiboken6", "shiboken2"};
for (size_t i = 0; i < (sizeof(shiboken_modules) / sizeof(shiboken_modules[0])); ++i) {
PyObject *mod = PyImport_ImportModule(shiboken_modules[i]);
if (!mod) {
PyErr_Clear();
continue;
}
PyObject *get_ptr = PyObject_GetAttrString(mod, "getCppPointer");
if (get_ptr && PyCallable_Check(get_ptr)) {
PyObject *ptrs = PyObject_CallFunctionObjArgs(get_ptr, obj, NULL);
if (ptrs) {
PyObject *addr = ptrs;
if (PyTuple_Check(ptrs) && PyTuple_Size(ptrs) > 0) {
addr = PyTuple_GetItem(ptrs, 0);
}
void *ptr = openshot_swig_pylong_as_ptr(addr);
Py_DECREF(ptrs);
if (ptr) {
Py_DECREF(get_ptr);
Py_DECREF(mod);
return ptr;
}
}
}
Py_XDECREF(get_ptr);
Py_DECREF(mod);
}
return nullptr;
}
static int openshot_swig_is_qwidget(PyObject *obj) {
void *ptr = openshot_swig_get_qwidget_ptr(obj);
if (ptr) {
return 1;
}
return obj == Py_None ? 1 : 0;
}
%}
@@ -138,6 +228,22 @@
}
}
%typemap(in) QWidget * {
void *ptr = openshot_swig_get_qwidget_ptr($input);
if (!ptr && $input != Py_None) {
SWIG_exception_fail(SWIG_TypeError, "Expected QWidget or Qt binding widget");
}
$1 = reinterpret_cast<QWidget*>(ptr);
}
%typemap(typecheck) QWidget * {
$1 = openshot_swig_is_qwidget($input);
}
%typemap(out) uintptr_t openshot::QtPlayer::GetRendererQObject {
$result = PyLong_FromVoidPtr(reinterpret_cast<void *>($1));
}
/* Wrap std templates (list, vector, etc...) */
+4 -1
View File
@@ -27,6 +27,10 @@
%include "std_vector.i"
%include "std_map.i"
%include <stdint.i>
%apply uint64_t { uintptr_t };
// Ignore QWidget overloads (Qt types are not wrapped in Ruby bindings)
%ignore openshot::QtPlayer::SetQWidget(QWidget *);
/* Unhandled STL Exception Handling */
%include <std_except.i>
@@ -276,4 +280,3 @@ typedef struct OpenShotByteBuffer {
%include "effects/Shift.h"
%include "effects/Wave.h"
+1 -1
View File
@@ -23,7 +23,7 @@ VideoRenderer::~VideoRenderer()
}
/// Override QWidget which needs to be painted
void VideoRenderer::OverrideWidget(int64_t qwidget_address)
void VideoRenderer::OverrideWidget(uintptr_t qwidget_address)
{
// re-cast QWidget pointer (long) as an actual QWidget
override_widget = reinterpret_cast<QWidget*>(qwidget_address);
+2 -1
View File
@@ -14,6 +14,7 @@
#define OPENSHOT_VIDEO_RENDERER_H
#include "../RendererBase.h"
#include <cstdint>
#include <QtCore/QObject>
#include <QtGui/QImage>
#include <memory>
@@ -30,7 +31,7 @@ public:
~VideoRenderer();
/// Override QWidget which needs to be painted
void OverrideWidget(int64_t qwidget_address);
void OverrideWidget(uintptr_t qwidget_address);
signals:
void present(const QImage &image);
+11 -5
View File
@@ -211,15 +211,21 @@ namespace openshot
return reader;
}
// Set the QWidget pointer to display the video on (as a LONG pointer id)
void QtPlayer::SetQWidget(int64_t qwidget_address) {
// Set the QWidget pointer to display the video on (as a pointer-sized unsigned id)
void QtPlayer::SetQWidget(uintptr_t qwidget_address) {
// Update override QWidget address on the video renderer
p->renderer->OverrideWidget(qwidget_address);
}
// Get the Renderer pointer address (for Python to cast back into a QObject)
int64_t QtPlayer::GetRendererQObject() {
return (int64_t)(VideoRenderer*)p->renderer;
// Set the QWidget pointer to display the video on (using a real QWidget pointer)
void QtPlayer::SetQWidget(QWidget *widget) {
SetQWidget(reinterpret_cast<uintptr_t>(widget));
}
// Get the Renderer pointer address (for Python to cast back into a VideoRenderer)
uintptr_t QtPlayer::GetRendererQObject() {
auto* vr = static_cast<VideoRenderer*>(p->renderer);
return reinterpret_cast<uintptr_t>(vr);
}
// Get the Playback speed
+10 -4
View File
@@ -14,6 +14,7 @@
#ifndef OPENSHOT_QT_PLAYER_H
#define OPENSHOT_QT_PLAYER_H
#include <cstdint>
#include <iostream>
#include <vector>
@@ -21,6 +22,8 @@
#include "Qt/PlayerPrivate.h"
#include "RendererBase.h"
class QWidget;
namespace openshot
{
using AudioDeviceList = std::vector<std::pair<std::string, std::string>>;
@@ -82,12 +85,15 @@ namespace openshot
void SetTimelineSource(const std::string &json);
/// Set the QWidget which will be used as the display (note: QLabel works well). This does not take a
/// normal pointer, but rather a LONG pointer id (and it re-casts the QWidget pointer inside libopenshot).
/// normal pointer, but rather a pointer-sized unsigned integer (and it re-casts the QWidget pointer inside libopenshot).
/// This is required due to SIP and SWIG incompatibility in the Python bindings.
void SetQWidget(int64_t qwidget_address);
void SetQWidget(uintptr_t qwidget_address);
/// Get the Renderer pointer address (for Python to cast back into a QObject)
int64_t GetRendererQObject();
/// Set the QWidget which will be used as the display (for SWIG/Python bindings)
void SetQWidget(QWidget *widget);
/// Get the Renderer pointer address (for Python to cast back into a VideoRenderer)
uintptr_t GetRendererQObject();
/// Get the Playback speed
float Speed();
+2 -1
View File
@@ -14,6 +14,7 @@
#define OPENSHOT_RENDERER_BASE_H
#include "Frame.h"
#include <cstdint>
#include <cstdlib> // for realloc
#include <memory>
@@ -35,7 +36,7 @@ namespace openshot
void paint(const std::shared_ptr<openshot::Frame> & frame);
/// Allow manual override of the QWidget that is used to display
virtual void OverrideWidget(int64_t qwidget_address) = 0;
virtual void OverrideWidget(uintptr_t qwidget_address) = 0;
protected:
RendererBase();
+1
View File
@@ -42,6 +42,7 @@ set(OPENSHOT_TESTS
KeyFrame
Point
Profiles
QtPlayer
QtImageReader
ReaderBase
Settings
+60
View File
@@ -0,0 +1,60 @@
/**
* @file
* @brief Unit tests for openshot::QtPlayer
* @author OpenShot Studios, LLC
*
* @ref License
*/
// Copyright (c) 2008-2025 OpenShot Studios, LLC
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "openshot_catch.h"
#include <memory>
#include "QtPlayer.h"
#include "Qt/VideoRenderer.h"
class QWidget;
namespace {
class TestRenderer : public openshot::RendererBase
{
public:
uintptr_t last_widget = 0;
void OverrideWidget(uintptr_t qwidget_address) override
{
last_widget = qwidget_address;
}
protected:
void render(std::shared_ptr<QImage> image) override
{
(void) image;
}
};
} // namespace
TEST_CASE("QtPlayer_GetRendererQObject_ReturnsVideoRendererAddress", "[libopenshot][qtplayer]")
{
auto renderer = std::make_unique<VideoRenderer>();
openshot::QtPlayer player(renderer.get());
auto addr = player.GetRendererQObject();
CHECK(addr == reinterpret_cast<uintptr_t>(renderer.get()));
}
TEST_CASE("QtPlayer_SetQWidget_Overload_ForwardsPointer", "[libopenshot][qtplayer]")
{
TestRenderer renderer;
openshot::QtPlayer player(&renderer);
char dummy = 0;
auto *widget = reinterpret_cast<QWidget*>(&dummy);
player.SetQWidget(widget);
CHECK(renderer.last_widget == reinterpret_cast<uintptr_t>(widget));
}