如何在python中调试win32com调用 [英] How to debug win32com call in python

查看:1261
本文介绍了如何在python中调试win32com调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了从日志记录脚本中收集输出,我想使用 onepy 来添加信息到OneNote 2013笔记本。不幸的是,由onepy提供的方法 update_page_content()对我来说不起作用。为了更深入地了解这个问题,我转而使用C#,其中存在OneNote API的许多在线示例,一些故障我设法得到以下简约C#示例:

  using System; 
使用OneNote = Microsoft.Office.Interop.OneNote;

class程序
{
static void Main(string [] args)
{
OneNote.Application onenoteApp = new OneNote.Application();
string xml =< one:Page xmlns:one = \http://schemas.microsoft.com/office/onenote/2013/onenote\...> ...< / one:Page>;
onenoteApp.UpdatePageContent(xml,DateTime.MinValue);
}
}

字符串 xml 是通过修改使用 GetPageContent 方法从OneNote检索的XML文档获得的,如我所链接的上一个问题所详述。 xml 的精确内容对于这个问题无关紧要,唯一重要的是上述程序无时间运行并更改现有的OneNote页面总是执行成功。



现在转到Python,我试图翻译我的简约程序,而不做实质的更改。我的结果如下所示:

  import win32com 
import pytz
import datetime

onenote_app = win32com.client.Dispatch('OneNote.Application.15')
xml =< one:Page xmlns:one = \http://schemas.microsoft.com/office/onenote / 2013 / onenote\...> ...< / one:Page>
date = pytz.utc.localize(datetime.datetime.fromordinal(1))
onenote_app.UpdatePageContent(xml,date)

我尝试非常小心为两个变量使用相同的值。当然,两个字符串 xml 的内容是相同的(复制和粘贴)。另外,根据VS2015调试器, DateTime.MinValue date 指的是同一天。然而,当我执行python程序时,我收到这个非常无用的错误。

  135 def UpdatePageContent(self,bstrPageChangesXmlIn = defaultNamedNotOptArg, dateExpectedLastModified =(1899,12,30,0,0,5,364,0),xsSchema = 2,force = False):
136 return self._oleobj_.InvokeTypes(1610743816,LCID,1,( 24,0),((8,1),(7,49),(3,49),(11,49)),bstrPageChangesXmlIn
- > 137,dateExpectedLastModified,xsSchema,force)
138
139 _prop_map_get_ = {

com_error:(-2147352567,发生异常,(0,无,无,无,0,-2147213296),无)$ b对于我的理解,C#和Python实际上都使用相同的库来执行它们的调用(两者都称为<$ c(code) $ c> Microsoft OneNote 15.0对象库
)。所以原则两个程序都应该可以正常工作。如果我在这一点上没有被误解,那么我就假定Python在调用库时有某些不同的东西。如何进一步跟踪这里的实际问题?有没有办法使用Visual Studio 2015的内置Python支持来更好地了解C#和Python代码的区别?

解决方案

由于jayongg已经在他的答案中指出,我的问题是作为参数传递给 UpdatePageContent()调用。但是,他的建议是将零作为最后修改日期,并禁用日期检查,详见文档并不像看起来那么简单。在我的答案中,我将详细介绍遇到的所有陷阱。



第一个问题显示在这个的问题。如果有人试图将 datetime 对象或普通整数传递给 UpdatePageContent(),则会出现如以下是提出的。

  135 def UpdatePageContent(self,bstrPageChangesXmlIn = defaultNamedNotOptArg,dateExpectedLastModified =(1899,12,30,0,0 ,0,5,364,0),xsSchema = 2,force = False):
