如何在python中调试win32com调用

本文关键字:调试 win32com 调用 python | 更新日期: 2023-09-27 18:31:19

为了从日志记录脚本收集输出,我想使用 onepy 向 OneNote 2013 笔记本添加信息。不幸的是,onepy update_page_content()提供的方法对我不起作用。为了更深入地了解这个问题,我切换到 C#,其中存在许多 OneNote API 的在线示例,经过一些麻烦,我设法让以下简约的 C# 示例正常工作:

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);
    }
}

字符串xml是通过修改 XML 文档获得的,该文档是使用 GetPageContent 方法从 OneNote 检索的,如我链接的上一个问题中所述。对于这个问题,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.MinValuedate都指同一日期。但是,当我执行python程序时,我收到这个非常无用的错误。

    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)

据我了解,C#和Python实际上都使用相同的库来执行它们的调用(都称为Microsoft OneNote 15.0 Object Library)。因此,原则上两个程序都应该正常工作。如果我在这一点上没有记错的话,我会假设 Python 在调用库时做了一些不同的事情。如何进一步追踪此处的实际问题是什么?有没有办法使用Visual Studio 2015的内置Python支持来更好地理解C#和Python代码之间的区别?

如何在python中调试win32com调用

正如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() cannot be applied to a naive 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: must be a pywintypes time object (got tuple)

显然,python API调用的一些内部被搞砸了。如果按照 onepy 项目页面上的说明并使用makepy来预生成 API 包装器,那么可以检查负责这些调用的源文件,但至少对我来说这不是很有启发性。我怀疑 InvokeTypes 方法试图将传递的日期放入 API 可以理解的格式中,但必要的要求并未在 Python 包装器本身中实现。

幸运的是,有一个非常简单的解决方法。诀窍是使传递的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)

瞧,将可变日期传递给调用工作得很好。

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

完全有道理,对吧?我的意思是,你会通过哪个日期而不是 1899-12-30?实际上,这确实是有道理的。这一天是都柏林儒略历零点的前一天。根据德语维基百科文章,这一天被Excel用作日期的零点,因此OneNote听起来

也这样做是合理的。

为什么会有一天的差异?显然,1900年被误认为是闰年,但事实并非如此。因此,1899-12-31 被转移到 1899-12-30,这正是 API 想要的。叹息。为什么关于如何存储日期有这么多不同的协议?哦,提一下。Office for Mac 使用 1904 作为零点。是的,当然。

错误代码 (-2147213296) 0x80042010,即:

hrLastModifiedDateDidNotMatch

0x80042010

上次修改日期不匹配。

https://msdn.microsoft.com/en-us/library/office/ff966472(v=office.14).aspx

您可以尝试传递上次修改日期 0。来自: https://msdn.microsoft.com/en-us/library/office/gg649853(v=office.14).aspx:dateExpectLastModified—(可选)您认为上次修改要更新的页面的日期和时间。如果为此参数传递非零值,则仅当您传递的值与上次修改页面的实际日期和时间匹配时,OneNote 才会继续更新。传递此参数的值有助于防止意外覆盖用户自上次修改页面以来所做的编辑。