From my other question here on SO, I asked how to retrieve the current playing song from Windows Media Player and Zune, I got an answer from a c++ dev who gave me an explanation of how I would do this for WMP.
However, I am no C++ dev, nor am I very experienced with the pywin32 library. And on-top of all that, the documentation on all this (especially concerning WMP) is horrible.
Therefor, I need your help understanding how I would do the following in Python.
Source
I have working code in C++ to print the name of media currently
playing in WMP. It's a simple console application (78 lines of code).
Steps:
1) implements a basic COM object implementing IUnknown, IOleClientSite, IServiceProvider and IWMPRemoteMediaServices. This is
straightforward (sort of, your mileage may vary) using the ATL
template CComObjectRootEx. The only methods needing (simple) code are
IServiceProvider::QueryService and
IWMPRemoteMediaServices::GetServiceType. All other methods may return
E_NOTIMPL
2) Instantiate the "WMPlayer.OCX" COM object (in my case, via CoCreateInstance)
3) Retrieve from the object an IOleObject interface pointer via QueryInterface
4) Instanciate an object from the class seen in 1) (I use the CComObject<>::CreateInstance template)
5) Use the SetClientSite method from the interface you got at 3), passing a pointer to your OleClientSite implementation.
6) During the SetClientSite call, WMP will callback you: fisrt asking for an IServiceProvider interface pointer, second calling the
QueryService method, asking for an IWMPRemoteMediaServices interface
pointer. Return your implementation of IWMPRemoteMediaServices and,
third, you will be called again via GetServiceType. You must then
return "Remote". You are now connected to the WMP running instance
7) Query the COM object for an IWMPMedia interface pointer
8) If 7) didn't gave NULL, read the the IWMPMedia::name property.
9) DONE
All the above was tested with VS2010 / Windows Seven, and with WMP
running (if there is no Media Player process running, just do
nothing).
I don't know if yoy can/want to implement COM interface and object in
Python. If you are interested by my C++ code, let me know. You could
use that code in a C++ DLL, and then call it from python.
I know a little bit about the win32api.
At the first step, I really don't know what to do, googling IOleClientSite results in the msdn documentation, it's an interface. However, that's where I get stuck already. I can't find anything (might just be my horrendous googling skills) on working with these things in Python.
The second step:
WMP = win32com.client.Dispatch("WMPlayer.OCX")
Alright, that's doable.
On to the third step. QueryInterface -
"regardless of the object you have, you can always call its QueryInterface() method to obtain a new interface, such as IStream."
source
However, not for me. As I understand his explanation, I think it means that every com object sort of "inherits" three methods from IUnknown, one of which is QueryInterface, however this does not seem the case since calling QueryInterface on my WMP object fails miserably. (Object has no attribute 'QueryInterface')
I could ramble on, but I believe you got the point, I have no idea how to work with this. Can anyone help me out with this one? Preferably with code examples, but resources/documentation is welcome too.
Almost final answser but CAN'T finish.
I seems that pythoncom can't be used to implement custom Interface without the help of a C++ module.
Here is an answser from Mark Hammon (Mon, 13 Jan 2003): How to create COM Servers with IID_IDTExtensibility2 interface
Sorry - you are SOL. To support arbitary interfaces, you need C++
support, in the form of an extension module. There is a new "Univgw"
that may help you out, but I dont know much about this
I am not able to find anything about that "Univgw" thing...
The comtypes python module is intended to resolve the problem, and I found links saying it does, but I can't make it works with my fresh new Python 3.3. It's Python 2.x code. comtypes seems outdated and unmaintained.
Step 1 OK for IOleClientSite and IServiceProvider, KO for IWMPRemoteMediaServices
Step 2, 3, 4 and 5 OK
Step 6, 7 and 8 can't be implemented without IWMPRemoteMediaServices :-(
disclaimer: complete newbie in Python, please don't yell
import pythoncom
import win32com.client as wc
from win32com.axcontrol import axcontrol
import win32com.server as ws
from win32com.server import util
from win32com.server.exception import COMException
import winerror
import pywintypes
# Windows Media Player Custom Interface IWMPRemoteMediaServices
IWMPRemoteMediaServices = pywintypes.IID("{CBB92747-741F-44FE-AB5B-F1A48F3B2A59}")
class OleClientSite:
_public_methods_ = [ 'SaveObject', 'GetMoniker', 'GetContainer', 'ShowObject', 'OnShowWindow', 'RequestNewObjectLayout', 'QueryService' ]
_com_interfaces_ = [ axcontrol.IID_IOleClientSite, pythoncom.IID_IServiceProvider ]
def SaveObject(self):
print("SaveObject")
raise COMException(hresult=winerror.E_NOTIMPL)
def GetMoniker(self, dwAssign, dwWhichMoniker):
print("GetMoniker ")
raise COMException(hresult=winerror.E_NOTIMPL)
def GetContainer(self):
print("GetContainer")
raise COMException(hresult=winerror.E_NOTIMPL)
def ShowObject(self):
print("ShowObject")
raise COMException(hresult=winerror.E_NOTIMPL)
def OnShowWindow(self, fShow):
print("ShowObject" + str(fShow))
raise COMException(hresult=winerror.E_NOTIMPL)
def RequestNewObjectLayout(self):
print("RequestNewObjectLayout")
raise COMException(hresult=winerror.E_NOTIMPL)
def QueryService(self, guidService, riid):
print("QueryService",guidService,riid)
if riid == IWMPRemoteMediaServices:
print("Known Requested IID, but can't implement!")
raise COMException(hresult=winerror.E_NOINTERFACE)
else:
print("Requested IID is not IWMPRemoteMediaServices" )
raise COMException(hresult=winerror.E_NOINTERFACE)
if __name__=='__main__':
wmp = wc.Dispatch("WMPlayer.OCX")
IOO = wmp._oleobj_.QueryInterface(axcontrol.IID_IOleObject)
pyOCS = OleClientSite()
comOCS = ws.util.wrap(pyOCS, axcontrol.IID_IOleClientSite)
IOO.SetClientSite(comOCS)
Related
I am trying to change the user of a print job in the queue, as I want to create it on a service account but send the job to another users follow-me printing queue. I'm using the win32 module in python. Here is an example of my code:
from win32 import win32print
JOB_INFO_LEVEL = 2
pclExample = open("sample.pcl")
printer_name = win32print.GetDefaultPrinter()
hPrinter = win32print.OpenPrinter(printer_name)
try:
jobID = win32print.StartDocPrinter(hPrinter, 1, ("PCL Data test", None, "RAW"))
# Here we try to change the user by extracting the job and then setting it again
jobInfoDict = win32print.GetJob(hPrinter, jobID , JOB_INFO_LEVEL )
jobInfoDict["pUserName"] = "exampleUser"
win32print.SetJob(hPrinter, jobID , JOB_INFO_LEVEL , jobInfoDict , win32print.JOB_CONTROL_RESUME )
try:
win32print.StartPagePrinter(hPrinter)
win32print.WritePrinter(hPrinter, pclExample)
win32print.EndPagePrinter(hPrinter)
finally:
win32print.EndDocPrinter(hPrinter)
finally:
win32print.ClosePrinter(hPrinter)
The problem is I get an error at the win32print.SetJob() line. If JOB_INFO_LEVEL is set to 1, then I get the following error:
(1804, 'SetJob', 'The specified datatype is invalid.')
This is a known bug to do with how the C++ works in the background (Issue here).
If JOB_INFO_LEVEL is set to 2, then I get the following error:
(1798, 'SetJob', 'The print processor is unknown.')
However, this is the processor that came from win32print.GetJob(). Without trying to change the user this prints fine, so I'm not sure what is wrong.
Any help would be hugely appreciated! :)
EDIT:
Using Python 3.8.5 and Pywin32 303
At the beginning I thought it was a misunderstanding (I was also a bit skeptical about the bug report), mainly because of the following paragraph (which apparently seems to be wrong) from [MS.Docs]: SetJob function (emphasis is mine):
The following members of a JOB_INFO_1, JOB_INFO_2, or JOB_INFO_4 structure are ignored on a call to SetJob: JobId, pPrinterName, pMachineName, pUserName, pDrivername, Size, Submitted, Time, and TotalPages.
But I did some tests and ran into the problem. The problem is as described in the bug: filling JOB_INFO_* string members (which are LPTSTRs) with char* data.
Submitted [GitHub]: mhammond/pywin32 - Fix: win32print.SetJob sending ANSI to UNICODE API (and none of the 2 errors pops up). It was merged to main on 220331.
When testing the fix, I was able to change various properties of an existing job, I was amazed that it didn't have to be valid data (like below), I'm a bit curious to see what would happen when the job would be executed (as now I don't have a connection to a printer):
Change pUserName to str(random.randint(0, 10000)) to make sure it changes on each script run (PrintScreens taken separately and assembled in Paint):
Ways to go further:
Wait for a new PyWin32 version (containing this fix) to be released. This is the recommended approach, but it will also take more time (and it's unclear when it will happen)
Get the sources, either:
from main
from b303 (last stable branch), and apply the (above) patch(1)
build the module (.pyd) and copy it in the PyWin32's site-packages directory on your Python installation(s). Faster, but it requires some deeper knowledge, and maintenance might become a nightmare
Footnotes
#1: Check [SO]: Run / Debug a Django application's UnitTests from the mouse right click context menu in PyCharm Community Edition? (#CristiFati's answer) (Patching UTRunner section) for how to apply patches (on Win).
So because I have the unity-gtk-module installed, all gtk-applications export their menu over the dbus SessionBus. My goal is to extract a list of all available menu entries. I've already implemented this with the help of pydbus, but for some reason, this solution is highly unstable and some applications just flat out crash. The unity-gtk-module uses Gio's g_dbus_connection_export_menu_model () to export its GMenuModel modeled menu over dbus, so I thought it would make sense to try to use Gio to process the exported menu. Gio uses the GDBusMenuModel class to retrieve a menu from the bus. Python uses PyGObject for wrapping Gio:
from gi.repository import Gio
connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
menuModel = Gio.DBusMenuModel.get(connection, [bus-name e.g. ":1.5"], [object-path e.g. "/com/canonical/unity/gtk/window/0"])
Now menuModel should be wrapping the GMenuModel from my application. At this point I'm honestly a bit confused about how exactly the GMenuModel works (the Description is not really helping) but it seems I have to use a GMenuAttributeIter object to iterate through the entries. But when I try this:
iter = Gio.MenuModel.iterate_item_attributes(menuModel, 0) #0 is the index of the root node
this happens:
GLib-GIO-CRITICAL **: g_dbus_menu_model_get_item_attributes: assertion 'proxy->items' failed
GLib-GIO-CRITICAL **: GMenuModel implementation 'GDBusMenuModel' doesn't override iterate_item_attributes() and fails to return sane calues from get_item_attributes()
This probably happens because GDBusMenuModel inherits GMenuModel which provides these methods, but is abstract, so GDBusMenuModel should override them, which it doesn't (see link above, it provides just g_dbus_menu_model_get ()). If this is the case, how am I supposed to actually use this class as a proxy? And if it's not, what am I doing wrong?
I justed logged in to SO the first time after a few years and remembered that I've actually found a solution to this question (I think). Honestly, I can't remember what half of these words even mean, but at the time I wrote a script to accomplish the task posed in the title, and as far as I remember, in the end, it worked out: https://gist.github.com/encomiastical/caa0ee955300bc2a40ef55d123b06212
I'm trying to use "PortableDevice.PortableDevice" COM API for my python application. When I try to generate python wrapper as follow:
comtypes.client.GetModule("C:\\Windows\\system32\\PortableDeviceApi.dll")
I get following error message:
assert sizeof(__MIDL_IOleAutomationTypes_0004) == 16, sizeof(__MIDL_IOleAutomationTypes_0004)
AssertionError: 8
Can anyone please help me to troubleshoot this issue?
The main reason why this fails is due to a kludge in comtypes, where the DECIMAL type is not properly defined. As is, it needs 64 bits, or 8 bytes, for a double float, but it should really take 16 bytes, or 128 bits, for the actual struct.
For your current purpose, you can get along with any definition of DECIMAL that has the proper size, so here's one:
# comtypes/automation.py
class tagDEC(Structure):
_fields_ = [("wReserved", c_ushort),
("scale", c_ubyte),
("sign", c_ubyte),
("Hi32", c_ulong),
("Lo64", c_ulonglong)]
DECIMAL = tagDEC
# comtypes/tools/tlbparser.py
DECIMAL_type = typedesc.Structure("DECIMAL",
align=alignment(automation.DECIMAL)*8,
members=[], bases=[],
size=sizeof(automation.DECIMAL)*8)
However, you'll probably stump over the fact that some methods in the Portable Device API are not suitable for automation.
For instance, IPortableDeviceManager::GetDevices has the unique attribute (in the actual PortableDeviceApi.idl file, not in the documentation), which means you can actually pass NULL. However, type libraries don't capture this information.
That same argument can actually be an array which size is determined by the next argument. Again, type libraries don't support this, only single-object top-level pointers. Moreover, the actual IDL doesn't have a size_is attribute, which means either that the method call will not work across apartments or that this interface has a custom marshaler.
A quick look at the Portable Device API in general shows that this pattern is consistently applied in other methods that actually use arrays. It seems as if someone familiar with the Win32 API made these methods, because there are a bunch of Win32 functions that are overloaded to fetch the size of some array when that array argument is NULL. This is not the usual COM way at all, it would be better to have two methods (with the same race condition between knowing the number of elements, allocating enough memory and fetching them), a single method with only out arguments (no race condition, but no control over memory usage) or use enumerators (e.g. IEnumPortableDevice, harder, but much cleaner).
Anyway, you can take the code that comtypes.client.GetModule("…PortableDeviceApi.dll") generates as a first step. Then, follow these instructions to make the Python methods actually call the COM methods in the documented way. For instance, IPortableManager::GetDevices would become:
# comtypes/gen/_1F001332_1A57_4934_BE31_AFFC99F4EE0A_0_1_0.py
class IPortableDeviceManager(comtypes.gen._00020430_0000_0000_C000_000000000046_0_2_0.IUnknown):
# ...
def GetDevices(self):
cPnPDeviceIDs = c_ulong(0)
self.__com_GetDevices(None, byref(cPnPDeviceIDs))
PnPDeviceIDs = (WSTRING * cPnPDeviceIDs.value)()
self.__com_GetDevices(PnPDeviceIDs, byref(cPnPDeviceIDs))
deviceIDs = PnPDeviceIDs[:cPnPDeviceIDs.value]
for i in range(cPnPDeviceIDs.value):
windll.ole32.CoTaskMemFree(cast(PnPDeviceIDs, POINTER(c_void_p))[i])
return deviceIDs
# ...
IPortableDeviceManager._methods_ = [
COMMETHOD([], HRESULT, 'GetDevices',
( ['in'], POINTER(WSTRING), 'pPnPDeviceIDs' ),
( ['in'], POINTER(c_ulong), 'pcPnPDeviceIDs' )),
# ...
The following test runs successfully, although it returns an empty list in my case, since I don't have any connected device right now:
# First run
import os
from comtypes.client import GetModule
GetModule(os.getenv("WINDIR") + "\\system32\\PortableDeviceApi.dll")
# Quit python
# Edit comtypes/gen/_1F001332_1A57_4934_BE31_AFFC99F4EE0A_0_1_0.py
# Subsequent runs
from comtypes.client import CreateObject
from comtypes.gen.PortableDeviceApiLib import *
CreateObject(PortableDeviceManager).GetDevices()
I can't provide more help without scavenging further into comtypes. I suggest you contact its authors or maintainers.
EDIT: Meanwhile, I created a ticket at the SourceForge site. Since the project was transitioning out of SourceForge, it seemed to be forgotten, but it wasn't (here too).
Have you tried to import the module since then?
I'm having quite a bit of trouble getting started with scripting in Mule. To be quite honest, I'm falling at the first hurdle - I can't find anything in the documentation which tells me how to access the payload or how to return data to my flow.
I'm using Jython 2.5 and Mule 3.4.
My flow is extremely simple: it takes some text from an Ajax source and simply echoes it. At the moment the Python script does nothing (as I cannot figure out how to get it to do something with the payload).
<flow name="Python Script" doc:name="Python Script">
<ajax:connector name="connector-ajax" serverUrl="http://192.168.0.1:8000" resourceBase="C:\mule\workspace\scripting\src\main\app\docroot" doc:name="Ajax" />
<scripting:component doc:name="Python">
<scripting:script engine="jython" file="C:\mule\workspace\scripting\src\main\app\python\myscript.py"/>
</scripting:component>
<echo-component doc:name="Echo"/>
</flow>
I have read through the Script Component Reference and the Scripting Module Reference - the module reference appears to have some relevant information but I can't figure out how to use it in Python.
I have also read through an article about 'Mule Punching' which seems like it would have answered my question if I was running version 2 of Mule. I attempted to use the same techniques in my Mule 3 project but it did not work.
Edit 24/07/2012
Using #ppiixx's response, I have got a little bit further with Python scripting.
Just having a single line of code, for example return len(payload) causes the Jython interpreter to throw an error as return cannot exist outside of a function. Fair enough, that's standard.
However, with the code
def main():
return len(payload)
main()
I get an error saying that 'No serializer can be found for class org.mule.transport.NullPayload'.
The log is below:
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ Started app 'python-test' +
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
INFO 2013-07-24 09:16:08,821 [[python-test].flow.stage1.02] org.mule.component.simple.LogComponent:
********************************************************************************
* Message received in service: flow. Content is: '{NullPayload}' *
********************************************************************************
ERROR 2013-07-24 09:16:08,849 [[python-test].flow.stage1.02] org.mule.exception.CatchMessagingExceptionStrategy:
********************************************************************************
Message : No serializer found for class org.mule.transport.NullPayload and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (org.codehaus.jackson.map.JsonMappingException)
Code : MULE_ERROR--2
--------------------------------------------------------------------------------
Exception stack is:
1. No serializer found for class org.mule.transport.NullPayload and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (org.codehaus.jackson.map.JsonMappingException)
org.codehaus.jackson.map.ser.impl.UnknownSerializer:52 (null)
2. No serializer found for class org.mule.transport.NullPayload and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (org.codehaus.jackson.map.JsonMappingException) (org.mule.api.transformer.TransformerException)
org.mule.module.json.transformers.ObjectToJson:107 (http://www.mulesoft.org/docs/site/current3/apidocs/org/mule/api/transformer/TransformerException.html)
This leads me to think that using return on its own isn't enough to return data to the flow.
I've read through the scripting module reference, and while it gives examples in Groovy, it does not give examples in Python so I'm not sure where I'm going wrong.
Have a look at the 'Script Context Bindings' section here.
Basically a number of variables are available in the script context including: message,payload and log.
To return data using the python engine you set the result variable.
result = len(payload)
There is a example in the mule github.
Alright, so a couple days ago I decided to try and write a primitive wrapper for the PARI library. Ever since then I've been playing with ctypes library in loading the dll and accessing the functions contained using code similar to the following:
from ctypes import *
libcyg=CDLL("<path/cygwin1.dll") #It needs cygwin to be loaded. Not sure why.
pari=CDLL("<path>/libpari-gmp-2.4.dll")
print pari.fibo #fibonacci function
#prints something like "<_FuncPtr object at 0x00BA5828>"
So the functions are there and they can potentially be accessed, but I always receive an access violation no matter what I try. For example:
pari.fibo(5) #access violation
pari.fibo(c_int(5)) #access violation
pari.fibo.argtypes = [c_long] #setting arguments manually
pari.fibo.restype = long #set the return type
pari.fibo(byref(c_int(5))) #access violation reading 0x04 consistently
and any variation on that, including setting argtypes to receive pointers.
The Pari .dll is written in C and the fibonacci function's syntax within the library is GEN fibo(long x).
Could it be the return type that's causing these errors, as it is not a standard int or long but a GEN type, which is unique to the PARI library? Any help would be appreciated. If anyone is able to successfully load the library and use ANY function from within python, please tell; I've been at this for hours now.
EDIT: Seems as though I was simply forgetting to initialize the library. After a quick pari.pari_init(4000000,500000) it stopped erroring. Now my problem lies in the in the fact that it returns a GEN object; which is fine, but whenever I try to reference the address to which it points, it's always 33554435, which I presume is still an address. I'm trying further commands and I'll update if I succeed in getting the correct value of something.
You have two problems here, one give fibo the correct return type and two convert the GEN return type to the value you are looking for.
Poking around the source code a bit, you'll find that GEN is defined as a pointer to a long. Also, at looks like the library provides some converting/printing GENs. I focused in on GENtostr since it would probably be safer for all the pari functions.
import cytpes
pari = ctypes.CDLL("./libpari.so.2.3.5") #I did this under linux
pari.fibo.restype = ctypes.POINTER(ctypes.c_long)
pari.GENtostr.restype = ctypes.POINTER(ctypes.c_char)
pari.pari_init(4000000,500000)
x = pari.fibo(100)
y = pari.GENtostr(x)
ctypes.string_at(y)
Results in:
'354224848179261915075'