python调用win32_如何在python中调试win32com调用

1586010002-jmsa.png

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 = " ... ";

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 = " ... "

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值