136 return self._oleobj_.InvokeTypes(1610743816,LCID,1,(24,0),((8,1) (7,49),(3,49),(11,49)),bstrPageChangesXmlIn
- > 137,dateExpectedLastModified,xsSchema,force)
138
139 _prop_map_get_ = {

ValueError:astimezone()不能应用于一个天真的datetime

,将日期参数留空并使用默认值不按预期方式工作:

  135 def UpdatePageContent(self,bstrPageChangesXmlIn = defaultNamedNotOptArg ,dateExpectedLastModified =(1899,12,30,0,0 ,0,5,364,0),xsSchema = 2,force = False):
136 return self._oleobj_.InvokeTypes(1610743816,LCID,1,(24,0),((8,1) (7,49),(3,49),(11,49)),bstrPageChangesXmlIn
- > 137,dateExpectedLastModified,xsSchema,force)
138
139 _prop_map_get_ = {

TypeError:必须是一个pywintypes时间对象(已获得元组)

显然,python API调用的一些内部被搞砸了。如果遵循项目页面上的说明,并使用 makepy 预先生成API包装器,然后可以检查负责这些调用的源文件,但至少对我来说这不是很有启发性。我怀疑 InvokeTypes 方法尝试将传递的日期设置为API可以理解的格式,但必需的要求不会在Python包装器本身中实现。 p>

幸运的是,有一个很简单的解决方法。诀窍是使得传递的 datetime 对象时区感知。要做到这一点,首先必须将其本地化。这可以例如使用 pytz 模块来完成。

  import datetime 
import pytz

date = pytz.utc.localize(datetime.datetime.fromordinal(1))

有了这个知识,API调用就起作用了,但是我提出了COM错误。这是jayongg的答案:我必须通过零作为最后修改日期(或者我需要知道的日期,最后一个修改发生,也应该工作)。现在棘手的问题是:什么是零?



在我的C#代码中,此日期由 DateTime.MinValue ,根据Visual Studio 2015等于0001-01-01 00:00:00。 Python的同一个日期是 datetime.datetime.fromordinal(1),但它仍然不能使呼叫工作。我必须怀疑Visual Studio的信息是错误的,或者有些魔术发生在两者之间,也许是类型转换VSDate - > Int - > APIDate。



那么怎么办我发现,这是正确的零日期通过?结果答案已经在那里,只需要知道在哪里看。如果有人在Python API包装器中进行检查,则会为该参数提供一个默认值:

  dateExpectedLastModified =(1899, 12,30,0,0,0,5,364,0)

以下片段。

 >> date = pytz.utc.localize(datetime.datetime(year = 1899,month = 12,day = 30))
>> print(tuple(date.timetuple()))
(1899,12,30,0,0,0,5,364,0)

和voilà,将变量日期传递给调用工作正常。

 >> onenote_app.UpdatePageContent(xml,date)
>>

完美无缺,对吧?我的意思是,你会通过哪个日期而不是1899-12-30?其实它确实有意义。这一天是都柏林朱利安的零点前一天日期。根据德国维基百科文章,这一天是使用Excel作为日期的零点,所以听起来似乎合理OneNote是一样的。



为什么有一天的差异?显然,1900年被错误地视为闰年,而不是闰年。因此1899-12-31转移到1899-12-30,这是API想要的。叹。为什么如何存储日期有如此多的不同协议?哦,只是提到。 Mac的办公室使用1904作为零点。是的,当然。


In order to collect output from a logging script, I would like to use onepy to add information to a OneNote 2013 notebook. Unfortunately, the method update_page_content() provided by onepy doesn't work for me. To get a deeper understanding of the problem, I switched over to C#, where many online examples for the OneNote API exist and after some trouble I managed to get the following minimalistic C# example to work:

using System;
using OneNote = Microsoft.Office.Interop.OneNote;

class Program
{
    static void Main(string[] args)
    {
        OneNote.Application onenoteApp = new OneNote.Application();
        string xml = "<one:Page xmlns:one=\"http://schemas.microsoft.com/office/onenote/2013/onenote\" ...> ... </one:Page>";
        onenoteApp.UpdatePageContent(xml, DateTime.MinValue);
    }
}

The string xml was obtained by modification of an XML document, which was retrieved from OneNote using the GetPageContent method, as detailed in my linked previous question. The precise content of xml doesn't matter for the sake of this question, the only important thing is that above program runs without problems time after time and changes to an existing OneNote page are always performed successfully.

Going over to Python now, I tried to translate my minimalistic program without making substantial changes. My result looks as follows:

import win32com
import pytz
import datetime

onenote_app = win32com.client.Dispatch('OneNote.Application.15')
xml = "<one:Page xmlns:one=\"http://schemas.microsoft.com/office/onenote/2013/onenote\" ...> ... </one:Page>"
date = pytz.utc.localize(datetime.datetime.fromordinal(1))
onenote_app.UpdatePageContent(xml, date)

I tried to put much care into using the same values for the two variables. Of course the content of the two strings xml is identical (copy&paste). Also, according to the VS2015 debugger, both DateTime.MinValue and date refer to the same date. When I execute the python program, however, I recieve this very unhelpful error.

    135         def UpdatePageContent(self, bstrPageChangesXmlIn=defaultNamedNotOptArg, dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0), xsSchema=2, force=False):
    136         return self._oleobj_.InvokeTypes(1610743816, LCID, 1, (24, 0), ((8, 1), (7, 49), (3, 49), (11, 49)),bstrPageChangesXmlIn
--> 137             , dateExpectedLastModified, xsSchema, force)
    138 
    139     _prop_map_get_ = {

com_error: (-2147352567, 'Exception occurred.', (0, None, None, None, 0, -2147213296), None)

To my understanding, both C# and Python actually use the same library to execute their calls (both called Microsoft OneNote 15.0 Object Library). So in principle both programs should work fine. If I am not mistaken in this point, I would thus assume that Python does something different in its call to the library. How can I trace further what the actual problem here is? Is there maybe a way to use the built-in Python support of Visual Studio 2015 to understand the difference between the C# and the Python code better?

解决方案

As jayongg is already pointing out in his answer, my problem is the date passed as an argument to the UpdatePageContent() call. However, his suggestion to pass zero as last modified date and to disable date checking as detailed in the documentation is not as straightforward as it may seem. In my answer I will detail all the pitfalls, which I encountered.

The first problem is shown in this question. If one tries to pass for example a datetime object or a plain integer to UpdatePageContent(), then an error like the following is raised.

    135         def UpdatePageContent(self, bstrPageChangesXmlIn=defaultNamedNotOptArg, dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0), xsSchema=2, force=False):
    136         return self._oleobj_.InvokeTypes(1610743816, LCID, 1, (24, 0), ((8, 1), (7, 49), (3, 49), (11, 49)),bstrPageChangesXmlIn
--> 137             , dateExpectedLastModified, xsSchema, force)
    138 
    139     _prop_map_get_ = {

ValueError: astimezone() cannot be applied to a naive datetime

In particular, leaving the date argument blank and using the default value does not work as expected:

    135         def UpdatePageContent(self, bstrPageChangesXmlIn=defaultNamedNotOptArg, dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0), xsSchema=2, force=False):
    136         return self._oleobj_.InvokeTypes(1610743816, LCID, 1, (24, 0), ((8, 1), (7, 49), (3, 49), (11, 49)),bstrPageChangesXmlIn
--> 137             , dateExpectedLastModified, xsSchema, force)
    138 
    139     _prop_map_get_ = {

TypeError: must be a pywintypes time object (got tuple)

Apparently some internals of the python API calls are messed up. If one follows the instructions on the onepy project page and uses makepy to pre-generte the API wrapper, then one can inspect the source files responsible for these calls, but at least for me this was not very instructive. I suspect that the InvokeTypes method tries to put the passed date into a format, which the API can understand, but the necessary requirements are not implemented in the Python wrapper itself.

Fortunately, there is a quite simple workaround. The trick is to make the passed datetime object timezone-aware. To do this, one has to localize it first. This can for example be done using the pytz module.

import datetime
import pytz

date = pytz.utc.localize(datetime.datetime.fromordinal(1))

With this knowledge the API call works, but the COM error from my question is raised. This is where jayongg's answer comes in: I have to pass zero as last modified date (or I need to know the date, when the last modification occurred, which should also work). Now the tricky question is: What is zero?

In my C# code, this date is given by DateTime.MinValue, which according to Visual Studio 2015 is equal to 0001-01-01 00:00:00. The same date in Python is datetime.datetime.fromordinal(1), but it still doesn't make the call work. I have to suspect that the information by Visual Studio is wrong or that some magic happens in between, maybe a type cast VSDate -> Int -> APIDate.

So how do I find out, which is the correct zero date to pass? Turns out the answer is already there, one just needs to know where to look. If one inspects in the Python API wrapper, there is a default value given for the argument in question:

dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0)

The same can be obtained with the following snippet.

>> date = pytz.utc.localize(datetime.datetime(year=1899, month=12, day=30))
>> print(tuple(date.timetuple()))
(1899, 12, 30, 0, 0, 0, 5, 364, 0)

And voilà , passing the variable date to the call works just fine.

>> onenote_app.UpdatePageContent(xml, date)
>>

Makes perfect sense, right? I mean, which other date would you pass instead of 1899-12-30? Actually it does make some sense. This day is one day before the zero point of the Dublin Julian Date. According to the German Wikipedia article this day is used by Excel as zero point for dates, so it sound plausible that OneNote does the same.

Why the one day difference? Apparently, the year 1900 is mis-treated as leap year, which it was not. Thereby 1899-12-31 is shifted to 1899-12-30, which is what the API wants. Sigh. Why are there so many different agreements on how to store dates? Oh, just to mention. Office for Mac uses 1904 as zero point. Yeah, of course.

这篇关于如何在python中调试win32com调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