【问题标题】:How to debug win32com call in python如何在python中调试win32com调用
【发布时间】:2016-01-20 15:30:19
【问题描述】:

为了从日志记录脚本收集输出,我想使用onepy 将信息添加到 OneNote 2013 笔记本。不幸的是,onepy 提供的方法update_page_content() 对我不起作用。为了更深入地理解这个问题,我切换到了 C#,那里有许多 OneNote API 的在线示例,在一些 trouble 之后,我设法让以下简约的 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 文档获得的,该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 代码之间的区别?

【问题讨论】:

    标签: c# python debugging onenote onenote-api


    【解决方案1】:

    正如 jayongg 在他的 answer 中已经指出的那样,我的问题是作为参数传递给 UpdatePageContent() 调用的日期。然而,他建议将零作为最后修改日期并禁用日期检查,如documentation 中所述,并不像看起来那么简单。在我的回答中,我将详细说明我遇到的所有陷阱。

    第一个问题显示在this 问题中。例如,如果尝试将 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 project page 上的说明并使用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)
    

    同样可以用下面的sn-p得到。

    >> 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 日,你还会通过哪个日期?其实还是有点道理的。这一天是Dublin Julian Date 零点的前一天。根据德国人Wikipedia article 的说法,Excel 将这一天用作日期的零点,因此 OneNote 也是如此。

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

    【讨论】:

      【解决方案2】:

      你的错误代码(-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: dateExpectedLastModified—(可选)您认为要更新的页面上次修改的日期和时间。 如果您为此参数传递一个非零值,只有当您传递的值与页面最后修改的实际日期和时间相匹配时,OneNote 才会继续更新。传递此参数的值有助于防止意外覆盖用户自上次修改页面以来所做的编辑。

      【讨论】:

      • 您的怀疑是正确的,问题在于过去的日期。同时,我运行了 python 程序,一旦我有时间,我会添加我的解决方案作为答案。但是,您没有回答我更笼统的问题:您怎么知道-2147213296 表示0x80042010?这两个数字似乎与我无关。至少将 -2147213296 转换为十六进制会给我 0x7ffbdff0,这看起来完全不相关。
      • 实际上它给你-0x7ffbdff0(负数)。您需要将其转换为正数。您可以通过将 0x100000000 添加到结果中来做到这一点。
      • 这是真的,但对我来说似乎是任意的。例如,我还可以添加 0xffffffff 并获得不同的结果。添加 0x100000000 是否是一般规则,这 总是 为真?另外,为什么没有什么可以增加正返回值?
      • 这不是任意的。添加 0x100000000(并丢弃进位位)是如何将有符号的 32 位整数转换为无符号整数的方法。
      猜你喜欢
      • 2012-02-02
      • 2013-05-19
      • 2011-11-02
      • 2013-01-21
      • 1970-01-01
      • 2016-06-01
      • 1970-01-01
      • 2018-06-07
      • 2016-10-22
      相关资源
      最近更新 更多