【问题标题】:Unspecified error when calling Word CentimetersToPoints via OLE通过 OLE 调用 Word CentimetersToPoints 时出现未指定的错误
【发布时间】:2013-04-29 12:51:38
【问题描述】:

当在 Delphi (XE) 中执行时,以下代码在 CentimetersToPoint 调用上失败并出现 OLE 800040005“未指定”错误,类似的 VBS 或 VBA 版本通过

var w : OleVariant;

w := CreateOleObject('Word.Application');
w.Visible := true;
Writeln(w.CentimetersToPoints(2.0));

类型库提供的 FWIW

/ [id(0x00000173), helpcontext(0x09700173)]
// single CentimetersToPoints([in] single Centimeters);

默认情况下,Delphi 只将浮点值作为 Double 传递,所以我尝试直接调用 IDispatch.Invoke 并将参数作为 VT_R4 传递,但没有更好的结果。

编辑:有效的 VB 版本(保存到 .vbs)

set w = CreateObject("Word.Application")
w.Visible = true
msgbox w.CentimetersToPoints(2.0)

对于可能出现的问题还有其他建议吗?

【问题讨论】:

  • 你试过TWordApplication组件来匹配实际安装的Office版本吗(Delphi xe2有三个版本)。也许你认为你错过了一两个空参数,比如 LCID。早期绑定将为您提供编译时检查,而不是延迟运行时检查
  • TWordApplication 不适用,这实际上是问题的简化且更易于复制的版本。类似的代码也适用于 VB。
  • 您甚至无法确定 VBA 是否通过 COM,甚至更不确定它是否可以通过 COM-to-OLE 网关工作。 TWordApplication 是调试这个的好尝试,为什么你声称它不能被使用?这不是简化的情况,而是一个不同的程序,通过不同的接口工作,并且缺乏类型安全之类的东西。 “最小化”有点过分了
  • 使用 CreateObject() 将与上面相同的代码放在 .VBS 中,它将使用 OLE,并且可以工作。 TWordApplication 在这里不相关,因为它是 COM,这里的问题是 OLE,这是一个最小化,因为原始问题带有更多涉及 OLE 的代码行,您只需要 Word 用上面的 sn-p 重现它。
  • 反对 DUPLICATE:Del[phi 开发人员可能会搜索带有 delphi 标记的问题并且会错过。另外我想知道这个调用序列是否可以在 RTL 中修补:pastebin.ca/2369858 - 它不是被忽视的保护,就像 Xe2u4 和 XE3 中的有符号整数一样,它是有意添加的保护。删除它会修复此调用,但可能会中断其他调用(哪些调用?)!

标签: delphi automation ms-word delphi-xe ole


【解决方案1】:

我最初怀疑问题在于该函数需要Single 并且Delphi 将您的浮点数转换为其他内容。当我在调试器中跟踪它时,我发现传递给Invoke 的变体具有VTypevarCurrency2 的货币值。我不确定这是怎么发生的!

正如我在回答这个question 时发现的那样,将单精度浮点数转换为变体非常棘手。我最初怀疑您可以使用我在那里提供的解决方案来解决您的问题。

function VarFromSingle(const Value: Single): Variant;
begin
  VarClear(Result);
  TVarData(Result).VSingle := Value;
  TVarData(Result).VType := varSingle;
end;

....

w := CreateOleObject('Word.Application');
w.Visible := true;
Writeln(w.CentimetersToPoints(VarFromSingle(2.0)));

但这也失败了,以同样的方式,原因我还不明白。

和你一样,我尝试使用IDispatch.Invoke 调用该函数。这是我想出的:

program SO16279098;

{$APPTYPE CONSOLE}

uses
  SysUtils, Variants, Windows, ComObj, ActiveX;

function VarFromSingle(const Value: Single): Variant;
begin
  VarClear(Result);
  TVarData(Result).VSingle := Value;
  TVarData(Result).VType := varSingle;
end;

var
  WordApp: Variant;
  param: Variant;
  retval: HRESULT;
  disp: IDispatch;
  Params: TDispParams;
  result: Variant;

begin
  try
    CoInitialize(nil);
    WordApp := CreateOleObject('Word.Application');
    disp := IDispatch(WordApp);

    param := VarFromSingle(2.0);
    Params := Default(TDispParams);
    Params.cArgs := 1;
    Params.rgvarg := @param;
    retval := disp.Invoke(
      371,//CentimetersToPoints
      GUID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      Params,
      @Result,
      nil,
      nil
    );
    // retval = E_FAIL

    Params := Default(TDispParams);
    retval := disp.Invoke(
      404,//ProductCode
      GUID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      Params,
      @Result,
      nil,
      nil
    );
    // retval = S_OK
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

我不能以这种方式调用CentimetersToPoints,但可以调用ProductCode 函数。

要添加到您的成功/失败指标集合中,当我使用 PowerShell 调用 CentimetersToPoints 函数时,我成功了。当我使用 Python 的win32com.client 调用时,我得到E_FAIL

显然我们缺少一些特殊的魔法成分。似乎所有的 MS 工具都知道这个魔法。

我的结论是不可能使用 Delphi 中实现的变体调度来调用CentimetersToPoints。它不知道魔法,不管魔法是什么。

显然可以在IDispatch 上调用Invoke 并成功。我们可以看出这一点,因为其他环境可以做到这一点。所以,我还不知道缺失的魔法是什么。

如果您可以使用早期绑定的 COM,那么您可以回避这个问题:

Writeln((IDispatch(w) as WordApplication).CentimetersToPoints(2.0));

好的,使用Hans Passanthelp,我有一些Delphi 代码可以调用这个函数:

program SO16279098;

{$APPTYPE CONSOLE}

uses
  SysUtils, Variants, Windows, ComObj, ActiveX;

function VarFromSingle(const Value: Single): Variant;
begin
  VarClear(Result);
  TVarData(Result).VSingle := Value;
  TVarData(Result).VType := varSingle;
end;

var
  WordApp: Variant;
  param: Variant;
  retval: HRESULT;
  disp: IDispatch;
  Params: TDispParams;
  result: Variant;

begin
  try
    CoInitialize(nil);
    WordApp := CreateOleObject('Word.Application');
    disp := IDispatch(WordApp);

    param := VarFromSingle(2.0);
    Params := Default(TDispParams);
    Params.cArgs := 1;
    Params.rgvarg := @param;
    retval := disp.Invoke(
      371,//CentimetersToPoints
      GUID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD or DISPATCH_PROPERTYGET,
      Params,
      @Result,
      nil,
      nil
    );
    Writeln(Result);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

由于未知原因,您需要包含DISPATCH_PROPERTYGET 以及DISPATCH_METHOD

question that I asked 可能会使这个问题重复。所以,我投票结束。

【讨论】:

  • 你的 sn-p 给出了同样的错误(正如我所写的,我注意到了这个问题并试图以同样的方式解决它)。
  • 这很奇怪,Single 必须以某种方式成为问题,Excel 中的相同方法有效,尽管它接受双精度而不是单...
  • 顺便说一句,我尝试了 IDisptach.Invoke 与硬编码单,我也得到了同样的错误。必须有其他事情发生,因为 VBS 设法成功拨打电话。
  • @EricGrange 我不知道我是否在帮助,但我至少提出了一个论点,即如果不使用Invoke,你就无法做到这一点。我希望您的代码与我的回答中的代码大致相同。如果该代码出现在问题中,将会节省时间。
  • @EricGrange 好吧,成功了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-07
  • 1970-01-01
  • 1970-01-01
  • 2019-05-04
相关资源
最近更新 更多