I have an old Windows DLL, without source code, who implement a table of utility functions. Years ago it was planned to convert it in a COM object so an IUnknown interface was implemented. To work with this DLL, there is a header file (simplified):
interface IFunctions : public IUnknown
{
virtual int function1(int p1, int p2) = 0;
virtual void function2(int p1) = 0;
// and the likes ...
}
But no CLSID was defined for IFunctions interface. And eventually the interface definition in header file is non-compliant with COM standard.
From C++ the DLL can be loaded with
CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER, clsid, ptr);
and with some pointer arithmetic from 'ptr' I find the addresses of funcion1(), etc. Since it worked, no complete COM implementation were done so I cannot QueryInterface for IFunctions interface because the interface is not a COM interface. In Windows Registry I find only the CLSID of the object and a reference to the DLL as it InprocServer32.
I do not have much experience in Python, but I need to use this DLL from Python, perhaps using ctypes and comtypes. I can load the DLL with (CLSID from registry)
unk = CreateObject('{11111111-2222-3333-4444-555555555555}', clsctx=comtypes.CLSCTX_INPROC_SERVER)
I know that in the VTable of the COM object function1() address is just after QueryInterface(), AddRef(), Release() but I cannot find a solution to implement a class like:
class DllFunction:
# not necessary, but for completeness ...
def QueryInterface(self, interface, iid=None):
return unk.QueryInterface(comtypes.IUnknown)
def AddRef(slef):
return unk.AddRef()
def Release(self):
return unk.Release()
# Functions I actually need to call from Python
def Function1(self, p1, p2):
# what to do ??
def Function2(self, p1):
# etc.
I would like to implement this solution in Python trying to avoid the development of an extension module in C++.
Thanks for any help.
Thanks to who provided some hints. Actually I cannot fix the DLL because I do not have the source code. Wrapping it in C++ was an option, but developing a wrapping Python module in C sounds better.
My plan was to use Python only, possibly without additional modules, so I managed to solve the issue using only ctypes. The following code show the solution. It works, but it needs some improvements (error checking, etc).
'''
Simple example of how to use the DLL from Python on Win32.
We need only ctypes.
'''
import ctypes
from ctypes import *
'''
We need a class to mirror GUID structure
'''
class GUID(Structure):
_fields_ = [("Data1", c_ulong),
("Data2", c_ushort),
("Data3", c_ushort),
("Data4", c_ubyte * 8)]
if __name__ == "__main__":
'''
COM APIs to activate/deactivate COM environment and load the COM object
'''
ole32=WinDLL('Ole32.dll')
CoInitialize = ole32.CoInitialize
CoUninitialize = ole32.CoUninitialize
CoCreateInstance = ole32.CoCreateInstance
'''
COM environment initialization
'''
rc = CoInitialize(None)
'''
To use CoCreate Instance in C (not C++):
void * driver = NULL;
rc = CoCreateInstance(&IID_Driver, // CLSID of the COM object
0, // no aggregation
CLSCTX_INPROC_SERVER, // CLSCTX_INPROC_SERVER = 1
&IID_Driver, // CLSID of the required interface
(void**)&driver); // result
In Python it is:
'''
clsid = GUID(0x11111111, 0x2222, 0x3333,
(0x44, 0x44, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55))
drv = c_void_p(None)
rc = CoCreateInstance(byref(clsid), 0, 1, byref(clsid), byref(drv))
'''
Pointers manipulation. Short form:
function = cast( c_void_p( cast(drv, POINTER(c_void_p))[0] ), POINTER(c_void_p))
'''
VTable = cast(drv, POINTER(c_void_p))
wk = c_void_p(VTable[0])
function = cast(wk, POINTER(c_void_p))
#print('VTbale address: ', hex(VTable[0]))
#print('QueryInterface address: ', hex(function[0]))
#print('AddRef address: ', hex(function[1]))
#print('Release address: ', hex(function[2]))
'''
To define functions from their addresses we first need to define their WINFUNCTYPE.
In C we call QueryInterface:
HRESULT rc = driver->lpVtbl->QueryInterface(driver, &IID_IUnknown, (void**)&iUnk);
So we need a long (HRESULT) return value and three pointers. It would be better to be
more accurate in pointer types, but ... it works!
We can use whatever names we want for function types and functions
'''
QueryInterfaceType = WINFUNCTYPE(c_long, c_void_p, c_void_p, c_void_p)
QueryInterface = QueryInterfaceType(function[0])
AddRefType = WINFUNCTYPE(c_ulong, c_void_p)
AddRef = AddRefType(function[1])
ReleaseType = WINFUNCTYPE(c_ulong, c_void_p)
Release = ReleaseType(function[2])
'''
The same for other functions, but library functions do not want 'this':
long rc = driver->lpVtbl->init(0);
'''
doThisType = WINFUNCTYPE(c_long, c_void_p)
doThis=doThisType(function[3])
getNameType = WINFUNCTYPE(c_int, c_char_p)
getName = getNameType(function[4])
getName.restype = None # to have None since function is void
getVersionType = WINFUNCTYPE(c_long)
getVersion = getVersionType(function[5])
getMessageType = WINFUNCTYPE(c_int, c_char_p)
getMessage = getMessageType(function[6])
getMessage.restype = None # to have None since function is void
'''
Now we can use functions in plain Python
'''
rc = doThis(0)
print(rc)
name = create_string_buffer(128)
rc = getName(name)
print(rc)
print(name.value)
ver = getVersion()
print(ver)
msg = create_string_buffer(256)
rc = getMessage(msg)
print(rc)
print(msg.value)
'''
Unload DLL and reset COM environment
'''
rc = Release(drv)
rc = CoUninitialize()
print("Done!")
I hope this example will be useful to somebody. It can be used to wrap a COM object without comtypes and, to me, clarify how ctypes works.
Related
Background: I'm coding in Python and trying to use the win32 IFileOperation to perform shell file operations. In particular, moving/copying a file or directory to a non-existant location requires you to get a IShellItem (or IShellItem2) item referring to the target directory, but SHCreateItemFromParsingName fails if the target doesn't exist yet. Following advice from these related questions:
Creating directories during a copy using IFileOperation
IFileSystemBindData implementation not fully working
I've handled creating the WIN32_FIND_DATAW structure, created the IBindCtx object via pywin32's pythoncom.CreateBindCtx(). The step I'm stuck on is implementing a IFileSysBindData class in Python.
Here's what I've got so far:
class FileSysBindData:
_public_methods_ = ['SetFindData', 'GetFindData']
_reg_clsid_ = '{01E18D10-4D8B-11d2-855D-006008059367}'
def SetFindData(self, win32_find_data):
self.win32_find_data = win32_find_data
return 0
def GetFindData(self, p_win32_find_data):
p_win32_find_data.contents = self.win32_find_data
return 0
bind_data = FileSysBindData()
ibindctx = pythoncom.CreateBindCtx()
# TODO: set the bind data
ibindctx.RegisterObjectParam('File System Bind Data', bind_data)
The last line gives:
ValueError: argument is not a COM object (got type=FileSysBindData)
So obviously there's more to do to get pywin32 to create this in a COM-compatible way.
But I'm stuck on how to use pywin32 (or another solution) to actually create it as a COM object, so I can pass it to the IBindCtx instance.
I'm not super familiar with COM in general, so reading the docs in pywin32 haven't helped me much. I don't even know if I'm supposed to register my class as a server, or use it as a client somehow.
The difficulty is you have to use two python packages:
comtypes to declare and implement custom COM objects
pywin32 because it has lots of already baked interop types, interfaces, classes, etc.
Here is the code, and you'll see the trick to pass a comtypes's custom COM object to pywin32:
import pythoncom
import ctypes
from comtypes.hresult import *
from comtypes import IUnknown, GUID, COMMETHOD, COMObject, HRESULT
from ctypes.wintypes import *
from ctypes import *
from win32com.shell import shell
from os import fspath
# ripped from
# <python install path>\Lib\site-packages\comtypes\test\test_win32com_interop.py
# We use the PyCom_PyObjectFromIUnknown function in pythoncomxxx.dll to
# convert a comtypes COM pointer into a pythoncom COM pointer.
# This is the C prototype; we must pass 'True' as third argument:
# PyObject *PyCom_PyObjectFromIUnknown(IUnknown *punk, REFIID riid, BOOL bAddRef)
_PyCom_PyObjectFromIUnknown = PyDLL(
pythoncom.__file__).PyCom_PyObjectFromIUnknown
_PyCom_PyObjectFromIUnknown.restype = py_object
_PyCom_PyObjectFromIUnknown.argtypes = (POINTER(IUnknown), c_void_p, BOOL)
def comtypes2pywin(ptr, interface=None):
"""Convert a comtypes pointer 'ptr' into a pythoncom PyI<interface> object.
'interface' specifies the interface we want; it must be a comtypes
interface class. The interface must be implemented by the object and
the interface must be known to pythoncom.
If 'interface' is not specified, comtypes.IUnknown is used.
"""
if interface is None:
interface = IUnknown
return _PyCom_PyObjectFromIUnknown(ptr, byref(interface._iid_), True)
class IFileSystemBindData(IUnknown):
"""The IFileSystemBindData interface
https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifilesystembinddata"""
_iid_ = GUID('{01e18d10-4d8b-11d2-855d-006008059367}')
_methods_ = [
COMMETHOD([], HRESULT, 'SetFindData',
(['in'], POINTER(WIN32_FIND_DATAW), 'pfd')),
COMMETHOD([], HRESULT, 'GetFindData',
(['out'], POINTER(WIN32_FIND_DATAW), 'pfd'))
]
class FileSystemBindData(COMObject):
"""Implements the IFileSystemBindData interface:
https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifilesystembinddata"""
_com_interfaces_ = [IFileSystemBindData]
def IFileSystemBindData_SetFindData(self, this, pfd):
self.pfd = pfd
return S_OK
def IFileSystemBindData_GetFindData(self, this, pfd):
pfd = self.pfd
return S_OK
find_data = WIN32_FIND_DATAW() # from wintypes
bind_data = FileSystemBindData()
# to avoid this long thing, we could declare a shorter helper on
# FileSystemBindData
bind_data.IFileSystemBindData_SetFindData(bind_data, ctypes.byref(find_data))
ctx = pythoncom.CreateBindCtx()
# we need this conversion to avoid
# "ValueError: argument is not a COM object (got type=FileSystemBindData)"
# error from pythoncom
pydata = comtypes2pywin(bind_data)
ctx.RegisterObjectParam('File System Bind Data', pydata)
item = shell.SHCreateItemFromParsingName(
fspath("z:\\blah\\blah"), ctx, shell.IID_IShellItem2)
SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000
print(item.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING)) # prints Z:\blah\blah
I'm trying to connect a LE device to my Linux laptop through a python script. Nevertheless, the device address must be specified as "random" for the connection to happen, and the examples that I have (mainly https://www.bluetooth.com/blog/the-bluetooth-for-linux-developers-study-guide/) doesn't show any way of doing it.
The device-api from BlueZ (https://github.com/bluez/bluez/blob/master/doc/device-api.txt) list it as one of its properties, but my knowledge is still incomplete, and I couldn't manage to find a way of setting this property.
Any idea, indication or example will be immensely helpful.
Following is my script
PATH_DA_BSN = "/org/bluez/hci0/dev_CA_DB_17_8A_02_97"
ADAPTER_NAME = "hci0"
BLUEZ_SERVICE_NAME = "org.bluez"
BLUEZ_NAMESPACE = "/org/bluez/"
DEVICE_INTERFACE = BLUEZ_SERVICE_NAME + ".Device1"
ADAPTER_INTERFACE = BLUEZ_SERVICE_NAME + ".Adapter1"
def connect():
global bus
global device_interface
try:
device_interface.Connect()
except Exception as e:
print("Failed to connect")
print(e.get_dbus_name())
print(e.get_dbus_message())
if ("UnknownObject" in e.get_dbus_name()):
print("Try scanning first to resolve this problem")
return bluetooth_constants.RESULT_EXCEPTION
else:
print("Connected OK")
return bluetooth_constants.RESULT_OK
bus = dbus.SystemBus()
bsn_proxy = bus.get_object(BLUEZ_SERVICE_NAME, PATH_DA_BSN)
device_interface = dbus.Interface(bsn_proxy, DEVICE_INTERFACE)
adapter_path = BLUEZ_NAMESPACE + ADAPTER_NAME
# acquire the adapter interface so we can call its methods
adapter_object = bus.get_object(BLUEZ_SERVICE_NAME, adapter_path)
adapter_interface = dbus.Interface(adapter_object, ADAPTER_INTERFACE)
print("Connecting to " + PATH_DA_BSN)
connect()
The AddressType is already set from when the device was discovered.
You can iterate through the already discovered devices using D-Bus's GetManagedObjects functionality to find what the address type is set to for each device.
An example using the pydbus bindings:
>>> import pydbus
>>> bus = pydbus.SystemBus()
>>> mngr = bus.get('org.bluez', '/')
>>> mngd_objs = mngr.GetManagedObjects()
>>> for path, iface in mngd_objs.items():
... if 'org.bluez.Device1' in iface.keys():
... print(iface.get('org.bluez.Device1', {}).get('AddressType'))
...
public
random
random
public
public
public
random
public
public
public
random
you have 2 options:
Do a discovery and connect when the device is found. In this case Bluez will already know the addresstype.
Call the method ConnectDevice which is available on an adapter. In this method you can pass the ‘random’ parameter. Keep in mind this method is marked as experimental.
I recommend option 1
I want to be able to pass a certificate to Python's ssl library without requiring a temporary file. It seems that the Python ssl module cannot do that.
To work around this problem I want to retrieve the underlying SSL_CTX struct stored in the ssl._ssl._SSLContext class from the native _ssl module. Using ctypes I could then manually call the respective SSL_CTX_* functions from libssl with that context. How to do that in C is shown here and I would do the same thing via ctypes.
Unfortunately, I'm stuck at the point where I managed to hook into the load_verify_locations function from ssl._ssl._SSLContext but seem to be unable to get the right memory address of the instance of the ssl._ssl._SSLContext struct. All the load_verify_locations function is seeing is the parent ssl.SSLContext object.
My question is, how do I get from an instance of a ssl.SSLContext object to the memory of the native base class ssl._ssl._SSLContext? If I would have that, I could easily access its ctx member.
Here is my code so far. Credits for how to monkeypatch a native Python module go to the forbidden fruit project by Lincoln Clarete
Py_ssize_t = hasattr(ctypes.pythonapi, 'Py_InitModule4_64') and ctypes.c_int64 or ctypes.c_int
class PyObject(ctypes.Structure):
pass
PyObject._fields_ = [
('ob_refcnt', Py_ssize_t),
('ob_type', ctypes.POINTER(PyObject)),
]
class SlotsProxy(PyObject):
_fields_ = [('dict', ctypes.POINTER(PyObject))]
class PySSLContext(ctypes.Structure):
pass
PySSLContext._fields_ = [
('ob_refcnt', Py_ssize_t),
('ob_type', ctypes.POINTER(PySSLContext)),
('ctx', ctypes.c_void_p),
]
name = ssl._ssl._SSLContext.__name__
target = ssl._ssl._SSLContext.__dict__
proxy_dict = SlotsProxy.from_address(id(target))
namespace = {}
ctypes.pythonapi.PyDict_SetItem(
ctypes.py_object(namespace),
ctypes.py_object(name),
proxy_dict.dict,
)
patchable = namespace[name]
old_value = patchable["load_verify_locations"]
libssl = ctypes.cdll.LoadLibrary("libssl.so.1.0.0")
libssl.SSL_CTX_set_verify.argtypes = (ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p)
libssl.SSL_CTX_get_verify_mode.argtypes = (ctypes.c_void_p,)
def load_verify_locations(self, cafile, capath, cadata):
print(self)
print(self.verify_mode)
addr = PySSLContext.from_address(id(self)).ctx
libssl.SSL_CTX_set_verify(addr, 1337, None)
print(libssl.SSL_CTX_get_verify_mode(addr))
print(self.verify_mode)
return old_value(self, cafile, capath, cadata)
patchable["load_verify_locations"] = load_verify_locations
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
The output is:
<ssl.SSLContext object at 0x7f4b81304ba8>
2
1337
2
This suggests, that whatever I'm changing is not the ssl context that Python knows about but some other random memory location.
To try out the code from above, you have to run a https server. Generate a self-signed SSL certificate using:
$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost' -nodes
And start a server using the following code:
import http.server, http.server
import ssl
httpd = http.server.HTTPServer(('localhost', 4443), http.server.SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket (httpd.socket, certfile='cert.pem', keyfile='key.pem', server_side=True)
httpd.serve_forever()
And then add the following line to the end of my example code above:
urllib.request.urlopen("https://localhost:4443", context=context)
Actual SSLContext answer forthcoming, the assumption is no longer correct.
See https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_verify_locations
There's a 3rd argument, cadata
The cadata object, if present, is either an ASCII string of one or
more PEM-encoded certificates or a bytes-like object of DER-encoded
certificates.
Apparently that's the case since Python 3.4
Getting the underlying PyObject context
This one's easy, ssl.SSLContext inherits from _ssl._SSLContext which in Python data model means that there's just one object at one memory address.
Therefore, ssl.SSLContext().load_verify_locations(...) will actually call:
ctx = \
ssl.SSLContext.__new__(<type ssl.SSLContext>, ...) # which calls
self = _ssl._SSLContext.__new__(<type ssl.SSLContext>, ...) # which calls
<type ssl.SSLContext>->tp_alloc() # which understands inheritance
self->ctx = SSL_CTX_new(...) # _ssl fields
self.set_ciphers(...) # ssl fields
return self
_ssl._SSLContext.load_verify_locations(ctx, ...)`.
The C implementation will get an object of seemingly wrong type, but that's OK because all the expected fields are there, as it was allocated by generic type->tp_alloc and fields were filled in first by _ssl._SSLContext and then by ssl.SSLContext.
Here's a demonstration (tedious details omitted):
# _parent.c
typedef struct {
PyObject_HEAD
} PyParent;
static PyObject* parent_new(PyTypeObject* type, PyObject* args,
PyObject* kwargs) {
PyParent* self = (PyParent*)type->tp_alloc(type, 0);
printf("Created parent %ld\n", (long)self);
return (PyObject*)self;
}
# child.py
class Child(_parent.Parent):
def foo(self):
print(id(self))
c1 = Child()
print("Created child:", id(c1))
# prints:
Created parent 139990593076080
Created child: 139990593076080
Getting the underlying OpenSSL context
typedef struct {
PyObject_HEAD
SSL_CTX *ctx;
<details skipped>
} PySSLContext;
Thus, ctx is at a known offset, which is:
PyObject_HEAD
This is a macro which expands to the declarations of the fields of the PyObject type; it is used when declaring new types which represent objects without a varying length. The specific fields it expands to depend on the definition of Py_TRACE_REFS. By default, that macro is not defined, and PyObject_HEAD expands to:
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
When Py_TRACE_REFS is defined, it expands to:
PyObject *_ob_next, *_ob_prev;
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
Thus, in a production (non-debug) build, and taking natural alignment into consideration, PySSLContext becomes:
struct {
void*;
void*;
SSL_CTX *ctx;
...
}
Therefore:
_ctx = _ssl._SSLContext(2)
c_ctx = ctypes.cast(id(_ctx), ctypes.POINTER(ctypes.c_void_p))
c_ctx[:3]
[1, 140486908969728, 94916219331584]
# refcnt, type, C ctx
Putting it all together
import ssl
import socket
import ctypes
import pytest
def contact_github(cafile=""):
ctx = ssl.SSLContext()
ctx.verify_mode = ssl.VerifyMode.CERT_REQUIRED
# ctx.load_verify_locations(cafile, "empty", None) done via ctypes
ssl_ctx = ctypes.cast(id(ctx), ctypes.POINTER(ctypes.c_void_p))[2]
cssl = ctypes.CDLL("/usr/lib/x86_64-linux-gnu/libssl.so.1.1")
cssl.SSL_CTX_load_verify_locations.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
assert cssl.SSL_CTX_load_verify_locations(ssl_ctx, cafile.encode("utf-8"), b"empty")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("github.com", 443))
ss = ctx.wrap_socket(s)
ss.send(b"GET / HTTP/1.0\n\n")
print(ss.recv(1024))
def test_wrong_cert():
with pytest.raises(ssl.SSLError):
contact_github(cafile="bad-cert.pem")
def test_correct_cert():
contact_github(cafile="good-cert.pem")
I want to apply a certain behaviour to a label. When a lateral button is clicked, the corresponding label should rotate 90 degrees. It can be easily done in vala, but I can't discover the particular syntax on genie.
The vala code I am trying to reproduce comes from elementary OS getting started guide:
hello_button.clicked.connect(() =>
{
hello_label.label = "Hello World!";
hello_button.sensitive = false;
});
rotate_button.clicked.connect(() =>
{
rotate_label.angle = 90;
rotate_label.label = "Verbal";
rotate_button.sensitive = false;
});
I actually managed to reproduce almost entirely the code in Genie, for the exception of the rotation. Here is how far I got:
/* ANOTHER GTK EXPERIMENT WITH GENIE BASED ON ELEMENTARY INTRODUCTORY PAGE
** compile with valac --pkg gtk+03.0 layoutgtkexample.gs */
[indent=4]
uses Gtk
init
Gtk.init (ref args)
var window = new Gtk.Window()
window.title = "Hello World!"
window.set_border_width(12)
var layout = new Gtk.Grid ()
layout.column_spacing = 6
layout.row_spacing = 6
var hello_button = new Gtk.Button.with_label("Say Hello")
var hello_label = new Gtk.Label("Hello")
var rotate_button = new Gtk.Button.with_label ("Rotate")
var rotate_label = new Gtk.Label("Horizontal")
// add first row of widgets
layout.attach (hello_button, 0, 0, 1,1)
layout.attach_next_to (hello_label, hello_button, PositionType.RIGHT, 1, 1)
// add second row of widgets
layout.attach(rotate_button, 0,1,1,1)
layout.attach_next_to(rotate_label, rotate_button, PositionType.RIGHT, 1, 1)
window.add(layout)
hello_button.clicked.connect(hello_pushed)
rotate_button.clicked.connect(rotate_pushed)
window.destroy.connect(Gtk.main_quit)
window.show_all ()
Gtk.main ()
def hello_pushed (btn:Button)
btn.label = "Hello World!"
btn.sensitive = false
def rotate_pushed (btn:Button)
btn.label = "Vertical"
//btn.angle = 90
btn.sensitive = false
The problem is to do with where identifiers are valid and is known as "scope".
The Vala example makes use of an anonymous function, also called a lambda expression in Vala. An anonymous function can be a "closure", when the variables in the scope that defines the anonymous function are also available within the anonymous function. This is useful because the callback occurs after the original block of code has been run, but the variables are still available within the callback. So in the Vala example, where both the button and label are defined in the enclosing scope, the button and label are also available in the callback anonymous function.
Unfortunately Genie isn't able to parse anonymous functions as function arguments, in this case within the connect() call. Although some work has been done on this in 2015. So you have rightly used a function name instead. The problem is the callback only passes the button as an argument and not the adjacent label. So to make the label available within the callback function we could use a class:
/* ANOTHER GTK EXPERIMENT WITH GENIE BASED ON ELEMENTARY INTRODUCTORY PAGE
** compile with valac --pkg gtk+-3.0 layoutgtkexample.gs */
[indent=4]
uses Gtk
init
Gtk.init (ref args)
new RotatingButtonWindow( "Hello World!" )
Gtk.main ()
class RotatingButtonWindow:Window
_hello_label:Label
_rotate_label:Label
construct( window_title:string )
title = window_title
set_border_width(12)
var layout = new Grid ()
layout.column_spacing = 6
layout.row_spacing = 6
// add 'hello' row of widgets
var hello_button = new Button.with_label("Say Hello")
_hello_label = new Label("Hello")
layout.attach (hello_button, 0, 0, 1,1)
layout.attach_next_to (_hello_label, hello_button, PositionType.RIGHT, 1, 1)
// add 'rotate' row of widgets
var rotate_button = new Button.with_label ("Rotate")
_rotate_label = new Label("Horizontal")
layout.attach(rotate_button, 0,1,1,1)
layout.attach_next_to(_rotate_label, rotate_button, PositionType.RIGHT, 1, 1)
add(layout)
hello_button.clicked.connect(hello_pushed)
rotate_button.clicked.connect(rotate_pushed)
destroy.connect(Gtk.main_quit)
show_all ()
def hello_pushed (btn:Button)
_hello_label.label = "Hello World!"
btn.sensitive = false
def rotate_pushed (btn:Button)
_rotate_label.label = "Vertical"
_rotate_label.angle = 90
btn.sensitive = false
A few notes:
By placing the definitions of the _hello_label and _rotate_label within the scope of the class they become available to all the functions defined in the class. Definitions like this are often called "fields". The underscore means they are not available outside the class, so in the example you cannot access them from init
construct() is called when the object is created, in the example the line new RotatingButtonWindow( "Hello World!" ) instantiates the object. If you repeat the line you will have two separate windows, that is two instances of the RotatingButtonWindow data type
You will notice that the RotatingButtonWindow type is also defined as a Window type. This means it is adding more detail to the Gtk.Window class. This is why title and set_border_width() can be used within the new class. They have been "inherited" from the parent Gtk.Window class
By using the Gtk namespace with uses Gtk we don't need to prefix everything with Gtk
As your Gtk application gets more complex you probably want to look at GtkBuilder. That allows windows and widgets to be laid out in an external file. Then use GResource to build the file into the binary of your application so there is no need to distribute the UI file separately.
In Ruby, objects have a handy method called method_missing which allows one to handle method calls for methods that have not even been (explicitly) defined:
Invoked by Ruby when obj is sent a message it cannot handle. symbol is the symbol for the method called, and args are any arguments that were passed to it. By default, the interpreter raises an error when this method is called. However, it is possible to override the method to provide more dynamic behavior. The example below creates a class Roman, which responds to methods with names consisting of roman numerals, returning the corresponding integer values.
class Roman
def romanToInt(str)
# ...
end
def method_missing(methId)
str = methId.id2name
romanToInt(str)
end
end
r = Roman.new
r.iv #=> 4
r.xxiii #=> 23
r.mm #=> 2000
For example, Ruby on Rails uses this to allow calls to methods such as find_by_my_column_name.
My question is, what other languages support an equivalent to method_missing, and how do you implement the equivalent in your code?
Smalltalk has the doesNotUnderstand message, which is probably the original implementation of this idea, given that Smalltalk is one of Ruby's parents. The default implementation displays an error window, but it can be overridden to do something more interesting.
PHP objects can be overloaded with the __call special method.
For example:
<?php
class MethodTest {
public function __call($name, $arguments) {
// Note: value of $name is case sensitive.
echo "Calling object method '$name' "
. implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
?>
Some use cases of method_missing can be implemented in Python using __getattr__ e.g.
class Roman(object):
def roman_to_int(self, roman):
# implementation here
def __getattr__(self, name):
return self.roman_to_int(name)
Then you can do:
>>> r = Roman()
>>> r.iv
4
I was looking for this before, and found a useful list (quickly being overtaken here) as part of the Merd project on SourceForge.
Construct Language
----------- ----------
AUTOLOAD Perl
AUTOSCALAR, AUTOMETH, AUTOLOAD... Perl6
__getattr__ Python
method_missing Ruby
doesNotUnderstand Smalltalk
__noSuchMethod__(17) CoffeeScript, JavaScript
unknown Tcl
no-applicable-method Common Lisp
doesNotRecognizeSelector Objective-C
TryInvokeMember(18) C#
match [name, args] { ... } E
the predicate fail Prolog
forward Io
With footnotes:
(17) firefox
(18) C# 4, only for "dynamic" objects
JavaScript has noSuchMethod, but unfortunately this is only supported by Firefox/Spidermonkey.
Here is an example:
wittyProjectName.__noSuchMethod__ = function __noSuchMethod__ (id, args) {
if (id == 'errorize') {
wittyProjectName.log("wittyProjectName.errorize has been deprecated.\n" +
"Use wittyProjectName.log(message, " +
"wittyProjectName.LOGTYPE_ERROR) instead.",
this.LOGTYPE_LOG);
// just act as a wrapper for the newer log method
args.push(this.LOGTYPE_ERROR);
this.log.apply(this, args);
}
}
Perl has AUTOLOAD which works on subroutines & class/object methods.
Subroutine example:
use 5.012;
use warnings;
sub AUTOLOAD {
my $sub_missing = our $AUTOLOAD;
$sub_missing =~ s/.*:://;
uc $sub_missing;
}
say foo(); # => FOO
Class/Object method call example:
use 5.012;
use warnings;
{
package Shout;
sub new { bless {}, shift }
sub AUTOLOAD {
my $method_missing = our $AUTOLOAD;
$method_missing =~ s/.*:://;
uc $method_missing;
}
}
say Shout->bar; # => BAR
my $shout = Shout->new;
say $shout->baz; # => BAZ
Objective-C supports the same thing and calls it forwarding.
This is accomplished in Lua by setting the __index key of a metatable.
t = {}
meta = {__index = function(_, idx) return function() print(idx) end end}
setmetatable(t, meta)
t.foo()
t.bar()
This code will output:
foo
bar
In Common Lisp, no-applicable-method may be used for this purpose, according to the Common Lisp Hyper Spec:
The generic function no-applicable-method is called when a generic function is invoked and no method on that generic function is applicable. The default method signals an error.
The generic function no-applicable-method is not intended to be called by programmers. Programmers may write methods for it.
So for example:
(defmethod no-applicable-method (gf &rest args)
;(error "No applicable method for args:~% ~s~% to ~s" args gf)
(%error (make-condition 'no-applicable-method :generic-function gf :arguments args) '()
;; Go past the anonymous frame to the frame for the caller of the generic function
(parent-frame (%get-frame-ptr))))
C# now has TryInvokeMember, for dynamic objects (inheriting from DynamicObject)
Actionscript 3.0 has a Proxy class that can be extended to provide this functionality.
dynamic class MyProxy extends Proxy {
flash_proxy override function callProperty(name:*, ...rest):* {
try {
// custom code here
}
catch (e:Error) {
// respond to error here
}
}
Tcl has something similar. Any time you call any command that can't be found, the procedure unknown will be called. While it's not something you normally use, it can be handy at times.
In CFML (ColdFusion, Railo, OpenBD), the onMissingMethod() event handler, defined within a component, will receive undefined method calls on that component. The arguments missingMethodName and missingMethodArguments are automatically passed in, allowing dynamic handling of the missing method call. This is the mechanism that facilitated the creation of implicit setter/getter schemes before they began to be built into the various CFML engines.
Its equivalent in Io is using the forward method.
From the docs:
If an object doesn't respond to a message, it will invoke its "forward" method if it has one....
Here is a simple example:
Shout := Object clone do (
forward := method (
method_missing := call message name
method_missing asUppercase
)
)
Shout baz println # => BAZ
/I3az/
Boo has IQuackFu - there is already an excellent summary on SO at how-can-i-intercept-a-method-call-in-boo
Here is an example:
class XmlObject(IQuackFu):
_element as XmlElement
def constructor(element as XmlElement):
_element = element
def QuackInvoke(name as string, args as (object)) as object:
pass # ignored
def QuackSet(name as string, parameters as (object), value) as object:
pass # ignored
def QuackGet(name as string, parameters as (object)) as object:
elements = _element.SelectNodes(name)
if elements is not null:
return XmlObject(elements[0]) if elements.Count == 1
return XmlObject(e) for e as XmlElement in elements
override def ToString():
return _element.InnerText